From 15c2e50338a514cb51e6ef3443261a7d3971f69c Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 3 Nov 2025 15:36:52 +0100 Subject: [PATCH 01/65] UI: fix typo Upload SSL certificate (#11869) --- ui/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4f450e940fc0..805ea1adae94 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2623,7 +2623,7 @@ "label.upload.icon": "Upload icon", "label.upload.iso.from.local": "Upload ISO from local", "label.upload.resource.icon": "Upload icon", -"label.upload.ssl.certificate": "Upload SSL cerficicate", +"label.upload.ssl.certificate": "Upload SSL certificate", "label.upload.template.from.local": "Upload Template from local", "label.upload.volume": "Upload volume", "label.upload.volume.from.local": "Upload Volume from local", From dbda673e1fa813856deb0f0b6328dad0222b702c Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Wed, 5 Nov 2025 16:53:04 +0530 Subject: [PATCH 02/65] Updating pom.xml version numbers for release 4.23.0.0-SNAPSHOT Signed-off-by: Harikrishna Patnala --- agent/pom.xml | 2 +- api/pom.xml | 2 +- client/pom.xml | 2 +- core/pom.xml | 2 +- debian/changelog | 8 ++++---- developer/pom.xml | 2 +- engine/api/pom.xml | 2 +- engine/components-api/pom.xml | 2 +- engine/orchestration/pom.xml | 2 +- engine/pom.xml | 2 +- engine/schema/pom.xml | 2 +- engine/service/pom.xml | 2 +- engine/storage/cache/pom.xml | 2 +- engine/storage/configdrive/pom.xml | 2 +- engine/storage/datamotion/pom.xml | 2 +- engine/storage/image/pom.xml | 2 +- engine/storage/integration-test/pom.xml | 2 +- engine/storage/object/pom.xml | 2 +- engine/storage/pom.xml | 2 +- engine/storage/snapshot/pom.xml | 2 +- engine/storage/volume/pom.xml | 2 +- engine/userdata/cloud-init/pom.xml | 2 +- engine/userdata/pom.xml | 2 +- framework/agent-lb/pom.xml | 2 +- framework/ca/pom.xml | 2 +- framework/cluster/pom.xml | 2 +- framework/config/pom.xml | 2 +- framework/db/pom.xml | 2 +- framework/direct-download/pom.xml | 2 +- framework/events/pom.xml | 2 +- framework/extensions/pom.xml | 6 +++--- framework/ipc/pom.xml | 2 +- framework/jobs/pom.xml | 2 +- framework/managed-context/pom.xml | 2 +- framework/pom.xml | 2 +- framework/quota/pom.xml | 2 +- framework/rest/pom.xml | 2 +- framework/security/pom.xml | 2 +- framework/spring/lifecycle/pom.xml | 2 +- framework/spring/module/pom.xml | 2 +- plugins/acl/dynamic-role-based/pom.xml | 2 +- plugins/acl/project-role-based/pom.xml | 2 +- plugins/acl/static-role-based/pom.xml | 2 +- .../affinity-group-processors/explicit-dedication/pom.xml | 2 +- plugins/affinity-group-processors/host-affinity/pom.xml | 2 +- .../affinity-group-processors/host-anti-affinity/pom.xml | 2 +- .../non-strict-host-affinity/pom.xml | 2 +- .../non-strict-host-anti-affinity/pom.xml | 2 +- plugins/alert-handlers/snmp-alerts/pom.xml | 2 +- plugins/alert-handlers/syslog-alerts/pom.xml | 2 +- plugins/api/discovery/pom.xml | 2 +- plugins/api/rate-limit/pom.xml | 2 +- plugins/api/solidfire-intg-test/pom.xml | 2 +- plugins/api/vmware-sioc/pom.xml | 2 +- plugins/backup/dummy/pom.xml | 2 +- plugins/backup/nas/pom.xml | 2 +- plugins/backup/networker/pom.xml | 2 +- plugins/backup/veeam/pom.xml | 2 +- plugins/ca/root-ca/pom.xml | 2 +- plugins/database/mysql-ha/pom.xml | 2 +- plugins/database/quota/pom.xml | 2 +- plugins/dedicated-resources/pom.xml | 2 +- plugins/deployment-planners/implicit-dedication/pom.xml | 2 +- plugins/deployment-planners/user-concentrated-pod/pom.xml | 2 +- plugins/deployment-planners/user-dispersing/pom.xml | 2 +- plugins/drs/cluster/balanced/pom.xml | 2 +- plugins/drs/cluster/condensed/pom.xml | 2 +- plugins/event-bus/inmemory/pom.xml | 2 +- plugins/event-bus/kafka/pom.xml | 2 +- plugins/event-bus/rabbitmq/pom.xml | 2 +- plugins/event-bus/webhook/pom.xml | 2 +- plugins/ha-planners/skip-heurestics/pom.xml | 2 +- plugins/host-allocators/random/pom.xml | 2 +- plugins/hypervisors/baremetal/pom.xml | 2 +- plugins/hypervisors/external/pom.xml | 2 +- plugins/hypervisors/hyperv/pom.xml | 2 +- plugins/hypervisors/kvm/pom.xml | 2 +- plugins/hypervisors/ovm/pom.xml | 2 +- plugins/hypervisors/ovm3/pom.xml | 2 +- plugins/hypervisors/simulator/pom.xml | 2 +- plugins/hypervisors/ucs/pom.xml | 2 +- plugins/hypervisors/vmware/pom.xml | 2 +- plugins/hypervisors/xenserver/pom.xml | 2 +- plugins/integrations/cloudian/pom.xml | 2 +- plugins/integrations/kubernetes-service/pom.xml | 2 +- plugins/integrations/prometheus/pom.xml | 2 +- plugins/maintenance/pom.xml | 2 +- plugins/metrics/pom.xml | 2 +- plugins/network-elements/bigswitch/pom.xml | 2 +- plugins/network-elements/brocade-vcs/pom.xml | 2 +- plugins/network-elements/cisco-vnmc/pom.xml | 2 +- plugins/network-elements/dns-notifier/pom.xml | 2 +- plugins/network-elements/elastic-loadbalancer/pom.xml | 2 +- plugins/network-elements/globodns/pom.xml | 2 +- plugins/network-elements/internal-loadbalancer/pom.xml | 2 +- plugins/network-elements/juniper-contrail/pom.xml | 2 +- plugins/network-elements/netris/pom.xml | 2 +- plugins/network-elements/netscaler/pom.xml | 2 +- plugins/network-elements/nicira-nvp/pom.xml | 2 +- plugins/network-elements/nsx/pom.xml | 2 +- plugins/network-elements/opendaylight/pom.xml | 2 +- plugins/network-elements/ovs/pom.xml | 2 +- plugins/network-elements/palo-alto/pom.xml | 2 +- plugins/network-elements/stratosphere-ssp/pom.xml | 2 +- plugins/network-elements/tungsten/pom.xml | 2 +- plugins/network-elements/vxlan/pom.xml | 2 +- plugins/outofbandmanagement-drivers/ipmitool/pom.xml | 2 +- .../outofbandmanagement-drivers/nested-cloudstack/pom.xml | 2 +- plugins/outofbandmanagement-drivers/redfish/pom.xml | 2 +- plugins/pom.xml | 2 +- plugins/storage-allocators/random/pom.xml | 2 +- plugins/storage/image/default/pom.xml | 2 +- plugins/storage/image/s3/pom.xml | 2 +- plugins/storage/image/sample/pom.xml | 2 +- plugins/storage/image/swift/pom.xml | 2 +- plugins/storage/object/ceph/pom.xml | 2 +- plugins/storage/object/cloudian/pom.xml | 2 +- plugins/storage/object/minio/pom.xml | 2 +- plugins/storage/object/simulator/pom.xml | 2 +- plugins/storage/sharedfs/storagevm/pom.xml | 2 +- plugins/storage/volume/adaptive/pom.xml | 2 +- plugins/storage/volume/cloudbyte/pom.xml | 2 +- plugins/storage/volume/datera/pom.xml | 2 +- plugins/storage/volume/default/pom.xml | 2 +- plugins/storage/volume/flasharray/pom.xml | 2 +- plugins/storage/volume/linstor/pom.xml | 2 +- plugins/storage/volume/nexenta/pom.xml | 2 +- plugins/storage/volume/primera/pom.xml | 2 +- plugins/storage/volume/sample/pom.xml | 2 +- plugins/storage/volume/scaleio/pom.xml | 2 +- plugins/storage/volume/solidfire/pom.xml | 2 +- plugins/storage/volume/storpool/pom.xml | 2 +- plugins/user-authenticators/ldap/pom.xml | 2 +- plugins/user-authenticators/md5/pom.xml | 2 +- plugins/user-authenticators/oauth2/pom.xml | 2 +- plugins/user-authenticators/pbkdf2/pom.xml | 2 +- plugins/user-authenticators/plain-text/pom.xml | 2 +- plugins/user-authenticators/saml2/pom.xml | 2 +- plugins/user-authenticators/sha256salted/pom.xml | 2 +- plugins/user-two-factor-authenticators/static-pin/pom.xml | 2 +- plugins/user-two-factor-authenticators/totp/pom.xml | 2 +- pom.xml | 2 +- quickcloud/pom.xml | 2 +- server/pom.xml | 4 ++-- services/console-proxy/pom.xml | 2 +- services/console-proxy/rdpconsole/pom.xml | 2 +- services/console-proxy/server/pom.xml | 2 +- services/pom.xml | 2 +- services/secondary-storage/controller/pom.xml | 2 +- services/secondary-storage/pom.xml | 2 +- services/secondary-storage/server/pom.xml | 2 +- systemvm/pom.xml | 2 +- test/pom.xml | 2 +- tools/apidoc/pom.xml | 2 +- tools/checkstyle/pom.xml | 2 +- tools/devcloud-kvm/pom.xml | 2 +- tools/devcloud4/pom.xml | 2 +- tools/docker/Dockerfile | 2 +- tools/docker/Dockerfile.marvin | 4 ++-- tools/marvin/pom.xml | 2 +- tools/marvin/setup.py | 2 +- tools/pom.xml | 2 +- usage/pom.xml | 2 +- utils/pom.xml | 2 +- vmware-base/pom.xml | 2 +- 165 files changed, 172 insertions(+), 172 deletions(-) diff --git a/agent/pom.xml b/agent/pom.xml index 4fa30e4f78e2..5ab6cfe17c13 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/api/pom.xml b/api/pom.xml index 405365451c6f..c80c35593451 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/client/pom.xml b/client/pom.xml index 94d844be3c42..d8fa433d5be3 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/core/pom.xml b/core/pom.xml index 1bf7c28674d6..f91a330b7ba9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/debian/changelog b/debian/changelog index 6d288afc4db0..02251137e9d0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,12 +1,12 @@ -cloudstack (4.22.1.0-SNAPSHOT) unstable; urgency=low +cloudstack (4.23.0.0-SNAPSHOT) unstable; urgency=low - * Update the version to 4.22.1.0-SNAPSHOT + * Update the version to 4.23.0.0-SNAPSHOT -- the Apache CloudStack project Thu, 30 Oct 2025 19:23:55 +0530 -cloudstack (4.22.1.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low +cloudstack (4.23.0.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low - * Update the version to 4.22.1.0-SNAPSHOT-SNAPSHOT + * Update the version to 4.23.0.0-SNAPSHOT-SNAPSHOT -- the Apache CloudStack project Thu, Aug 28 11:58:36 2025 +0530 diff --git a/developer/pom.xml b/developer/pom.xml index de6a8ef3d10a..e2fd782fd25f 100644 --- a/developer/pom.xml +++ b/developer/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/engine/api/pom.xml b/engine/api/pom.xml index 2f7e15aaab05..cb50ef0cd46b 100644 --- a/engine/api/pom.xml +++ b/engine/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/components-api/pom.xml b/engine/components-api/pom.xml index 8caf8ccbff69..49d41d36f83d 100644 --- a/engine/components-api/pom.xml +++ b/engine/components-api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml index cd5578d245ca..fda63d2558b0 100755 --- a/engine/orchestration/pom.xml +++ b/engine/orchestration/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/pom.xml b/engine/pom.xml index 821a4f8f54ce..2de84eab85af 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index 7d88f649245c..654cd14a25d3 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/service/pom.xml b/engine/service/pom.xml index c429ebf5da5a..9b833804064e 100644 --- a/engine/service/pom.xml +++ b/engine/service/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT cloud-engine-service war diff --git a/engine/storage/cache/pom.xml b/engine/storage/cache/pom.xml index 2e736ad42e55..ac330ca69974 100644 --- a/engine/storage/cache/pom.xml +++ b/engine/storage/cache/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml index d5ef49d7ef78..43eb249b5386 100644 --- a/engine/storage/configdrive/pom.xml +++ b/engine/storage/configdrive/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/datamotion/pom.xml b/engine/storage/datamotion/pom.xml index aa5a6d1f8f87..d3b6bb543520 100644 --- a/engine/storage/datamotion/pom.xml +++ b/engine/storage/datamotion/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/image/pom.xml b/engine/storage/image/pom.xml index 0232e404f5a7..2cdef352e7b8 100644 --- a/engine/storage/image/pom.xml +++ b/engine/storage/image/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/integration-test/pom.xml b/engine/storage/integration-test/pom.xml index 2ca31465e08d..13f0b45dd21f 100644 --- a/engine/storage/integration-test/pom.xml +++ b/engine/storage/integration-test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/object/pom.xml b/engine/storage/object/pom.xml index a9e3aeac4674..d5f2a793948c 100644 --- a/engine/storage/object/pom.xml +++ b/engine/storage/object/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/pom.xml b/engine/storage/pom.xml index 72e9def90a3d..a2044c6f4f86 100644 --- a/engine/storage/pom.xml +++ b/engine/storage/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/storage/snapshot/pom.xml b/engine/storage/snapshot/pom.xml index 14b0ce92f8b0..177ab1069d9f 100644 --- a/engine/storage/snapshot/pom.xml +++ b/engine/storage/snapshot/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/volume/pom.xml b/engine/storage/volume/pom.xml index 42399bc75622..5422218ef02b 100644 --- a/engine/storage/volume/pom.xml +++ b/engine/storage/volume/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/userdata/cloud-init/pom.xml b/engine/userdata/cloud-init/pom.xml index 2b5d55948ede..a49a8ece0cef 100644 --- a/engine/userdata/cloud-init/pom.xml +++ b/engine/userdata/cloud-init/pom.xml @@ -23,7 +23,7 @@ cloud-engine org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/engine/userdata/pom.xml b/engine/userdata/pom.xml index 001825841565..56c181ae0602 100644 --- a/engine/userdata/pom.xml +++ b/engine/userdata/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/agent-lb/pom.xml b/framework/agent-lb/pom.xml index cba750d3c7fb..b9d728f9bdc6 100644 --- a/framework/agent-lb/pom.xml +++ b/framework/agent-lb/pom.xml @@ -24,7 +24,7 @@ cloudstack-framework org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/ca/pom.xml b/framework/ca/pom.xml index e7d85c089bb7..b090dbe40df8 100644 --- a/framework/ca/pom.xml +++ b/framework/ca/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/cluster/pom.xml b/framework/cluster/pom.xml index da3de5ebf751..2dd28e8e628f 100644 --- a/framework/cluster/pom.xml +++ b/framework/cluster/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/config/pom.xml b/framework/config/pom.xml index 9fbbc4a0cd72..dea2f14420ec 100644 --- a/framework/config/pom.xml +++ b/framework/config/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/db/pom.xml b/framework/db/pom.xml index c03fb0654bd8..04d0fcc7e899 100644 --- a/framework/db/pom.xml +++ b/framework/db/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/direct-download/pom.xml b/framework/direct-download/pom.xml index 913bef072a04..22e878178ae4 100644 --- a/framework/direct-download/pom.xml +++ b/framework/direct-download/pom.xml @@ -32,7 +32,7 @@ cloudstack-framework org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/events/pom.xml b/framework/events/pom.xml index 173b18a4df2e..3a0d37468688 100644 --- a/framework/events/pom.xml +++ b/framework/events/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/extensions/pom.xml b/framework/extensions/pom.xml index 76952555698a..4d42d6840ddf 100644 --- a/framework/extensions/pom.xml +++ b/framework/extensions/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml @@ -41,13 +41,13 @@ org.apache.cloudstack cloud-engine-schema - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT compile org.apache.cloudstack cloud-engine-components-api - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT compile diff --git a/framework/ipc/pom.xml b/framework/ipc/pom.xml index 0d13898f3f84..d9fa9d852714 100644 --- a/framework/ipc/pom.xml +++ b/framework/ipc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/jobs/pom.xml b/framework/jobs/pom.xml index f3249727c3a4..ad342acf8d83 100644 --- a/framework/jobs/pom.xml +++ b/framework/jobs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/managed-context/pom.xml b/framework/managed-context/pom.xml index c0950855ef45..70f20ecb04e3 100644 --- a/framework/managed-context/pom.xml +++ b/framework/managed-context/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/framework/pom.xml b/framework/pom.xml index 02ce0a09b817..337e5b0268b2 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/framework/quota/pom.xml b/framework/quota/pom.xml index 52f0d658f009..70cb3ac8cd53 100644 --- a/framework/quota/pom.xml +++ b/framework/quota/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/rest/pom.xml b/framework/rest/pom.xml index bb69f909ba22..e2e787aec460 100644 --- a/framework/rest/pom.xml +++ b/framework/rest/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml cloud-framework-rest diff --git a/framework/security/pom.xml b/framework/security/pom.xml index a36978e4ef64..30940b68f1a9 100644 --- a/framework/security/pom.xml +++ b/framework/security/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/framework/spring/lifecycle/pom.xml b/framework/spring/lifecycle/pom.xml index ff6b59567dad..90a26a7be238 100644 --- a/framework/spring/lifecycle/pom.xml +++ b/framework/spring/lifecycle/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/framework/spring/module/pom.xml b/framework/spring/module/pom.xml index 77a9646a984e..f44cffa30b10 100644 --- a/framework/spring/module/pom.xml +++ b/framework/spring/module/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/acl/dynamic-role-based/pom.xml b/plugins/acl/dynamic-role-based/pom.xml index f975afc6e5fa..0b0b2300e777 100644 --- a/plugins/acl/dynamic-role-based/pom.xml +++ b/plugins/acl/dynamic-role-based/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/acl/project-role-based/pom.xml b/plugins/acl/project-role-based/pom.xml index ae00e850aa7e..2c351bdbc8ca 100644 --- a/plugins/acl/project-role-based/pom.xml +++ b/plugins/acl/project-role-based/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/acl/static-role-based/pom.xml b/plugins/acl/static-role-based/pom.xml index ee088e3bd432..c6e790b485e6 100644 --- a/plugins/acl/static-role-based/pom.xml +++ b/plugins/acl/static-role-based/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/explicit-dedication/pom.xml b/plugins/affinity-group-processors/explicit-dedication/pom.xml index 1d5b5303c7e5..769773e9dca3 100644 --- a/plugins/affinity-group-processors/explicit-dedication/pom.xml +++ b/plugins/affinity-group-processors/explicit-dedication/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/host-affinity/pom.xml b/plugins/affinity-group-processors/host-affinity/pom.xml index f0c13e050361..925da93b29c7 100644 --- a/plugins/affinity-group-processors/host-affinity/pom.xml +++ b/plugins/affinity-group-processors/host-affinity/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/affinity-group-processors/host-anti-affinity/pom.xml b/plugins/affinity-group-processors/host-anti-affinity/pom.xml index 00895373ca94..9b83518d7792 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 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml 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 fc626289a35d..f0bc9e0d7e93 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 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml 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 b474f7647a9b..b12e49f62ddb 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 @@ -32,7 +32,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/alert-handlers/snmp-alerts/pom.xml b/plugins/alert-handlers/snmp-alerts/pom.xml index 3beb2ce8d914..ea89037aa2be 100644 --- a/plugins/alert-handlers/snmp-alerts/pom.xml +++ b/plugins/alert-handlers/snmp-alerts/pom.xml @@ -24,7 +24,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/alert-handlers/syslog-alerts/pom.xml b/plugins/alert-handlers/syslog-alerts/pom.xml index 5241ec011436..a4ae4f089530 100644 --- a/plugins/alert-handlers/syslog-alerts/pom.xml +++ b/plugins/alert-handlers/syslog-alerts/pom.xml @@ -24,7 +24,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/discovery/pom.xml b/plugins/api/discovery/pom.xml index 525a5583e51e..b0f29612911c 100644 --- a/plugins/api/discovery/pom.xml +++ b/plugins/api/discovery/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/rate-limit/pom.xml b/plugins/api/rate-limit/pom.xml index 4d802cabac4f..294ff18bcc9b 100644 --- a/plugins/api/rate-limit/pom.xml +++ b/plugins/api/rate-limit/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/solidfire-intg-test/pom.xml b/plugins/api/solidfire-intg-test/pom.xml index ca2a0328cade..0cc9d1aba5a2 100644 --- a/plugins/api/solidfire-intg-test/pom.xml +++ b/plugins/api/solidfire-intg-test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/api/vmware-sioc/pom.xml b/plugins/api/vmware-sioc/pom.xml index 10a1853fedc1..e523568dc506 100644 --- a/plugins/api/vmware-sioc/pom.xml +++ b/plugins/api/vmware-sioc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/dummy/pom.xml b/plugins/backup/dummy/pom.xml index c15b5dcab63b..63c89cdb3e06 100644 --- a/plugins/backup/dummy/pom.xml +++ b/plugins/backup/dummy/pom.xml @@ -23,7 +23,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/nas/pom.xml b/plugins/backup/nas/pom.xml index aae1e61970cc..3c7cd8ab681f 100644 --- a/plugins/backup/nas/pom.xml +++ b/plugins/backup/nas/pom.xml @@ -25,7 +25,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/networker/pom.xml b/plugins/backup/networker/pom.xml index 55b827e29d14..cad2b3454355 100644 --- a/plugins/backup/networker/pom.xml +++ b/plugins/backup/networker/pom.xml @@ -25,7 +25,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml index dd145bc5e113..a2dcbd1d3487 100644 --- a/plugins/backup/veeam/pom.xml +++ b/plugins/backup/veeam/pom.xml @@ -23,7 +23,7 @@ cloudstack-plugins org.apache.cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/ca/root-ca/pom.xml b/plugins/ca/root-ca/pom.xml index 501ba480b80e..2224ae06c850 100644 --- a/plugins/ca/root-ca/pom.xml +++ b/plugins/ca/root-ca/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/database/mysql-ha/pom.xml b/plugins/database/mysql-ha/pom.xml index 518443c91e72..eb1b1d571f11 100644 --- a/plugins/database/mysql-ha/pom.xml +++ b/plugins/database/mysql-ha/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/database/quota/pom.xml b/plugins/database/quota/pom.xml index 5ecbefbcf2b6..52645385aaba 100644 --- a/plugins/database/quota/pom.xml +++ b/plugins/database/quota/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/dedicated-resources/pom.xml b/plugins/dedicated-resources/pom.xml index 8d4c21e9cb26..557668661f3f 100644 --- a/plugins/dedicated-resources/pom.xml +++ b/plugins/dedicated-resources/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/plugins/deployment-planners/implicit-dedication/pom.xml b/plugins/deployment-planners/implicit-dedication/pom.xml index bfc0e24a8ac8..c33d6d07293c 100644 --- a/plugins/deployment-planners/implicit-dedication/pom.xml +++ b/plugins/deployment-planners/implicit-dedication/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/deployment-planners/user-concentrated-pod/pom.xml b/plugins/deployment-planners/user-concentrated-pod/pom.xml index ebbd879076c3..20420fbb0802 100644 --- a/plugins/deployment-planners/user-concentrated-pod/pom.xml +++ b/plugins/deployment-planners/user-concentrated-pod/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/deployment-planners/user-dispersing/pom.xml b/plugins/deployment-planners/user-dispersing/pom.xml index d811b5e88d1b..85c839ceaca0 100644 --- a/plugins/deployment-planners/user-dispersing/pom.xml +++ b/plugins/deployment-planners/user-dispersing/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/drs/cluster/balanced/pom.xml b/plugins/drs/cluster/balanced/pom.xml index 5267d991b846..497dfcbede14 100644 --- a/plugins/drs/cluster/balanced/pom.xml +++ b/plugins/drs/cluster/balanced/pom.xml @@ -27,7 +27,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/drs/cluster/condensed/pom.xml b/plugins/drs/cluster/condensed/pom.xml index d34f3564db51..6e7c33ef55dc 100644 --- a/plugins/drs/cluster/condensed/pom.xml +++ b/plugins/drs/cluster/condensed/pom.xml @@ -27,7 +27,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/event-bus/inmemory/pom.xml b/plugins/event-bus/inmemory/pom.xml index 7704867bc708..5eadc3673f3c 100644 --- a/plugins/event-bus/inmemory/pom.xml +++ b/plugins/event-bus/inmemory/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/kafka/pom.xml b/plugins/event-bus/kafka/pom.xml index 5231a723baab..c57572533ad5 100644 --- a/plugins/event-bus/kafka/pom.xml +++ b/plugins/event-bus/kafka/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/rabbitmq/pom.xml b/plugins/event-bus/rabbitmq/pom.xml index 738cc7fce6dc..3012ac1057a7 100644 --- a/plugins/event-bus/rabbitmq/pom.xml +++ b/plugins/event-bus/rabbitmq/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/event-bus/webhook/pom.xml b/plugins/event-bus/webhook/pom.xml index 2bbd5b55c01b..cb19b224ce87 100644 --- a/plugins/event-bus/webhook/pom.xml +++ b/plugins/event-bus/webhook/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/ha-planners/skip-heurestics/pom.xml b/plugins/ha-planners/skip-heurestics/pom.xml index e010e7108fc6..43f3abf9cb1d 100644 --- a/plugins/ha-planners/skip-heurestics/pom.xml +++ b/plugins/ha-planners/skip-heurestics/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/host-allocators/random/pom.xml b/plugins/host-allocators/random/pom.xml index d55436e846d3..061caf1573ff 100644 --- a/plugins/host-allocators/random/pom.xml +++ b/plugins/host-allocators/random/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/baremetal/pom.xml b/plugins/hypervisors/baremetal/pom.xml index 384090e04507..fc4803b79029 100755 --- a/plugins/hypervisors/baremetal/pom.xml +++ b/plugins/hypervisors/baremetal/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-baremetal diff --git a/plugins/hypervisors/external/pom.xml b/plugins/hypervisors/external/pom.xml index 8359b1de3ca3..1c581f3c0e5f 100644 --- a/plugins/hypervisors/external/pom.xml +++ b/plugins/hypervisors/external/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-external diff --git a/plugins/hypervisors/hyperv/pom.xml b/plugins/hypervisors/hyperv/pom.xml index 758cee0ee63f..c67943ab2430 100644 --- a/plugins/hypervisors/hyperv/pom.xml +++ b/plugins/hypervisors/hyperv/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index 8ed960c6bc0c..a7c5461e7980 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/ovm/pom.xml b/plugins/hypervisors/ovm/pom.xml index 2149926348ee..d29f9afa73a0 100644 --- a/plugins/hypervisors/ovm/pom.xml +++ b/plugins/hypervisors/ovm/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/ovm3/pom.xml b/plugins/hypervisors/ovm3/pom.xml index c88284c8d297..4214ee8a71b3 100644 --- a/plugins/hypervisors/ovm3/pom.xml +++ b/plugins/hypervisors/ovm3/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/simulator/pom.xml b/plugins/hypervisors/simulator/pom.xml index f5942328e3cf..8ef011fa9710 100644 --- a/plugins/hypervisors/simulator/pom.xml +++ b/plugins/hypervisors/simulator/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-simulator diff --git a/plugins/hypervisors/ucs/pom.xml b/plugins/hypervisors/ucs/pom.xml index 521dd7416b5c..67bde3b64294 100644 --- a/plugins/hypervisors/ucs/pom.xml +++ b/plugins/hypervisors/ucs/pom.xml @@ -23,7 +23,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml cloud-plugin-hypervisor-ucs diff --git a/plugins/hypervisors/vmware/pom.xml b/plugins/hypervisors/vmware/pom.xml index 355d4488ba90..7637cb7db6d0 100644 --- a/plugins/hypervisors/vmware/pom.xml +++ b/plugins/hypervisors/vmware/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/hypervisors/xenserver/pom.xml b/plugins/hypervisors/xenserver/pom.xml index cafa999d42dc..4d7ebf69db21 100644 --- a/plugins/hypervisors/xenserver/pom.xml +++ b/plugins/hypervisors/xenserver/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/cloudian/pom.xml b/plugins/integrations/cloudian/pom.xml index 69321f9e558c..1c806acec7ad 100644 --- a/plugins/integrations/cloudian/pom.xml +++ b/plugins/integrations/cloudian/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/kubernetes-service/pom.xml b/plugins/integrations/kubernetes-service/pom.xml index 9aad4d6d4304..6b5cec95aab9 100644 --- a/plugins/integrations/kubernetes-service/pom.xml +++ b/plugins/integrations/kubernetes-service/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/integrations/prometheus/pom.xml b/plugins/integrations/prometheus/pom.xml index 378eab52471f..6f6b34215a76 100644 --- a/plugins/integrations/prometheus/pom.xml +++ b/plugins/integrations/prometheus/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/maintenance/pom.xml b/plugins/maintenance/pom.xml index 5290b5277674..5b129863a297 100644 --- a/plugins/maintenance/pom.xml +++ b/plugins/maintenance/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/plugins/metrics/pom.xml b/plugins/metrics/pom.xml index 6cba7d8bd043..7f8e56de1835 100644 --- a/plugins/metrics/pom.xml +++ b/plugins/metrics/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/plugins/network-elements/bigswitch/pom.xml b/plugins/network-elements/bigswitch/pom.xml index b1a163eb3ee3..cd5ca8b5e999 100644 --- a/plugins/network-elements/bigswitch/pom.xml +++ b/plugins/network-elements/bigswitch/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/brocade-vcs/pom.xml b/plugins/network-elements/brocade-vcs/pom.xml index e3d7a6cf3adc..1eb716a6f9b0 100644 --- a/plugins/network-elements/brocade-vcs/pom.xml +++ b/plugins/network-elements/brocade-vcs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/cisco-vnmc/pom.xml b/plugins/network-elements/cisco-vnmc/pom.xml index 56ff642f549a..c183d03259ae 100644 --- a/plugins/network-elements/cisco-vnmc/pom.xml +++ b/plugins/network-elements/cisco-vnmc/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/dns-notifier/pom.xml b/plugins/network-elements/dns-notifier/pom.xml index 7f87c49a0d48..b57bfb94e69a 100644 --- a/plugins/network-elements/dns-notifier/pom.xml +++ b/plugins/network-elements/dns-notifier/pom.xml @@ -22,7 +22,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml cloud-plugin-example-dns-notifier diff --git a/plugins/network-elements/elastic-loadbalancer/pom.xml b/plugins/network-elements/elastic-loadbalancer/pom.xml index 21ed6e32429b..22c61f71087d 100644 --- a/plugins/network-elements/elastic-loadbalancer/pom.xml +++ b/plugins/network-elements/elastic-loadbalancer/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/globodns/pom.xml b/plugins/network-elements/globodns/pom.xml index a03bb1e3cd26..70bf2287e2ac 100644 --- a/plugins/network-elements/globodns/pom.xml +++ b/plugins/network-elements/globodns/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/internal-loadbalancer/pom.xml b/plugins/network-elements/internal-loadbalancer/pom.xml index beb5a54f933b..2051a7c25bb5 100644 --- a/plugins/network-elements/internal-loadbalancer/pom.xml +++ b/plugins/network-elements/internal-loadbalancer/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/juniper-contrail/pom.xml b/plugins/network-elements/juniper-contrail/pom.xml index 0b3f8bf0d203..6d76cedf8ddb 100644 --- a/plugins/network-elements/juniper-contrail/pom.xml +++ b/plugins/network-elements/juniper-contrail/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/netris/pom.xml b/plugins/network-elements/netris/pom.xml index 1a3fe5d7e29b..295fa643097f 100644 --- a/plugins/network-elements/netris/pom.xml +++ b/plugins/network-elements/netris/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/netscaler/pom.xml b/plugins/network-elements/netscaler/pom.xml index ab51db5b4809..88a8880e5095 100644 --- a/plugins/network-elements/netscaler/pom.xml +++ b/plugins/network-elements/netscaler/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/nicira-nvp/pom.xml b/plugins/network-elements/nicira-nvp/pom.xml index 40d4863decdf..10a584312071 100644 --- a/plugins/network-elements/nicira-nvp/pom.xml +++ b/plugins/network-elements/nicira-nvp/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/nsx/pom.xml b/plugins/network-elements/nsx/pom.xml index 7dc31d625f3c..e6431d074cac 100644 --- a/plugins/network-elements/nsx/pom.xml +++ b/plugins/network-elements/nsx/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/opendaylight/pom.xml b/plugins/network-elements/opendaylight/pom.xml index a71b86dfecbf..38934e16da2c 100644 --- a/plugins/network-elements/opendaylight/pom.xml +++ b/plugins/network-elements/opendaylight/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/ovs/pom.xml b/plugins/network-elements/ovs/pom.xml index 24f622548c23..ef1326cca6ee 100644 --- a/plugins/network-elements/ovs/pom.xml +++ b/plugins/network-elements/ovs/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/palo-alto/pom.xml b/plugins/network-elements/palo-alto/pom.xml index 3e821ec8109a..ee16eccb162d 100644 --- a/plugins/network-elements/palo-alto/pom.xml +++ b/plugins/network-elements/palo-alto/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/stratosphere-ssp/pom.xml b/plugins/network-elements/stratosphere-ssp/pom.xml index 1703012eff36..0288c8a5dd79 100644 --- a/plugins/network-elements/stratosphere-ssp/pom.xml +++ b/plugins/network-elements/stratosphere-ssp/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/tungsten/pom.xml b/plugins/network-elements/tungsten/pom.xml index 793d754baffc..eb83c468f0c4 100644 --- a/plugins/network-elements/tungsten/pom.xml +++ b/plugins/network-elements/tungsten/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/network-elements/vxlan/pom.xml b/plugins/network-elements/vxlan/pom.xml index 1192c98acafa..e350a3cb98f6 100644 --- a/plugins/network-elements/vxlan/pom.xml +++ b/plugins/network-elements/vxlan/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml index debeb7daab29..82ee440c7eee 100644 --- a/plugins/outofbandmanagement-drivers/ipmitool/pom.xml +++ b/plugins/outofbandmanagement-drivers/ipmitool/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml index 9c5e44195abd..aefaf6d56569 100644 --- a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml +++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/outofbandmanagement-drivers/redfish/pom.xml b/plugins/outofbandmanagement-drivers/redfish/pom.xml index 0d1b292812d7..eecfe1a7add5 100644 --- a/plugins/outofbandmanagement-drivers/redfish/pom.xml +++ b/plugins/outofbandmanagement-drivers/redfish/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/pom.xml b/plugins/pom.xml index 16e5ed1d8f59..e7d13871285e 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/plugins/storage-allocators/random/pom.xml b/plugins/storage-allocators/random/pom.xml index b1a1c2604a74..db55faba4f11 100644 --- a/plugins/storage-allocators/random/pom.xml +++ b/plugins/storage-allocators/random/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/storage/image/default/pom.xml b/plugins/storage/image/default/pom.xml index 1bac038c6a97..0696ca054ff0 100644 --- a/plugins/storage/image/default/pom.xml +++ b/plugins/storage/image/default/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/s3/pom.xml b/plugins/storage/image/s3/pom.xml index 9c3ea94130dd..607830bc2e82 100644 --- a/plugins/storage/image/s3/pom.xml +++ b/plugins/storage/image/s3/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/sample/pom.xml b/plugins/storage/image/sample/pom.xml index 586eb10f28f8..e7044868e7bf 100644 --- a/plugins/storage/image/sample/pom.xml +++ b/plugins/storage/image/sample/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/image/swift/pom.xml b/plugins/storage/image/swift/pom.xml index 407106ce935e..1caffdae6c72 100644 --- a/plugins/storage/image/swift/pom.xml +++ b/plugins/storage/image/swift/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/object/ceph/pom.xml b/plugins/storage/object/ceph/pom.xml index f6138add95d3..1f5a4bc50551 100644 --- a/plugins/storage/object/ceph/pom.xml +++ b/plugins/storage/object/ceph/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/object/cloudian/pom.xml b/plugins/storage/object/cloudian/pom.xml index 81c78a8d346a..818469290def 100644 --- a/plugins/storage/object/cloudian/pom.xml +++ b/plugins/storage/object/cloudian/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/object/minio/pom.xml b/plugins/storage/object/minio/pom.xml index 61b9838de49a..d69c2a7498fa 100644 --- a/plugins/storage/object/minio/pom.xml +++ b/plugins/storage/object/minio/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/object/simulator/pom.xml b/plugins/storage/object/simulator/pom.xml index 6efbd1c41aa3..dc4ab0146067 100644 --- a/plugins/storage/object/simulator/pom.xml +++ b/plugins/storage/object/simulator/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/sharedfs/storagevm/pom.xml b/plugins/storage/sharedfs/storagevm/pom.xml index 34594898cc05..2a4ea7411e0d 100644 --- a/plugins/storage/sharedfs/storagevm/pom.xml +++ b/plugins/storage/sharedfs/storagevm/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/adaptive/pom.xml b/plugins/storage/volume/adaptive/pom.xml index e625cd66f53b..337544b9d887 100644 --- a/plugins/storage/volume/adaptive/pom.xml +++ b/plugins/storage/volume/adaptive/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/cloudbyte/pom.xml b/plugins/storage/volume/cloudbyte/pom.xml index 77dadb4ec8fe..453a03ee155d 100644 --- a/plugins/storage/volume/cloudbyte/pom.xml +++ b/plugins/storage/volume/cloudbyte/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/datera/pom.xml b/plugins/storage/volume/datera/pom.xml index 84a5e4c27480..6c4b5808a890 100644 --- a/plugins/storage/volume/datera/pom.xml +++ b/plugins/storage/volume/datera/pom.xml @@ -16,7 +16,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/default/pom.xml b/plugins/storage/volume/default/pom.xml index 02e2fb88b229..03d26fb7fd19 100644 --- a/plugins/storage/volume/default/pom.xml +++ b/plugins/storage/volume/default/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/flasharray/pom.xml b/plugins/storage/volume/flasharray/pom.xml index a6da4cf9d582..5adad7a3cdf6 100644 --- a/plugins/storage/volume/flasharray/pom.xml +++ b/plugins/storage/volume/flasharray/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/linstor/pom.xml b/plugins/storage/volume/linstor/pom.xml index ee72aab66a38..0b78f0e26a1e 100644 --- a/plugins/storage/volume/linstor/pom.xml +++ b/plugins/storage/volume/linstor/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/nexenta/pom.xml b/plugins/storage/volume/nexenta/pom.xml index d9ed6fd64a49..d1de59bd24e4 100644 --- a/plugins/storage/volume/nexenta/pom.xml +++ b/plugins/storage/volume/nexenta/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/primera/pom.xml b/plugins/storage/volume/primera/pom.xml index 4f1583a8a7b3..ac4351b2f926 100644 --- a/plugins/storage/volume/primera/pom.xml +++ b/plugins/storage/volume/primera/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/sample/pom.xml b/plugins/storage/volume/sample/pom.xml index 0a9de06ba623..425303a9eb26 100644 --- a/plugins/storage/volume/sample/pom.xml +++ b/plugins/storage/volume/sample/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/scaleio/pom.xml b/plugins/storage/volume/scaleio/pom.xml index 79a707ba7d54..b16640482c8c 100644 --- a/plugins/storage/volume/scaleio/pom.xml +++ b/plugins/storage/volume/scaleio/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/solidfire/pom.xml b/plugins/storage/volume/solidfire/pom.xml index 65a91afc10e3..65ba90b0c386 100644 --- a/plugins/storage/volume/solidfire/pom.xml +++ b/plugins/storage/volume/solidfire/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/storage/volume/storpool/pom.xml b/plugins/storage/volume/storpool/pom.xml index 0829575b2295..7bdc76da81fc 100644 --- a/plugins/storage/volume/storpool/pom.xml +++ b/plugins/storage/volume/storpool/pom.xml @@ -17,7 +17,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../../pom.xml diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index 7f58d8fa28a7..c02d3d511e6e 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/md5/pom.xml b/plugins/user-authenticators/md5/pom.xml index 11f0a4272307..f0f998c42c16 100644 --- a/plugins/user-authenticators/md5/pom.xml +++ b/plugins/user-authenticators/md5/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/oauth2/pom.xml b/plugins/user-authenticators/oauth2/pom.xml index 8caf06ff8143..6ab7b9f5faba 100644 --- a/plugins/user-authenticators/oauth2/pom.xml +++ b/plugins/user-authenticators/oauth2/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/pbkdf2/pom.xml b/plugins/user-authenticators/pbkdf2/pom.xml index 75351f20f3f4..6ffa1188e13c 100644 --- a/plugins/user-authenticators/pbkdf2/pom.xml +++ b/plugins/user-authenticators/pbkdf2/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/plain-text/pom.xml b/plugins/user-authenticators/plain-text/pom.xml index ceddc54e34d4..54a9650b64ff 100644 --- a/plugins/user-authenticators/plain-text/pom.xml +++ b/plugins/user-authenticators/plain-text/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/saml2/pom.xml b/plugins/user-authenticators/saml2/pom.xml index 8802ab9260f0..4cd6f70d096c 100644 --- a/plugins/user-authenticators/saml2/pom.xml +++ b/plugins/user-authenticators/saml2/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-authenticators/sha256salted/pom.xml b/plugins/user-authenticators/sha256salted/pom.xml index 2938e21e2094..2b47e010c26d 100644 --- a/plugins/user-authenticators/sha256salted/pom.xml +++ b/plugins/user-authenticators/sha256salted/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-two-factor-authenticators/static-pin/pom.xml b/plugins/user-two-factor-authenticators/static-pin/pom.xml index 1fe95fa3913d..522e2451e7e4 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 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/plugins/user-two-factor-authenticators/totp/pom.xml b/plugins/user-two-factor-authenticators/totp/pom.xml index dcf051f92170..eb3cc0b1ca61 100644 --- a/plugins/user-two-factor-authenticators/totp/pom.xml +++ b/plugins/user-two-factor-authenticators/totp/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-plugins - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index cc2cb759b1cc..2a36c1cc4efa 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT pom Apache CloudStack Apache CloudStack is an IaaS ("Infrastructure as a Service") cloud orchestration platform. diff --git a/quickcloud/pom.xml b/quickcloud/pom.xml index d4d759f8986a..cc0bacbd7e77 100644 --- a/quickcloud/pom.xml +++ b/quickcloud/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/server/pom.xml b/server/pom.xml index 3324cdb2e619..2b35a0f42ac8 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT @@ -194,7 +194,7 @@ org.apache.cloudstack cloud-framework-extensions - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT compile diff --git a/services/console-proxy/pom.xml b/services/console-proxy/pom.xml index 43d32fdf61b9..31a5a13a8821 100644 --- a/services/console-proxy/pom.xml +++ b/services/console-proxy/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-services - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/console-proxy/rdpconsole/pom.xml b/services/console-proxy/rdpconsole/pom.xml index 541648827c66..058ea891f9cd 100644 --- a/services/console-proxy/rdpconsole/pom.xml +++ b/services/console-proxy/rdpconsole/pom.xml @@ -26,7 +26,7 @@ org.apache.cloudstack cloudstack-service-console-proxy - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/console-proxy/server/pom.xml b/services/console-proxy/server/pom.xml index cff9ae3cfcb6..3f5b9db68c2e 100644 --- a/services/console-proxy/server/pom.xml +++ b/services/console-proxy/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-console-proxy - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/pom.xml b/services/pom.xml index 2205e460ee0c..b6a456b159d8 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/controller/pom.xml b/services/secondary-storage/controller/pom.xml index e3847e294fc1..d8934bbb08bf 100644 --- a/services/secondary-storage/controller/pom.xml +++ b/services/secondary-storage/controller/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-secondary-storage - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/pom.xml b/services/secondary-storage/pom.xml index c1d3f9016151..0cc0115c0caa 100644 --- a/services/secondary-storage/pom.xml +++ b/services/secondary-storage/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack-services - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/services/secondary-storage/server/pom.xml b/services/secondary-storage/server/pom.xml index ca878ce9ba61..e6aec8a42f70 100644 --- a/services/secondary-storage/server/pom.xml +++ b/services/secondary-storage/server/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-service-secondary-storage - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/systemvm/pom.xml b/systemvm/pom.xml index e2a0315300ba..9bffc45cf4eb 100644 --- a/systemvm/pom.xml +++ b/systemvm/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/test/pom.xml b/test/pom.xml index dd1bdf4ceb84..7f315bb92158 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/tools/apidoc/pom.xml b/tools/apidoc/pom.xml index e9b81eedd5fb..b0e081df97f4 100644 --- a/tools/apidoc/pom.xml +++ b/tools/apidoc/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/tools/checkstyle/pom.xml b/tools/checkstyle/pom.xml index 0fb0efacd608..63f840d64f21 100644 --- a/tools/checkstyle/pom.xml +++ b/tools/checkstyle/pom.xml @@ -22,7 +22,7 @@ Apache CloudStack Developer Tools - Checkstyle Configuration org.apache.cloudstack checkstyle - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT UTF-8 diff --git a/tools/devcloud-kvm/pom.xml b/tools/devcloud-kvm/pom.xml index 4990f0af8be3..a8cd23db9799 100644 --- a/tools/devcloud-kvm/pom.xml +++ b/tools/devcloud-kvm/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/tools/devcloud4/pom.xml b/tools/devcloud4/pom.xml index 1aa48c4b2d0d..1af63b439ad7 100644 --- a/tools/devcloud4/pom.xml +++ b/tools/devcloud4/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 67a986e994ab..fd813e077fd1 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -19,7 +19,7 @@ FROM ubuntu:22.04 -LABEL Vendor="Apache.org" License="ApacheV2" Version="4.22.1.0-SNAPSHOT" Author="Apache CloudStack " +LABEL Vendor="Apache.org" License="ApacheV2" Version="4.23.0.0-SNAPSHOT" Author="Apache CloudStack " ARG DEBIAN_FRONTEND=noninteractive diff --git a/tools/docker/Dockerfile.marvin b/tools/docker/Dockerfile.marvin index d615a78eb6bb..f0a55109402a 100644 --- a/tools/docker/Dockerfile.marvin +++ b/tools/docker/Dockerfile.marvin @@ -19,11 +19,11 @@ # build for cloudstack_home_dir not this folder FROM python:2 -LABEL Vendor="Apache.org" License="ApacheV2" Version="4.22.1.0-SNAPSHOT" Author="Apache CloudStack " +LABEL Vendor="Apache.org" License="ApacheV2" Version="4.23.0.0-SNAPSHOT" Author="Apache CloudStack " ENV WORK_DIR=/marvin -ENV PKG_URL=https://builds.cloudstack.org/job/build-master-marvin/lastSuccessfulBuild/artifact/tools/marvin/dist/Marvin-4.22.1.0-SNAPSHOT.tar.gz +ENV PKG_URL=https://builds.cloudstack.org/job/build-master-marvin/lastSuccessfulBuild/artifact/tools/marvin/dist/Marvin-4.23.0.0-SNAPSHOT.tar.gz RUN apt-get update && apt-get install -y vim RUN pip install --upgrade paramiko nose requests diff --git a/tools/marvin/pom.xml b/tools/marvin/pom.xml index 907e9931015d..8e81a394685c 100644 --- a/tools/marvin/pom.xml +++ b/tools/marvin/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloud-tools - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index ad0f245d68f3..05ce9d41023c 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.22.1.0-SNAPSHOT" +VERSION = "4.23.0.0-SNAPSHOT" setup(name="Marvin", version=VERSION, diff --git a/tools/pom.xml b/tools/pom.xml index ffa363d4a265..00f055a42d4a 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/usage/pom.xml b/usage/pom.xml index 7f5b0ba527d5..e1e0b0f17c7a 100644 --- a/usage/pom.xml +++ b/usage/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/utils/pom.xml b/utils/pom.xml index 530e1131f78e..fe0f696e52c7 100755 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/vmware-base/pom.xml b/vmware-base/pom.xml index 9b7b989a8901..7c405176189c 100644 --- a/vmware-base/pom.xml +++ b/vmware-base/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.22.1.0-SNAPSHOT + 4.23.0.0-SNAPSHOT From a50de029bf1d22c2a9ef0de8591f61de0f350f92 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Thu, 6 Nov 2025 05:09:00 -0500 Subject: [PATCH 03/65] Add empty Provider value in Network/VPC Offering form (#11982) --- ui/src/views/offering/AddNetworkOffering.vue | 1 + ui/src/views/offering/AddVpcOffering.vue | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue index 2b3275a11ed5..abb70abda27a 100644 --- a/ui/src/views/offering/AddNetworkOffering.vue +++ b/ui/src/views/offering/AddNetworkOffering.vue @@ -135,6 +135,7 @@ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" :placeholder="apiParams.provider.description" > + {{ }} {{ $t('label.nsx') }} {{ $t('label.netris') }} diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue index d909cbdc3dca..32aa3e8d3583 100644 --- a/ui/src/views/offering/AddVpcOffering.vue +++ b/ui/src/views/offering/AddVpcOffering.vue @@ -83,6 +83,7 @@ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" :placeholder="apiParams.provider.description" > + {{ }} {{ $t('label.nsx') }} {{ $t('label.netris') }} From 8c86f24261c5133831d8412aec6067a794a701dd Mon Sep 17 00:00:00 2001 From: Phsm Qwerty Date: Fri, 7 Nov 2025 14:31:34 +0100 Subject: [PATCH 04/65] enhancement: add instance info as Libvirt metadata (#11061) --- .../api/to/VirtualMachineMetadataTO.java | 182 +++++++++++++++ .../cloud/agent/api/to/VirtualMachineTO.java | 9 + .../resource/LibvirtComputingResource.java | 12 + .../hypervisor/kvm/resource/LibvirtVMDef.java | 209 ++++++++++++++---- .../cloud/hypervisor/HypervisorGuruBase.java | 170 +++++++++++++- .../java/com/cloud/hypervisor/KVMGuru.java | 4 +- 6 files changed, 547 insertions(+), 39 deletions(-) create mode 100644 api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java new file mode 100644 index 000000000000..5b22afdedd53 --- /dev/null +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api.to; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VirtualMachineMetadataTO { + // VM details + private final String name; + private final String internalName; + private final String displayName; + private final String instanceUuid; + private final Integer cpuCores; + private final Integer memory; + private final Long created; + private final Long started; + + // Owner details + private final String ownerDomainUuid; + private final String ownerDomainName; + private final String ownerAccountUuid; + private final String ownerAccountName; + private final String ownerProjectUuid; + private final String ownerProjectName; + + // Host and service offering + private final String serviceOfferingName; + private final List serviceOfferingHostTags; + + // zone, pod, and cluster details + private final String zoneName; + private final String zoneUuid; + private final String podName; + private final String podUuid; + private final String clusterName; + private final String clusterUuid; + + // resource tags + private final Map resourceTags; + + public VirtualMachineMetadataTO( + String name, String internalName, String displayName, String instanceUuid, Integer cpuCores, Integer memory, Long created, Long started, + String ownerDomainUuid, String ownerDomainName, String ownerAccountUuid, String ownerAccountName, String ownerProjectUuid, String ownerProjectName, + String serviceOfferingName, List serviceOfferingHostTags, + String zoneName, String zoneUuid, String podName, String podUuid, String clusterName, String clusterUuid, Map resourceTags) { + /* + * Something failed in the metadata shall not be a fatal error, the VM can still be started + * Thus, the unknown fields just get an explicit "unknown" value so it can be fixed in case + * there are bugs on some execution paths. + * */ + + this.name = (name != null) ? name : "unknown"; + this.internalName = (internalName != null) ? internalName : "unknown"; + this.displayName = (displayName != null) ? displayName : "unknown"; + this.instanceUuid = (instanceUuid != null) ? instanceUuid : "unknown"; + this.cpuCores = (cpuCores != null) ? cpuCores : -1; + this.memory = (memory != null) ? memory : -1; + this.created = (created != null) ? created : 0; + this.started = (started != null) ? started : 0; + this.ownerDomainUuid = (ownerDomainUuid != null) ? ownerDomainUuid : "unknown"; + this.ownerDomainName = (ownerDomainName != null) ? ownerDomainName : "unknown"; + this.ownerAccountUuid = (ownerAccountUuid != null) ? ownerAccountUuid : "unknown"; + this.ownerAccountName = (ownerAccountName != null) ? ownerAccountName : "unknown"; + this.ownerProjectUuid = (ownerProjectUuid != null) ? ownerProjectUuid : "unknown"; + this.ownerProjectName = (ownerProjectName != null) ? ownerProjectName : "unknown"; + this.serviceOfferingName = (serviceOfferingName != null) ? serviceOfferingName : "unknown"; + this.serviceOfferingHostTags = (serviceOfferingHostTags != null) ? serviceOfferingHostTags : new ArrayList<>(); + this.zoneName = (zoneName != null) ? zoneName : "unknown"; + this.zoneUuid = (zoneUuid != null) ? zoneUuid : "unknown"; + this.podName = (podName != null) ? podName : "unknown"; + this.podUuid = (podUuid != null) ? podUuid : "unknown"; + this.clusterName = (clusterName != null) ? clusterName : "unknown"; + this.clusterUuid = (clusterUuid != null) ? clusterUuid : "unknown"; + + this.resourceTags = (resourceTags != null) ? resourceTags : new HashMap<>(); + } + + public String getName() { + return name; + } + + public String getInternalName() { + return internalName; + } + + public String getDisplayName() { + return displayName; + } + + public String getInstanceUuid() { + return instanceUuid; + } + + public Integer getCpuCores() { + return cpuCores; + } + + public Integer getMemory() { + return memory; + } + + public Long getCreated() { return created; } + + public Long getStarted() { + return started; + } + + public String getOwnerDomainUuid() { + return ownerDomainUuid; + } + + public String getOwnerDomainName() { + return ownerDomainName; + } + + public String getOwnerAccountUuid() { + return ownerAccountUuid; + } + + public String getOwnerAccountName() { + return ownerAccountName; + } + + public String getOwnerProjectUuid() { + return ownerProjectUuid; + } + + public String getOwnerProjectName() { + return ownerProjectName; + } + + public String getserviceOfferingName() { + return serviceOfferingName; + } + + public List getserviceOfferingHostTags() { + return serviceOfferingHostTags; + } + + public String getZoneName() { + return zoneName; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public String getPodName() { + return podName; + } + + public String getPodUuid() { + return podUuid; + } + + public String getClusterName() { + return clusterName; + } + + public String getClusterUuid() { + return clusterUuid; + } + + public Map getResourceTags() { return resourceTags; } +} diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java index cffb98740805..e26cc1e9f029 100644 --- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java @@ -89,6 +89,7 @@ public class VirtualMachineTO { private DeployAsIsInfoTO deployAsIsInfo; private String metadataManufacturer; private String metadataProductName; + private VirtualMachineMetadataTO metadata; public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader, String os, boolean enableHA, boolean limitCpuUse, String vncPassword) { @@ -494,6 +495,14 @@ public void setMetadataProductName(String metadataProductName) { this.metadataProductName = metadataProductName; } + public VirtualMachineMetadataTO getMetadata() { + return metadata; + } + + public void setMetadata(VirtualMachineMetadataTO metadata) { + this.metadata = metadata; + } + @Override public String toString() { return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); 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 0aa094e56d99..fbfe3ef20eb0 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 @@ -69,6 +69,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import com.cloud.agent.api.to.VirtualMachineMetadataTO; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.command.CommandInfo; import org.apache.cloudstack.command.ReconcileCommandService; @@ -199,6 +200,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MetadataDef; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceAgentExecutor; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceExecutor; import com.cloud.hypervisor.kvm.resource.rolling.maintenance.RollingMaintenanceServiceExecutor; @@ -3011,9 +3013,19 @@ private void configureVM(VirtualMachineTO vmTO, LibvirtVMDef vm, Map components = new HashMap(); private static final int NUMBER_OF_IOTHREADS = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IOTHREADS); + public static class MetadataDef { + private VirtualMachineMetadataTO metaTO; + + public MetadataDef(VirtualMachineMetadataTO data) { + metaTO = data; + } + + public VirtualMachineMetadataTO getMetadata() { + return metaTO; + } + + @Override + public String toString() { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setNamespaceAware(true); + Document doc = null; + try { + doc = docFactory.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + LOGGER.warn("Could not create a new DOM XML document. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + Element metadata = doc.createElement("metadata"); // + Element instance = doc.createElementNS("http://cloudstack.apache.org/instance", "cloudstack:instance"); // + + Element zone = doc.createElement("cloudstack:zone"); + zone.setAttribute("uuid", metaTO.getZoneUuid()); + zone.setTextContent(metaTO.getZoneName()); + instance.appendChild(zone); + + Element pod = doc.createElement("cloudstack:pod"); + pod.setAttribute("uuid", metaTO.getPodUuid()); + pod.setTextContent(metaTO.getPodName()); + instance.appendChild(pod); + + Element cluster = doc.createElement("cloudstack:cluster"); + cluster.setAttribute("uuid", metaTO.getClusterUuid()); + cluster.setTextContent(metaTO.getClusterName()); + instance.appendChild(cluster); + + Element instanceName = doc.createElement("cloudstack:name"); + instanceName.setTextContent(metaTO.getName()); + instance.appendChild(instanceName); + + Element instanceInternalName = doc.createElement("cloudstack:internal_name"); + instanceInternalName.setTextContent(metaTO.getInternalName()); + instance.appendChild(instanceInternalName); + + Element instanceDisplayName = doc.createElement("cloudstack:display_name"); + instanceDisplayName.setTextContent(metaTO.getDisplayName()); + instance.appendChild(instanceDisplayName); + + Element instanceUuid = doc.createElement("cloudstack:uuid"); + instanceUuid.setTextContent(metaTO.getInstanceUuid()); + instance.appendChild(instanceUuid); + + Element serviceOffering = doc.createElement("cloudstack:service_offering"); // + + Element computeOfferingName = doc.createElement("cloudstack:name"); + computeOfferingName.setTextContent(metaTO.getserviceOfferingName()); + serviceOffering.appendChild(computeOfferingName); + + Element cpu = doc.createElement("cloudstack:cpu"); + cpu.setTextContent(metaTO.getCpuCores().toString()); + serviceOffering.appendChild(cpu); + + Element memory = doc.createElement("cloudstack:memory"); + memory.setTextContent(metaTO.getMemory().toString()); + serviceOffering.appendChild(memory); + + Element hostTags = doc.createElement("cloudstack:host_tags"); + List tags = metaTO.getserviceOfferingHostTags(); + if (tags != null) { + for (String i : metaTO.getserviceOfferingHostTags()) { + Element tag = doc.createElement("cloudstack:tag"); + tag.setTextContent(i); + hostTags.appendChild(tag); + } + } + serviceOffering.appendChild(hostTags); + + instance.appendChild(serviceOffering); // + + Element creationTime = doc.createElement("cloudstack:created_at"); + creationTime.setTextContent( + LocalDateTime.ofInstant(Instant.ofEpochSecond(metaTO.getCreated()), ZoneOffset.UTC).format(ISO_LOCAL_DATE_TIME) + ); + instance.appendChild(creationTime); + + Element startedTime = doc.createElement("cloudstack:started_at"); + startedTime.setTextContent( + LocalDateTime.ofInstant(Instant.ofEpochSecond(metaTO.getStarted()), ZoneOffset.UTC).format(ISO_LOCAL_DATE_TIME) + ); + instance.appendChild(startedTime); + + Element owner = doc.createElement("cloudstack:owner"); // + + Element domain = doc.createElement("cloudstack:domain"); + domain.setAttribute("uuid", metaTO.getOwnerDomainUuid()); + domain.setTextContent(metaTO.getOwnerDomainName()); + owner.appendChild(domain); + + Element account = doc.createElement("cloudstack:account"); + account.setAttribute("uuid", metaTO.getOwnerAccountUuid()); + account.setTextContent(metaTO.getOwnerAccountName()); + owner.appendChild(account); + + Element project = doc.createElement("cloudstack:project"); + project.setAttribute("uuid", metaTO.getOwnerProjectUuid()); + project.setTextContent(metaTO.getOwnerProjectName()); + owner.appendChild(project); + + instance.appendChild(owner); // + + Element resourceTags = doc.createElement("cloudstack:resource_tags"); // + for (Map.Entry entry : metaTO.getResourceTags().entrySet()) { + Element tag = doc.createElement("cloudstack:resource_tag"); // + tag.setAttribute("key", entry.getKey()); + tag.setTextContent(entry.getValue()); + resourceTags.appendChild(tag); // + } + instance.appendChild(resourceTags); // + + metadata.appendChild(instance); // + doc.appendChild(metadata); // + + Transformer transformer = null; + try { + transformer = TransformerFactory.newInstance().newTransformer(); + } catch (TransformerConfigurationException e) { + LOGGER.warn("Could not create an XML transformer. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + try { + transformer.transform(domSource, result); + } catch (TransformerException e) { + LOGGER.warn("Could not generate metadata XML. The metadata will not be included in the libvirt domain XML: {}", e.getMessage()); + return ""; + } + + return writer.toString(); + } + } + public static class GuestDef { enum GuestType { KVM, XEN, EXE, LXC @@ -2163,34 +2334,6 @@ public String toString() { } } - public class MetadataDef { - Map customNodes = new HashMap<>(); - - public T getMetadataNode(Class fieldClass) { - T field = (T) customNodes.get(fieldClass.getName()); - if (field == null) { - try { - field = fieldClass.newInstance(); - customNodes.put(field.getClass().getName(), field); - } catch (InstantiationException | IllegalAccessException e) { - LOGGER.debug("No default constructor available in class " + fieldClass.getName() + ", ignoring exception", e); - } - } - return field; - } - - @Override - public String toString() { - StringBuilder fsBuilder = new StringBuilder(); - fsBuilder.append("\n"); - for (Object field : customNodes.values()) { - fsBuilder.append(field.toString()); - } - fsBuilder.append("\n"); - return fsBuilder.toString(); - } - } - public static class RngDef { enum RngModel { VIRTIO("virtio"); @@ -2493,15 +2636,6 @@ public DevicesDef getDevices() { return null; } - public MetadataDef getMetaData() { - MetadataDef o = (MetadataDef) components.get(MetadataDef.class.toString()); - if (o == null) { - o = new MetadataDef(); - addComp(o); - } - return o; - } - @Override public String toString() { StringBuilder vmBuilder = new StringBuilder(); @@ -2513,6 +2647,7 @@ public String toString() { if (_desc != null) { vmBuilder.append("" + _desc + "\n"); } + for (Object o : components.values()) { vmBuilder.append(o.toString()); } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index d40b5b226986..928107be9cec 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -16,6 +16,9 @@ // under the License. package com.cloud.hypervisor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -24,18 +27,31 @@ import javax.inject.Inject; import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.agent.api.to.VirtualMachineMetadataTO; import com.cloud.cpu.CPU; +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.HostPodDao; import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.gpu.VgpuProfileVO; import com.cloud.gpu.dao.VgpuProfileDao; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.projects.ProjectVO; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.server.ResourceTag; +import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.dao.UserVmDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -97,7 +113,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis @Inject protected AccountManager accountManager; @Inject - private DomainDao domainDao; + protected DomainDao domainDao; @Inject private DataCenterDao dcDao; @Inject @@ -125,7 +141,19 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis @Inject private UserVmManager userVmManager; @Inject + protected UserVmDao userVmDao; + @Inject + protected ProjectDao projectDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected HostPodDao hostPodDao; + @Inject private ConfigurationManager configurationManager; + @Inject + ResourceTagDao tagsDao; public static ConfigKey VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor = new ConfigKey("Advanced", Boolean.class, "vm.min.memory.equals.memory.divided.by.mem.overprovisioning.factor", "true", "If we set this to 'true', a minimum memory (memory/ mem.overprovisioning.factor) will be set to the VM, independent of using a scalable service offering or not.", true, ConfigKey.Scope.Cluster); @@ -470,4 +498,144 @@ public boolean removeVMTemplateOutOfBand(DataStoreTO templateLocation, String te logger.error("Unsupported operation: cannot remove template file"); return false; } + + /** + * Generates VirtualMachineMetadataTO object from VirtualMachineProfile + * It is a helper function to be used in the inherited classes to avoid repetition + * while generating metadata for multiple Guru implementations + * + * @param vmProfile virtual machine profile object + * @return A VirtualMachineMetadataTO ready to be appended to VirtualMachineTO object + * @see KVMGuru + */ + protected VirtualMachineMetadataTO makeVirtualMachineMetadata(VirtualMachineProfile vmProfile) { + String vmName = "unknown", + instanceName = "unknown", + displayName = "unknown", + instanceUuid = "unknown", + clusterName = "unknown", + clusterUuid = "unknown", + zoneUuid = "unknown", + zoneName = "unknown", + podUuid = "unknown", + podName = "unknown", + domainUuid = "unknown", + domainName = "unknown", + accountUuid = "unknown", + accountName = "unknown", + projectName = "", // the project can be empty + projectUuid = "", // the project can be empty + serviceOfferingName = "unknown"; + long created = 0L; + Integer cpuCores = -1, memory = -1; + List serviceOfferingTags = new ArrayList<>(); + HashMap resourceTags = new HashMap<>(); + + UserVmVO vmVO = userVmDao.findById(vmProfile.getVirtualMachine().getId()); + if (vmVO != null) { + instanceUuid = vmVO.getUuid(); + vmName = vmVO.getHostName(); // this returns the VM name field + instanceName = vmVO.getInstanceName(); + displayName = vmVO.getDisplayName(); + created = vmVO.getCreated().getTime() / 1000L; + + HostVO host = hostDao.findById(vmVO.getHostId()); + if (host != null) { + // Find zone and cluster + Long clusterId = host.getClusterId(); + ClusterVO cluster = clusterDao.findById(clusterId); + + if (cluster != null) { + clusterName = cluster.getName(); + clusterUuid = cluster.getUuid(); + + DataCenterVO zone = dataCenterDao.findById(cluster.getDataCenterId()); + if (zone != null) { + zoneUuid = zone.getUuid(); + zoneName = zone.getName(); + } + + HostPodVO pod = hostPodDao.findById(cluster.getPodId()); + if (pod != null) { + podUuid = pod.getUuid(); + podName = pod.getName(); + } + } + } else { + logger.warn("Could not find the Host object for the virtual machine (null value returned). Libvirt metadata for cluster, pod, zone will not be populated."); + } + + DomainVO domain = domainDao.findById(vmVO.getDomainId()); + if (domain != null) { + domainUuid = domain.getUuid(); + domainName = domain.getName(); + } else { + logger.warn("Could not find the Domain object for the virtual machine (null value returned). Libvirt metadata for domain will not be populated."); + } + + Account account = accountManager.getAccount(vmVO.getAccountId()); + if (account != null) { + accountUuid = account.getUuid(); + accountName = account.getName(); + + ProjectVO project = projectDao.findByProjectAccountId(account.getId()); + if (project != null) { + projectName = project.getName(); + projectUuid = project.getUuid(); + } + } else { + logger.warn("Could not find the Account object for the virtual machine (null value returned). Libvirt metadata for account and project will not be populated."); + } + + List resourceTagsList = tagsDao.listBy(vmVO.getId(), ResourceTag.ResourceObjectType.UserVm); + if (resourceTagsList != null) { + for (ResourceTag tag : resourceTagsList) { + resourceTags.put(tag.getKey(), tag.getValue()); + } + } + } else { + logger.warn("Could not find the VirtualMachine object by its profile (null value returned). Libvirt metadata will not be populated."); + } + + ServiceOffering serviceOffering = vmProfile.getServiceOffering(); + if (serviceOffering != null) { + serviceOfferingName = serviceOffering.getName(); + cpuCores = serviceOffering.getCpu(); + memory = serviceOffering.getRamSize(); + + String hostTagsCommaSeparated = serviceOffering.getHostTag(); + if (hostTagsCommaSeparated != null) { // when service offering has no host tags, this value is null + serviceOfferingTags = Arrays.asList(hostTagsCommaSeparated.split(",")); + } + } else { + logger.warn("Could not find the ServiceOffering object by its profile (null value returned). Libvirt metadata for service offering will not be populated."); + } + + + return new VirtualMachineMetadataTO( + vmName, // name + instanceName, // internalName + displayName, // displayName + instanceUuid , // instanceUUID + cpuCores, // cpuCores + memory, // memory + created, // created, unix epoch in seconds + System.currentTimeMillis() / 1000L, // started, unix epoch in seconds + domainUuid, // ownerDomainUUID + domainName, // ownerDomainName + accountUuid, // ownerAccountUUID + accountName, // ownerAccountName + projectUuid, + projectName, + serviceOfferingName, + serviceOfferingTags, // serviceOfferingTags + zoneName, + zoneUuid, + podName, + podUuid, + clusterName, + clusterUuid, + resourceTags + ); + } } diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java index 64881df4c826..d907e726b202 100644 --- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java +++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java @@ -155,7 +155,6 @@ protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile v } @Override - public VirtualMachineTO implement(VirtualMachineProfile vm) { VirtualMachineTO to = toVirtualMachineTO(vm); setVmQuotaPercentage(to, vm); @@ -170,6 +169,9 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) { configureVmOsDescription(virtualMachine, to, host); configureVmMemoryAndCpuCores(to, host, virtualMachine, vm); + + to.setMetadata(makeVirtualMachineMetadata(vm)); + return to; } From 40c8bc528d3201d88ead398a4c82ed1f50fffde7 Mon Sep 17 00:00:00 2001 From: Davi Torres <90287660+daviftorres@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:33:07 -0500 Subject: [PATCH 05/65] Keeping consistency with other error messages. (#11649) Co-authored-by: Davi Torres Co-authored-by: dahn --- .../command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java | 2 +- .../command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java | 2 +- .../cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java | 2 +- .../cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java | 2 +- engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java | 2 +- .../src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java | 2 +- server/src/main/java/com/cloud/api/query/QueryManagerImpl.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java index fdd98fc3a0a4..8609c87d8f1d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java @@ -63,7 +63,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update load balancer health check policy ID= " + id; + return "Update load balancer health check policy ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java index b2137cf262d8..21c18d584ab0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java @@ -62,7 +62,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update load balancer stickiness policy ID= " + id; + return "Update load balancer stickiness policy ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java index 62dd6167b753..efa809a2d782 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java @@ -66,7 +66,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Updating site-to-site VPN connection id= " + id; + return "Updating site-to-site VPN connection ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java index 9fe5ae0480f7..e614920d8de3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java @@ -63,7 +63,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update site-to-site VPN gateway id= " + id; + return "Update site-to-site VPN gateway ID = " + id; } @Override diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java index 41bcb3155e54..d3775fe35409 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 @@ -782,7 +782,7 @@ public List> countVmsBySize(long dcId, int li result.add(new Ternary(rs.getInt(1), rs.getInt(2), rs.getInt(3))); } } catch (Exception e) { - logger.warn("Error counting vms by size for dcId= " + dcId, e); + logger.warn("Error counting vms by size for Data Center ID = " + dcId, e); } return result; } 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 6ac49967540a..339573c10fa3 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 @@ -886,7 +886,7 @@ public Long countByZoneAndStateAndHostTag(long dcId, State state, String hostTag return rs.getLong(1); } } catch (Exception e) { - logger.warn(String.format("Error counting vms by host tag for dcId= %s, hostTag= %s", dcId, hostTag), e); + logger.warn(String.format("Error counting vms by host tag for dcId = %s, hostTag = %s", dcId, hostTag), e); } return 0L; } 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 e1dc73a3225d..4b469abe1fc2 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -2106,7 +2106,7 @@ private Pair, Integer> listProjectsInternal(ListProjectsCmd } if (domainId != null && !domainId.equals(caller.getDomainId())) { - throw new PermissionDeniedException("Can't list domain id= " + domainId + " projects; unauthorized"); + throw new PermissionDeniedException("Can't list domain ID = " + domainId + " projects; unauthorized"); } if (StringUtils.isNotEmpty(username) && !username.equals(user.getUsername())) { From 23fb0e2ccb44ba42dae530b81729d2441fd8b039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20B=C3=B6ck?= <89930804+erikbocks@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:13:00 -0300 Subject: [PATCH 06/65] Update GUI Kubernetes logo (#11895) --- ui/src/assets/icons/kubernetes.svg | 5 +++++ ui/src/components/header/CreateMenu.vue | 6 ++++-- ui/src/config/section/compute.js | 3 ++- ui/src/config/section/image.js | 3 ++- ui/src/utils/renderIcon.js | 5 +++-- 5 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 ui/src/assets/icons/kubernetes.svg diff --git a/ui/src/assets/icons/kubernetes.svg b/ui/src/assets/icons/kubernetes.svg new file mode 100644 index 000000000000..db1f37bdfee1 --- /dev/null +++ b/ui/src/assets/icons/kubernetes.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ui/src/components/header/CreateMenu.vue b/ui/src/components/header/CreateMenu.vue index 8c39ec5b8a07..aa1d020bdb60 100644 --- a/ui/src/components/header/CreateMenu.vue +++ b/ui/src/components/header/CreateMenu.vue @@ -26,7 +26,7 @@ @@ -50,6 +50,8 @@ + + diff --git a/ui/src/config/section/tools.js b/ui/src/config/section/tools.js index a07228ca87b4..5b7f4b9af325 100644 --- a/ui/src/config/section/tools.js +++ b/ui/src/config/section/tools.js @@ -116,6 +116,10 @@ export default { name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) }, + { + name: 'filters', + component: shallowRef(defineAsyncComponent(() => import('@/components/view/WebhookFiltersTab.vue'))) + }, { name: 'recent.deliveries', component: shallowRef(defineAsyncComponent(() => import('@/components/view/WebhookDeliveriesTab.vue'))) From c465caf81e743b1a0d6f919e472e2d82d1c71a66 Mon Sep 17 00:00:00 2001 From: dahn Date: Tue, 6 Jan 2026 08:17:37 +0100 Subject: [PATCH 52/65] Adjust close periods (#12376) --- .github/workflows/stale.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f12fbe93de66..e90c75979b6d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -33,9 +33,11 @@ jobs: stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.' stale-pr-message: 'This PR is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.' close-issue-message: 'This issue was closed because it has been stale for 120 days with no activity.' - close-pr-message: 'This PR was closed because it has been stale for 120 days with no activity.' + close-pr-message: 'This PR was closed because it has been stale for 240 days with no activity.' stale-issue-label: 'no-issue-activity' stale-pr-label: 'no-pr-activity' days-before-stale: 120 + days-before-close: -1 + days-before-pr-close: 240 exempt-issue-labels: 'gsoc,good-first-issue,long-term-plan' exempt-pr-labels: 'status:ready-for-merge,status:needs-testing,status:on-hold' From e47d7bc6ff12167f97932c922475ce8cb6593abe Mon Sep 17 00:00:00 2001 From: John Bampton Date: Thu, 8 Jan 2026 02:22:52 +1000 Subject: [PATCH 53/65] [CI] Dependabot: add a cooldown period for new releases (#12384) --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 88985cbdef1e..41b307863fc3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,3 +26,5 @@ updates: directory: "/" # Location of package manifests schedule: interval: "daily" + cooldown: + default-days: 7 From fd1c67f47390d2d4ff8e121d83de7284e0211c1b Mon Sep 17 00:00:00 2001 From: John Bampton Date: Thu, 8 Jan 2026 20:26:40 +1000 Subject: [PATCH 54/65] Standardize and auto add license headers to properties files (#12231) --- .pre-commit-config.yaml | 10 +++++++ .../hypervisors/ovm3/sonar-project.properties | 28 +++++++++---------- .../ovm3/src/test/resources/log4j.properties | 28 +++++++++---------- .../src/test/resources/log4j.properties | 26 +++++++++-------- systemvm/agent/conf/environment.properties | 17 +++++++++++ 5 files changed, 69 insertions(+), 40 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef0b0c204ebd..26adafcbf268 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,6 +62,16 @@ repos: - .github/workflows/license-templates/LICENSE.txt - --fuzzy-match-generates-todo exclude: ^(CHANGES|ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE)\.md$|^ui/docs/(full|smoke)-test-plan\.template\.md$ + - id: insert-license + name: add license for all properties files + description: automatically adds a licence header to all properties files that don't have a license header + files: \.properties$ + args: + - --comment-style + - '|#|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo - id: insert-license name: add license for all Shell files description: automatically adds a licence header to all Shell files that don't have a license header diff --git a/plugins/hypervisors/ovm3/sonar-project.properties b/plugins/hypervisors/ovm3/sonar-project.properties index d632dfb9f916..7355f1df4f78 100644 --- a/plugins/hypervisors/ovm3/sonar-project.properties +++ b/plugins/hypervisors/ovm3/sonar-project.properties @@ -1,19 +1,19 @@ -#Licensed to the Apache Software Foundation (ASF) under one -#or more contributor license agreements. See the NOTICE file -#distributed with this work for additional information -#regarding copyright ownership. The ASF licenses this file -#to you under the Apache License, Version 2.0 (the -#"License"); you may not use this file except in compliance -#with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, -#software distributed under the License is distributed on an -#"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -#KIND, either express or implied. See the License for the -#specific language governing permissions and limitations -#under the License. +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. # Required metadata sonar.projectKey=cloud-plugin-hypervisor-ovm3 diff --git a/plugins/hypervisors/ovm3/src/test/resources/log4j.properties b/plugins/hypervisors/ovm3/src/test/resources/log4j.properties index 82ee5c55c4c0..0f72e39d8554 100644 --- a/plugins/hypervisors/ovm3/src/test/resources/log4j.properties +++ b/plugins/hypervisors/ovm3/src/test/resources/log4j.properties @@ -1,19 +1,19 @@ -#Licensed to the Apache Software Foundation (ASF) under one -#or more contributor license agreements. See the NOTICE file -#distributed with this work for additional information -#regarding copyright ownership. The ASF licenses this file -#to you under the Apache License, Version 2.0 (the -#"License"); you may not use this file except in compliance -#with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, -#software distributed under the License is distributed on an -#"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -#KIND, either express or implied. See the License for the -#specific language governing permissions and limitations -#under the License. +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. # Root logger option log4j.rootLogger=DEBUG, stdout diff --git a/plugins/network-elements/globodns/src/test/resources/log4j.properties b/plugins/network-elements/globodns/src/test/resources/log4j.properties index 1bac606ff63d..8b1d961f7b63 100644 --- a/plugins/network-elements/globodns/src/test/resources/log4j.properties +++ b/plugins/network-elements/globodns/src/test/resources/log4j.properties @@ -1,17 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. # Define the root logger with appender file #log = /var/log/log4j diff --git a/systemvm/agent/conf/environment.properties b/systemvm/agent/conf/environment.properties index 269acad91525..20ca1a2b4322 100644 --- a/systemvm/agent/conf/environment.properties +++ b/systemvm/agent/conf/environment.properties @@ -1,2 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + paths.script=../../scripts/storage/secondary/ paths.pid=. From bc76f2042d74ecee12a4cbea95e70b4bd75aae85 Mon Sep 17 00:00:00 2001 From: Tonitzpp <134986282+Tonitzpp@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:55:34 -0300 Subject: [PATCH 55/65] Change migration volume exception messages (#12367) Co-authored-by: toni.zamparetti Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../main/java/com/cloud/storage/VolumeApiServiceImpl.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 4f03e7881737..bdf9bc1bc1d2 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -2179,14 +2179,16 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff } Collections.shuffle(suitableStoragePoolsWithEnoughSpace); MigrateVolumeCmd migrateVolumeCmd = new MigrateVolumeCmd(volume.getId(), suitableStoragePoolsWithEnoughSpace.get(0).getId(), newDiskOffering.getId(), true); + String volumeUuid = volume.getUuid(); try { Volume result = migrateVolume(migrateVolumeCmd); volume = (result != null) ? _volsDao.findById(result.getId()) : null; if (volume == null) { - throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume: %s migration failed to storage pool %s", volume, suitableStoragePools.get(0))); + throw new CloudRuntimeException("Change offering for the volume failed."); } } catch (Exception e) { - throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume: %s migration failed to storage pool %s due to %s", volume, suitableStoragePools.get(0), e.getMessage())); + logger.error("Volume change offering operation failed for volume ID: {} migration failed to storage pool {} due to {}", volumeUuid, suitableStoragePoolsWithEnoughSpace.get(0).getId(), e.getMessage()); + throw new CloudRuntimeException("Change offering for the volume failed.", e); } } @@ -2199,7 +2201,7 @@ public Volume changeDiskOfferingForVolumeInternal(Long volumeId, Long newDiskOff if (volumeMigrateRequired) { logger.warn(String.format("Volume change offering operation succeeded for volume ID: %s but volume resize operation failed, so please try resize volume operation separately", volume.getUuid())); } else { - throw new CloudRuntimeException(String.format("Volume change offering operation failed for volume ID: %s due to resize volume operation failed", volume.getUuid())); + throw new CloudRuntimeException(String.format("Volume disk offering change operation failed for volume ID [%s] because the volume resize operation failed.", volume.getUuid())); } } } From 1ef636577167cc7cc7a6ce63c54b54c1ab32aabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20B=C3=B6ck?= <89930804+erikbocks@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:23:46 -0300 Subject: [PATCH 56/65] Change internal ID to UUID in user disable event (#11824) --- .../cloudstack/api/command/admin/user/DisableUserCmd.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java index 974c1c7bebed..6ce669d8523d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java @@ -78,12 +78,12 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "disabling user: " + getId(); + return "disabling user: " + this._uuidMgr.getUuid(User.class, getId()); } @Override public void execute() { - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + this._uuidMgr.getUuid(User.class, getId())); UserAccount user = _regionService.disableUser(this); if (user != null) { From 1b861dad48fe4987b691eab7ed102638d5c1f550 Mon Sep 17 00:00:00 2001 From: "Suyang(Dawson) Chen" Date: Fri, 9 Jan 2026 03:30:17 -0500 Subject: [PATCH 57/65] Cleanup: Standardize logger message formatting in ApiServer.java (#11188) --- .../src/main/java/com/cloud/api/ApiServer.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index 5a3c8c2c7179..95aca28b53f2 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -461,14 +461,14 @@ public boolean start() { final Long snapshotLimit = ConcurrentSnapshotsThresholdPerHost.value(); if (snapshotLimit == null || snapshotLimit <= 0) { - logger.debug("Global concurrent snapshot config parameter " + ConcurrentSnapshotsThresholdPerHost.value() + " is less or equal 0; defaulting to unlimited"); + logger.debug("Global concurrent snapshot config parameter {} is less or equal 0; defaulting to unlimited", ConcurrentSnapshotsThresholdPerHost.value()); } else { dispatcher.setCreateSnapshotQueueSizeLimit(snapshotLimit); } final Long migrationLimit = VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value(); if (migrationLimit == null || migrationLimit <= 0) { - logger.debug("Global concurrent migration config parameter " + VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value() + " is less or equal 0; defaulting to unlimited"); + logger.debug("Global concurrent migration config parameter {} is less or equal 0; defaulting to unlimited", VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value()); } else { dispatcher.setMigrateQueueSizeLimit(migrationLimit); } @@ -647,7 +647,7 @@ public String handleRequest(final Map params, final String responseType, final S logValue = (value == null) ? "'null'" : value[0]; } - logger.trace(" key: " + keyStr + ", value: " + logValue); + logger.trace(" key: {}, value: {}", keyStr, logValue); } } throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "Invalid request, no command sent"); @@ -707,7 +707,7 @@ public String handleRequest(final Map params, final String responseType, final S buf.append(obj.getUuid()); buf.append(" "); } - logger.info("PermissionDenied: " + ex.getMessage() + " on objs: [" + buf + "]"); + logger.info("PermissionDenied: {} on objs: [{}]", ex.getMessage(), buf); } else { logger.info("PermissionDenied: {}", ex.getMessage()); } @@ -1035,7 +1035,7 @@ public boolean verifyRequest(final Map requestParameters, fina // if api/secret key are passed to the parameters if ((signature == null) || (apiKey == null)) { - logger.debug("Expired session, missing signature, or missing apiKey -- ignoring request. Signature: " + signature + ", apiKey: " + apiKey); + logger.warn("Expired session, missing signature, or missing apiKey -- ignoring request. Signature: {}, apiKey: {}", signature, apiKey); return false; // no signature, bad request } @@ -1258,7 +1258,7 @@ public ResponseObject loginUser(final HttpSession session, final String username float offsetInHrs = 0f; if (timezone != null) { final TimeZone t = TimeZone.getTimeZone(timezone); - logger.info("Current user logged in under " + timezone + " timezone"); + logger.info("Current user logged in under {} timezone", timezone); final java.util.Date date = new java.util.Date(); final long longDate = date.getTime(); @@ -1410,9 +1410,9 @@ private void checkCommandAvailable(final User user, final String commandName, fi final Boolean apiSourceCidrChecksEnabled = ApiServiceConfiguration.ApiSourceCidrChecksEnabled.value(); if (apiSourceCidrChecksEnabled) { - logger.debug("CIDRs from which account '" + account.toString() + "' is allowed to perform API calls: " + accessAllowedCidrs); + logger.debug("CIDRs from which account '{}' is allowed to perform API calls: {}", account.toString(), accessAllowedCidrs); if (!NetUtils.isIpInCidrList(remoteAddress, accessAllowedCidrs.split(","))) { - logger.warn("Request by account '" + account.toString() + "' was denied since " + remoteAddress + " does not match " + accessAllowedCidrs); + logger.warn("Request by account '{}' was denied since {} does not match {}", account.toString(), remoteAddress, accessAllowedCidrs); throw new OriginDeniedException("Calls from disallowed origin", account, remoteAddress); } } From 2358632253a0a0da74b81028e6c63aeb49df7e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20B=C3=B6ck?= <89930804+erikbocks@users.noreply.github.com> Date: Mon, 12 Jan 2026 04:20:31 -0300 Subject: [PATCH 58/65] Fixed User type accounts being able to change resource limits of their own domain and account (#12046) Co-authored-by: Lucas Martins <56271185+lucas-a-martins@users.noreply.github.com> --- .../com/cloud/resourcelimit/ResourceLimitManagerImpl.java | 5 +++++ .../cloud/resourcelimit/ResourceLimitManagerImplTest.java | 1 + 2 files changed, 6 insertions(+) diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 9a6c8a85f18e..648abf0d9384 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -903,6 +903,11 @@ protected void addTaggedResourceLimits(List limits, ResourceTyp public ResourceLimitVO updateResourceLimit(Long accountId, Long domainId, Integer typeId, Long max, String tag) { Account caller = CallContext.current().getCallingAccount(); + if (caller.getType().equals(Account.Type.NORMAL)) { + logger.info("Throwing exception because only root admins and domain admins are allowed to update resource limits."); + throw new PermissionDeniedException("Your account does not have the permission to update resource limits."); + } + if (max == null) { max = (long)Resource.RESOURCE_UNLIMITED; } else if (max < Resource.RESOURCE_UNLIMITED) { diff --git a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java index a968a2da0b7d..0b0b8c5e43fe 100644 --- a/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java +++ b/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java @@ -147,6 +147,7 @@ public void setUp() throws Exception { overrideDefaultConfigValue(ResourceLimitService.ResourceLimitStorageTags, "_defaultValue", StringUtils.join(storageTags, ",")); Account account = mock(Account.class); + when(account.getType()).thenReturn(Account.Type.ADMIN); User user = mock(User.class); CallContext.register(user, account); } From b8813c7b243787b8b33080d7fea3327d709bcccf Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 12 Jan 2026 16:50:15 +0530 Subject: [PATCH 59/65] UI: Add info for 'Use primary storage replication' in snapshot view(s) (#11943) --- .../api/command/user/snapshot/CopySnapshotCmd.java | 6 +++++- .../api/command/user/snapshot/CreateSnapshotCmd.java | 5 ++++- .../command/user/snapshot/CreateSnapshotPolicyCmd.java | 6 +++++- .../com/cloud/storage/snapshot/SnapshotManager.java | 4 +++- ui/src/views/storage/FormSchedule.vue | 8 +++++++- ui/src/views/storage/SnapshotZones.vue | 7 ++++++- ui/src/views/storage/TakeSnapshot.vue | 10 ++++++++-- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java index ac54ebbd8f8c..519f9876b960 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java @@ -97,7 +97,11 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { "The snapshot will always be made available in the zone in which the volume is present. Currently supported for StorPool only") protected List storagePoolIds; - @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, since = "4.21.0", description = "This parameter enables the option the snapshot to be copied to supported primary storage") + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + since = "4.21.0", + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") protected Boolean useStorageReplication; ///////////////////////////////////////////////////// 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 3a49bad8fcb9..f78112d679fe 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 @@ -112,7 +112,10 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd { since = "4.21.0") protected List storagePoolIds; - @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, description = "This parameter enables the option the snapshot to be copied to supported primary storage") + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") protected Boolean useStorageReplication; private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject; 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 24d756befaba..b1e7b2a00040 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java @@ -94,7 +94,11 @@ public class CreateSnapshotPolicyCmd extends BaseCmd { since = "4.21.0") protected List storagePoolIds; - @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, type=CommandType.BOOLEAN, required = false, since = "4.21.0", description = "This parameter enables the option the snapshot to be copied to supported primary storage") + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + since = "4.21.0", + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") protected Boolean useStorageReplication; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// 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 b245a3719694..10dcc2683de8 100644 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java @@ -68,7 +68,9 @@ public interface SnapshotManager extends Configurable { "Whether to show chain size (sum of physical size of snapshot and all its parents) for incremental snapshots in the snapshot response", true, ConfigKey.Scope.Global, null); - public static final ConfigKey UseStorageReplication = new ConfigKey(Boolean.class, "use.storage.replication", "Snapshots", "false", "For snapshot copy to another primary storage in a different zone. Supports only StorPool storage for now", true, ConfigKey.Scope.StoragePool, null); + ConfigKey UseStorageReplication = new ConfigKey<>(Boolean.class, "use.storage.replication", "Snapshots", "false", + "For snapshot copy to another primary storage in a different zone. This is supported only for StorPool storage for now.", + true, ConfigKey.Scope.StoragePool, null); void deletePoliciesForVolume(Long volumeId); diff --git a/ui/src/views/storage/FormSchedule.vue b/ui/src/views/storage/FormSchedule.vue index 433e399d2a8c..baecd3bb5be8 100644 --- a/ui/src/views/storage/FormSchedule.vue +++ b/ui/src/views/storage/FormSchedule.vue @@ -174,7 +174,10 @@ - + + @@ -310,6 +313,9 @@ export default { storagePools: [] } }, + beforeCreate () { + this.apiParams = this.$getApiParams('createSnapshotPolicy') + }, created () { this.initForm() this.volumeId = this.resource.id diff --git a/ui/src/views/storage/SnapshotZones.vue b/ui/src/views/storage/SnapshotZones.vue index ed46ce4172ad..f37996a3f293 100644 --- a/ui/src/views/storage/SnapshotZones.vue +++ b/ui/src/views/storage/SnapshotZones.vue @@ -137,7 +137,10 @@ - + + @@ -236,6 +239,7 @@ import { isAdmin } from '@/role' import OsLogo from '@/components/widgets/OsLogo' import ResourceIcon from '@/components/view/ResourceIcon' import TooltipButton from '@/components/widgets/TooltipButton' +import TooltipLabel from '@/components/widgets/TooltipLabel' import BulkActionProgress from '@/components/view/BulkActionProgress' import Status from '@/components/widgets/Status' import eventBus from '@/config/eventBus' @@ -244,6 +248,7 @@ export default { name: 'SnapshotZones', components: { TooltipButton, + TooltipLabel, OsLogo, ResourceIcon, BulkActionProgress, diff --git a/ui/src/views/storage/TakeSnapshot.vue b/ui/src/views/storage/TakeSnapshot.vue index fc80e6d775f9..9e17e0683b83 100644 --- a/ui/src/views/storage/TakeSnapshot.vue +++ b/ui/src/views/storage/TakeSnapshot.vue @@ -66,7 +66,10 @@ - + + @@ -93,7 +96,10 @@ - + + From a566af35f5c8c34f6694a60dcd76f195d7caa6d4 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 15 Jan 2026 19:14:51 +0100 Subject: [PATCH 60/65] Review comment on pull request #12436 --- pull/12436 | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pull/12436 diff --git a/pull/12436 b/pull/12436 new file mode 100644 index 000000000000..ca31e875a3e2 --- /dev/null +++ b/pull/12436 @@ -0,0 +1,35 @@ +Thanks for the changes — overall these look reasonable, but I have a few comments and suggestions: + +1) Bug in DeployVnfAppliance.vue +- The new check uses .filter(...) in a boolean context: + (this.vnfNicNetworks[deviceId].service.filter(svc => svc.name === 'SecurityGroupProvider')) +- In JavaScript, an array (including an empty array) is truthy, so this condition will always evaluate to true even when there are no matching services. This is likely a functional bug. +- Suggested fix: use .some or check length: + this.vnfNicNetworks[deviceId].service && this.vnfNicNetworks[deviceId].service.some(svc => svc.name === 'SecurityGroupProvider') + or + Array.isArray(this.vnfNicNetworks[deviceId].service) && this.vnfNicNetworks[deviceId].service.filter(...).length > 0 + +2) Expanded details in network.js and VnfAppliancesTab.vue — performance and consistency +- You expanded the API "details" from 'servoff,tmpl,nics' to 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff'. That will return many more fields and could affect performance (more data transferred / processed). Please confirm all newly requested details are required for the UI use-cases in these views. +- Also note an inconsistency in parameter casing: + - ui/src/config/section/network.js uses isvnf: true (lowercase 'v') + - ui/src/views/network/VnfAppliancesTab.vue uses isVnf: true (camelCase) + Verify which exact param the backend expects (case-sensitive). Recommend standardizing to the correct form across files. + +3) Logic change in DeployVnfAppliance.vue — intent clarification +- Previously the code checked zone.securitygroupsenabled for Shared networks. The new code checks whether the network has the SecurityGroupProvider service. Please confirm this is the intended semantic change (i.e., switching from a zone-level setting to checking per-network provider capability). If both checks are relevant, combine them appropriately. + +4) Localization text change +- The new label "Configure network rules for VNF's management interfaces" is fine and shorter. Minor nit: consider removing the possessive and using "Configure network rules for VNF management interfaces" to match other labels' style, but this is optional. + +5) Safety / defensive coding +- In places where you access this.vnfNicNetworks[deviceId].service, add defensive checks (Array.isArray(...)) before calling array methods to avoid runtime errors if service is undefined. + +6) Testing suggestions +- Please test deploy flows in zones/networks: + - Shared network with SecurityGroupProvider present + - Shared network without SecurityGroupProvider + - Isolated networks and VPC cases + - Confirm that expanding "details" doesn't degrade list performance in views that call the API frequently. + +If you'd like, I can re-run these checks or suggest a patch for the .some change. Thanks! \ No newline at end of file From b31c2f4cae1a9c4d29e9e5a745b1c77df7d93c81 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 15 Jan 2026 19:17:12 +0100 Subject: [PATCH 61/65] Revert "Review comment on pull request #12436" This reverts commit a566af35f5c8c34f6694a60dcd76f195d7caa6d4. --- pull/12436 | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 pull/12436 diff --git a/pull/12436 b/pull/12436 deleted file mode 100644 index ca31e875a3e2..000000000000 --- a/pull/12436 +++ /dev/null @@ -1,35 +0,0 @@ -Thanks for the changes — overall these look reasonable, but I have a few comments and suggestions: - -1) Bug in DeployVnfAppliance.vue -- The new check uses .filter(...) in a boolean context: - (this.vnfNicNetworks[deviceId].service.filter(svc => svc.name === 'SecurityGroupProvider')) -- In JavaScript, an array (including an empty array) is truthy, so this condition will always evaluate to true even when there are no matching services. This is likely a functional bug. -- Suggested fix: use .some or check length: - this.vnfNicNetworks[deviceId].service && this.vnfNicNetworks[deviceId].service.some(svc => svc.name === 'SecurityGroupProvider') - or - Array.isArray(this.vnfNicNetworks[deviceId].service) && this.vnfNicNetworks[deviceId].service.filter(...).length > 0 - -2) Expanded details in network.js and VnfAppliancesTab.vue — performance and consistency -- You expanded the API "details" from 'servoff,tmpl,nics' to 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff'. That will return many more fields and could affect performance (more data transferred / processed). Please confirm all newly requested details are required for the UI use-cases in these views. -- Also note an inconsistency in parameter casing: - - ui/src/config/section/network.js uses isvnf: true (lowercase 'v') - - ui/src/views/network/VnfAppliancesTab.vue uses isVnf: true (camelCase) - Verify which exact param the backend expects (case-sensitive). Recommend standardizing to the correct form across files. - -3) Logic change in DeployVnfAppliance.vue — intent clarification -- Previously the code checked zone.securitygroupsenabled for Shared networks. The new code checks whether the network has the SecurityGroupProvider service. Please confirm this is the intended semantic change (i.e., switching from a zone-level setting to checking per-network provider capability). If both checks are relevant, combine them appropriately. - -4) Localization text change -- The new label "Configure network rules for VNF's management interfaces" is fine and shorter. Minor nit: consider removing the possessive and using "Configure network rules for VNF management interfaces" to match other labels' style, but this is optional. - -5) Safety / defensive coding -- In places where you access this.vnfNicNetworks[deviceId].service, add defensive checks (Array.isArray(...)) before calling array methods to avoid runtime errors if service is undefined. - -6) Testing suggestions -- Please test deploy flows in zones/networks: - - Shared network with SecurityGroupProvider present - - Shared network without SecurityGroupProvider - - Isolated networks and VPC cases - - Confirm that expanding "details" doesn't degrade list performance in views that call the API frequently. - -If you'd like, I can re-run these checks or suggest a patch for the .some change. Thanks! \ No newline at end of file From 002d9768b2886f34ab2a572bfa462583e5dd5680 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:18:37 +0530 Subject: [PATCH 62/65] Add settings to mark cryptographic algorithms in vpn customer gateways as excluded or obsolete (#12193) This PR introduces several configuration settings using which an operator can mark certain cryptographic algorithms and parameters as excluded or obsolete for VPN Customer Gateway creation for Site-to-Site VPN. Cloud providers following modern security frameworks (e.g., ISO 27001/27017) are required to enforce and communicate approved cryptographic standards. CloudStack currently accepts several weak or deprecated algorithms without guidance to users. This PR closes that gap by giving operators explicit control over what is disallowed vs discouraged, improving security posture without breaking existing deployments. These settings are: 1. vpn.customer.gateway.excluded.encryption.algorithms 2. vpn.customer.gateway.excluded.hashing.algorithms 3. vpn.customer.gateway.excluded.ike.versions 4. vpn.customer.gateway.excluded.dh.group 5. vpn.customer.gateway.obsolete.encryption.algorithms 6. vpn.customer.gateway.obsolete.hashing.algorithms 7. vpn.customer.gateway.obsolete.ike.versions 8. vpn.customer.gateway.obsolete.dh.group --- .../main/java/com/cloud/event/EventTypes.java | 2 + .../apache/cloudstack/alert/AlertService.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 4 + .../user/config/ListCapabilitiesCmd.java | 16 + .../api/response/CapabilitiesResponse.java | 10 + .../Site2SiteCustomerGatewayResponse.java | 16 + .../ConfigKeyScheduledExecutionWrapper.java | 2 +- .../com/cloud/alert/AlertManagerImpl.java | 3 +- .../java/com/cloud/api/ApiResponseHelper.java | 13 + .../network/vpn/Site2SiteVpnManager.java | 6 + .../network/vpn/Site2SiteVpnManagerImpl.java | 279 +++++- .../cloud/server/ManagementServerImpl.java | 56 +- .../vpn/Site2SiteVpnManagerImplTest.java | 944 ++++++++++++++++++ .../vpc/MockSite2SiteVpnManagerImpl.java | 17 +- ui/public/locales/en.json | 22 +- ui/src/components/view/ListView.vue | 8 + ui/src/config/section/network.js | 10 +- .../network/CreateVpnCustomerGateway.vue | 360 +------ .../network/UpdateVpnCustomerGateway.vue | 129 +++ ui/src/views/network/VpnCustomerGateway.vue | 581 +++++++++++ 20 files changed, 2122 insertions(+), 357 deletions(-) create mode 100644 server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java create mode 100644 ui/src/views/network/UpdateVpnCustomerGateway.vue create mode 100644 ui/src/views/network/VpnCustomerGateway.vue diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 38e601c790a7..d2989a8ffdc9 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -503,6 +503,7 @@ public class EventTypes { public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE = "VPN.S2S.CUSTOMER.GATEWAY.CREATE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE = "VPN.S2S.CUSTOMER.GATEWAY.DELETE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE = "VPN.S2S.CUSTOMER.GATEWAY.UPDATE"; + public static final String EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS = "VPN.S2S.GATEWAY.OBSOLETE.PARAMS"; public static final String EVENT_S2S_VPN_CONNECTION_CREATE = "VPN.S2S.CONNECTION.CREATE"; public static final String EVENT_S2S_VPN_CONNECTION_DELETE = "VPN.S2S.CONNECTION.DELETE"; public static final String EVENT_S2S_VPN_CONNECTION_RESET = "VPN.S2S.CONNECTION.RESET"; @@ -1151,6 +1152,7 @@ public class EventTypes { entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE, Site2SiteCustomerGateway.class); + entityEventDetails.put(EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_CREATE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_DELETE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_RESET, Site2SiteVpnConnection.class); 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 d8e471756a02..cc3188feeca5 100644 --- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java +++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java @@ -74,6 +74,7 @@ private AlertType(short type, String name, boolean isDefault) { public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PUBLIC.IFACE.MTU", true); public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true); public static final AlertType ALERT_TYPE_EXTENSION_PATH_NOT_READY = new AlertType((short)33, "ALERT.TYPE.EXTENSION.PATH.NOT.READY", true); + public static final AlertType ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS = new AlertType((short)34, "ALERT.S2S.VPN.GATEWAY.OBSOLETE.PARAMETERS", true); public static final AlertType ALERT_TYPE_BACKUP_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_BACKUP_STORAGE, "ALERT.STORAGE.BACKUP", true); public static final AlertType ALERT_TYPE_OBJECT_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_OBJECT_STORAGE, "ALERT.STORAGE.OBJECT", true); 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 3e36d933772b..896031806d53 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1364,6 +1364,10 @@ public class ApiConstants { public static final String RECURSIVE_DOMAINS = "recursivedomains"; + public static final String VPN_CUSTOMER_GATEWAY_PARAMETERS = "vpncustomergatewayparameters"; + public static final String OBSOLETE_PARAMETERS = "obsoleteparameters"; + public static final String EXCLUDED_PARAMETERS = "excludedparameters"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). 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 ed1bd7b063b2..94b6062b6212 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -21,7 +21,9 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.CapabilitiesResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.config.ApiServiceConfiguration; import com.cloud.user.Account; @@ -30,12 +32,22 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListCapabilitiesCmd extends BaseCmd { + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the domain for listing capabilities.", + since = "4.23.0") + private Long domainId; @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; } + public Long getDomainId() { + return domainId; + } + @Override public void execute() { Map capabilities = _mgr.listCapabilities(this); @@ -76,6 +88,10 @@ public void execute() { response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH)); response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED)); + if (capabilities.containsKey(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS)) { + Map vpnCustomerGatewayParameters = (Map) capabilities.get(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS); + response.setVpnCustomerGatewayParameters(vpnCustomerGatewayParameters); + } response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index 930d1a50de0f..816216962808 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.Map; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -153,6 +155,10 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2") private Boolean additionalConfigEnabled; + @SerializedName(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS) + @Param(description = "Excluded and obsolete VPN customer gateway cryptographic parameters") + private Map vpnCustomerGatewayParameters; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -280,4 +286,8 @@ public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) { this.additionalConfigEnabled = additionalConfigEnabled; } + + public void setVpnCustomerGatewayParameters(Map vpnCustomerGatewayParameters) { + this.vpnCustomerGatewayParameters = vpnCustomerGatewayParameters; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java index 4e5820279a2e..b121ef7ce61e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java @@ -114,6 +114,14 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation @Param(description = "Which IKE Version to use, one of ike (autoselect), IKEv1, or IKEv2. Defaults to ike") private String ikeVersion; + @SerializedName(ApiConstants.OBSOLETE_PARAMETERS) + @Param(description = "Contains the list of obsolete/insecure cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String obsoleteParameters; + + @SerializedName(ApiConstants.EXCLUDED_PARAMETERS) + @Param(description = "Contains the list of excluded/not allowed cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String excludedParameters; + public void setId(String id) { this.id = id; } @@ -202,4 +210,12 @@ public void setDomainPath(String domainPath) { this.domainPath = domainPath; } + public void setContainsObsoleteParameters(String obsoleteParameters) { + this.obsoleteParameters = obsoleteParameters; + } + + public void setContainsExcludedParameters(String excludedParameters) { + this.excludedParameters = excludedParameters; + } + } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java index b8d7e7829711..a02ee8abee09 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyScheduledExecutionWrapper.java @@ -66,7 +66,7 @@ public ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorServi this.unit = unit; } - protected ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command, + public ConfigKeyScheduledExecutionWrapper(ScheduledExecutorService executorService, Runnable command, ConfigKey configKey, int enableIntervalSeconds, TimeUnit unit) { validateArgs(executorService, command, configKey); this.executorService = executorService; diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index 3eab3fbfeb1b..308c4443cb8b 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -112,7 +112,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi , AlertType.ALERT_TYPE_OOBM_AUTH_ERROR , AlertType.ALERT_TYPE_HA_ACTION , AlertType.ALERT_TYPE_CA_CERT - , AlertType.ALERT_TYPE_EXTENSION_PATH_NOT_READY); + , AlertType.ALERT_TYPE_EXTENSION_PATH_NOT_READY + , AlertType.ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS); private static final long INITIAL_CAPACITY_CHECK_DELAY = 30L * 1000L; // Thirty seconds expressed in milliseconds. diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 3c7eeaf3b8f9..ce794cf5388f 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -50,6 +50,7 @@ import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.network.vpc.VpcGateway; +import com.cloud.network.vpn.Site2SiteVpnManager; import com.cloud.storage.BucketVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -528,6 +529,8 @@ public class ApiResponseHelper implements ResponseGenerator { @Inject RoutedIpv4Manager routedIpv4Manager; @Inject + Site2SiteVpnManager site2SiteVpnManager; + @Inject ResourceIconManager resourceIconManager; public static String getPrettyDomainPath(String path) { @@ -3884,6 +3887,16 @@ public Site2SiteCustomerGatewayResponse createSite2SiteCustomerGatewayResponse(S response.setRemoved(result.getRemoved()); response.setIkeVersion(result.getIkeVersion()); response.setSplitConnections(result.getSplitConnections()); + + Set obsoleteParameters = site2SiteVpnManager.getObsoleteVpnGatewayParameters(result); + if (CollectionUtils.isNotEmpty(obsoleteParameters)) { + response.setContainsObsoleteParameters(obsoleteParameters.toString()); + } + Set excludedParameters = site2SiteVpnManager.getExcludedVpnGatewayParameters(result); + if (CollectionUtils.isNotEmpty(excludedParameters)) { + response.setContainsExcludedParameters(excludedParameters.toString()); + } + response.setObjectName("vpncustomergateway"); response.setHasAnnotation(annotationDao.hasAnnotations(result.getUuid(), AnnotationService.EntityType.VPN_CUSTOMER_GATEWAY.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java index 25c84d6d956b..9cf604f85070 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java @@ -17,11 +17,17 @@ package com.cloud.network.vpn; import java.util.List; +import java.util.Set; +import com.cloud.network.Site2SiteCustomerGateway; import com.cloud.network.dao.Site2SiteVpnConnectionVO; import com.cloud.vm.DomainRouterVO; public interface Site2SiteVpnManager extends Site2SiteVpnService { + Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw); + + Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw); + boolean cleanupVpnConnectionByVpc(long vpcId); boolean cleanupVpnGatewayByVpc(long vpcId); diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index ad1d1f02682e..09236eb0d6e7 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -17,15 +17,22 @@ package com.cloud.network.vpn; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.framework.config.Configurable; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.user.vpn.CreateVpnConnectionCmd; @@ -41,9 +48,16 @@ import org.apache.cloudstack.api.command.user.vpn.UpdateVpnCustomerGatewayCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ConfigKeyScheduledExecutionWrapper; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import com.cloud.utils.concurrency.NamedThreadFactory; + +import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; @@ -72,9 +86,11 @@ import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; @@ -88,7 +104,52 @@ import com.cloud.vm.dao.DomainRouterDao; @Component -public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager { +public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager, Configurable { + + // Configuration keys for VPN gateway cryptographic parameter controls + public static final ConfigKey VpnCustomerGatewayExcludedEncryptionAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.encryption.algorithms", "", + "Comma-separated list of encryption algorithms that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are aes128, aes192 and aes256 and 3des.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedHashingAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.hashing.algorithms", "", + "Comma-separated list of hashing algorithms that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are sha1, sha256, sha384 and sha512 and md5.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedIkeVersions = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.ike.versions", "", + "Comma-separated list of IKE versions that are excluded and cannot be selected by end users for VPN Customer Gateways. Allowed values are ikev, ikev1 and ikev2.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayExcludedDhGroup = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.excluded.dh.group", "", + "Comma-separated list of Diffie-Hellman groups that are excluded and cannot be selected by end users for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are modp1024, modp1536, modp2048, modp3072, modp4096, modp6144 and modp8192.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteEncryptionAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.encryption.algorithms", "", + "Comma-separated list of encryption algorithms that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are aes128, aes192 and aes256 and 3des.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteHashingAlgorithms = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.hashing.algorithms", "", + "Comma-separated list of hashing algorithms that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are sha1, sha256, sha384 and sha512 and md5.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteIkeVersions = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.ike.versions", "", + "Comma-separated list of IKE versions that are marked as obsolete/insecure for VPN Customer Gateways. Allowed values are ikev, ikev1 and ikev2.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteDhGroup = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, String.class, "vpn.customer.gateway.obsolete.dh.group", "", + "Comma-separated list of Diffie-Hellman groups that are marked as obsolete/insecure for VPN Customer Gateways." + + "Applies to both IKE and ESP phases. Allowed values are modp1024, modp1536, modp2048, modp3072, modp4096, modp6144 and modp8192.", + true, ConfigKey.Scope.Domain); + public static final ConfigKey VpnCustomerGatewayObsoleteCheckInterval = new ConfigKey( + ConfigKey.CATEGORY_NETWORK, Long.class, "vpn.customer.gateway.obsolete.check.interval", "0", + "Interval in hours to periodically check VPN customer gateways for obsolete/excluded parameters and generate events and alerts. " + + "Set to 0 to disable. Default: 0 (disabled).", + true, ConfigKey.Scope.Global); List _s2sProviders; @Inject @@ -117,9 +178,12 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn private IpAddressManager ipAddressManager; @Inject private VpcManager vpcManager; + @Inject + private AlertManager _alertMgr; int _connLimit; int _subnetsLimit; + private ScheduledExecutorService _vpnCheckExecutor; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -127,6 +191,7 @@ public boolean configure(String name, Map params) throws Configu _connLimit = NumbersUtil.parseInt(configs.get(Config.Site2SiteVpnConnectionPerVpnGatewayLimit.key()), 4); _subnetsLimit = NumbersUtil.parseInt(configs.get(Config.Site2SiteVpnSubnetsPerCustomerGatewayLimit.key()), 10); assert (_s2sProviders.iterator().hasNext()) : "Did not get injected with a list of S2S providers!"; + _vpnCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpnCustomerGateway-ExcludedAndObsoleteCheck")); return true; } @@ -146,7 +211,7 @@ public Site2SiteVpnGateway createVpnGateway(CreateVpnGatewayCmd cmd) { } Site2SiteVpnGatewayVO gws = _vpnGatewayDao.findByVpcId(vpcId); if (gws != null) { - throw new InvalidParameterValueException(String.format("The VPN gateway of VPC %s already existed!", vpc)); + throw new InvalidParameterValueException(String.format("The VPN gateway of VPC %s already exists!", vpc)); } IPAddressVO requestedIp = _ipAddressDao.findById(cmd.getIpAddressId()); @@ -187,6 +252,113 @@ private IPAddressVO getIpAddressIdForVpn(Long vpcId, Long vpcOferingId, IPAddres } } + private void validateVpnCryptographicParameters(String ikePolicy, String espPolicy, String ikeVersion, Long domainId) { + String excludedEncryption = VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + + Set excludedParameters = getVpnGatewayParametersInBlockedList(ikePolicy, espPolicy, ikeVersion, + excludedEncryption, excludedHashing, excludedIkeVersions, excludedDhGroup); + if (!excludedParameters.isEmpty()) { + throw new InvalidParameterValueException("The following excluded cryptographic parameter(s) cannot be used in a VPN Customer Gateway: " + excludedParameters.toString()); + } + } + + @Override + public Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + Long domainId = customerGw.getDomainId(); + String excludedEncryption = VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + + return getVpnGatewayParametersInBlockedList(customerGw.getIkePolicy(), customerGw.getEspPolicy(), customerGw.getIkeVersion(), + excludedEncryption, excludedHashing, excludedIkeVersions, excludedDhGroup); + } + + @Override + public Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + Long domainId = customerGw.getDomainId(); + String obsoleteEncryption = VpnCustomerGatewayObsoleteEncryptionAlgorithms.valueIn(domainId); + String obsoleteHashing = VpnCustomerGatewayObsoleteHashingAlgorithms.valueIn(domainId); + String obsoleteIkeVersions = VpnCustomerGatewayObsoleteIkeVersions.valueIn(domainId); + String obsoleteDhGroup = VpnCustomerGatewayObsoleteDhGroup.valueIn(domainId); + + return getVpnGatewayParametersInBlockedList(customerGw.getIkePolicy(), customerGw.getEspPolicy(), customerGw.getIkeVersion(), + obsoleteEncryption, obsoleteHashing, obsoleteIkeVersions, obsoleteDhGroup); + } + + private Set getVpnGatewayParametersInBlockedList(String ikePolicy, String espPolicy, String ikeVersion, + String blockedEncryptionList, String blockedHashingList, + String blockedIkeVersionList, String blockedDhGroupList) { + + Set blockedParameters = new HashSet<>(); + if (StringUtils.isEmpty(blockedEncryptionList) + && StringUtils.isEmpty(blockedHashingList) + && StringUtils.isEmpty(blockedIkeVersionList) + && StringUtils.isEmpty(blockedDhGroupList)) { + return blockedParameters; + } + + if (isParameterInList(ikeVersion, blockedIkeVersionList)) { + blockedParameters.add(ikeVersion); + } + + Set ikePolicyResult = getVpnGatewayPolicyParametersInBlockedList(ikePolicy, "IKE", blockedEncryptionList, blockedHashingList, blockedDhGroupList); + if (CollectionUtils.isNotEmpty(ikePolicyResult)) { + blockedParameters.addAll(ikePolicyResult); + } + + Set espPolicyResult = getVpnGatewayPolicyParametersInBlockedList(espPolicy, "ESP", blockedEncryptionList, blockedHashingList, blockedDhGroupList); + if (CollectionUtils.isNotEmpty(espPolicyResult)) { + blockedParameters.addAll(espPolicyResult); + } + + return blockedParameters; + } + + private Set getVpnGatewayPolicyParametersInBlockedList(String policy, String policyType, String blockedEncryptionList, String blockedHashingList, String blockedDhGroupList) { + + String trimmedPolicy = policy.trim(); + String cipherHash = trimmedPolicy.split(";")[0]; + String[] parts = cipherHash.split("-"); + + String encryption = parts[0].trim(); + String hashing = parts.length > 1 ? parts[1].trim() : ""; + + Set blockedParameters = new HashSet<>(); + if (isParameterInList(encryption, blockedEncryptionList)) { + blockedParameters.add(encryption); + } + + if (isParameterInList(hashing, blockedHashingList)) { + blockedParameters.add(hashing); + } + + if (!trimmedPolicy.equals(cipherHash)) { + String dhGroup = trimmedPolicy.split(";")[1].trim(); + if (isParameterInList(dhGroup, blockedDhGroupList)) { + blockedParameters.add(dhGroup); + } + } + return blockedParameters; + } + + private boolean isParameterInList(String parameter, String list) { + if (StringUtils.isEmpty(list) || StringUtils.isEmpty(parameter)) { + return false; + } + + String[] entries = list.split(","); + for (String item : entries) { + if (item != null && item.trim().equalsIgnoreCase(parameter.trim())) { + return true; + } + } + return false; + } + protected void checkCustomerGatewayCidrList(String guestCidrList) { String[] cidrList = guestCidrList.split(","); if (cidrList.length > _subnetsLimit) { @@ -235,6 +407,13 @@ public Site2SiteCustomerGateway createCustomerGateway(CreateVpnCustomerGatewayCm if (!NetUtils.isValidS2SVpnPolicy("esp", espPolicy)) { throw new InvalidParameterValueException("The customer gateway ESP policy " + espPolicy + " is invalid!"); } + + String ikeVersion = cmd.getIkeVersion(); + if (ikeVersion == null) { + ikeVersion = "ike"; + } + validateVpnCryptographicParameters(ikePolicy, espPolicy, ikeVersion, owner.getDomainId()); + Long ikeLifetime = cmd.getIkeLifetime(); if (ikeLifetime == null) { // Default value of lifetime is 1 day @@ -264,7 +443,7 @@ public Site2SiteCustomerGateway createCustomerGateway(CreateVpnCustomerGatewayCm long accountId = owner.getAccountId(); if (_customerGatewayDao.findByNameAndAccountId(name, accountId) != null) { - throw new InvalidParameterValueException("The customer gateway with name " + name + " already existed!"); + throw new InvalidParameterValueException("The customer gateway with name " + name + " already exists!"); } Boolean splitConnections = cmd.getSplitConnections(); @@ -272,11 +451,6 @@ public Site2SiteCustomerGateway createCustomerGateway(CreateVpnCustomerGatewayCm splitConnections = false; } - String ikeVersion = cmd.getIkeVersion(); - if (ikeVersion == null) { - ikeVersion = "ike"; - } - checkCustomerGatewayCidrList(peerCidrList); Site2SiteCustomerGatewayVO gw = @@ -374,7 +548,7 @@ private void validateVpnConnectionOfTheRightAccount(Site2SiteCustomerGateway cus private void validateVpnConnectionDoesntExist(Site2SiteCustomerGateway customerGateway, Site2SiteVpnGateway vpnGateway) { if (_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(vpnGateway.getId(), customerGateway.getId()) != null) { - throw new InvalidParameterValueException(String.format("The vpn connection with customer gateway %s and vpn gateway %s already existed!", customerGateway, vpnGateway)); + throw new InvalidParameterValueException(String.format("The vpn connection with customer gateway %s and vpn gateway %s already exists!", customerGateway, vpnGateway)); } } @@ -521,6 +695,10 @@ public Site2SiteCustomerGateway updateCustomerGateway(UpdateVpnCustomerGatewayCm if (!NetUtils.isValidS2SVpnPolicy("esp", espPolicy)) { throw new InvalidParameterValueException("The customer gateway ESP policy" + espPolicy + " is invalid!"); } + + String ikeVersion = cmd.getIkeVersion(); + validateVpnCryptographicParameters(ikePolicy, espPolicy, ikeVersion, gw.getDomainId()); + Long ikeLifetime = cmd.getIkeLifetime(); if (ikeLifetime == null) { // Default value of lifetime is 1 day @@ -550,14 +728,12 @@ public Site2SiteCustomerGateway updateCustomerGateway(UpdateVpnCustomerGatewayCm Boolean splitConnections = cmd.getSplitConnections(); - String ikeVersion = cmd.getIkeVersion(); - checkCustomerGatewayCidrList(guestCidrList); long accountId = gw.getAccountId(); Site2SiteCustomerGatewayVO existedGw = _customerGatewayDao.findByNameAndAccountId(name, accountId); if (existedGw != null && existedGw.getId() != gw.getId()) { - throw new InvalidParameterValueException("The customer gateway with name " + name + " already existed!"); + throw new InvalidParameterValueException("The customer gateway with name " + name + " already exists!"); } gw.setName(name); @@ -977,4 +1153,83 @@ public Site2SiteVpnGateway updateVpnGateway(Long id, String customId, Boolean fo return _vpnGatewayDao.findById(id); } + + @Override + public boolean start() { + ConfigKeyScheduledExecutionWrapper runner = new ConfigKeyScheduledExecutionWrapper( + _vpnCheckExecutor, + new CheckVpnCustomerGatewayObsoleteParametersTask(), + VpnCustomerGatewayObsoleteCheckInterval, + 3600, + TimeUnit.HOURS); + runner.start(); + return true; + } + + @Override + public boolean stop() { + if (_vpnCheckExecutor != null) { + _vpnCheckExecutor.shutdownNow(); + } + return true; + } + + @Override + public String getConfigComponentName() { + return Site2SiteVpnManager.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { VpnCustomerGatewayExcludedEncryptionAlgorithms, VpnCustomerGatewayExcludedHashingAlgorithms, + VpnCustomerGatewayExcludedIkeVersions, VpnCustomerGatewayExcludedDhGroup, VpnCustomerGatewayObsoleteEncryptionAlgorithms, + VpnCustomerGatewayObsoleteHashingAlgorithms, VpnCustomerGatewayObsoleteIkeVersions, VpnCustomerGatewayObsoleteDhGroup, + VpnCustomerGatewayObsoleteCheckInterval}; + } + + protected class CheckVpnCustomerGatewayObsoleteParametersTask extends ManagedContextRunnable { + + @Override + protected void runInContext() { + List allGateways = _customerGatewayDao.listAll(); + int obsoleteCount = 0; + int excludedCount = 0; + + for (Site2SiteCustomerGatewayVO gateway : allGateways) { + Set excludedParameters = getExcludedVpnGatewayParameters(gateway); + Set obsoleteParameters = getObsoleteVpnGatewayParameters(gateway); + + List message = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(excludedParameters)) { + excludedCount++; + message.add("excluded parameter(s) " + excludedParameters.toString()); + } + if (CollectionUtils.isNotEmpty(obsoleteParameters)) { + obsoleteCount++; + message.add("obsolete parameter(s) " + obsoleteParameters.toString()); + } + + if (CollectionUtils.isNotEmpty(message)) { + Account account = _accountDao.findById(gateway.getAccountId()); + String description = String.format("VPN customer gateway '%s' (Account: %s) contains %s.", + gateway.getName(), account.getAccountName(), String.join(" and ", message)); + ActionEventUtils.onActionEvent(User.UID_SYSTEM, gateway.getAccountId(), gateway.getDomainId(), + EventTypes.EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, description, + gateway.getId(), Site2SiteCustomerGateway.class.getSimpleName()); + } + } + + List message = new ArrayList<>(); + if (excludedCount > 0) { + message.add("excluded parameters: " + excludedCount); + } + if (obsoleteCount > 0) { + message.add("obsolete parameters: " + obsoleteCount); + } + if (CollectionUtils.isNotEmpty(message)) { + String subject = String.format("VPN customer gateways using " + String.join(", ", message)); + _alertMgr.sendAlert(AlertService.AlertType.ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS, 0L, 0L, subject, null); + } + } + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 47dcf60eb32d..8ee35cc35ec0 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -720,6 +720,7 @@ import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEvent; @@ -761,6 +762,7 @@ import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; +import com.cloud.network.vpn.Site2SiteVpnManagerImpl; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerDao; @@ -4778,6 +4780,14 @@ public Map listCapabilities(final ListCapabilitiesCmd cmd) { final Map capabilities = new HashMap<>(); final Account caller = getCaller(); + Long domainId = cmd.getDomainId(); + if (domainId == null) { + domainId = caller.getDomainId(); + } else { + Domain domain = _domainDao.findById(domainId); + _accountService.checkAccess(caller, domain); + } + final boolean isCallerRootAdmin = _accountService.isRootAdmin(caller.getId()); final boolean isCallerAdmin = isCallerRootAdmin || _accountService.isAdmin(caller.getId()); boolean securityGroupsEnabled = false; @@ -4812,7 +4822,7 @@ public Map listCapabilities(final ListCapabilitiesCmd cmd) { final boolean allowUserExpungeRecoverVolume = (VolumeApiServiceImpl.AllowUserExpungeRecoverVolume.valueIn(caller.getId()) | isCallerAdmin); final boolean allowUserForceStopVM = (UserVmManager.AllowUserForceStopVm.valueIn(caller.getId()) | isCallerAdmin); - final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId())); + final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(domainId)); final boolean kubernetesServiceEnabled = Boolean.parseBoolean(_configDao.getValue("cloud.kubernetes.service.enabled")); final boolean kubernetesClusterExperimentalFeaturesEnabled = Boolean.parseBoolean(_configDao.getValue("cloud.kubernetes.cluster.experimental.features.enabled")); @@ -4865,9 +4875,53 @@ public Map listCapabilities(final ListCapabilitiesCmd cmd) { } capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId())); + Map vpnParams = getVpnCustomerGatewayParameters(domainId); + if (!vpnParams.isEmpty()) { + capabilities.put(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS, vpnParams); + } + return capabilities; } + private Map getVpnCustomerGatewayParameters(Long domainId) { + Map vpnParams = new HashMap<>(); + + String excludedEncryption = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms.valueIn(domainId); + String excludedHashing = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms.valueIn(domainId); + String excludedIkeVersions = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions.valueIn(domainId); + String excludedDhGroup = Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup.valueIn(domainId); + String obsoleteEncryption = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms.valueIn(domainId); + String obsoleteHashing = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms.valueIn(domainId); + String obsoleteIkeVersions = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions.valueIn(domainId); + String obsoleteDhGroup = Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup.valueIn(domainId); + + if (!excludedEncryption.isEmpty()) { + vpnParams.put("excludedencryptionalgorithms", excludedEncryption); + } + if (!obsoleteEncryption.isEmpty()) { + vpnParams.put("obsoleteencryptionalgorithms", obsoleteEncryption); + } + if (!excludedHashing.isEmpty()) { + vpnParams.put("excludedhashingalgorithms", excludedHashing); + } + if (!obsoleteHashing.isEmpty()) { + vpnParams.put("obsoletehashingalgorithms", obsoleteHashing); + } + if (!excludedIkeVersions.isEmpty()) { + vpnParams.put("excludedikeversions", excludedIkeVersions); + } + if (!obsoleteIkeVersions.isEmpty()) { + vpnParams.put("obsoleteikeversions", obsoleteIkeVersions); + } + if (!excludedDhGroup.isEmpty()) { + vpnParams.put("excludeddhgroups", excludedDhGroup); + } + if (!obsoleteDhGroup.isEmpty()) { + vpnParams.put("obsoletedhgroups", obsoleteDhGroup); + } + return vpnParams; + } + @Override public GuestOSVO getGuestOs(final Long guestOsId) { return _guestOSDao.findById(guestOsId); diff --git a/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java b/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java new file mode 100644 index 000000000000..291d3a4aa812 --- /dev/null +++ b/server/src/test/java/com/cloud/network/vpn/Site2SiteVpnManagerImplTest.java @@ -0,0 +1,944 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.vpn; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Site2SiteVpnConnection; +import com.cloud.network.Site2SiteVpnConnection.State; +import com.cloud.network.Site2SiteVpnGateway; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayDao; +import com.cloud.network.dao.Site2SiteCustomerGatewayVO; +import com.cloud.network.dao.Site2SiteVpnConnectionDao; +import com.cloud.network.dao.Site2SiteVpnConnectionVO; +import com.cloud.network.dao.Site2SiteVpnGatewayDao; +import com.cloud.network.dao.Site2SiteVpnGatewayVO; +import com.cloud.network.element.Site2SiteVpnServiceProvider; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; +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.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +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; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnConnectionCmd; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnCustomerGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.DeleteVpnGatewayCmd; +import org.apache.cloudstack.api.command.user.vpn.ResetVpnConnectionCmd; +import org.apache.cloudstack.context.CallContext; +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.junit.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +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.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.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class Site2SiteVpnManagerImplTest { + + @Mock + private Site2SiteCustomerGatewayDao _customerGatewayDao; + @Mock + private Site2SiteVpnGatewayDao _vpnGatewayDao; + @Mock + private Site2SiteVpnConnectionDao _vpnConnectionDao; + @Mock + private VpcDao _vpcDao; + @Mock + private IPAddressDao _ipAddressDao; + @Mock + private VpcManager _vpcMgr; + @Mock + private AccountManager _accountMgr; + @Mock + private AnnotationDao annotationDao; + @Mock + private List _s2sProviders; + @Mock + VpcOfferingServiceMapDao vpcOfferingServiceMapDao; + + @InjectMocks + private Site2SiteVpnManagerImpl site2SiteVpnManager; + + private AccountVO account; + private UserVO user; + private VpcVO vpc; + private IPAddressVO ipAddress; + private Site2SiteVpnGatewayVO vpnGateway; + private Site2SiteCustomerGatewayVO customerGateway; + private Site2SiteVpnConnectionVO vpnConnection; + + private static final Long ACCOUNT_ID = 1L; + private static final Long DOMAIN_ID = 2L; + private static final Long VPC_ID = 3L; + private static final Long VPN_GATEWAY_ID = 4L; + private static final Long CUSTOMER_GATEWAY_ID = 5L; + private static final Long VPN_CONNECTION_ID = 6L; + private static final Long IP_ADDRESS_ID = 7L; + + @Before + public void setUp() throws Exception { + account = new AccountVO("testaccount", DOMAIN_ID, "networkdomain", Account.Type.NORMAL, UUID.randomUUID().toString()); + account.setId(ACCOUNT_ID); + user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, account); + + vpc = mock(VpcVO.class); + when(vpc.getId()).thenReturn(VPC_ID); + when(vpc.getAccountId()).thenReturn(ACCOUNT_ID); + when(vpc.getDomainId()).thenReturn(DOMAIN_ID); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + + ipAddress = mock(IPAddressVO.class); + when(ipAddress.getId()).thenReturn(IP_ADDRESS_ID); + when(ipAddress.getVpcId()).thenReturn(VPC_ID); + + vpnGateway = mock(Site2SiteVpnGatewayVO.class); + when(vpnGateway.getId()).thenReturn(VPN_GATEWAY_ID); + when(vpnGateway.getVpcId()).thenReturn(VPC_ID); + when(vpnGateway.getAccountId()).thenReturn(ACCOUNT_ID); + when(vpnGateway.getDomainId()).thenReturn(DOMAIN_ID); + + customerGateway = mock(Site2SiteCustomerGatewayVO.class); + when(customerGateway.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(customerGateway.getAccountId()).thenReturn(ACCOUNT_ID); + when(customerGateway.getDomainId()).thenReturn(DOMAIN_ID); + when(customerGateway.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(customerGateway.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(customerGateway.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(customerGateway.getIkeVersion()).thenReturn("ike"); + + vpnConnection = new Site2SiteVpnConnectionVO(ACCOUNT_ID, DOMAIN_ID, VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID, false); + vpnConnection.setState(State.Pending); + + when(_accountMgr.getAccount(ACCOUNT_ID)).thenReturn(account); + doNothing().when(_accountMgr).checkAccess(any(Account.class), nullable(SecurityChecker.AccessType.class), anyBoolean(), any()); + } + + @After + public void tearDown() throws Exception { + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms); + resetConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup); + CallContext.unregister(); + } + + private void setConfigKeyValue(ConfigKey configKey, String value) { + try { + Field valueField = ConfigKey.class.getDeclaredField("_value"); + valueField.setAccessible(true); + valueField.set(configKey, value); + + Field dynamicField = ConfigKey.class.getDeclaredField("_isDynamic"); + dynamicField.setAccessible(true); + dynamicField.setBoolean(configKey, false); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to set ConfigKey value", e); + } + } + + private void resetConfigKeyValue(ConfigKey configKey) { + try { + Field valueField = ConfigKey.class.getDeclaredField("_value"); + valueField.setAccessible(true); + valueField.set(configKey, null); + + Field dynamicField = ConfigKey.class.getDeclaredField("_isDynamic"); + dynamicField.setAccessible(true); + dynamicField.setBoolean(configKey, true); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to reset ConfigKey value", e); + } + } + + @Test + public void testCreateVpnGatewaySuccess() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + when(cmd.isDisplay()).thenReturn(true); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + when(_ipAddressDao.listByAssociatedVpc(VPC_ID, true)).thenReturn(List.of(ipAddress)); + when(_vpnGatewayDao.persist(any(Site2SiteVpnGatewayVO.class))).thenReturn(vpnGateway); + + Site2SiteVpnGateway result = site2SiteVpnManager.createVpnGateway(cmd); + + assertNotNull(result); + verify(_vpnGatewayDao).persist(any(Site2SiteVpnGatewayVO.class)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnGatewayInvalidVpc() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(null); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnGatewayAlreadyExists() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = CloudRuntimeException.class) + public void testCreateVpnGatewayNoSourceNatIp() { + CreateVpnGatewayCmd cmd = mock(CreateVpnGatewayCmd.class); + when(cmd.getVpcId()).thenReturn(VPC_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + when(_ipAddressDao.listByAssociatedVpc(VPC_ID, true)).thenReturn(new ArrayList<>()); + + site2SiteVpnManager.createVpnGateway(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIp() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("invalid-ip"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("invalid-ip")).thenReturn(false); + netUtilsMock.when(() -> NetUtils.verifyDomainName("invalid-ip")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidCidrList() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("invalid-cidr"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("invalid-cidr")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIkePolicy() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("invalid-policy"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "invalid-policy")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidEspPolicy() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("invalid-policy"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "invalid-policy")).thenReturn(false); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayWithExcludedParameters() throws Exception { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getName()).thenReturn("test-gateway"); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIpsecPsk()).thenReturn("test-psk"); + when(cmd.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getIkeVersion()).thenReturn("ike"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "3des-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayDuplicateName() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getName()).thenReturn("test-gateway"); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + when(_customerGatewayDao.findByNameAndAccountId("test-gateway", ACCOUNT_ID)).thenReturn(customerGateway); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidIkeLifetime() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getIkeLifetime()).thenReturn(86401L); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayInvalidEspLifetime() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspLifetime()).thenReturn(86401L); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList("192.168.1.0/24")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayTooManySubnets() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + String tooManyCidrs = "192.168.1.0/24,192.168.2.0/24,192.168.3.0/24,192.168.4.0/24,192.168.5.0/24," + + "192.168.6.0/24,192.168.7.0/24,192.168.8.0/24,192.168.9.0/24,192.168.10.0/24,192.168.11.0/24"; + when(cmd.getGuestCidrList()).thenReturn(tooManyCidrs); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList(tooManyCidrs)).thenReturn(true); + netUtilsMock.when(() -> NetUtils.getCleanIp4CidrList(tooManyCidrs)).thenReturn(tooManyCidrs); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateCustomerGatewayOverlappingSubnets() { + CreateVpnCustomerGatewayCmd cmd = mock(CreateVpnCustomerGatewayCmd.class); + when(cmd.getGatewayIp()).thenReturn("1.2.3.4"); + when(cmd.getGuestCidrList()).thenReturn("192.168.1.0/24,192.168.1.0/25"); + when(cmd.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + String cidrList = "192.168.1.0/24,192.168.1.0/25"; + netUtilsMock.when(() -> NetUtils.isValidIp4("1.2.3.4")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidCidrList(cidrList)).thenReturn(true); + netUtilsMock.when(() -> NetUtils.getCleanIp4CidrList(cidrList)).thenReturn(cidrList); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("ike", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isValidS2SVpnPolicy("esp", "aes128-sha256;modp2048")).thenReturn(true); + netUtilsMock.when(() -> NetUtils.isNetworksOverlap("192.168.1.0/24", "192.168.1.0/25")).thenReturn(true); + + site2SiteVpnManager.createCustomerGateway(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnConnectionCidrOverlapWithVpc() { + CreateVpnConnectionCmd cmd = mock(CreateVpnConnectionCmd.class); + when(cmd.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(cmd.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + Site2SiteCustomerGatewayVO customerGw = mock(Site2SiteCustomerGatewayVO.class); + when(customerGw.getGuestCidrList()).thenReturn("10.0.0.0/24"); + when(customerGw.getAccountId()).thenReturn(ACCOUNT_ID); + when(customerGw.getDomainId()).thenReturn(DOMAIN_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGw); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID)).thenReturn(null); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + + try (MockedStatic netUtilsMock = Mockito.mockStatic(NetUtils.class)) { + netUtilsMock.when(() -> NetUtils.isNetworksOverlap("10.0.0.0/16", "10.0.0.0/24")).thenReturn(true); + + site2SiteVpnManager.createVpnConnection(cmd); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpnConnectionExceedsLimit() { + CreateVpnConnectionCmd cmd = mock(CreateVpnConnectionCmd.class); + when(cmd.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(cmd.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(cmd.getEntityOwnerId()).thenReturn(ACCOUNT_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.findByVpnGatewayIdAndCustomerGatewayId(VPN_GATEWAY_ID, CUSTOMER_GATEWAY_ID)).thenReturn(null); + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpcDao.findById(VPC_ID)).thenReturn(vpc); + + List existingConns = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + existingConns.add(mock(Site2SiteVpnConnectionVO.class)); + } + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(existingConns); + + site2SiteVpnManager.createVpnConnection(cmd); + } + + @Test + public void testDeleteCustomerGatewaySuccess() { + DeleteVpnCustomerGatewayCmd cmd = mock(DeleteVpnCustomerGatewayCmd.class); + when(cmd.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteCustomerGateway(cmd); + + assertTrue(result); + verify(_customerGatewayDao).remove(CUSTOMER_GATEWAY_ID); + } + + @Test(expected = InvalidParameterValueException.class) + public void testDeleteCustomerGatewayWithConnections() { + DeleteVpnCustomerGatewayCmd cmd = mock(DeleteVpnCustomerGatewayCmd.class); + when(cmd.getId()).thenReturn(CUSTOMER_GATEWAY_ID); + + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(List.of(vpnConnection)); + + site2SiteVpnManager.deleteCustomerGateway(cmd); + } + + @Test + public void testDeleteVpnGatewaySuccess() { + DeleteVpnGatewayCmd cmd = mock(DeleteVpnGatewayCmd.class); + when(cmd.getId()).thenReturn(VPN_GATEWAY_ID); + + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteVpnGateway(cmd); + + assertTrue(result); + verify(_vpnGatewayDao).remove(VPN_GATEWAY_ID); + } + + @Test(expected = InvalidParameterValueException.class) + public void testDeleteVpnGatewayWithConnections() { + DeleteVpnGatewayCmd cmd = mock(DeleteVpnGatewayCmd.class); + when(cmd.getId()).thenReturn(VPN_GATEWAY_ID); + + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(List.of(vpnConnection)); + + site2SiteVpnManager.deleteVpnGateway(cmd); + } + + @Test + public void testDeleteVpnConnectionSuccess() throws ResourceUnavailableException { + DeleteVpnConnectionCmd cmd = mock(DeleteVpnConnectionCmd.class); + when(cmd.getId()).thenReturn(VPN_CONNECTION_ID); + + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Pending); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + boolean result = site2SiteVpnManager.deleteVpnConnection(cmd); + + assertTrue(result); + verify(_vpnConnectionDao).remove(VPN_CONNECTION_ID); + } + + @Test + public void testStartVpnConnectionSuccess() throws ResourceUnavailableException { + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Pending); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(vpnConnection); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + Site2SiteVpnConnection result = site2SiteVpnManager.startVpnConnection(VPN_CONNECTION_ID); + + assertNotNull(result); + verify(_vpnConnectionDao, org.mockito.Mockito.atLeastOnce()).persist(any(Site2SiteVpnConnectionVO.class)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testStartVpnConnectionWrongState() throws ResourceUnavailableException { + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Connected); + + site2SiteVpnManager.startVpnConnection(VPN_CONNECTION_ID); + } + + @Test + public void testResetVpnConnectionSuccess() throws ResourceUnavailableException { + ResetVpnConnectionCmd cmd = mock(ResetVpnConnectionCmd.class); + when(cmd.getId()).thenReturn(VPN_CONNECTION_ID); + + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + vpnConnection.setState(State.Connected); + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.stopSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(vpnConnection); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + Site2SiteVpnConnection result = site2SiteVpnManager.resetVpnConnection(cmd); + + assertNotNull(result); + } + + @Test + public void testCleanupVpnConnectionByVpc() { + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(vpnConnection)); + + boolean result = site2SiteVpnManager.cleanupVpnConnectionByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnConnectionDao).remove(vpnConnection.getId()); + } + + @Test + public void testCleanupVpnGatewayByVpc() { + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(vpnGateway); + when(_vpnConnectionDao.listByVpnGatewayId(VPN_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.cleanupVpnGatewayByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnGatewayDao).remove(VPN_GATEWAY_ID); + } + + @Test + public void testCleanupVpnGatewayByVpcNotFound() { + when(_vpnGatewayDao.findByVpcId(VPC_ID)).thenReturn(null); + + boolean result = site2SiteVpnManager.cleanupVpnGatewayByVpc(VPC_ID); + + assertTrue(result); + verify(_vpnGatewayDao, never()).remove(anyLong()); + } + + @Test + public void testGetConnectionsForRouter() { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(VPC_ID); + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(vpnConnection)); + + List result = site2SiteVpnManager.getConnectionsForRouter(router); + + assertNotNull(result); + assertEquals(1, result.size()); + } + + @Test + public void testGetConnectionsForRouterNoVpc() { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(null); + + List result = site2SiteVpnManager.getConnectionsForRouter(router); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testDeleteCustomerGatewayByAccount() { + when(_customerGatewayDao.listByAccountId(ACCOUNT_ID)).thenReturn(List.of(customerGateway)); + when(_vpnConnectionDao.listByCustomerGatewayId(CUSTOMER_GATEWAY_ID)).thenReturn(new ArrayList<>()); + + boolean result = site2SiteVpnManager.deleteCustomerGatewayByAccount(ACCOUNT_ID); + + assertTrue(result); + verify(_customerGatewayDao).remove(CUSTOMER_GATEWAY_ID); + } + + @Test + public void testReconnectDisconnectedVpnByVpc() throws ResourceUnavailableException { + Site2SiteVpnConnectionVO conn = mock(Site2SiteVpnConnectionVO.class); + when(conn.getId()).thenReturn(VPN_CONNECTION_ID); + when(conn.getState()).thenReturn(State.Disconnected); + when(conn.getCustomerGatewayId()).thenReturn(CUSTOMER_GATEWAY_ID); + when(conn.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID); + when(_vpnConnectionDao.listByVpcId(VPC_ID)).thenReturn(List.of(conn)); + when(_customerGatewayDao.findById(CUSTOMER_GATEWAY_ID)).thenReturn(customerGateway); + when(_vpnConnectionDao.acquireInLockTable(VPN_CONNECTION_ID)).thenReturn(conn); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + Site2SiteVpnServiceProvider provider = mock(Site2SiteVpnServiceProvider.class); + when(provider.startSite2SiteVpn(any(Site2SiteVpnConnection.class))).thenReturn(true); + when(_s2sProviders.iterator()).thenReturn(List.of(provider).iterator()); + when(_vpnConnectionDao.persist(any(Site2SiteVpnConnectionVO.class))).thenReturn(conn); + when(_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(anyLong(), anyBoolean())).thenReturn(true); + + site2SiteVpnManager.reconnectDisconnectedVpnByVpc(VPC_ID); + + verify(_vpnConnectionDao, org.mockito.Mockito.atLeastOnce()).persist(any(Site2SiteVpnConnectionVO.class)); + } + + @Test + public void testUpdateVpnConnection() { + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + when(_vpnConnectionDao.update(anyLong(), any(Site2SiteVpnConnectionVO.class))).thenReturn(true); + when(_vpnConnectionDao.findById(VPN_CONNECTION_ID)).thenReturn(vpnConnection); + + Site2SiteVpnConnection result = site2SiteVpnManager.updateVpnConnection(VPN_CONNECTION_ID, "custom-id", true); + + assertNotNull(result); + } + + @Test + public void testUpdateVpnGateway() { + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + when(_vpnGatewayDao.update(anyLong(), any(Site2SiteVpnGatewayVO.class))).thenReturn(true); + when(_vpnGatewayDao.findById(VPN_GATEWAY_ID)).thenReturn(vpnGateway); + + Site2SiteVpnGateway result = site2SiteVpnManager.updateVpnGateway(VPN_GATEWAY_ID, "custom-id", true); + + assertNotNull(result); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedIkeVersion() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ikev1"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, "ikev1"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded IKE version", result.isEmpty()); + assertEquals("Should detect excluded IKE version", "[ikev1]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedEncryption() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded encryption algorithm", result.isEmpty()); + assertEquals("Should detect excluded encryption algorithm", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedHashing() throws Exception { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-md5;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "aes128"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, "md5"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded algorithms", result.isEmpty()); + assertEquals("Should detect excluded algorithms", "[aes128, md5]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedDhGroup() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp1024"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, "modp1024"); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded DH group", result.isEmpty()); + assertEquals("Should detect excluded DH group", "[modp1024]", result.toString()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersNoExcludedParameters() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertTrue("Should not detect excluded parameters when none are configured", result.isEmpty()); + } + + @Test + public void testVpnGatewayContainsExcludedParametersWithExcludedEspPolicy() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayExcludedDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getExcludedVpnGatewayParameters(gw); + assertFalse("Should detect excluded encryption in ESP policy", result.isEmpty()); + assertEquals("Should detect excluded encryption in ESP policy", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteIkeVersion() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ikev1"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, "ikev1"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete IKE version", result.isEmpty()); + assertEquals("Should detect obsolete IKE version", "[ikev1]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteEncryption() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete encryption algorithm", result.isEmpty()); + assertEquals("Should detect obsolete encryption algorithm", "[3des]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteHashing() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-md5;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, "md5"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete hashing algorithm", result.isEmpty()); + assertEquals("Should detect obsolete hashing algorithm", "[md5]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteDhGroup() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp1024"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, "modp1024"); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete DH group", result.isEmpty()); + assertEquals("Should detect obsolete DH group", "[modp1024]", result.toString()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersNoObsoleteParameters() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertTrue("Should not detect obsolete parameters when none are configured", result.isEmpty()); + } + + @Test + public void testVpnGatewayContainsObsoleteParametersWithObsoleteEspPolicy() { + Site2SiteCustomerGatewayVO gw = mock(Site2SiteCustomerGatewayVO.class); + when(gw.getIkePolicy()).thenReturn("aes128-sha256;modp2048"); + when(gw.getEspPolicy()).thenReturn("3des-sha256;modp2048"); + when(gw.getIkeVersion()).thenReturn("ike"); + when(gw.getDomainId()).thenReturn(DOMAIN_ID); + + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteIkeVersions, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteEncryptionAlgorithms, "3des"); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteHashingAlgorithms, ""); + setConfigKeyValue(Site2SiteVpnManagerImpl.VpnCustomerGatewayObsoleteDhGroup, ""); + + java.util.Set result = site2SiteVpnManager.getObsoleteVpnGatewayParameters(gw); + assertFalse("Should detect obsolete encryption in ESP policy", result.isEmpty()); + assertEquals("Should detect obsolete encryption in ESP policy", "[3des]", result.toString()); + } +} diff --git a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java index 3558eed1cfa1..af6c1c599aab 100644 --- a/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java @@ -23,7 +23,6 @@ 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; @@ -41,11 +40,13 @@ import org.springframework.stereotype.Component; import javax.naming.ConfigurationException; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; @Component -public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager, Site2SiteVpnService { +public class MockSite2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager { /* (non-Javadoc) * @see com.cloud.network.vpn.Site2SiteVpnService#createVpnGateway(org.apache.cloudstack.api.commands.CreateVpnGatewayCmd) @@ -275,4 +276,16 @@ public Site2SiteVpnGateway updateVpnGateway(Long id, String customId, Boolean fo return null; } + @Override + public Set getExcludedVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + // TODO Auto-generated method stub + return new HashSet<>(); + } + + @Override + public Set getObsoleteVpnGatewayParameters(Site2SiteCustomerGateway customerGw) { + // TODO Auto-generated method stub + return new HashSet<>(); + } + } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 86df45411e9e..c20827f2100b 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -807,7 +807,7 @@ "label.delete.tungsten.service.group": "Delete Service Group", "label.delete.volumes": "Data volumes to be deleted", "label.delete.vpn.connection": "Delete VPN connection", -"label.delete.vpn.customer.gateway": "Delete VPN customer gateway", +"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.webhook": "Delete Webhook", @@ -2629,6 +2629,7 @@ "label.update.to": "updated to", "label.update.traffic.label": "Update traffic labels", "label.update.vmware.datacenter": "Update VMWare datacenter", +"label.update.vpn.customer.gateway": "Update VPN Customer Gateway", "label.update.webhook": "Update Webhook", "label.updating": "Updating", "label.upgrade.router.newer.template": "Upgrade router to use newer Template", @@ -3093,8 +3094,8 @@ "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...", -"message.add.vpn.customer.gateway": "Adding VPN customer gateway", -"message.add.vpn.customer.gateway.processing": "Creation of VPN customer gateway is in progress", +"message.add.vpn.customer.gateway": "Adding VPN Customer Gateway", +"message.add.vpn.customer.gateway.processing": "Creation of VPN Customer Gateway is in progress", "message.add.vpn.gateway": "Please confirm that you want to add a VPN Gateway.", "message.add.vpn.gateway.failed": "Adding VPN gateway failed", "message.add.vpn.gateway.processing": "Adding VPN gateway...", @@ -3242,7 +3243,7 @@ "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.create.vpn.customer.gateway.failed": "VPN Customer Gateway creation failed.", "message.creating.autoscale.vmgroup": "Creating AutoScaling Group", "message.creating.autoscale.vmprofile": "Creating AutoScale Instance profile", "message.creating.autoscale.scaledown.conditions": "Creating ScaleDown conditions", @@ -3293,7 +3294,7 @@ "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.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.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.delete.vpn.gateway.failed": "Failed to delete VPN Gateway.", "message.delete.webhook": "Please confirm that you want to delete this Webhook.", @@ -3834,7 +3835,7 @@ "message.success.add.tungsten.routing.policy": "Successfully added Tungsten-Fabric routing policy", "message.success.add.vpc": "Successfully added a Virtual Private Cloud", "message.success.add.vpc.network": "Successfully added a VPC network", -"message.success.add.vpn.customer.gateway": "Successfully added VPN customer gateway", +"message.success.add.vpn.customer.gateway": "Successfully added VPN Customer Gateway", "message.success.add.vpn.gateway": "Successfully added VPN gateway", "message.success.add.webhook.filter": "Successfully added Webhook Filter", "message.success.assign.sslcert": "Successfully assigned SSL certificate", @@ -3960,6 +3961,7 @@ "message.success.update.network": "Successfully updated Network", "message.success.update.template": "Successfully updated Template", "message.success.update.user": "Successfully updated User", +"message.success.update.vpn.customer.gateway": "Successfully updated VPN Customer Gateway", "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.", @@ -3987,6 +3989,9 @@ "message.update.condition.failed": "Failed to update condition", "message.update.condition.processing": "Updating condition...", "message.update.failed": "Update failed", +"message.update.vpn.customer.gateway": "Update VPN Customer Gateway", +"message.update.vpn.customer.gateway.failed": "Updating the VPN Customer Gateway failed", +"message.update.vpn.customer.gateway.processing": "Updating VPN Customer Gateway...", "message.test.webhook.delivery": "Test delivery to the Webhook with an optional payload", "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.", @@ -4074,6 +4079,11 @@ "message.volumes.managed": "Volumes controlled by CloudStack.", "message.volumes.unmanaged": "Volumes not controlled by CloudStack.", "message.vpc.restart.required": "Restart is required for VPC(s). Click here to view VPC(s) which require restart.", +"message.vpn.customer.gateway.contains.excluded.obsolete.parameters": "This VPN Customer Gateway contains cryptographic parameters that are marked as excluded or obsolete by the Administrator. Consider changing them using the Update VPN Customer Gateway form.", +"message.vpn.customer.gateway.excluded.parameter": " is marked as excluded. Please choose another value.", +"message.vpn.customer.gateway.obsolete.parameter": " is marked as obsolete/insecure. Please choose another value.", +"message.vpn.customer.gateway.obsolete.parameter.tooltip": "This parameter value is marked as obsolete/insecure.", +"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.change.primary.storage.scope": "This feature is tested and supported for the following configurations:
KVM - NFS/Ceph - DefaultPrimary
VMware - NFS - DefaultPrimary
*There might be extra steps involved to make it work for other configurations.", "message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index ba75465d8924..375fddcb55c6 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -170,6 +170,14 @@ + +   + + + + import('@/views/network/UpdateVpnCustomerGateway.vue'))) }, { api: 'deleteVpnCustomerGateway', diff --git a/ui/src/views/network/CreateVpnCustomerGateway.vue b/ui/src/views/network/CreateVpnCustomerGateway.vue index f71fc4709e8d..155765a276f5 100644 --- a/ui/src/views/network/CreateVpnCustomerGateway.vue +++ b/ui/src/views/network/CreateVpnCustomerGateway.vue @@ -15,352 +15,58 @@ // specific language governing permissions and limitations // under the License. + - diff --git a/ui/src/views/network/UpdateVpnCustomerGateway.vue b/ui/src/views/network/UpdateVpnCustomerGateway.vue new file mode 100644 index 000000000000..2d54e8e031ed --- /dev/null +++ b/ui/src/views/network/UpdateVpnCustomerGateway.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. + + + diff --git a/ui/src/views/network/VpnCustomerGateway.vue b/ui/src/views/network/VpnCustomerGateway.vue new file mode 100644 index 000000000000..c1b1ed78ce06 --- /dev/null +++ b/ui/src/views/network/VpnCustomerGateway.vue @@ -0,0 +1,581 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT 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 8b2f1f19c27bebf908f9cda53ec1fadd005e2520 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Mon, 19 Jan 2026 03:51:47 -0500 Subject: [PATCH 63/65] Support dedicating backup offerings to domains (#12194) * Add support for dedicating backup offerings to domains * Add tests and UI support and update response params * add license header * exclude backupofferingdetailsvo from sonar * fix pre-commit checks - missing / extra EOF line * add test * EOF * filter backup offerings by domain id * add unit tests * add more unit tests and remove response file from code coverage check * update checks * address review comments: extract common code, fix tests * added bean definition * address comments * add unit tests to increase coverage * pre-commit check failure fix * address merge issue * allow updating backup offering when only domain id is modified --- .../java/com/cloud/user/AccountService.java | 3 + .../cloudstack/acl/SecurityChecker.java | 4 + .../cloudstack/api/BaseBackupListCmd.java | 2 +- .../admin/backup/ImportBackupOfferingCmd.java | 22 ++ .../admin/backup/UpdateBackupOfferingCmd.java | 28 +- .../network/UpdateNetworkOfferingCmd.java | 65 +--- .../admin/offering/UpdateDiskOfferingCmd.java | 62 +-- .../offering/UpdateServiceOfferingCmd.java | 62 +-- .../admin/vpc/UpdateVPCOfferingCmd.java | 64 +-- .../offering/DomainAndZoneIdResolver.java | 114 ++++++ .../api/response/BackupOfferingResponse.java | 19 + .../cloudstack/backup/BackupManager.java | 2 + .../offering/DomainAndZoneIdResolverTest.java | 149 +++++++ .../backup/BackupOfferingDetailsVO.java | 86 ++++ .../cloudstack/backup/BackupOfferingVO.java | 7 + .../backup/dao/BackupOfferingDaoImpl.java | 23 +- .../backup/dao/BackupOfferingDetailsDao.java | 32 ++ .../dao/BackupOfferingDetailsDaoImpl.java | 101 +++++ ...s-between-management-and-usage-context.xml | 3 +- .../META-INF/db/schema-42210to42300.sql | 10 + .../dao/BackupOfferingDetailsDaoImplTest.java | 251 ++++++++++++ .../hypervisors/ovm3/sonar-project.properties | 2 +- .../management/MockAccountManager.java | 6 + pom.xml | 2 + .../java/com/cloud/acl/DomainChecker.java | 33 ++ .../ConfigurationManagerImpl.java | 47 +-- .../com/cloud/network/vpc/VpcManagerImpl.java | 31 +- .../com/cloud/user/AccountManagerImpl.java | 16 + .../java/com/cloud/utils/DomainHelper.java | 63 +++ .../cloudstack/backup/BackupManagerImpl.java | 117 +++++- .../core/spring-server-core-misc-context.xml | 2 + .../java/com/cloud/acl/DomainCheckerTest.java | 45 +++ .../ConfigurationManagerImplTest.java | 3 + .../com/cloud/vm/UserVmManagerImplTest.java | 3 +- .../cloudstack/backup/BackupManagerTest.java | 366 +++++++++++++++++- .../CreateNetworkOfferingTest.java | 4 + tools/marvin/setup.py | 2 +- ui/src/config/section/offering.js | 6 +- .../views/offering/ImportBackupOffering.vue | 69 +++- 39 files changed, 1610 insertions(+), 316 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java create mode 100644 api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java create mode 100644 engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java create mode 100644 engine/schema/src/test/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImplTest.java create mode 100644 server/src/main/java/com/cloud/utils/DomainHelper.java diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 09fe5ffc0590..8f29a2fbc428 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -36,6 +36,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; +import org.apache.cloudstack.backup.BackupOffering; public interface AccountService { @@ -115,6 +116,8 @@ User createUser(String userName, String password, String firstName, String lastN void checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException; + void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException; + void checkAccess(User user, ControlledEntity entity); void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException; diff --git a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java index 82a8ec5fe932..fa17df7c6ed4 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java @@ -27,6 +27,8 @@ import com.cloud.user.User; import com.cloud.utils.component.Adapter; +import org.apache.cloudstack.backup.BackupOffering; + /** * SecurityChecker checks the ownership and access control to objects within */ @@ -145,4 +147,6 @@ boolean checkAccess(Account caller, AccessType accessType, String action, Contro boolean checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException; boolean checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException; + + boolean checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java index 0aa8366bcd5c..2a64a1fb6fd8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -25,7 +25,7 @@ import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.context.CallContext; -public abstract class BaseBackupListCmd extends BaseListCmd { +public abstract class BaseBackupListCmd extends BaseListAccountResourcesCmd { protected void setupResponseBackupOfferingsList(final List offerings, final Integer count) { final ListResponse response = new ListResponse<>(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java index 2e73698e7aa1..5e702585a2c3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupOffering; @@ -40,6 +41,11 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.collections.CollectionUtils; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; @APICommand(name = "importBackupOffering", description = "Imports a backup offering using a backup provider", @@ -76,6 +82,13 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { description = "Whether users are allowed to create adhoc backups and backup schedules", required = true) private Boolean userDrivenBackups; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = DomainResponse.class, + description = "the ID of the containing domain(s), null for public offerings") + private List domainIds; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -100,6 +113,15 @@ public Boolean getUserDrivenBackups() { return userDrivenBackups == null ? false : userDrivenBackups; } + public List getDomainIds() { + if (CollectionUtils.isNotEmpty(domainIds)) { + Set set = new LinkedHashSet<>(domainIds); + domainIds.clear(); + domainIds.addAll(set); + } + return domainIds; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java index a645b1e0c8db..2f0dd6acd0e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java @@ -25,19 +25,24 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; import com.cloud.utils.exception.CloudRuntimeException; +import java.util.List; +import java.util.function.LongFunction; + @APICommand(name = "updateBackupOffering", description = "Updates a backup offering.", responseObject = BackupOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.16.0") -public class UpdateBackupOfferingCmd extends BaseCmd { +public class UpdateBackupOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { @Inject private BackupManager backupManager; @@ -57,6 +62,13 @@ public class UpdateBackupOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = CommandType.BOOLEAN, description = "Whether to allow user driven backups or not") private Boolean allowUserDrivenBackups; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.STRING, + description = "the ID of the containing domain(s) as comma separated string, public for public offerings", + since = "4.23.0", + length = 4096) + private String domainIds; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -82,7 +94,7 @@ public Boolean getAllowUserDrivenBackups() { @Override public void execute() { try { - if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null) { + if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null && CollectionUtils.isEmpty(getDomainIds())) { throw new InvalidParameterValueException(String.format("Can't update Backup Offering [id: %s] because there are no parameters to be updated, at least one of the", "following should be informed: name, description or allowUserDrivenBackups.", id)); } @@ -103,6 +115,18 @@ public void execute() { } } + public List getDomainIds() { + // backupManager may be null in unit tests where the command is spied without injection. + // Avoid creating a method reference to a null receiver which causes NPE. When backupManager + // is null, pass null as the defaultDomainsProvider so resolveDomainIds will simply return + // an empty list or parse the explicit domainIds string. + LongFunction> defaultDomainsProvider = null; + if (backupManager != null) { + defaultDomainsProvider = backupManager::getBackupOfferingDomains; + } + return resolveDomainIds(domainIds, id, defaultDomainsProvider, "backup offering"); + } + @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java index 9af10262b2d5..e3fac81a7932 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.network; -import java.util.ArrayList; import java.util.List; import org.apache.cloudstack.api.APICommand; @@ -26,18 +25,16 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.NetworkOfferingResponse; -import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; -import com.cloud.exception.InvalidParameterValueException; + import com.cloud.offering.NetworkOffering; import com.cloud.user.Account; @APICommand(name = "updateNetworkOffering", description = "Updates a network offering.", responseObject = NetworkOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateNetworkOfferingCmd extends BaseCmd { +public class UpdateNetworkOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -129,63 +126,11 @@ public String getTags() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getNetworkOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getNetworkOfferingDomains, "network offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getNetworkOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getNetworkOfferingZones, "network offering"); } ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java index 2f07f85f9836..917d7ff42d82 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import java.util.ArrayList; import java.util.List; import com.cloud.offering.DiskOffering.State; @@ -27,19 +26,18 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.DiskOffering; import com.cloud.user.Account; @APICommand(name = "updateDiskOffering", description = "Updates a disk offering.", responseObject = DiskOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateDiskOfferingCmd extends BaseCmd { +public class UpdateDiskOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -151,63 +149,11 @@ public Boolean getDisplayOffering() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getDiskOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getDiskOfferingDomains, "disk offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getDiskOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getDiskOfferingZones, "disk offering"); } public String getTags() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java index 4027662574ab..3a6d6639a5b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -28,19 +27,18 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; @APICommand(name = "updateServiceOffering", description = "Updates a service offering.", responseObject = ServiceOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateServiceOfferingCmd extends BaseCmd { +public class UpdateServiceOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -130,63 +128,11 @@ public Integer getSortKey() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getServiceOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getServiceOfferingDomains, "service offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getServiceOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getServiceOfferingZones, "service offering"); } public String getStorageTags() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java index b8a8077b30b5..300584428eae 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.vpc; -import java.util.ArrayList; import java.util.List; import org.apache.cloudstack.api.APICommand; @@ -26,19 +25,16 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.VpcOfferingResponse; -import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.event.EventTypes; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.vpc.VpcOffering; import com.cloud.user.Account; @APICommand(name = "updateVPCOffering", description = "Updates VPC offering", responseObject = VpcOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateVPCOfferingCmd extends BaseAsyncCmd { +public class UpdateVPCOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -92,63 +88,11 @@ public String getState() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_vpcProvSvc.getVpcOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _vpcProvSvc::getVpcOfferingDomains, "VPC offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_vpcProvSvc.getVpcOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _vpcProvSvc::getVpcOfferingZones, "VPC offering"); } public Integer getSortKey() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java b/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java new file mode 100644 index 000000000000..b302c4a9beec --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.offering; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.LongFunction; + +import com.cloud.dc.DataCenter; +import com.cloud.domain.Domain; +import com.cloud.exception.InvalidParameterValueException; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Helper for commands that accept a domainIds or zoneIds string and need to + * resolve them to lists of IDs, falling back to an offering-specific + * default provider. + */ +public interface DomainAndZoneIdResolver { + /** + * Parse the provided domainIds string and return a list of domain IDs. + * If domainIds is empty, the defaultDomainsProvider will be invoked with the + * provided resource id to obtain the current domains. + */ + default List resolveDomainIds(final String domainIds, final Long id, final LongFunction> defaultDomainsProvider, final String resourceTypeName) { + final List validDomainIds = new ArrayList<>(); + final BaseCmd base = (BaseCmd) this; + final Logger logger = LogManager.getLogger(base.getClass()); + + if (StringUtils.isEmpty(domainIds)) { + if (defaultDomainsProvider != null) { + final List defaults = defaultDomainsProvider.apply(id); + if (defaults != null) { + validDomainIds.addAll(defaults); + } + } + return validDomainIds; + } + + final String[] domains = domainIds.split(","); + final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName; + for (String domain : domains) { + final String trimmed = domain == null ? "" : domain.trim(); + if (trimmed.isEmpty() || "public".equalsIgnoreCase(trimmed)) { + continue; + } + + final Domain validDomain = base._entityMgr.findByUuid(Domain.class, trimmed); + if (validDomain == null) { + logger.warn("Invalid domain specified for {}", type); + throw new InvalidParameterValueException("Failed to create " + type + " because invalid domain has been specified."); + } + validDomainIds.add(validDomain.getId()); + } + + return validDomainIds; + } + + /** + * Parse the provided zoneIds string and return a list of zone IDs. + * If zoneIds is empty, the defaultZonesProvider will be invoked with the + * provided resource id to obtain the current zones. + */ + default List resolveZoneIds(final String zoneIds, final Long id, final LongFunction> defaultZonesProvider, final String resourceTypeName) { + final List validZoneIds = new ArrayList<>(); + final BaseCmd base = (BaseCmd) this; + final Logger logger = LogManager.getLogger(base.getClass()); + + if (StringUtils.isEmpty(zoneIds)) { + if (defaultZonesProvider != null) { + final List defaults = defaultZonesProvider.apply(id); + if (defaults != null) { + validZoneIds.addAll(defaults); + } + } + return validZoneIds; + } + + final String[] zones = zoneIds.split(","); + final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName; + for (String zone : zones) { + final String trimmed = zone == null ? "" : zone.trim(); + if (trimmed.isEmpty() || "all".equalsIgnoreCase(trimmed)) { + continue; + } + + final DataCenter validZone = base._entityMgr.findByUuid(DataCenter.class, trimmed); + if (validZone == null) { + logger.warn("Invalid zone specified for {}: {}", type, trimmed); + throw new InvalidParameterValueException("Failed to create " + type + " because invalid zone has been specified."); + } + validZoneIds.add(validZone.getId()); + } + + return validZoneIds; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java index b3a7d0362198..c4f3ee31dadc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java @@ -61,6 +61,16 @@ public class BackupOfferingResponse extends BaseResponse { @Param(description = "Zone name") private String zoneName; + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the domain ID(s) this backup offering belongs to.", + since = "4.23.0") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain name(s) this backup offering belongs to.", + since = "4.23.0") + private String domain; + @SerializedName(ApiConstants.CROSS_ZONE_INSTANCE_CREATION) @Param(description = "the backups with this offering can be used to create Instances on all Zones", since = "4.22.0") private Boolean crossZoneInstanceCreation; @@ -108,4 +118,13 @@ public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) { public void setCreated(Date created) { this.created = created; } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setDomain(String domain) { + this.domain = domain; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index db051313d962..cbaf61405970 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -136,6 +136,8 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer */ BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd); + List getBackupOfferingDomains(final Long offeringId); + /** * List backup offerings * @param ListBackupOfferingsCmd API cmd diff --git a/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java b/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java new file mode 100644 index 000000000000..e679bbf2d1f1 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java @@ -0,0 +1,149 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.offering; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.LongFunction; + +import com.cloud.dc.DataCenter; +import com.cloud.domain.Domain; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.db.EntityManager; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.ServerApiException; +import org.junit.Assert; +import org.junit.Test; + +public class DomainAndZoneIdResolverTest { + static class TestCmd extends BaseCmd implements DomainAndZoneIdResolver { + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + // No implementation needed for tests + } + + @Override + public String getCommandName() { + return "test"; + } + + @Override + public long getEntityOwnerId() { + return 1L; + } + } + + private void setEntityMgr(final BaseCmd cmd, final EntityManager entityMgr) throws Exception { + Field f = BaseCmd.class.getDeclaredField("_entityMgr"); + f.setAccessible(true); + f.set(cmd, entityMgr); + } + + @Test + public void resolveDomainIds_usesDefaultProviderWhenEmpty() { + TestCmd cmd = new TestCmd(); + + final LongFunction> defaultsProvider = id -> Arrays.asList(100L, 200L); + + List result = cmd.resolveDomainIds("", 42L, defaultsProvider, "offering"); + Assert.assertEquals(Arrays.asList(100L, 200L), result); + } + + @Test + public void resolveDomainIds_resolvesValidUuids() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + Domain d1 = mock(Domain.class); + when(d1.getId()).thenReturn(10L); + Domain d2 = mock(Domain.class); + when(d2.getId()).thenReturn(20L); + + when(em.findByUuid(Domain.class, "uuid1")).thenReturn(d1); + when(em.findByUuid(Domain.class, "uuid2")).thenReturn(d2); + + List ids = cmd.resolveDomainIds("uuid1, public, uuid2", null, null, "template"); + Assert.assertEquals(Arrays.asList(10L, 20L), ids); + } + + @Test + public void resolveDomainIds_invalidUuid_throws() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + when(em.findByUuid(Domain.class, "bad-uuid")).thenReturn(null); + + Assert.assertThrows(InvalidParameterValueException.class, + () -> cmd.resolveDomainIds("bad-uuid", null, null, "offering")); + } + + @Test + public void resolveZoneIds_usesDefaultProviderWhenEmpty() { + TestCmd cmd = new TestCmd(); + + final LongFunction> defaultsProvider = id -> Collections.singletonList(300L); + + List result = cmd.resolveZoneIds("", 99L, defaultsProvider, "offering"); + Assert.assertEquals(Collections.singletonList(300L), result); + } + + @Test + public void resolveZoneIds_resolvesValidUuids() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + DataCenter z1 = mock(DataCenter.class); + when(z1.getId()).thenReturn(30L); + DataCenter z2 = mock(DataCenter.class); + when(z2.getId()).thenReturn(40L); + + when(em.findByUuid(DataCenter.class, "zone-1")).thenReturn(z1); + when(em.findByUuid(DataCenter.class, "zone-2")).thenReturn(z2); + + List ids = cmd.resolveZoneIds("zone-1, all, zone-2", null, null, "service"); + Assert.assertEquals(Arrays.asList(30L, 40L), ids); + } + + @Test + public void resolveZoneIds_invalidUuid_throws() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + when(em.findByUuid(DataCenter.class, "bad-zone")).thenReturn(null); + + Assert.assertThrows(InvalidParameterValueException.class, + () -> cmd.resolveZoneIds("bad-zone", null, null, "offering")); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java new file mode 100644 index 000000000000..6bdf7602a9d4 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "backup_offering_details") +public class BackupOfferingDetailsVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_offering_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value") + private String value; + + @Column(name = "display") + private boolean display = true; + + protected BackupOfferingDetailsVO() { + } + + public BackupOfferingDetailsVO(long backupOfferingId, String name, String value, boolean display) { + this.resourceId = backupOfferingId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getResourceId() { + return resourceId; + } + + public void setResourceId(long backupOfferingId) { + this.resourceId = backupOfferingId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean isDisplay() { + return display; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java index d30385af575d..ebeb7d4a2d59 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.backup; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + import java.util.Date; import java.util.UUID; @@ -131,4 +133,9 @@ public void setDescription(String description) { public Date getCreated() { return created; } + + @Override + public String toString() { + return String.format("Backup offering %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "name", "uuid")); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index a41e4e70d339..708faeef4643 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -20,6 +20,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupOfferingVO; @@ -30,10 +32,16 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import java.util.List; + public class BackupOfferingDaoImpl extends GenericDaoBase implements BackupOfferingDao { @Inject DataCenterDao dataCenterDao; + @Inject + BackupOfferingDetailsDao backupOfferingDetailsDao; + @Inject + DomainDao domainDao; private SearchBuilder backupPoliciesSearch; @@ -51,8 +59,9 @@ protected void init() { @Override public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, Boolean crossZoneInstanceCreation) { - DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); + DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); + List domainIds = backupOfferingDetailsDao.findDomainIds(offering.getId()); BackupOfferingResponse response = new BackupOfferingResponse(); response.setId(offering.getUuid()); response.setName(offering.getName()); @@ -64,6 +73,18 @@ public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, response.setZoneId(zone.getUuid()); response.setZoneName(zone.getName()); } + if (domainIds != null && !domainIds.isEmpty()) { + String domainUUIDs = domainIds.stream().map(Long::valueOf).map(domainId -> { + DomainVO domain = domainDao.findById(domainId); + return domain != null ? domain.getUuid() : ""; + }).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse(""); + String domainNames = domainIds.stream().map(Long::valueOf).map(domainId -> { + DomainVO domain = domainDao.findById(domainId); + return domain != null ? domain.getName() : ""; + }).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse(""); + response.setDomain(domainNames); + response.setDomainId(domainUUIDs); + } if (crossZoneInstanceCreation) { response.setCrossZoneInstanceCreation(true); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java new file mode 100644 index 000000000000..390fcba1e0e7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import java.util.List; + +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import com.cloud.utils.db.GenericDao; + +public interface BackupOfferingDetailsDao extends GenericDao, ResourceDetailsDao { + List findDomainIds(final long resourceId); + List findZoneIds(final long resourceId); + String getDetail(Long backupOfferingId, String key); + List findOfferingIdsByDomainIds(List domainIds); + void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List filteredDomainIds); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java new file mode 100644 index 000000000000..f052c93f9817 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.cloud.utils.db.DB; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +@Component +public class BackupOfferingDetailsDaoImpl extends ResourceDetailsDaoBase implements BackupOfferingDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new BackupOfferingDetailsVO(resourceId, key, value, display)); + } + + @Override + public List findDomainIds(long resourceId) { + final List domainIds = new ArrayList<>(); + for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.DOMAIN_ID)) { + final Long domainId = Long.valueOf(detail.getValue()); + if (domainId > 0) { + domainIds.add(domainId); + } + } + return domainIds; + } + + @Override + public List findZoneIds(long resourceId) { + final List zoneIds = new ArrayList<>(); + for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.ZONE_ID)) { + final Long zoneId = Long.valueOf(detail.getValue()); + if (zoneId > 0) { + zoneIds.add(zoneId); + } + } + return zoneIds; + } + + @Override + public String getDetail(Long backupOfferingId, String key) { + String detailValue = null; + BackupOfferingDetailsVO backupOfferingDetail = findDetail(backupOfferingId, key); + if (backupOfferingDetail != null) { + detailValue = backupOfferingDetail.getValue(); + } + return detailValue; + } + + @Override + public List findOfferingIdsByDomainIds(List domainIds) { + Object[] dIds = domainIds.stream().map(s -> String.valueOf(s)).collect(Collectors.toList()).toArray(); + return findResourceIdsByNameAndValueIn("domainid", dIds); + } + + @DB + @Override + public void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List filteredDomainIds) { + SearchBuilder sb = createSearchBuilder(); + List detailsVO = new ArrayList<>(); + sb.and("offeringId", sb.entity().getResourceId(), SearchCriteria.Op.EQ); + sb.and("detailName", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("offeringId", String.valueOf(backupOfferingId)); + sc.setParameters("detailName", ApiConstants.DOMAIN_ID); + remove(sc); + for (Long domainId : filteredDomainIds) { + detailsVO.add(new BackupOfferingDetailsVO(backupOfferingId, ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); + } + if (!detailsVO.isEmpty()) { + for (BackupOfferingDetailsVO detailVO : detailsVO) { + persist(detailVO); + } + } + } +} diff --git a/engine/schema/src/main/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 d308a9e5aaf9..1846c3c62a0e 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 @@ -71,6 +71,7 @@ - + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 07f394b19c90..d330ecd0c0d5 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -19,6 +19,16 @@ -- Schema upgrade from 4.22.1.0 to 4.23.0.0 --; +CREATE TABLE `cloud`.`backup_offering_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `backup_offering_id` bigint unsigned NOT NULL COMMENT 'Backup offering id', + `name` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_offering_details__backup_offering_id` FOREIGN KEY `fk_offering_details__backup_offering_id`(`backup_offering_id`) REFERENCES `backup_offering`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- Update value to random for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_random -- Update value to firstfit for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_firstfit UPDATE `cloud`.`configuration` SET value='random' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_random'; diff --git a/engine/schema/src/test/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImplTest.java b/engine/schema/src/test/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImplTest.java new file mode 100644 index 000000000000..fc8f2d0fcf71 --- /dev/null +++ b/engine/schema/src/test/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImplTest.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 org.apache.cloudstack.backup.dao; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.db.SearchCriteria; + +@RunWith(MockitoJUnitRunner.class) +public class BackupOfferingDetailsDaoImplTest { + + @Spy + @InjectMocks + private BackupOfferingDetailsDaoImpl backupOfferingDetailsDao; + + private static final long RESOURCE_ID = 1L; + private static final long OFFERING_ID = 100L; + private static final String TEST_KEY = "testKey"; + private static final String TEST_VALUE = "testValue"; + + @Test + public void testAddDetail() { + BackupOfferingDetailsVO detailVO = new BackupOfferingDetailsVO(RESOURCE_ID, TEST_KEY, TEST_VALUE, true); + + Assert.assertEquals("Resource ID should match", RESOURCE_ID, detailVO.getResourceId()); + Assert.assertEquals("Detail name/key should match", TEST_KEY, detailVO.getName()); + Assert.assertEquals("Detail value should match", TEST_VALUE, detailVO.getValue()); + Assert.assertTrue("Display flag should be true", detailVO.isDisplay()); + + BackupOfferingDetailsVO detailVOHidden = new BackupOfferingDetailsVO(RESOURCE_ID, "hiddenKey", "hiddenValue", false); + Assert.assertFalse("Display flag should be false", detailVOHidden.isDisplay()); + } + + @Test + public void testFindDomainIdsWithMultipleDomains() { + List mockDetails = Arrays.asList( + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "1", false), + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "2", false), + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "3", false) + ); + + Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID); + + List domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID); + + Assert.assertNotNull(domainIds); + Assert.assertEquals(3, domainIds.size()); + Assert.assertEquals(Arrays.asList(1L, 2L, 3L), domainIds); + } + + @Test + public void testFindDomainIdsWithEmptyList() { + Mockito.doReturn(Collections.emptyList()).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID); + + List domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID); + + Assert.assertNotNull(domainIds); + Assert.assertTrue(domainIds.isEmpty()); + } + + @Test + public void testFindDomainIdsExcludesZeroOrNegativeValues() { + List mockDetails = Arrays.asList( + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "1", false), + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "0", false), + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "-1", false), + createDetailVO(RESOURCE_ID, ApiConstants.DOMAIN_ID, "2", false) + ); + + Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.DOMAIN_ID); + + List domainIds = backupOfferingDetailsDao.findDomainIds(RESOURCE_ID); + + Assert.assertNotNull(domainIds); + Assert.assertEquals(2, domainIds.size()); + Assert.assertEquals(Arrays.asList(1L, 2L), domainIds); + } + + @Test + public void testFindZoneIdsWithMultipleZones() { + List mockDetails = Arrays.asList( + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "10", false), + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "20", false), + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "30", false) + ); + + Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.ZONE_ID); + + List zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID); + + Assert.assertNotNull(zoneIds); + Assert.assertEquals(3, zoneIds.size()); + Assert.assertEquals(Arrays.asList(10L, 20L, 30L), zoneIds); + } + + @Test + public void testFindZoneIdsWithEmptyList() { + Mockito.doReturn(Collections.emptyList()).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.ZONE_ID); + + List zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID); + + Assert.assertNotNull(zoneIds); + Assert.assertTrue(zoneIds.isEmpty()); + } + + @Test + public void testFindZoneIdsExcludesZeroOrNegativeValues() { + List mockDetails = Arrays.asList( + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "10", false), + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "0", false), + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "-5", false), + createDetailVO(RESOURCE_ID, ApiConstants.ZONE_ID, "20", false) + ); + + Mockito.doReturn(mockDetails).when(backupOfferingDetailsDao) + .findDetails(RESOURCE_ID, ApiConstants.ZONE_ID); + + List zoneIds = backupOfferingDetailsDao.findZoneIds(RESOURCE_ID); + + Assert.assertNotNull(zoneIds); + Assert.assertEquals(2, zoneIds.size()); + Assert.assertEquals(Arrays.asList(10L, 20L), zoneIds); + } + + @Test + public void testGetDetailWhenDetailExists() { + BackupOfferingDetailsVO mockDetail = createDetailVO(OFFERING_ID, TEST_KEY, TEST_VALUE, true); + + Mockito.doReturn(mockDetail).when(backupOfferingDetailsDao) + .findDetail(OFFERING_ID, TEST_KEY); + + String detailValue = backupOfferingDetailsDao.getDetail(OFFERING_ID, TEST_KEY); + + Assert.assertNotNull(detailValue); + Assert.assertEquals(TEST_VALUE, detailValue); + } + + @Test + public void testGetDetailWhenDetailDoesNotExist() { + Mockito.doReturn(null).when(backupOfferingDetailsDao) + .findDetail(OFFERING_ID, TEST_KEY); + + String detailValue = backupOfferingDetailsDao.getDetail(OFFERING_ID, TEST_KEY); + + Assert.assertNull(detailValue); + } + + @Test + public void testFindOfferingIdsByDomainIds() { + List domainIds = Arrays.asList(1L, 2L, 3L); + List expectedOfferingIds = Arrays.asList(100L, 101L, 102L); + + Mockito.doReturn(expectedOfferingIds).when(backupOfferingDetailsDao) + .findResourceIdsByNameAndValueIn(Mockito.eq("domainid"), Mockito.any(Object[].class)); + + List offeringIds = backupOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds); + + Assert.assertNotNull(offeringIds); + Assert.assertEquals(expectedOfferingIds, offeringIds); + Mockito.verify(backupOfferingDetailsDao).findResourceIdsByNameAndValueIn( + Mockito.eq("domainid"), Mockito.any(Object[].class)); + } + + @Test + public void testFindOfferingIdsByDomainIdsWithEmptyList() { + List domainIds = Collections.emptyList(); + List expectedOfferingIds = Collections.emptyList(); + + Mockito.doReturn(expectedOfferingIds).when(backupOfferingDetailsDao) + .findResourceIdsByNameAndValueIn(Mockito.eq("domainid"), Mockito.any(Object[].class)); + + List offeringIds = backupOfferingDetailsDao.findOfferingIdsByDomainIds(domainIds); + + Assert.assertNotNull(offeringIds); + Assert.assertTrue(offeringIds.isEmpty()); + } + + @Test + @SuppressWarnings("unchecked") + public void testUpdateBackupOfferingDomainIdsDetail() { + List newDomainIds = Arrays.asList(1L, 2L, 3L); + + Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class)); + Mockito.doReturn(null).when(backupOfferingDetailsDao).persist(Mockito.any(BackupOfferingDetailsVO.class)); + + backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, newDomainIds); + + Mockito.verify(backupOfferingDetailsDao, Mockito.times(3)).persist(Mockito.any(BackupOfferingDetailsVO.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testUpdateBackupOfferingDomainIdsDetailWithEmptyList() { + List emptyDomainIds = Collections.emptyList(); + + Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class)); + + backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, emptyDomainIds); + + Mockito.verify(backupOfferingDetailsDao, Mockito.never()).persist(Mockito.any(BackupOfferingDetailsVO.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testUpdateBackupOfferingDomainIdsDetailWithSingleDomain() { + List singleDomainId = Collections.singletonList(5L); + + Mockito.doReturn(0).when(backupOfferingDetailsDao).remove(Mockito.any(SearchCriteria.class)); + Mockito.doReturn(null).when(backupOfferingDetailsDao).persist(Mockito.any(BackupOfferingDetailsVO.class)); + + backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(OFFERING_ID, singleDomainId); + + Mockito.verify(backupOfferingDetailsDao, Mockito.times(1)).persist(Mockito.any(BackupOfferingDetailsVO.class)); + } + + private BackupOfferingDetailsVO createDetailVO(long resourceId, String name, String value, boolean display) { + return new BackupOfferingDetailsVO(resourceId, name, value, display); + } +} diff --git a/plugins/hypervisors/ovm3/sonar-project.properties b/plugins/hypervisors/ovm3/sonar-project.properties index 7355f1df4f78..7cc3a41f2a5f 100644 --- a/plugins/hypervisors/ovm3/sonar-project.properties +++ b/plugins/hypervisors/ovm3/sonar-project.properties @@ -24,7 +24,7 @@ sonar.projectVersion=1.0 sonar.sources=src sonar.binaries=target/classes -# Exclussions +# Exclusions sonar.exclusions=**/*Test.java # Language 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 2ff68b4836f4..54e76463bcaa 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 @@ -30,6 +30,7 @@ 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.backup.BackupOffering; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.acl.ControlledEntity; @@ -491,6 +492,11 @@ public void checkAccess(Account account, VpcOffering vof, DataCenter zone) throw // TODO Auto-generated method stub } + @Override + public void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException { + // TODO Auto-generated method stub + } + @Override public Pair> getKeys(GetUserKeysCmd cmd){ return null; diff --git a/pom.xml b/pom.xml index 33b232045cad..fa57b98ce7b4 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,8 @@ 4.22.0.0 apache https://sonarcloud.io + engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java + api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java 11 diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 24b6346d0afb..0500960abb13 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -32,6 +32,8 @@ import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.springframework.stereotype.Component; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; +import org.apache.cloudstack.backup.BackupOffering; import com.cloud.dc.DataCenter; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; @@ -70,6 +72,8 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { @Inject DomainDao _domainDao; @Inject + BackupOfferingDetailsDao backupOfferingDetailsDao; + @Inject AccountDao _accountDao; @Inject LaunchPermissionDao _launchPermissionDao; @@ -474,6 +478,35 @@ else if (_accountService.isNormalUser(account.getId()) return hasAccess; } + @Override + public boolean checkAccess(Account account, BackupOffering backupOffering) throws PermissionDeniedException { + boolean hasAccess = false; + if (account == null || backupOffering == null) { + hasAccess = true; + } else { + if (_accountService.isRootAdmin(account.getId())) { + hasAccess = true; + } + else if (_accountService.isNormalUser(account.getId()) + || account.getType() == Account.Type.RESOURCE_DOMAIN_ADMIN + || _accountService.isDomainAdmin(account.getId()) + || account.getType() == Account.Type.PROJECT) { + final List boDomainIds = backupOfferingDetailsDao.findDomainIds(backupOffering.getId()); + if (boDomainIds.isEmpty()) { + hasAccess = true; + } else { + for (Long domainId : boDomainIds) { + if (_domainDao.isChildDomain(domainId, account.getDomainId())) { + hasAccess = true; + break; + } + } + } + } + } + return hasAccess; + } + @Override public boolean checkAccess(Account account, DataCenter zone) throws PermissionDeniedException { if (account == null || zone.getDomainId() == null) {//public zone diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index e8dd45ce7191..5331cb10ed6d 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -49,6 +49,11 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.consoleproxy.ConsoleProxyManager; +import com.cloud.network.router.VirtualNetworkApplianceManager; +import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.utils.DomainHelper; +import com.cloud.vm.VirtualMachineManager; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; @@ -150,7 +155,6 @@ import com.cloud.capacity.CapacityManager; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.Resource.ResourceType; -import com.cloud.consoleproxy.ConsoleProxyManager; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; @@ -245,7 +249,6 @@ import com.cloud.network.element.NetrisProviderVO; import com.cloud.network.element.NsxProviderVO; import com.cloud.network.netris.NetrisService; -import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.rules.LoadBalancerContainer.Scheme; import com.cloud.network.vpc.VpcManager; import com.cloud.offering.DiskOffering; @@ -280,7 +283,6 @@ import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; -import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.test.IPRangeConfig; import com.cloud.user.Account; import com.cloud.user.AccountDetailVO; @@ -314,7 +316,6 @@ import com.cloud.utils.net.NetUtils; import com.cloud.vm.NicIpAlias; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.NicIpAliasDao; import com.cloud.vm.dao.NicIpAliasVO; @@ -399,6 +400,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati ClusterDao _clusterDao; @Inject AlertManager _alertMgr; + @Inject + DomainHelper domainHelper; List _secChecker; List externalProvisioners; @@ -3519,7 +3522,7 @@ protected ServiceOfferingVO createServiceOffering(final long userId, final boole final boolean isCustomized, final boolean encryptRoot, Long vgpuProfileId, Integer gpuCount, Boolean gpuDisplay, final boolean purgeResources, Integer leaseDuration, VMLeaseManager.ExpiryAction leaseExpiryAction) { // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); // Check if user exists in the system final User user = _userDao.findById(userId); @@ -3908,7 +3911,7 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) final Account account = _accountDao.findById(user.getAccountId()); // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); Collections.sort(filteredDomainIds); // avoid domain update of service offering if any instance is associated to it @@ -4118,7 +4121,7 @@ protected DiskOfferingVO createDiskOffering(final Long userId, final List } // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); // Check if user exists in the system final User user = _userDao.findById(userId); @@ -4394,7 +4397,7 @@ public DiskOffering updateDiskOffering(final UpdateDiskOfferingCmd cmd) { final Account account = _accountDao.findById(user.getAccountId()); // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); Collections.sort(filteredDomainIds); List filteredZoneIds = new ArrayList<>(); @@ -7401,7 +7404,7 @@ public NetworkOfferingVO doInTransaction(final TransactionStatus status) { } if (offering != null) { // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); List detailsVO = new ArrayList<>(); for (Long domainId : filteredDomainIds) { detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.domainid, String.valueOf(domainId), false)); @@ -7867,7 +7870,7 @@ public NetworkOffering updateNetworkOffering(final UpdateNetworkOfferingCmd cmd) } // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); Collections.sort(filteredDomainIds); List filteredZoneIds = new ArrayList<>(); @@ -8434,30 +8437,6 @@ private boolean checkOverlapPortableIpRange(final int regionId, final String new return false; } - private List filterChildSubDomains(final List domainIds) { - List filteredDomainIds = new ArrayList<>(); - if (domainIds != null) { - filteredDomainIds.addAll(domainIds); - } - if (filteredDomainIds.size() > 1) { - for (int i = filteredDomainIds.size() - 1; i >= 1; i--) { - long first = filteredDomainIds.get(i); - for (int j = i - 1; j >= 0; j--) { - long second = filteredDomainIds.get(j); - if (_domainDao.isChildDomain(filteredDomainIds.get(i), filteredDomainIds.get(j))) { - filteredDomainIds.remove(j); - i--; - } - if (_domainDao.isChildDomain(filteredDomainIds.get(j), filteredDomainIds.get(i))) { - filteredDomainIds.remove(i); - break; - } - } - } - } - return filteredDomainIds; - } - protected void validateCacheMode(String cacheMode){ if(cacheMode != null && !Enums.getIfPresent(DiskOffering.DiskCacheMode.class, 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 e4219c858da6..60b93d409aab 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -63,6 +63,7 @@ import com.cloud.network.element.NsxProviderVO; import com.cloud.network.rules.RulesManager; import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.utils.DomainHelper; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Sets; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -285,6 +286,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Inject DomainDao domainDao; @Inject + DomainHelper domainHelper; + @Inject private AnnotationDao annotationDao; @Inject NetworkOfferingDao _networkOfferingDao; @@ -636,7 +639,7 @@ public VpcOffering createVpcOffering(final String name, final String displayText } // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); final Map> svcProviderMap = new HashMap>(); final Set defaultProviders = new HashSet(); @@ -1118,7 +1121,7 @@ private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingN // Filter child domains when both parent and child domains are present - List filteredDomainIds = filterChildSubDomains(domainIds); + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); Collections.sort(filteredDomainIds); List filteredZoneIds = new ArrayList<>(); @@ -3658,30 +3661,6 @@ private boolean rollingRestartVpc(final Vpc vpc, final ReservationContext contex return _ntwkMgr.areRoutersRunning(routerDao.listByVpcId(vpc.getId())); } - private List filterChildSubDomains(final List domainIds) { - List filteredDomainIds = new ArrayList<>(); - if (domainIds != null) { - filteredDomainIds.addAll(domainIds); - } - if (filteredDomainIds.size() > 1) { - for (int i = filteredDomainIds.size() - 1; i >= 1; i--) { - long first = filteredDomainIds.get(i); - for (int j = i - 1; j >= 0; j--) { - long second = filteredDomainIds.get(j); - if (domainDao.isChildDomain(filteredDomainIds.get(i), filteredDomainIds.get(j))) { - filteredDomainIds.remove(j); - i--; - } - if (domainDao.isChildDomain(filteredDomainIds.get(j), filteredDomainIds.get(i))) { - filteredDomainIds.remove(i); - break; - } - } - } - } - return filteredDomainIds; - } - protected boolean isGlobalAcl(Long aclVpcId) { return aclVpcId != null && aclVpcId == 0; } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index dd60fbfb9cca..6f68a1048676 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -67,6 +67,7 @@ import org.apache.cloudstack.auth.UserAuthenticator; import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication; import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; +import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.config.ApiServiceConfiguration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -3574,6 +3575,21 @@ public void checkAccess(Account account, VpcOffering vof, DataCenter zone) throw throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + vof); } + @Override + public void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException { + for (SecurityChecker checker : _securityCheckers) { + if (checker.checkAccess(account, bof)) { + if (logger.isDebugEnabled()) { + logger.debug("Access granted to " + account + " to " + bof + " by " + checker.getName()); + } + return; + } + } + + assert false : "How can all of the security checkers pass on checking this caller?"; + throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + bof); + } + @Override public void checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { for (SecurityChecker checker : _securityCheckers) { diff --git a/server/src/main/java/com/cloud/utils/DomainHelper.java b/server/src/main/java/com/cloud/utils/DomainHelper.java new file mode 100644 index 000000000000..480726d256b8 --- /dev/null +++ b/server/src/main/java/com/cloud/utils/DomainHelper.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.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.domain.dao.DomainDao; + +@Component +public class DomainHelper { + + @Inject + private DomainDao domainDao; + + /** + * + * @param domainIds List of domain IDs to filter + * @return Filtered list containing only domains that are not descendants of other domains in the list + */ + public List filterChildSubDomains(final List domainIds) { + if (domainIds == null || domainIds.size() <= 1) { + return domainIds == null ? new ArrayList<>() : new ArrayList<>(domainIds); + } + + final List result = new ArrayList<>(); + for (final Long candidate : domainIds) { + boolean isDescendant = false; + for (final Long other : domainIds) { + if (Objects.equals(candidate, other)) { + continue; + } + if (domainDao.isChildDomain(other, candidate)) { + isDescendant = true; + break; + } + } + if (!isDescendant) { + result.add(candidate); + } + } + return result; + } +} 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 ef3ba917de74..5743c7299231 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -38,6 +38,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.DomainHelper; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.InternalIdentity; @@ -68,6 +69,7 @@ import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; @@ -81,12 +83,12 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import com.amazonaws.util.CollectionUtils; import com.cloud.alert.AlertManager; import com.cloud.api.ApiDispatcher; import com.cloud.api.ApiGsonHelper; @@ -184,6 +186,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { @Inject private BackupOfferingDao backupOfferingDao; @Inject + private BackupOfferingDetailsDao backupOfferingDetailsDao; + @Inject private VMInstanceDao vmInstanceDao; @Inject private AccountService accountService; @@ -237,6 +241,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private AlertManager alertManager; @Inject private GuestOSDao _guestOSDao; + @Inject + private DomainHelper domainHelper; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -280,6 +286,20 @@ public BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd) { throw new CloudRuntimeException("A backup offering with the same name already exists in this zone"); } + if (CollectionUtils.isNotEmpty(cmd.getDomainIds())) { + for (final Long domainId: cmd.getDomainIds()) { + if (domainDao.findById(domainId) == null) { + throw new InvalidParameterValueException("Please specify a valid domain id"); + } + } + } + + final Account caller = CallContext.current().getCallingAccount(); + List filteredDomainIds = cmd.getDomainIds() == null ? new ArrayList<>() : new ArrayList<>(cmd.getDomainIds()); + if (filteredDomainIds.size() > 1) { + filteredDomainIds = domainHelper.filterChildSubDomains(filteredDomainIds); + } + final BackupProvider provider = getBackupProvider(cmd.getZoneId()); if (!provider.isValidProviderOffering(cmd.getZoneId(), cmd.getExternalId())) { throw new CloudRuntimeException("Backup offering '" + cmd.getExternalId() + "' does not exist on provider " + provider.getName() + " on zone " + cmd.getZoneId()); @@ -292,15 +312,34 @@ public BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd) { if (savedOffering == null) { throw new CloudRuntimeException("Unable to create backup offering: " + cmd.getExternalId() + ", name: " + cmd.getName()); } + if (CollectionUtils.isNotEmpty(filteredDomainIds)) { + List detailsVOList = new ArrayList<>(); + for (Long domainId : filteredDomainIds) { + detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); + } + if (!detailsVOList.isEmpty()) { + backupOfferingDetailsDao.saveDetails(detailsVOList); + } + } logger.debug("Successfully created backup offering " + cmd.getName() + " mapped to backup provider offering " + cmd.getExternalId()); return savedOffering; } + @Override + public List getBackupOfferingDomains(Long offeringId) { + final BackupOffering backupOffering = backupOfferingDao.findById(offeringId); + if (backupOffering == null) { + throw new InvalidParameterValueException("Unable to find backup offering for id: " + offeringId); + } + return backupOfferingDetailsDao.findDomainIds(offeringId); + } + @Override public Pair, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd) { final Long offeringId = cmd.getOfferingId(); final Long zoneId = cmd.getZoneId(); final String keyword = cmd.getKeyword(); + Long domainId = cmd.getDomainId(); if (offeringId != null) { BackupOfferingVO offering = backupOfferingDao.findById(offeringId); @@ -314,8 +353,13 @@ public Pair, Integer> listBackupOfferings(final ListBackupO SearchBuilder sb = backupOfferingDao.createSearchBuilder(); sb.and("zone_id", sb.entity().getZoneId(), SearchCriteria.Op.EQ); sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + CallContext ctx = CallContext.current(); final Account caller = ctx.getCallingAccount(); + if (Account.Type.ADMIN != caller.getType() && domainId == null) { + domainId = caller.getDomainId(); + } + if (Account.Type.NORMAL == caller.getType()) { sb.and("user_backups_allowed", sb.entity().isUserDrivenBackupAllowed(), SearchCriteria.Op.EQ); } @@ -328,13 +372,36 @@ public Pair, Integer> listBackupOfferings(final ListBackupO if (keyword != null) { sc.setParameters("name", "%" + keyword + "%"); } + if (Account.Type.NORMAL == caller.getType()) { sc.setParameters("user_backups_allowed", true); } + Pair, Integer> result = backupOfferingDao.searchAndCount(sc, searchFilter); + + if (domainId != null) { + List filteredOfferings = new ArrayList<>(); + for (BackupOfferingVO offering : result.first()) { + List offeringDomains = backupOfferingDetailsDao.findDomainIds(offering.getId()); + if (offeringDomains.isEmpty() || offeringDomains.contains(domainId) || containsParentDomain(offeringDomains, domainId)) { + filteredOfferings.add(offering); + } + } + return new Pair<>(new ArrayList<>(filteredOfferings), filteredOfferings.size()); + } + return new Pair<>(new ArrayList<>(result.first()), result.second()); } + private boolean containsParentDomain(List offeringDomains, Long domainId) { + for (Long offeringDomainId : offeringDomains) { + if (domainDao.isChildDomain(offeringDomainId, domainId)) { + return true; + } + } + return false; + } + @Override public boolean deleteBackupOffering(final Long offeringId) { final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); @@ -342,6 +409,8 @@ public boolean deleteBackupOffering(final Long offeringId) { throw new CloudRuntimeException("Could not find a backup offering with id: " + offeringId); } + accountManager.checkAccess(CallContext.current().getCallingAccount(), offering); + if (backupDao.listByOfferingId(offering.getId()).size() > 0) { throw new CloudRuntimeException("Backup Offering cannot be removed as it has backups associated with it."); } @@ -452,6 +521,12 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { throw new CloudRuntimeException("Provided backup offering does not exist"); } + Account owner = accountManager.getAccount(vm.getAccountId()); + if (owner == null) { + throw new CloudRuntimeException("Unable to find the owner of the VM"); + } + accountManager.checkAccess(owner, offering); + final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (backupProvider == null) { throw new CloudRuntimeException("Failed to get the backup provider for the zone, please contact the administrator"); @@ -762,10 +837,11 @@ protected boolean deleteAllVmBackupSchedules(long vmId) { @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) public boolean createBackup(CreateBackupCmd cmd, Object job) throws ResourceAllocationException { Long vmId = cmd.getVmId(); + Account caller = CallContext.current().getCallingAccount(); final VMInstanceVO vm = findVmById(vmId); validateBackupForZone(vm.getDataCenterId()); - accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); + accountManager.checkAccess(caller, null, true, vm); if (vm.getBackupOfferingId() == null) { throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering"); @@ -1065,7 +1141,7 @@ public boolean restoreBackup(final Long backupId) { } // This is done to handle historic backups if any with Veeam / Networker plugins - List backupVolumes = CollectionUtils.isNullOrEmpty(backup.getBackedUpVolumes()) ? + List backupVolumes = CollectionUtils.isEmpty(backup.getBackedUpVolumes()) ? vm.getBackupVolumeList() : backup.getBackedUpVolumes(); List vmVolumes = volumeDao.findByInstance(vm.getId()); if (vmVolumes.size() != backupVolumes.size()) { @@ -2112,11 +2188,15 @@ public BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupO String name = updateBackupOfferingCmd.getName(); String description = updateBackupOfferingCmd.getDescription(); Boolean allowUserDrivenBackups = updateBackupOfferingCmd.getAllowUserDrivenBackups(); + List domainIds = updateBackupOfferingCmd.getDomainIds(); BackupOfferingVO backupOfferingVO = backupOfferingDao.findById(id); if (backupOfferingVO == null) { throw new InvalidParameterValueException(String.format("Unable to find Backup Offering with id: [%s].", id)); } + + accountManager.checkAccess(CallContext.current().getCallingAccount(), backupOfferingVO); + logger.debug("Trying to update Backup Offering {} to {}.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backupOfferingVO, "uuid", "name", "description", "userDrivenBackupAllowed"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(updateBackupOfferingCmd, "name", "description", "allowUserDrivenBackups")); @@ -2139,16 +2219,43 @@ public BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupO fields.add("allowUserDrivenBackups: " + allowUserDrivenBackups); } - if (!backupOfferingDao.update(id, offering)) { + if (CollectionUtils.isNotEmpty(domainIds)) { + for (final Long domainId: domainIds) { + if (domainDao.findById(domainId) == null) { + throw new InvalidParameterValueException("Please specify a valid domain id"); + } + } + } + List filteredDomainIds = domainHelper.filterChildSubDomains(domainIds); + Collections.sort(filteredDomainIds); + + boolean success = backupOfferingDao.update(id, offering); + if (!success) { logger.warn(String.format("Couldn't update Backup offering (%s) with [%s].", backupOfferingVO, String.join(", ", fields))); } + if (success || fields.isEmpty()) { + List existingDomainIds = backupOfferingDetailsDao.findDomainIds(id); + Collections.sort(existingDomainIds); + updateBackupOfferingDomainDetails(id, filteredDomainIds, existingDomainIds); + } + BackupOfferingVO response = backupOfferingDao.findById(id); CallContext.current().setEventDetails(String.format("Backup Offering updated [%s].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(response, "id", "name", "description", "userDrivenBackupAllowed", "externalId"))); return response; } + private void updateBackupOfferingDomainDetails(Long id, List filteredDomainIds, List existingDomainIds) { + if (existingDomainIds == null) { + existingDomainIds = new ArrayList<>(); + } + + if(!filteredDomainIds.equals(existingDomainIds)) { + backupOfferingDetailsDao.updateBackupOfferingDomainIdsDetail(id, filteredDomainIds); + } + } + Map getDetailsFromBackupDetails(Long backupId) { Map details = backupDetailsDao.listDetailsKeyPairs(backupId, true); if (details == null) { @@ -2270,7 +2377,7 @@ public void checkAndRemoveBackupOfferingBeforeExpunge(VirtualMachine vm) { return; } List backupsForVm = backupDao.listByVmIdAndOffering(vm.getDataCenterId(), vm.getId(), vm.getBackupOfferingId()); - if (org.apache.commons.collections.CollectionUtils.isEmpty(backupsForVm)) { + if (CollectionUtils.isEmpty(backupsForVm)) { removeVMFromBackupOffering(vm.getId(), true); } else { throw new CloudRuntimeException(String.format("This Instance [uuid: %s, name: %s] has a " 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 c633a3b0abd2..f4fd57d59fc4 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,6 @@ + + diff --git a/server/src/test/java/com/cloud/acl/DomainCheckerTest.java b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java index a5ec41306d85..8c7817c2b842 100644 --- a/server/src/test/java/com/cloud/acl/DomainCheckerTest.java +++ b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java @@ -18,6 +18,9 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.backup.BackupOfferingVO; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -35,6 +38,8 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.Ternary; +import java.util.Collections; + @RunWith(MockitoJUnitRunner.class) public class DomainCheckerTest { @@ -46,6 +51,8 @@ public class DomainCheckerTest { DomainDao _domainDao; @Mock ProjectManager _projectMgr; + @Mock + BackupOfferingDetailsDao backupOfferingDetailsDao; @Spy @InjectMocks @@ -163,4 +170,42 @@ public void testProjectOwnerCannotAccess() { domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ListEntry); } + @Test + public void testBackupOfferingAccessRootAdmin() { + Account rootAdmin = Mockito.mock(Account.class); + Mockito.when(rootAdmin.getId()).thenReturn(1L); + BackupOfferingVO backupOfferingVO = Mockito.mock(BackupOfferingVO.class); + Mockito.when(_accountService.isRootAdmin(rootAdmin.getId())).thenReturn(true); + + boolean hasAccess = domainChecker.checkAccess(rootAdmin, backupOfferingVO); + Assert.assertTrue(hasAccess); + } + + @Test + public void testBackupOfferingAccessDomainAdmin() { + Account domainAdmin = Mockito.mock(Account.class); + Mockito.when(domainAdmin.getId()).thenReturn(2L); + BackupOfferingVO backupOfferingVO = Mockito.mock(BackupOfferingVO.class); + AccountVO owner = Mockito.mock(AccountVO.class); + Mockito.when(_accountService.isDomainAdmin(domainAdmin.getId())).thenReturn(true); + Mockito.when(domainAdmin.getDomainId()).thenReturn(10L); + Mockito.when(_domainDao.isChildDomain(100L, 10L)).thenReturn(true); + Mockito.when(backupOfferingDetailsDao.findDomainIds(backupOfferingVO.getId())).thenReturn(Collections.singletonList(100L)); + + boolean hasAccess = domainChecker.checkAccess(domainAdmin, backupOfferingVO); + Assert.assertTrue(hasAccess); + } + + @Test + public void testBackupOfferingAccessNoAccess() { + Account normalUser = Mockito.mock(Account.class); + Mockito.when(normalUser.getId()).thenReturn(3L); + BackupOfferingVO backupOfferingVO = Mockito.mock(BackupOfferingVO.class); + Mockito.when(_accountService.isRootAdmin(normalUser.getId())).thenReturn(false); + Mockito.when(_accountService.isDomainAdmin(normalUser.getId())).thenReturn(false); + + boolean hasAccess = domainChecker.checkAccess(normalUser, backupOfferingVO); + Assert.assertFalse(hasAccess); + } + } diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java index 60263cea9b88..8295202fcc53 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java @@ -49,6 +49,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountManagerImpl; import com.cloud.user.User; +import com.cloud.utils.DomainHelper; import com.cloud.utils.Pair; import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.SearchCriteria; @@ -178,6 +179,8 @@ public class ConfigurationManagerImplTest { PrimaryDataStoreDao storagePoolDao; @Mock StoragePoolDetailsDao storagePoolDetailsDao; + @Mock + DomainHelper domainHelper; DeleteZoneCmd deleteZoneCmd; CreateNetworkOfferingCmd createNetworkOfferingCmd; diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index fe4ea0838f16..1ec141a8be13 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -59,6 +59,7 @@ import java.util.TimeZone; import java.util.UUID; +import com.cloud.domain.Domain; import com.cloud.storage.dao.SnapshotPolicyDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; @@ -3107,7 +3108,7 @@ public void moveVmToUserTestAccountManagerCheckAccessThrowsPermissionDeniedExcep configureDoNothingForMethodsThatWeDoNotWantToTest(); - doThrow(PermissionDeniedException.class).when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.any()); + doThrow(PermissionDeniedException.class).when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class)); Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.moveVmToUser(assignVmCmdMock)); } 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 8b13fd474947..a9c083228e2b 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -60,6 +60,7 @@ import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.DateUtil; +import com.cloud.utils.DomainHelper; import com.cloud.utils.Pair; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -80,11 +81,13 @@ import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; @@ -241,6 +244,12 @@ public class BackupManagerTest { @Mock private GuestOSDao _guestOSDao; + @Mock + private BackupOfferingDetailsDao backupOfferingDetailsDao; + + @Mock + DomainHelper domainHelper; + private Gson gson; private String[] hostPossibleValues = {"127.0.0.1", "hostname"}; @@ -352,6 +361,7 @@ public void testUpdateBackupOfferingSuccess() { when(cmd.getName()).thenReturn("New name"); when(cmd.getDescription()).thenReturn("New description"); when(cmd.getAllowUserDrivenBackups()).thenReturn(true); + when(backupOfferingDetailsDao.findDomainIds(id)).thenReturn(Collections.emptyList()); BackupOffering updated = backupManager.updateBackupOffering(cmd); assertEquals("New name", updated.getName()); @@ -1081,7 +1091,7 @@ public void testGetRootDiskInfoFromBackup() { assertEquals("root-disk-offering-uuid", VmDiskInfo.getDiskOffering().getUuid()); assertEquals(Long.valueOf(5), VmDiskInfo.getSize()); - assertEquals(null, VmDiskInfo.getDeviceId()); + assertNull(VmDiskInfo.getDeviceId()); } @Test @@ -1106,7 +1116,7 @@ public void testImportBackupOffering() { assertEquals("Test Offering", result.getName()); assertEquals("Test Description", result.getDescription()); - assertEquals(true, result.isUserDrivenBackupAllowed()); + assertTrue(result.isUserDrivenBackupAllowed()); assertEquals("external-id", result.getExternalId()); assertEquals("testbackupprovider", result.getProvider()); } @@ -1149,6 +1159,8 @@ public void testAssignVMToBackupOffering() { VMInstanceVO vm = mock(VMInstanceVO.class); when(vm.getId()).thenReturn(vmId); BackupOfferingVO offering = mock(BackupOfferingVO.class); + Account owner = mock(Account.class); + overrideBackupFrameworkConfigValue(); @@ -1159,6 +1171,8 @@ public void testAssignVMToBackupOffering() { when(vm.getBackupOfferingId()).thenReturn(null); when(offering.getProvider()).thenReturn("testbackupprovider"); when(backupProvider.assignVMToBackupOffering(vm, offering)).thenReturn(true); + when(vm.getAccountId()).thenReturn(3L); + when(accountManager.getAccount(vm.getAccountId())).thenReturn(owner); when(vmInstanceDao.update(1L, vm)).thenReturn(true); try (MockedStatic ignored2 = Mockito.mockStatic(UsageEventUtils.class)) { @@ -2156,4 +2170,352 @@ public void testRestoreBackupVolumeMismatch() { verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); verify(volumeDao, times(1)).findByInstance(vmId); } + + @Test + public void getBackupOfferingDomainsTestOfferingNotFound() { + Long offeringId = 1L; + when(backupOfferingDao.findById(offeringId)).thenReturn(null); + + InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, + () -> backupManager.getBackupOfferingDomains(offeringId)); + assertEquals("Unable to find backup offering for id: " + offeringId, exception.getMessage()); + } + + @Test + public void getBackupOfferingDomainsTestReturnsDomains() { + Long offeringId = 1L; + BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.findById(offeringId)).thenReturn(offering); + when(backupOfferingDetailsDao.findDomainIds(offeringId)).thenReturn(List.of(10L, 20L)); + + List result = backupManager.getBackupOfferingDomains(offeringId); + + assertEquals(2, result.size()); + assertTrue(result.contains(10L)); + assertTrue(result.contains(20L)); + } + + @Test + public void testUpdateBackupOfferingThrowsWhenDomainIdInvalid() { + Long id = 1234L; + UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class); + when(cmd.getId()).thenReturn(id); + when(cmd.getDomainIds()).thenReturn(List.of(99L)); + + when(domainDao.findById(99L)).thenReturn(null); + + InvalidParameterValueException exception = Assert.assertThrows(InvalidParameterValueException.class, + () -> backupManager.updateBackupOffering(cmd)); + assertEquals("Please specify a valid domain id", exception.getMessage()); + } + + @Test + public void testUpdateBackupOfferingPersistsDomainDetailsWhenProvided() { + Long id = 1234L; + Long domainId = 11L; + UpdateBackupOfferingCmd cmd = Mockito.spy(UpdateBackupOfferingCmd.class); + when(cmd.getId()).thenReturn(id); + when(cmd.getDomainIds()).thenReturn(List.of(domainId)); + + DomainVO domain = Mockito.mock(DomainVO.class); + when(domainDao.findById(domainId)).thenReturn(domain); + + when(domainHelper.filterChildSubDomains(List.of(domainId))).thenReturn(new ArrayList<>(List.of(domainId))); + when(backupOfferingDetailsDao.findDomainIds(id)).thenReturn(new ArrayList<>()); + + BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); + BackupOfferingVO offeringUpdate = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.findById(id)).thenReturn(offering); + when(backupOfferingDao.createForUpdate(id)).thenReturn(offeringUpdate); + when(backupOfferingDao.update(id, offeringUpdate)).thenReturn(true); + + BackupOffering updated = backupManager.updateBackupOffering(cmd); + + verify(backupOfferingDetailsDao, times(1)).updateBackupOfferingDomainIdsDetail(id, List.of(domainId)); + } + + @Test + public void testListBackupOfferingsWithDomainFilteringIncludesGlobalOfferings() { + Long requestedDomainId = 3L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(requestedDomainId); + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO globalOffering = createMockOffering(1L, "Global Offering"); + BackupOfferingVO domainOffering = createMockOffering(2L, "Domain Offering"); + + List allOfferings = List.of(globalOffering, domainOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)).thenReturn(Collections.emptyList()); + when(backupOfferingDetailsDao.findDomainIds(2L)).thenReturn(List.of(2L)); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(1, result.first().size()); + assertEquals("Global Offering", result.first().get(0).getName()); + } + } + + @Test + public void testListBackupOfferingsWithDomainFilteringIncludesDirectDomainMapping() { + Long requestedDomainId = 3L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(requestedDomainId); + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO directDomainOffering = createMockOffering(1L, "Direct Domain Offering"); + BackupOfferingVO otherDomainOffering = createMockOffering(2L, "Other Domain Offering"); + + List allOfferings = List.of(directDomainOffering, otherDomainOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)).thenReturn(List.of(requestedDomainId)); + when(backupOfferingDetailsDao.findDomainIds(2L)).thenReturn(List.of(5L)); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(1, result.first().size()); + assertEquals("Direct Domain Offering", result.first().get(0).getName()); + } + } + + @Test + public void testListBackupOfferingsWithDomainFilteringIncludesParentDomainOfferings() { + Long parentDomainId = 1L; + Long childDomainId = 3L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(childDomainId); + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO parentDomainOffering = createMockOffering(1L, "Parent Domain Offering"); + BackupOfferingVO siblingDomainOffering = createMockOffering(2L, "Sibling Domain Offering"); + + List allOfferings = List.of(parentDomainOffering, siblingDomainOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)).thenReturn(List.of(parentDomainId)); + when(backupOfferingDetailsDao.findDomainIds(2L)).thenReturn(List.of(4L)); + + when(domainDao.isChildDomain(parentDomainId, childDomainId)).thenReturn(true); + when(domainDao.isChildDomain(4L, childDomainId)).thenReturn(false); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(1, result.first().size()); + assertEquals("Parent Domain Offering", result.first().get(0).getName()); + } + } + + @Test + public void testListBackupOfferingsWithDomainFilteringExcludesSiblingDomainOfferings() { + Long requestedDomainId = 3L; + Long siblingDomainId = 4L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(requestedDomainId); + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO siblingOffering = createMockOffering(1L, "Sibling Domain Offering"); + List allOfferings = List.of(siblingOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)).thenReturn(List.of(siblingDomainId)); + when(domainDao.isChildDomain(siblingDomainId, requestedDomainId)).thenReturn(false); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(0, result.first().size()); + } + } + + @Test + public void testListBackupOfferingsWithDomainFilteringMultipleDomainMappings() { + Long requestedDomainId = 5L; + Long parentDomainId1 = 1L; + Long parentDomainId2 = 2L; + Long unrelatedDomainId = 8L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(requestedDomainId); + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO multiDomainOffering = createMockOffering(1L, "Multi-Domain Offering"); + List allOfferings = List.of(multiDomainOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)) + .thenReturn(List.of(parentDomainId1, unrelatedDomainId, parentDomainId2)); + + when(domainDao.isChildDomain(parentDomainId1, requestedDomainId)).thenReturn(false); + when(domainDao.isChildDomain(unrelatedDomainId, requestedDomainId)).thenReturn(false); + when(domainDao.isChildDomain(parentDomainId2, requestedDomainId)).thenReturn(true); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(1, result.first().size()); + assertEquals("Multi-Domain Offering", result.first().get(0).getName()); + } + } + + @Test + public void testListBackupOfferingsNormalUserDefaultsToDomainFiltering() { + Long userDomainId = 7L; + + ListBackupOfferingsCmd cmd = + Mockito.mock(ListBackupOfferingsCmd.class); + when(cmd.getOfferingId()).thenReturn(null); + when(cmd.getDomainId()).thenReturn(null); // User didn't pass domain filter + when(cmd.getStartIndex()).thenReturn(0L); + when(cmd.getPageSizeVal()).thenReturn(20L); + + BackupOfferingVO globalOffering = createMockOffering(1L, "Global Offering"); + BackupOfferingVO userDomainOffering = createMockOffering(2L, "User Domain Offering"); + BackupOfferingVO otherDomainOffering = createMockOffering(3L, "Other Domain Offering"); + + List allOfferings = List.of(globalOffering, userDomainOffering, otherDomainOffering); + + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + BackupOfferingVO entityMock = Mockito.mock(BackupOfferingVO.class); + when(backupOfferingDao.createSearchBuilder()).thenReturn(sb); + when(sb.entity()).thenReturn(entityMock); + when(sb.and(Mockito.anyString(), Mockito.any(), Mockito.any(SearchCriteria.Op.class))).thenReturn(sb); + when(sb.create()).thenReturn(sc); + when(backupOfferingDao.searchAndCount(Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(allOfferings, allOfferings.size())); + + when(backupOfferingDetailsDao.findDomainIds(1L)).thenReturn(Collections.emptyList()); // Global + when(backupOfferingDetailsDao.findDomainIds(2L)).thenReturn(List.of(userDomainId)); // User's domain + when(backupOfferingDetailsDao.findDomainIds(3L)).thenReturn(List.of(99L)); // Other domain + + when(domainDao.isChildDomain(99L, userDomainId)).thenReturn(false); + + Account account = Mockito.mock(Account.class); + when(account.getType()).thenReturn(Account.Type.NORMAL); + when(account.getDomainId()).thenReturn(userDomainId); + + try (MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { + CallContext contextMock = Mockito.mock(CallContext.class); + mockedCallContext.when(CallContext::current).thenReturn(contextMock); + when(contextMock.getCallingAccount()).thenReturn(account); + + Pair, Integer> result = backupManager.listBackupOfferings(cmd); + + assertEquals(2, result.first().size()); + assertTrue(result.first().stream().anyMatch(o -> o.getName().equals("Global Offering"))); + assertTrue(result.first().stream().anyMatch(o -> o.getName().equals("User Domain Offering"))); + } + } + + private BackupOfferingVO createMockOffering(Long id, String name) { + BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); + when(offering.getId()).thenReturn(id); + when(offering.getName()).thenReturn(name); + return offering; + } + } 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 2fa9a2d7a44b..f175c5674224 100644 --- a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java +++ b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java @@ -27,6 +27,7 @@ import javax.inject.Inject; +import com.cloud.utils.DomainHelper; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -98,6 +99,9 @@ public class CreateNetworkOfferingTest extends TestCase { @Mock LoadBalancerVMMapDao _loadBalancerVMMapDao; + @Mock + DomainHelper domainHelper; + @Mock AnnotationDao annotationDao; @Inject diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 05ce9d41023c..6c9aa087bc7d 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.23.0.0-SNAPSHOT" +VERSION = "4.23.0.0" setup(name="Marvin", version=VERSION, diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index 4a32619b8c2f..bc95772d6f7a 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -340,9 +340,9 @@ export default { icon: 'cloud-upload-outlined', docHelp: 'adminguide/virtual_machines.html#backup-offerings', permission: ['listBackupOfferings'], - searchFilters: ['zoneid'], - columns: ['name', 'description', 'zonename'], - details: ['name', 'id', 'description', 'externalid', 'zone', 'allowuserdrivenbackups', 'created'], + searchFilters: ['zoneid', 'domainid'], + columns: ['name', 'description', 'domain', 'zonename'], + details: ['name', 'id', 'description', 'externalid', 'domain', 'zone', 'allowuserdrivenbackups', 'created'], related: [{ name: 'vm', title: 'label.instances', diff --git a/ui/src/views/offering/ImportBackupOffering.vue b/ui/src/views/offering/ImportBackupOffering.vue index b8ac7d8e8e65..f680eacd4a7d 100644 --- a/ui/src/views/offering/ImportBackupOffering.vue +++ b/ui/src/views/offering/ImportBackupOffering.vue @@ -85,6 +85,33 @@
+ + + + + + + + + + + {{ opt.path || opt.name || opt.description }} + + + +
{{ this.$t('label.cancel') }} {{ this.$t('label.ok') }} @@ -96,6 +123,7 @@