diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c6c90a6183..fd3c8f8ac67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.10' architecture: 'x64' - name: Install Build Dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 133e2c35b4e..ceffb42c79b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: fail-fast: false matrix: tests: [ "smoke/test_accounts + smoke/test_account_access smoke/test_affinity_groups smoke/test_affinity_groups_projects smoke/test_annotations @@ -225,7 +226,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.10' architecture: 'x64' - name: Install Build Dependencies @@ -283,7 +284,7 @@ jobs: - name: Start CloudStack Management Server with Simulator run: | - export MAVEN_OPTS="-Xmx4096m -XX:MaxPermSize=800m -Djava.security.egd=file:/dev/urandom -javaagent:jacoco/lib/jacocoagent.jar=address=*,port=36320,output=tcpserver" + export MAVEN_OPTS="-Xmx4096m -XX:MaxMetaspaceSize=800m -Djava.security.egd=file:/dev/urandom -javaagent:jacoco/lib/jacocoagent.jar=address=*,port=36320,output=tcpserver --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED" echo -e "\nStarting simulator" set +e mvn -Dsimulator -Dorg.eclipse.jetty.annotations.maxWait=120 -pl :cloud-client-ui jetty:run 2>&1 > /tmp/jetty-log || true & diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index a8f923d708d..c77783746ca 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -36,11 +36,11 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: 'maven' - name: Build CloudStack with Quality Checks diff --git a/.github/workflows/main-sonar-check.yml b/.github/workflows/main-sonar-check.yml index 07d15583c82..8248e48022a 100644 --- a/.github/workflows/main-sonar-check.yml +++ b/.github/workflows/main-sonar-check.yml @@ -36,11 +36,11 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK11 + - name: Set up JDK17 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: 'maven' - name: Cache SonarCloud packages diff --git a/.github/workflows/rat.yml b/.github/workflows/rat.yml index b8f83de8194..52ce343841b 100644 --- a/.github/workflows/rat.yml +++ b/.github/workflows/rat.yml @@ -31,10 +31,10 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '11' + java-version: '17' distribution: 'adopt' architecture: x64 cache: maven diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml index 5d1bcc5dc22..c36bceb2b90 100644 --- a/.github/workflows/sonar-check.yml +++ b/.github/workflows/sonar-check.yml @@ -38,11 +38,11 @@ jobs: ref: "refs/pull/${{ github.event.number }}/merge" fetch-depth: 0 - - name: Set up JDK11 + - name: Set up JDK17 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '17' cache: 'maven' - name: Cache SonarCloud packages diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 476526aff32..56f757133b7 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -36,7 +36,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 - name: Env details run: | diff --git a/INSTALL.md b/INSTALL.md index 6586e4e57fc..e133e7d7b91 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -78,7 +78,7 @@ Clear old database (if any) and deploy the database schema: Export the following variable if you need to run and debug the management server: - $ export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=500m -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n" + $ export MAVEN_OPTS="-Xmx1024m -XX:MaxMetaspaceSize=500m -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n" Start the management server: 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 b4f4619be9a..6f24b1cd6ca 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 @@ -84,6 +84,8 @@ public class VirtualMachineTO { Map extraConfig = new HashMap<>(); Map networkIdToNetworkNameMap = new HashMap<>(); DeployAsIsInfoTO deployAsIsInfo; + String metadataManufacturer; + String metadataProductName; 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) { @@ -429,6 +431,22 @@ public class VirtualMachineTO { this.deployAsIsInfo = deployAsIsInfo; } + public String getMetadataManufacturer() { + return metadataManufacturer; + } + + public void setMetadataManufacturer(String metadataManufacturer) { + this.metadataManufacturer = metadataManufacturer; + } + + public String getMetadataProductName() { + return metadataProductName; + } + + public void setMetadataProductName(String metadataProductName) { + this.metadataProductName = metadataProductName; + } + @Override public String toString() { return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index d4235cbc9bc..f84769bae4d 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -242,6 +242,8 @@ public class EventTypes { public static final String EVENT_ROLE_UPDATE = "ROLE.UPDATE"; public static final String EVENT_ROLE_DELETE = "ROLE.DELETE"; public static final String EVENT_ROLE_IMPORT = "ROLE.IMPORT"; + public static final String EVENT_ROLE_ENABLE = "ROLE.ENABLE"; + public static final String EVENT_ROLE_DISABLE = "ROLE.DISABLE"; public static final String EVENT_ROLE_PERMISSION_CREATE = "ROLE.PERMISSION.CREATE"; public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE"; public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE"; @@ -333,6 +335,7 @@ public class EventTypes { public static final String EVENT_SNAPSHOT_OFF_PRIMARY = "SNAPSHOT.OFF_PRIMARY"; public static final String EVENT_SNAPSHOT_DELETE = "SNAPSHOT.DELETE"; public static final String EVENT_SNAPSHOT_REVERT = "SNAPSHOT.REVERT"; + public static final String EVENT_SNAPSHOT_EXTRACT = "SNAPSHOT.EXTRACT"; public static final String EVENT_SNAPSHOT_POLICY_CREATE = "SNAPSHOTPOLICY.CREATE"; public static final String EVENT_SNAPSHOT_POLICY_UPDATE = "SNAPSHOTPOLICY.UPDATE"; public static final String EVENT_SNAPSHOT_POLICY_DELETE = "SNAPSHOTPOLICY.DELETE"; @@ -841,6 +844,8 @@ public class EventTypes { entityEventDetails.put(EVENT_ROLE_UPDATE, Role.class); entityEventDetails.put(EVENT_ROLE_DELETE, Role.class); entityEventDetails.put(EVENT_ROLE_IMPORT, Role.class); + entityEventDetails.put(EVENT_ROLE_ENABLE, Role.class); + entityEventDetails.put(EVENT_ROLE_DISABLE, Role.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_CREATE, RolePermission.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_UPDATE, RolePermission.class); entityEventDetails.put(EVENT_ROLE_PERMISSION_DELETE, RolePermission.class); @@ -897,6 +902,7 @@ public class EventTypes { // Snapshots entityEventDetails.put(EVENT_SNAPSHOT_CREATE, Snapshot.class); entityEventDetails.put(EVENT_SNAPSHOT_DELETE, Snapshot.class); + entityEventDetails.put(EVENT_SNAPSHOT_EXTRACT, Snapshot.class); entityEventDetails.put(EVENT_SNAPSHOT_ON_PRIMARY, Snapshot.class); entityEventDetails.put(EVENT_SNAPSHOT_OFF_PRIMARY, Snapshot.class); entityEventDetails.put(EVENT_SNAPSHOT_POLICY_CREATE, SnapshotPolicy.class); diff --git a/api/src/main/java/com/cloud/storage/Upload.java b/api/src/main/java/com/cloud/storage/Upload.java index 59d203ac73a..4e696e877cc 100644 --- a/api/src/main/java/com/cloud/storage/Upload.java +++ b/api/src/main/java/com/cloud/storage/Upload.java @@ -40,7 +40,7 @@ public interface Upload extends InternalIdentity, Identity { } public static enum Type { - VOLUME, TEMPLATE, ISO + VOLUME, SNAPSHOT, TEMPLATE, ISO } public static enum Mode { diff --git a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java index 0893f337ce2..67afd6aa4e2 100644 --- a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java +++ b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd; @@ -106,6 +107,16 @@ public interface SnapshotApiService { */ Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner); + /** + * Extracts the snapshot to a particular location. + * + * @param cmd + * the command specifying url (where the snapshot needs to be extracted to), zoneId (zone where the snapshot exists) and + * id (the id of the snapshot) + * + */ + String extractSnapshot(ExtractSnapshotCmd cmd); + /** * Archives a snapshot from primary storage to secondary storage. * @param id Snapshot ID diff --git a/api/src/main/java/org/apache/cloudstack/acl/Role.java b/api/src/main/java/org/apache/cloudstack/acl/Role.java index 5e5ffd583d8..ce4166ef4c8 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/Role.java +++ b/api/src/main/java/org/apache/cloudstack/acl/Role.java @@ -21,7 +21,18 @@ import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; public interface Role extends RoleEntity, InternalIdentity, Identity { + + enum State { + ENABLED, DISABLED; + + @Override + public String toString(){ + return super.toString().toLowerCase(); + } + } + RoleType getRoleType(); boolean isDefault(); boolean isPublicRole(); + State getState(); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java index 07f62a7e7f8..68204d43253 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -54,6 +54,10 @@ public interface RoleService { boolean deleteRole(Role role); + boolean enableRole(Role role); + + boolean disableRole(Role role); + RolePermission findRolePermission(Long id); RolePermission findRolePermissionByRoleIdAndRule(Long roleId, String rule); @@ -76,7 +80,7 @@ public interface RoleService { */ List listRoles(); - Pair, Integer> listRoles(Long startIndex, Long limit); + Pair, Integer> listRoles(String state, Long startIndex, Long limit); /** * Find all roles that have the giving {@link String} as part of their name. @@ -84,14 +88,14 @@ public interface RoleService { */ List findRolesByName(String name); - Pair, Integer> findRolesByName(String name, String keyword, Long startIndex, Long limit); + Pair, Integer> findRolesByName(String name, String keyword, String state, Long startIndex, Long limit); /** * Find all roles by {@link RoleType}. If the role type is {@link RoleType#Admin}, the calling account must be a root admin, otherwise we return an empty list. */ List findRolesByType(RoleType roleType); - Pair, Integer> findRolesByType(RoleType roleType, Long startIndex, Long limit); + Pair, Integer> findRolesByType(RoleType roleType, String state, Long startIndex, Long limit); List findAllPermissionsBy(Long roleId); diff --git a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java index 22842b834fe..6cda8d07bd8 100644 --- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java @@ -56,6 +56,10 @@ public class AffinityGroupResponse extends BaseResponse implements ControlledVie @Param(description = "the domain name of the affinity group") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the affinity group belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the project ID of the affinity group") private String projectId; @@ -115,6 +119,11 @@ public class AffinityGroupResponse extends BaseResponse implements ControlledVie this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public int hashCode() { final int prime = 31; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java b/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java index 3e06fc0e44e..38047235273 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java @@ -32,4 +32,9 @@ public enum ApiArgValidator { * Validates if the parameter is an UUID with the method {@link UuidUtils#isUuid(String)}. */ UuidString, + + /** + * Validates if the parameter is a valid RFC Compliance domain name. + */ + RFCComplianceDomainName, } 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 7a167a7aeb6..2f0e4f16797 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -696,6 +696,7 @@ public class ApiConstants { public static final String TRAFFIC_TYPE_IMPLEMENTOR = "traffictypeimplementor"; public static final String KEYWORD = "keyword"; public static final String LIST_ALL = "listall"; + public static final String LIST_ONLY_REMOVED = "listonlyremoved"; public static final String LIST_SYSTEM_VMS = "listsystemvms"; public static final String IP_RANGES = "ipranges"; public static final String IPV6_ROUTING = "ip6routing"; @@ -1141,6 +1142,11 @@ public class ApiConstants { public static final String NFS_MOUNT_OPTIONS = "nfsmountopts"; + public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " + + "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + + "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + + "value will be applied."; + /** * 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/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index ef759aaf9c3..a4d52384df3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -345,9 +345,11 @@ public interface ResponseGenerator { SecurityGroupResponse createSecurityGroupResponse(SecurityGroup group); - ExtractResponse createExtractResponse(Long uploadId, Long id, Long zoneId, Long accountId, String mode, String url); + ExtractResponse createImageExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url); - ExtractResponse createExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url); + ExtractResponse createVolumeExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url); + + ExtractResponse createSnapshotExtractResponse(Long id, Long zoneId, Long accountId, String url); String toSerializedString(CreateCmdResponse response, String responseType); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java index 36e22acff91..a90fc4aebe9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java @@ -89,12 +89,11 @@ public class DeleteAccountCmd extends BaseAsyncCmd { CallContext.current().setEventDetails("Account ID: " + (account != null ? account.getUuid() : getId())); // Account not found is already handled by service boolean result = _regionService.deleteUserAccount(this); - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); - } else { + if (!result) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete user account and all corresponding users"); } + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java new file mode 100644 index 00000000000..80cb92c8362 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.acl; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.RoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "disableRole", description = "Disables a role", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin}) +public class DisableRoleCmd extends BaseCmd { + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, required = true, entityType = RoleResponse.class, + description = "ID of the role", validations = {ApiArgValidator.PositiveNumber}) + private Long roleId; + + public Long getRoleId() { + return roleId; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + Role role = roleService.findRole(getRoleId()); + if (role == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find the role with provided id"); + } + CallContext.current().setEventDetails("Role id: " + role.getId()); + boolean result = roleService.disableRole(role); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java new file mode 100644 index 00000000000..c4a6505d52f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.acl; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.RoleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "enableRole", description = "Enables a role", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.20.0", + authorized = {RoleType.Admin}) +public class EnableRoleCmd extends BaseCmd { + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, required = true, entityType = RoleResponse.class, + description = "ID of the role", validations = {ApiArgValidator.PositiveNumber}) + private Long roleId; + + public Long getRoleId() { + return roleId; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + Role role = roleService.findRole(getRoleId()); + if (role == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find the role with provided id"); + } + CallContext.current().setEventDetails("Role id: " + role.getId()); + boolean result = roleService.enableRole(role); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java index fef2b27eaa5..d82cc852e4f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ListRolesCmd.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.cloud.exception.InvalidParameterValueException; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; @@ -51,6 +52,9 @@ public class ListRolesCmd extends BaseListCmd { @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "List role by role type, valid options are: Admin, ResourceAdmin, DomainAdmin, User.") private String roleType; + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "List role by role type status, valid options are: enabled, disabled") + private String roleState; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +74,17 @@ public class ListRolesCmd extends BaseListCmd { return null; } + public Role.State getRoleState() { + if (roleState == null) { + return null; + } + try { + return Role.State.valueOf(roleState.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new InvalidParameterValueException("Unrecognized role state value"); + } + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -93,6 +108,7 @@ public class ListRolesCmd extends BaseListCmd { roleResponse.setDescription(role.getDescription()); roleResponse.setIsDefault(role.isDefault()); roleResponse.setPublicRole(role.isPublicRole()); + roleResponse.setState(role.getState().toString()); roleResponse.setObjectName("role"); roleResponses.add(roleResponse); } @@ -104,14 +120,16 @@ public class ListRolesCmd extends BaseListCmd { @Override public void execute() { Pair, Integer> roles; + Role.State state = getRoleState(); + String roleStateStr = state != null ? state.toString() : null; if (getId() != null && getId() > 0L) { roles = new Pair<>(Collections.singletonList(roleService.findRole(getId(), true)), 1); } else if (StringUtils.isNotBlank(getName()) || StringUtils.isNotBlank(getKeyword())) { - roles = roleService.findRolesByName(getName(), getKeyword(), getStartIndex(), getPageSizeVal()); + roles = roleService.findRolesByName(getName(), getKeyword(), roleStateStr, getStartIndex(), getPageSizeVal()); } else if (getRoleType() != null) { - roles = roleService.findRolesByType(getRoleType(), getStartIndex(), getPageSizeVal()); + roles = roleService.findRolesByType(getRoleType(), roleStateStr, getStartIndex(), getPageSizeVal()); } else { - roles = roleService.listRoles(getStartIndex(), getPageSizeVal()); + roles = roleService.listRoles(roleStateStr, getStartIndex(), getPageSizeVal()); } setupResponse(roles); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java index 4c317d06b13..b3d816adc3f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/RoleCmd.java @@ -59,6 +59,7 @@ public abstract class RoleCmd extends BaseCmd { response.setRoleType(role.getRoleType()); response.setDescription(role.getDescription()); response.setPublicRole(role.isPublicRole()); + response.setState(role.getState().toString()); response.setResponseName(getCommandName()); response.setObjectName("role"); setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java index 79554f44782..468c87d4d99 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/heuristics/RemoveSecondaryStorageSelectorCmd.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.secstorage.heuristics.Heuristic; @APICommand(name = "removeSecondaryStorageSelector", description = "Removes an existing secondary storage selector.", since = "4.19.0", responseObject = - SecondaryStorageHeuristicsResponse.class, requestHasSensitiveInfo = false, entityType = {Heuristic.class}, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) + SuccessResponse.class, requestHasSensitiveInfo = false, entityType = {Heuristic.class}, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) public class RemoveSecondaryStorageSelectorCmd extends BaseCmd { @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, entityType = SecondaryStorageHeuristicsResponse.class, required = true, description = "The unique identifier of the secondary storage selector to be removed.") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageTypesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageTypesCmd.java index 2772743c75a..b993735dba7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageTypesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageTypesCmd.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UsageTypeResponse; +import org.apache.cloudstack.usage.UsageTypes; import com.cloud.user.Account; @@ -37,8 +38,8 @@ public class ListUsageTypesCmd extends BaseCmd { @Override public void execute() { - List result = _usageService.listUsageTypes(); - ListResponse response = new ListResponse(); + List result = UsageTypes.listUsageTypes(); + ListResponse response = new ListResponse<>(); response.setResponses(result); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index d38ae057f05..9cd845c774c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -103,7 +103,7 @@ public class AddUserToProjectCmd extends BaseAsyncCmd { @Override public String getEventDescription() { - return "Adding user "+getUsername()+" to Project "+getProjectId(); + return "Adding user " + getUsername() + " to project: " + getProjectId(); } ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java index 8319911c5c8..0731d837804 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java @@ -81,7 +81,6 @@ public class DeleteUserFromProjectCmd extends BaseAsyncCmd { return "Removing user " + userId + " from project: " + projectId; } - @Override public long getEntityOwnerId() { Project project = _projectService.getProject(projectId); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java index 5db680066a6..7861c1e5d41 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java @@ -120,7 +120,7 @@ public class ExtractIsoCmd extends BaseAsyncCmd { CallContext.current().setEventDetails(getEventDescription()); String uploadUrl = _templateService.extract(this); if (uploadUrl != null) { - ExtractResponse response = _responseGenerator.createExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); + ExtractResponse response = _responseGenerator.createImageExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); response.setResponseName(getCommandName()); response.setObjectName("iso"); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java new file mode 100644 index 00000000000..3f0f82ea4e3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.snapshot; + +import com.cloud.event.EventTypes; +import com.cloud.storage.Snapshot; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "extractSnapshot", description = "Returns a download URL for extracting a snapshot. It must be in the Backed Up state.", since = "4.20.0", + responseObject = ExtractResponse.class, entityType = {Snapshot.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ExtractSnapshotCmd extends BaseAsyncCmd { + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL(accessType = AccessType.OperateEntry) + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=SnapshotResponse.class, required=true, since="4.20.0", description="the ID of the snapshot") + private Long id; + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, since="4.20.0", + description = "the ID of the zone where the snapshot is located") + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getZoneId() { + return zoneId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.Snapshot; + } + + @Override + public Long getApiResourceId() { + return getId(); + } + + /** + * @return ID of the snapshot to extract, if any. Otherwise returns the ACCOUNT_ID_SYSTEM, so ERROR events will be traceable. + */ + @Override + public long getEntityOwnerId() { + Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId()); + if (snapshot != null) { + return snapshot.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_SNAPSHOT_EXTRACT; + } + + @Override + public String getEventDescription() { + return "Snapshot extraction job"; + } + + @Override + public void execute() { + CallContext.current().setEventDetails("Snapshot ID: " + this._uuidMgr.getUuid(Snapshot.class, getId())); + String uploadUrl = _snapshotService.extractSnapshot(this); + logger.info("Extract URL [{}] of snapshot [{}].", uploadUrl, id); + if (uploadUrl != null) { + ExtractResponse response = _responseGenerator.createSnapshotExtractResponse(id, zoneId, getEntityOwnerId(), uploadUrl); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract snapshot"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java index 245baf1e07e..5e9bf317fe1 100755 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.user.template; import org.apache.commons.lang3.BooleanUtils; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -53,6 +54,9 @@ public class DeleteTemplateCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force delete a template.", since = "4.9+") private Boolean forced; + @Parameter(name = ApiConstants.IS_SYSTEM, type = CommandType.BOOLEAN, required = false, description = "Necessary if the template's type is system.", since = "4.20.0", authorized = {RoleType.Admin}) + private Boolean isSystem; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -69,6 +73,10 @@ public class DeleteTemplateCmd extends BaseAsyncCmd { return BooleanUtils.toBooleanDefaultIfNull(forced, false); } + public boolean getIsSystem() { + return BooleanUtils.toBooleanDefaultIfNull(isSystem, false); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java index ce6ba5e300c..0fa0679bfd9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java @@ -120,8 +120,9 @@ public class ExtractTemplateCmd extends BaseAsyncCmd { CallContext.current().setEventDetails(getEventDescription()); String uploadUrl = _templateService.extract(this); if (uploadUrl != null) { - ExtractResponse response = _responseGenerator.createExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); + ExtractResponse response = _responseGenerator.createImageExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); response.setResponseName(getCommandName()); + response.setObjectName("template"); this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract template"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 68dd49d9ce5..52d42a95d98 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -31,6 +31,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; @@ -97,7 +98,7 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "the ID of the template for the virtual machine") private Long templateId; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine", validations = {ApiArgValidator.RFCComplianceDomainName}) private String name; @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "an optional user generated name for the virtual machine") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/GetVMPasswordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/GetVMPasswordCmd.java index ce6114c7fd8..11534fd4375 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/GetVMPasswordCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/GetVMPasswordCmd.java @@ -16,9 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; -import java.security.InvalidParameterException; - - import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; @@ -28,6 +25,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.GetVMPasswordResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; @@ -61,7 +59,7 @@ public class GetVMPasswordCmd extends BaseCmd { public void execute() { String passwd = _mgr.getVMPassword(this); if (passwd == null || passwd.equals("")) - throw new InvalidParameterException("No password for VM with id '" + getId() + "' found."); + throw new InvalidParameterValueException("No password for VM with id '" + getId() + "' found."); setResponseObject(new GetVMPasswordResponse(getCommandName(), passwd)); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index 024337356fd..e5d625fe70d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.response.UserDataResponse; import org.apache.cloudstack.acl.RoleType; @@ -104,7 +106,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, description = "true if VM contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory. This can be updated only when dynamic scaling is enabled on template, service offering and the corresponding global setting") protected Boolean isDynamicallyScalable; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "new host name of the vm. The VM has to be stopped/started for this update to take affect", since = "4.4") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "new host name of the vm. The VM has to be stopped/started for this update to take affect", validations = {ApiArgValidator.RFCComplianceDomainName}, since = "4.4") private String name; @Parameter(name = ApiConstants.INSTANCE_NAME, type = CommandType.STRING, description = "instance name of the user vm", since = "4.4", authorized = {RoleType.Admin}) @@ -141,7 +143,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, + " Example: dhcpoptionsnetworklist[0].dhcp:114=url&dhcpoptionsetworklist[0].networkid=networkid&dhcpoptionsetworklist[0].dhcp:66=www.test.com") private Map dhcpOptionsNetworkList; - @Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "an optional URL encoded string that can be passed to the virtual machine upon successful deployment", authorized = { RoleType.Admin }, length = 5120) + @Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "an optional URL encoded string that can be passed to the virtual machine upon successful deployment", length = 5120) private String extraConfig; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java index 1146f80f0e2..9445aba23c0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java @@ -31,9 +31,7 @@ import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; -import com.cloud.dc.DataCenter; import com.cloud.event.EventTypes; -import com.cloud.storage.Upload; import com.cloud.storage.Volume; import com.cloud.user.Account; @@ -124,20 +122,8 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId())); String uploadUrl = _volumeService.extractVolume(this); if (uploadUrl != null) { - ExtractResponse response = new ExtractResponse(); + ExtractResponse response = _responseGenerator.createVolumeExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); response.setResponseName(getCommandName()); - response.setObjectName("volume"); - Volume vol = _entityMgr.findById(Volume.class, id); - response.setId(vol.getUuid()); - response.setName(vol.getName()); - DataCenter zone = _entityMgr.findById(DataCenter.class, zoneId); - response.setZoneId(zone.getUuid()); - response.setZoneName(zone.getName()); - response.setMode(mode); - response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString()); - Account account = _entityMgr.findById(Account.class, getEntityOwnerId()); - response.setAccountId(account.getUuid()); - response.setUrl(uploadUrl); setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract volume"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java index 9e950310cdc..59ba7e94b04 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java @@ -131,6 +131,7 @@ public class AddVpnUserCmd extends BaseAsyncCreateCmd { if (domain != null) { vpnResponse.setDomainId(domain.getUuid()); vpnResponse.setDomainName(domain.getName()); + vpnResponse.setDomainPath(domain.getPath()); } vpnResponse.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AcquireIPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AcquireIPAddressResponse.java index 7270fa949c8..06351a40d9d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AcquireIPAddressResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AcquireIPAddressResponse.java @@ -75,6 +75,10 @@ public class AcquireIPAddressResponse extends BaseResponse implements Controlle @Param(description = "the domain the public IP address is associated with") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the public IP address belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.FOR_VIRTUAL_NETWORK) @Param(description = "the virtual network for the IP address") private Boolean forVirtualNetwork; @@ -190,6 +194,11 @@ public class AcquireIPAddressResponse extends BaseResponse implements Controlle this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setForVirtualNetwork(Boolean forVirtualNetwork) { this.forVirtualNetwork = forVirtualNetwork; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AcquirePodIpCmdResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AcquirePodIpCmdResponse.java index 77c4d0d3ffc..2226efd063d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AcquirePodIpCmdResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AcquirePodIpCmdResponse.java @@ -44,7 +44,7 @@ public class AcquirePodIpCmdResponse extends BaseResponse { @SerializedName(ApiConstants.NIC_ID) @Param(description = "the ID of the nic") - private Long instanceId; + private Long nicId; @SerializedName(ApiConstants.HOST_MAC) @Param(description = "MAC address of the pod the IP") @@ -58,8 +58,8 @@ public class AcquirePodIpCmdResponse extends BaseResponse { this.ipAddress = ipAddress; } - public void setInstanceId(Long instanceId) { - this.instanceId = instanceId; + public void setNicId(Long nicId) { + this.nicId = nicId; } public void setPodId(long podId) { @@ -82,8 +82,8 @@ public class AcquirePodIpCmdResponse extends BaseResponse { return id; } - public Long getInstanceId() { - return instanceId; + public Long getNicId() { + return nicId; } public long getPodId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerResponse.java index 53e3f868a3b..0b6485bfc14 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerResponse.java @@ -76,6 +76,10 @@ public class ApplicationLoadBalancerResponse extends BaseResponse implements Con @Param(description = "the domain of the Load Balancer") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the Load Balancer belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName("loadbalancerrule") @Param(description = "the list of rules associated with the Load Balancer", responseObject = ApplicationLoadBalancerRuleResponse.class) private List lbRules; @@ -107,6 +111,11 @@ public class ApplicationLoadBalancerResponse extends BaseResponse implements Con this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AutoScalePolicyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AutoScalePolicyResponse.java index ae3462f1fec..e3f9902dec6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AutoScalePolicyResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AutoScalePolicyResponse.java @@ -74,6 +74,10 @@ public class AutoScalePolicyResponse extends BaseResponse implements ControlledE @Param(description = "the domain name of the autoscale policy") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the autoscale policy belongs", since = "4.19.2.0") + private String domainPath; + @Override public String getObjectId() { return this.id; @@ -118,6 +122,11 @@ public class AutoScalePolicyResponse extends BaseResponse implements ControlledE this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmGroupResponse.java index 656a62ddda3..b1389acdecb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmGroupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmGroupResponse.java @@ -123,6 +123,10 @@ public class AutoScaleVmGroupResponse extends BaseResponseWithAnnotations implem @Param(description = "the domain name of the vm group") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the vm group belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.FOR_DISPLAY) @Param(description = "is group for display to the regular user", since = "4.4", authorized = {RoleType.Admin}) private Boolean forDisplay; @@ -227,6 +231,11 @@ public class AutoScaleVmGroupResponse extends BaseResponseWithAnnotations implem this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java index 678c4fcaaca..22e4eb2288b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java @@ -114,6 +114,10 @@ public class AutoScaleVmProfileResponse extends BaseResponse implements Controll @Param(description = "the domain name of the vm profile") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the vm profile belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.FOR_DISPLAY) @Param(description = "is profile for display to the regular user", since = "4.4", authorized = {RoleType.Admin}) private Boolean forDisplay; @@ -196,6 +200,10 @@ public class AutoScaleVmProfileResponse extends BaseResponse implements Controll this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java index a1cce6e43d7..f2dd365452c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java @@ -54,6 +54,10 @@ public class BucketResponse extends BaseResponseWithTagInformation implements Co @SerializedName(ApiConstants.DOMAIN) @Param(description = "the domain associated with the bucket") private String domainName; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the bucket belongs", since = "4.19.2.0") + private String domainPath; @SerializedName(ApiConstants.OBJECT_STORAGE_ID) @Param(description = "id of the object storage hosting the Bucket; returned to admin user only") private String objectStoragePoolId; @@ -146,6 +150,11 @@ public class BucketResponse extends BaseResponseWithTagInformation implements Co this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java index 72dab3da3b1..ca01a2012f6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java @@ -73,7 +73,7 @@ public class ClusterResponse extends BaseResponseWithAnnotations { @SerializedName("capacity") @Param(description = "the capacity of the Cluster", responseObject = CapacityResponse.class) - private List capacitites; + private List capacities; @SerializedName("cpuovercommitratio") @Param(description = "The cpu overcommit ratio of the cluster") @@ -171,12 +171,12 @@ public class ClusterResponse extends BaseResponseWithAnnotations { this.managedState = managedState; } - public List getCapacitites() { - return capacitites; + public List getCapacities() { + return capacities; } - public void setCapacitites(ArrayList arrayList) { - this.capacitites = arrayList; + public void setCapacities(ArrayList arrayList) { + this.capacities = arrayList; } public void setCpuOvercommitRatio(String cpuovercommitratio) { @@ -219,4 +219,32 @@ public class ClusterResponse extends BaseResponseWithAnnotations { public Map getResourceDetails() { return resourceDetails; } + + public String getCpuovercommitratio() { + return cpuovercommitratio; + } + + public void setCpuovercommitratio(String cpuovercommitratio) { + this.cpuovercommitratio = cpuovercommitratio; + } + + public String getMemoryovercommitratio() { + return memoryovercommitratio; + } + + public void setMemoryovercommitratio(String memoryovercommitratio) { + this.memoryovercommitratio = memoryovercommitratio; + } + + public String getOvm3vip() { + return ovm3vip; + } + + public void setOvm3vip(String ovm3vip) { + this.ovm3vip = ovm3vip; + } + + public void setCapacities(List capacities) { + this.capacities = capacities; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConditionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConditionResponse.java index 15671430bf1..1038177cb86 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ConditionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConditionResponse.java @@ -61,6 +61,10 @@ public class ConditionResponse extends BaseResponse implements ControlledEntityR @Param(description = "the domain name of the owner.") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the Condition owner belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.ZONE_ID) @Param(description = "zone id of counter") private String zoneId; @@ -138,4 +142,9 @@ public class ConditionResponse extends BaseResponse implements ControlledEntityR public void setDomainName(String domainName) { this.domain = domainName; } + + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ControlledEntityResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ControlledEntityResponse.java index 598ef082253..dc021705d78 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ControlledEntityResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ControlledEntityResponse.java @@ -27,4 +27,6 @@ public interface ControlledEntityResponse { public void setDomainId(String domainId); public void setDomainName(String domainName); + + public void setDomainPath(String domainPath); } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ControlledViewEntityResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ControlledViewEntityResponse.java index abe4dd77143..730c2c38fc5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ControlledViewEntityResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ControlledViewEntityResponse.java @@ -27,4 +27,6 @@ public interface ControlledViewEntityResponse { public void setDomainId(String domainId); public void setDomainName(String domainName); + + public void setDomainPath(String domainPath); } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java index 99e5f6ccdfa..b23d0f4b527 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java @@ -185,6 +185,10 @@ public class DomainRouterResponse extends BaseResponseWithAnnotations implements @Param(description = "the domain associated with the router") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the router belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.SERVICE_OFFERING_ID) @Param(description = "the ID of the service offering of the virtual machine") private String serviceOfferingId; @@ -381,6 +385,10 @@ public class DomainRouterResponse extends BaseResponseWithAnnotations implements this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setPublicNetworkId(String publicNetworkId) { this.publicNetworkId = publicNetworkId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java index 8f65492cb70..751d00922f1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java @@ -69,6 +69,10 @@ public class EventResponse extends BaseResponse implements ControlledViewEntityR @Param(description = "the name of the account's domain") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the account's domain belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.RESOURCE_ID) @Param(description = "the id of the resource", since = "4.17.0") private String resourceId; @@ -132,6 +136,11 @@ public class EventResponse extends BaseResponse implements ControlledViewEntityR this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setResourceId(String resourceId) { this.resourceId = resourceId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GlobalLoadBalancerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GlobalLoadBalancerResponse.java index aac7e29a173..d41e4d2ab34 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GlobalLoadBalancerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GlobalLoadBalancerResponse.java @@ -82,6 +82,10 @@ public class GlobalLoadBalancerResponse extends BaseResponse implements Controll @Param(description = "the domain of the load balancer rule") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the load balancer rule belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.LOAD_BALANCER_RULE) @Param(description = "List of load balancer rules that are part of GSLB rule", responseObject = LoadBalancerResponse.class) private List siteLoadBalancers; @@ -143,6 +147,11 @@ public class GlobalLoadBalancerResponse extends BaseResponse implements Controll this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setSiteLoadBalancers(List siteLoadBalancers) { this.siteLoadBalancers = siteLoadBalancers; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanRangeResponse.java index fab2a2cb4ce..133338d27ae 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanRangeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanRangeResponse.java @@ -44,6 +44,10 @@ public class GuestVlanRangeResponse extends BaseResponse implements ControlledEn @Param(description = "the domain name of the guest VLAN range") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the guest VLAN range belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.GUEST_VLAN_RANGE) @Param(description = "the guest VLAN range") private String guestVlanRange; @@ -83,6 +87,10 @@ public class GuestVlanRangeResponse extends BaseResponse implements ControlledEn this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setGuestVlanRange(String guestVlanRange) { this.guestVlanRange = guestVlanRange; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanResponse.java index 6bcc1d35cb1..6716911ab82 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanResponse.java @@ -49,6 +49,10 @@ public class GuestVlanResponse extends BaseResponse implements ControlledEntityR @Param(description = "the domain name of the guest VLAN range") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the guest VLAN range belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the project id of the guest VLAN range") private String projectId; @@ -108,6 +112,10 @@ public class GuestVlanResponse extends BaseResponse implements ControlledEntityR this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java index 8d98b6573f4..274c1fd43c1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java @@ -280,7 +280,7 @@ public class HostResponse extends BaseResponseWithAnnotations { @SerializedName("ueficapability") @Param(description = "true if the host has capability to support UEFI boot") - private Boolean uefiCapabilty; + private Boolean uefiCapability; @SerializedName(ApiConstants.ENCRYPTION_SUPPORTED) @Param(description = "true if the host supports encryption", since = "4.18") @@ -735,7 +735,7 @@ public class HostResponse extends BaseResponseWithAnnotations { return clusterType; } - public Boolean isLocalStorageActive() { + public Boolean getLocalStorageActive() { return localStorageActive; } @@ -755,7 +755,7 @@ public class HostResponse extends BaseResponseWithAnnotations { return hasEnoughCapacity; } - public Boolean isSuitableForMigration() { + public Boolean getSuitableForMigration() { return suitableForMigration; } @@ -767,8 +767,8 @@ public class HostResponse extends BaseResponseWithAnnotations { return haHost; } - public void setUefiCapabilty(Boolean hostCapability) { - this.uefiCapabilty = hostCapability; + public void setUefiCapability(Boolean hostCapability) { + this.uefiCapability = hostCapability; } public void setEncryptionSupported(Boolean encryptionSupported) { @@ -786,4 +786,76 @@ public class HostResponse extends BaseResponseWithAnnotations { public void setIsTagARule(Boolean tagARule) { isTagARule = tagARule; } + + public Long getCpuAllocatedValue() { + return cpuAllocatedValue; + } + + public String getCpuAllocatedPercentage() { + return cpuAllocatedPercentage; + } + + public String getCpuAllocatedWithOverprovisioning() { + return cpuAllocatedWithOverprovisioning; + } + + public Double getCpuloadaverage() { + return cpuloadaverage; + } + + public void setCpuloadaverage(Double cpuloadaverage) { + this.cpuloadaverage = cpuloadaverage; + } + + public String getMemWithOverprovisioning() { + return memWithOverprovisioning; + } + + public String getMemoryAllocatedPercentage() { + return memoryAllocatedPercentage; + } + + public Long getMemoryAllocatedBytes() { + return memoryAllocatedBytes; + } + + public Boolean getTagARule() { + return isTagARule; + } + + public void setTagARule(Boolean tagARule) { + isTagARule = tagARule; + } + + public Boolean getHasEnoughCapacity() { + return hasEnoughCapacity; + } + + public void setDetails(Map details) { + this.details = details; + } + + public String getAnnotation() { + return annotation; + } + + public Date getLastAnnotated() { + return lastAnnotated; + } + + public String getUsername() { + return username; + } + + public Boolean getUefiCapability() { + return uefiCapability; + } + + public Boolean getEncryptionSupported() { + return encryptionSupported; + } + + public Boolean getInstanceConversionSupported() { + return instanceConversionSupported; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java index 8a9bf7789dd..0018edc8638 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java @@ -75,6 +75,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co @Param(description = "the domain the public IP address is associated with") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the public IP address belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.FOR_VIRTUAL_NETWORK) @Param(description = "the virtual network for the IP address") private Boolean forVirtualNetwork; @@ -211,6 +215,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setForVirtualNetwork(Boolean forVirtualNetwork) { this.forVirtualNetwork = forVirtualNetwork; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java index e1241cc19bc..9c7a4fc09a1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java @@ -63,6 +63,10 @@ public class InstanceGroupResponse extends BaseResponseWithAnnotations implement @Param(description = "the domain name of the instance group") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the instance group belongs to", since = "4.19.2.0") + private String domainPath; + public void setId(String id) { this.id = id; } @@ -90,6 +94,11 @@ public class InstanceGroupResponse extends BaseResponseWithAnnotations implement this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerResponse.java index 32748269587..e520dec09e7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerResponse.java @@ -87,6 +87,10 @@ public class LoadBalancerResponse extends BaseResponse implements ControlledEnti @Param(description = "the domain of the load balancer rule") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the load balancer rule belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.STATE) @Param(description = "the state of the rule") private String state; @@ -158,6 +162,11 @@ public class LoadBalancerResponse extends BaseResponse implements ControlledEnti this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setState(String state) { this.state = state; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java index 330f91e69f3..a471045eb67 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java @@ -167,4 +167,8 @@ public class ManagementServerResponse extends BaseResponse { public void setServiceIp(String serviceIp) { this.serviceIp = serviceIp; } + + public String getKernelVersion() { + return kernelVersion; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index d34f949372a..1049740bf55 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -424,6 +424,7 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement this.domain = domain; } + @Override public void setDomainPath(String domainPath) { this.domainPath = domainPath; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/OvsProviderResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/OvsProviderResponse.java index ac5b9e309c8..2b67e1618dc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/OvsProviderResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/OvsProviderResponse.java @@ -54,6 +54,10 @@ public class OvsProviderResponse extends BaseResponse implements @Param(description = "the domain associated with the provider") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the provider belongs", since = "4.19.2.0") + private String domainPath; + @Override public void setAccountName(String accountName) { this.accountName = accountName; @@ -73,6 +77,10 @@ public class OvsProviderResponse extends BaseResponse implements this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/PrivateGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/PrivateGatewayResponse.java index 65401eb2a02..414aed94bad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/PrivateGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/PrivateGatewayResponse.java @@ -89,6 +89,10 @@ public class PrivateGatewayResponse extends BaseResponseWithAssociatedNetwork im @Param(description = "the domain associated with the private gateway") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the private gateway belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.STATE) @Param(description = "State of the gateway, can be Creating, Ready, Deleting") private String state; @@ -165,6 +169,10 @@ public class PrivateGatewayResponse extends BaseResponseWithAssociatedNetwork im this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java index aab03074155..d035622f65f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java @@ -73,6 +73,10 @@ public class ProjectAccountResponse extends BaseResponse implements ControlledVi @Param(description = "name of the Domain the account belongs too") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the account belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.USER) @Param(description = "the list of users associated with account", responseObject = UserResponse.class) private List users; @@ -110,6 +114,11 @@ public class ProjectAccountResponse extends BaseResponse implements ControlledVi this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setUserId(String userId) { this.userId = userId; } public void setProjectRoleId(String projectRoleId) { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java index 4462ea91568..ad3f99f6b3f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java @@ -51,6 +51,10 @@ public class ProjectInvitationResponse extends BaseResponse implements Controlle @Param(description = "the domain name where the project belongs to") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the project belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.ACCOUNT) @Param(description = "the account name of the project's owner") private String accountName; @@ -87,6 +91,11 @@ public class ProjectInvitationResponse extends BaseResponse implements Controlle this.domainName = domain; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setAccountName(String accountName) { this.accountName = accountName; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RemoteAccessVpnResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RemoteAccessVpnResponse.java index 0e078bea5bd..54164e3ac34 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RemoteAccessVpnResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RemoteAccessVpnResponse.java @@ -65,6 +65,10 @@ public class RemoteAccessVpnResponse extends BaseResponse implements ControlledE @Param(description = "the domain name of the account of the remote access vpn") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the remote access vpn belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.STATE) @Param(description = "the state of the rule") private String state; @@ -104,6 +108,10 @@ public class RemoteAccessVpnResponse extends BaseResponse implements ControlledE this.domainName = name; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setState(String state) { this.state = state; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceCountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceCountResponse.java index 3a698618585..74511a0f743 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ResourceCountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceCountResponse.java @@ -45,6 +45,10 @@ public class ResourceCountResponse extends BaseResponse implements ControlledEnt @Param(description = "the domain name for which resource count's are updated") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the resource counts are updated", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description = "resource type. Values include 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11. See the resourceType parameter for more information on these values.") private String resourceType; @@ -76,6 +80,11 @@ public class ResourceCountResponse extends BaseResponse implements ControlledEnt this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setResourceType(Resource.ResourceType resourceType) { this.resourceType = Integer.valueOf(resourceType.getOrdinal()).toString(); this.resourceTypeName = resourceType.getName(); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitResponse.java index 72c1c66f5e8..b5b03873f3f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitResponse.java @@ -40,6 +40,10 @@ public class ResourceLimitResponse extends BaseResponse implements ControlledEnt @Param(description = "the domain name of the resource limit") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the resource limit belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.RESOURCE_TYPE) @Param(description = "resource type. Values include 0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11. See the resourceType parameter for more information on these values.") private String resourceType; @@ -84,6 +88,10 @@ public class ResourceLimitResponse extends BaseResponse implements ControlledEnt this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setResourceType(Resource.ResourceType resourceType) { this.resourceType = Integer.valueOf(resourceType.getOrdinal()).toString(); this.resourceTypeName = resourceType.getName(); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceTagResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceTagResponse.java index 44325560534..26e4d19cdba 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ResourceTagResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceTagResponse.java @@ -61,6 +61,10 @@ public class ResourceTagResponse extends BaseResponse implements ControlledViewE @Param(description = "the domain associated with the tag") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain associated with the tag", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.CUSTOMER) @Param(description = "customer associated with the tag") private String customer; @@ -96,6 +100,11 @@ public class ResourceTagResponse extends BaseResponse implements ControlledViewE this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java index 1861028f0ed..92e3b46139f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RoleResponse.java @@ -36,6 +36,10 @@ public class RoleResponse extends BaseRoleResponse { @Param(description = "true if role is default, false otherwise") private Boolean isDefault; + @SerializedName(ApiConstants.STATE) + @Param(description = "the state of the role") + private String state; + public void setRoleType(RoleType roleType) { if (roleType != null) { this.roleType = roleType.name(); @@ -45,4 +49,8 @@ public class RoleResponse extends BaseRoleResponse { public void setIsDefault(Boolean isDefault) { this.isDefault = isDefault; } + + public void setState(String state) { + this.state = state; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupResponse.java index c96421b0a40..de486b5a374 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupResponse.java @@ -63,6 +63,10 @@ public class SecurityGroupResponse extends BaseResponse implements ControlledVie @Param(description = "the domain name of the security group") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the security group belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName("ingressrule") @Param(description = "the list of ingress rules associated with the security group", responseObject = SecurityGroupRuleResponse.class) private Set ingressRules; @@ -126,6 +130,11 @@ public class SecurityGroupResponse extends BaseResponse implements ControlledVie this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setSecurityGroupIngressRules(Set securityGroupRules) { this.ingressRules = securityGroupRules; } 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 babc9bf4432..4ae140ec573 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 @@ -98,6 +98,10 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation @Param(description = "the domain name of the owner") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the owner", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.REMOVED) @Param(description = "the date and time the host was removed") private Date removed; @@ -193,4 +197,9 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation this.domain = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnConnectionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnConnectionResponse.java index 1f7509239d1..a9fd0f9703c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnConnectionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnConnectionResponse.java @@ -120,6 +120,10 @@ public class Site2SiteVpnConnectionResponse extends BaseResponse implements Cont @Param(description = "the domain name of the owner") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the owner", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date and time the host was created") private Date created; @@ -241,6 +245,11 @@ public class Site2SiteVpnConnectionResponse extends BaseResponse implements Cont this.domain = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setForDisplay(Boolean forDisplay) { this.forDisplay = forDisplay; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnGatewayResponse.java index cdd8e4f3d87..1e63ba896c7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnGatewayResponse.java @@ -66,6 +66,10 @@ public class Site2SiteVpnGatewayResponse extends BaseResponse implements Control @Param(description = "the domain name of the owner") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the owner", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.REMOVED) @Param(description = "the date and time the host was removed") private Date removed; @@ -119,6 +123,10 @@ public class Site2SiteVpnGatewayResponse extends BaseResponse implements Control this.domain = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setForDisplay(Boolean forDisplay) { this.forDisplay = forDisplay; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java index 02132416b8a..9f7a7f42dec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java @@ -47,6 +47,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements @Param(description = "the domain name of the snapshot's account") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the snapshot's account belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the project id of the snapshot") private String projectId; @@ -187,6 +191,11 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setSnapshotType(String snapshotType) { this.snapshotType = snapshotType; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java index 79c7f9040a1..51f8a130383 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java @@ -70,6 +70,10 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit @Param(description = "the domain associated with the static route") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path associated with the static route", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.TAGS) @Param(description = "the list of resource tags associated with static route", responseObject = ResourceTagResponse.class) private List tags; @@ -114,6 +118,10 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java index 9e7f5159e0e..bd468a9201f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -371,4 +371,16 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { public void setNfsMountOpts(String nfsMountOpts) { this.nfsMountOpts = nfsMountOpts; } + + public Long getAllocatedIops() { + return allocatedIops; + } + + public Boolean getTagARule() { + return isTagARule; + } + + public void setTagARule(Boolean tagARule) { + isTagARule = tagARule; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java index 3abd44941d9..604c9f0955f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java @@ -135,6 +135,10 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @Param(description = "the name of the domain to which the template belongs") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the template belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.DOMAIN_ID) @Param(description = "the ID of the domain to which the template belongs") private String domainId; @@ -354,6 +358,11 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setDomainId(String domainId) { this.domainId = domainId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UsageRecordResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UsageRecordResponse.java index 4522315b499..4aeded64287 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UsageRecordResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UsageRecordResponse.java @@ -52,6 +52,10 @@ public class UsageRecordResponse extends BaseResponseWithTagInformation implemen @Param(description = "the domain the resource is associated with") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the usage reocrd belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.ZONE_ID) @Param(description = "the zone ID") private String zoneId; @@ -277,6 +281,10 @@ public class UsageRecordResponse extends BaseResponseWithTagInformation implemen this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setNetworkId(String networkId) { this.networkId = networkId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UsageTypeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UsageTypeResponse.java index 83b97f00c15..5beef5ac556 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UsageTypeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UsageTypeResponse.java @@ -25,12 +25,16 @@ import com.cloud.serializer.Param; public class UsageTypeResponse extends BaseResponse { - @SerializedName("usagetypeid") - @Param(description = "usage type") + @SerializedName("id") + @Param(description = "Usage type ID") private Integer usageType; + @SerializedName(ApiConstants.NAME) + @Param(description = "Usage type name") + private String name; + @SerializedName(ApiConstants.DESCRIPTION) - @Param(description = "description of usage type") + @Param(description = "Usage type description") private String description; public String getDescription() { @@ -49,10 +53,10 @@ public class UsageTypeResponse extends BaseResponse { this.usageType = usageType; } - public UsageTypeResponse(Integer usageType, String description) { + public UsageTypeResponse(Integer usageType, String name, String description) { this.usageType = usageType; + this.name = name; this.description = description; setObjectName("usagetype"); } - } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java index e69094c8f80..2dfc66fa7d5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserDataResponse.java @@ -54,6 +54,10 @@ public class UserDataResponse extends BaseResponseWithAnnotations implements Con @SerializedName(ApiConstants.DOMAIN) @Param(description="the domain name of the userdata owner") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the userdata owner belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.USER_DATA) @Param(description="base64 encoded userdata content") private String userData; @@ -143,4 +147,9 @@ public class UserDataResponse extends BaseResponseWithAnnotations implements Con public void setDomainName(String domain) { this.domain = domain; } + + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 8deae7d80d3..a9e4587169c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -82,6 +82,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the name of the domain in which the virtual machine exists") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain in which the virtual machine exists", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date when this virtual machine was created") private Date created; @@ -703,6 +707,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public void setCreated(Date created) { this.created = created; } @@ -1133,4 +1141,8 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co } this.vnfDetails.put(key,value); } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java index 9b553ed0744..703f27b537c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java @@ -108,6 +108,10 @@ public class VMSnapshotResponse extends BaseResponseWithTagInformation implement @Param(description = "the domain associated with the disk volume") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the disk volume belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.HYPERVISOR) @Param(description = "the type of hypervisor on which snapshot is stored") private String hypervisor; @@ -261,6 +265,11 @@ public class VMSnapshotResponse extends BaseResponseWithTagInformation implement this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setTags(Set tags) { this.tags = tags; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VirtualRouterProviderResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VirtualRouterProviderResponse.java index c3b46710f6f..b9d18e23810 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VirtualRouterProviderResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VirtualRouterProviderResponse.java @@ -60,6 +60,10 @@ public class VirtualRouterProviderResponse extends BaseResponse implements Contr @Param(description = "the domain associated with the provider") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the provider belongs", since = "4.19.2.0") + private String domainPath; + @Override public void setAccountName(String accountName) { this.accountName = accountName; @@ -79,6 +83,10 @@ public class VirtualRouterProviderResponse extends BaseResponse implements Contr this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java index aac6dd3c577..1492c23e882 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java @@ -55,6 +55,10 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit @Param(description = "the domain name of the VLAN IP range") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the VLAN IP range belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.POD_ID) @Param(description = "the Pod ID for the VLAN IP range") private String podId; @@ -166,6 +170,11 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setPodId(String podId) { this.podId = podId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java index 726c9adf8a3..623499822cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java @@ -145,6 +145,10 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the domain associated with the disk volume") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain the disk volume belongs to", since = "4.19.2.0") + private String domainPath; + @SerializedName("storagetype") @Param(description = "shared or local storage") private String storageType; @@ -211,7 +215,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co @SerializedName("destroyed") @Param(description = "the boolean state of whether the volume is destroyed or not") - private Boolean destroyed; + private boolean destroyed; @SerializedName(ApiConstants.SERVICE_OFFERING_ID) @Param(description = "ID of the service offering for root disk") @@ -227,7 +231,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co @SerializedName("isextractable") @Param(description = "true if the volume is extractable, false otherwise") - private Boolean extractable; + private boolean extractable; @SerializedName(ApiConstants.STATUS) @Param(description = "the status of the volume") @@ -235,7 +239,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co @SerializedName(ApiConstants.DISPLAY_VOLUME) @Param(description = "an optional field whether to the display the volume to the end user or not.", authorized = {RoleType.Admin}) - private Boolean displayVolume; + private boolean displayVolume; @SerializedName(ApiConstants.PATH) @Param(description = "the path of the volume") @@ -318,11 +322,11 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co return this.getId(); } - public Boolean isDestroyed() { + public boolean isDestroyed() { return destroyed; } - public void setDestroyed(Boolean destroyed) { + public void setDestroyed(boolean destroyed) { this.destroyed = destroyed; } @@ -409,6 +413,11 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + public void setStorageType(String storageType) { this.storageType = storageType; } @@ -521,7 +530,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co this.serviceOfferingDisplayText = serviceOfferingDisplayText; } - public void setExtractable(Boolean extractable) { + public void setExtractable(boolean extractable) { this.extractable = extractable; } @@ -539,7 +548,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co this.projectName = projectName; } - public void setDisplayVolume(Boolean displayVm) { + public void setDisplayVolume(boolean displayVm) { this.displayVolume = displayVm; } @@ -755,7 +764,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co return serviceOfferingDisplayText; } - public Boolean getExtractable() { + public boolean isExtractable() { return extractable; } @@ -763,7 +772,7 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co return status; } - public Boolean getDisplayVolume() { + public boolean isDisplayVolume() { return displayVolume; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index 610416d7b0e..6f91da7d2d7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -96,6 +96,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "the domain name of the owner") private String domain; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the owner", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.NETWORK) @Param(description = "the list of networks belongign to the VPC", responseObject = NetworkResponse.class) private List networks; @@ -209,6 +213,11 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll domain = domainName; } + @Override + public void setDomainPath(String path) { + this.domainPath = path; + } + public void setZoneId(final String zoneId) { this.zoneId = zoneId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpnUsersResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpnUsersResponse.java index d3e4d941678..3247f534133 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpnUsersResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpnUsersResponse.java @@ -48,6 +48,10 @@ public class VpnUsersResponse extends BaseResponse implements ControlledEntityRe @Param(description = "the domain name of the account of the remote access vpn") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the remote access vpn belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.PROJECT_ID) @Param(description = "the project id of the vpn") private String projectId; @@ -83,6 +87,11 @@ public class VpnUsersResponse extends BaseResponse implements ControlledEntityRe this.domainName = name; } + @Override + public void setDomainPath(String path) { + this.domainPath = path; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java index a898cd9d577..0d76fd2d3f9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java @@ -95,7 +95,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @SerializedName("securitygroupsenabled") @Param(description = "true if security groups support is enabled, false otherwise") - private Boolean securityGroupsEnabled; + private boolean securityGroupsEnabled; @SerializedName("allocationstate") @Param(description = "the allocation state of the cluster") @@ -111,11 +111,11 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @SerializedName("capacity") @Param(description = "the capacity of the Zone", responseObject = CapacityResponse.class) - private List capacitites; + private List capacities; @SerializedName(ApiConstants.LOCAL_STORAGE_ENABLED) @Param(description = "true if local storage offering enabled, false otherwise") - private Boolean localStorageEnabled; + private boolean localStorageEnabled; @SerializedName(ApiConstants.TAGS) @Param(description = "the list of resource tags associated with zone.", responseObject = ResourceTagResponse.class, since = "4.3") @@ -201,7 +201,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso this.networkType = networkType; } - public void setSecurityGroupsEnabled(Boolean securityGroupsEnabled) { + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -217,15 +217,15 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso this.dhcpProvider = dhcpProvider; } - public void setCapacitites(List capacitites) { - this.capacitites = capacitites; + public void setCapacities(List capacities) { + this.capacities = capacities; } public void setDomainName(String domainName) { this.domainName = domainName; } - public void setLocalStorageEnabled(Boolean localStorageEnabled) { + public void setLocalStorageEnabled(boolean localStorageEnabled) { this.localStorageEnabled = localStorageEnabled; } @@ -328,8 +328,8 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso return dhcpProvider; } - public List getCapacitites() { - return capacitites; + public List getCapacities() { + return capacities; } public boolean isLocalStorageEnabled() { @@ -344,6 +344,18 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso return resourceDetails; } + public Boolean getAllowUserSpecifyVRMtu() { + return allowUserSpecifyVRMtu; + } + + public Integer getRouterPrivateInterfaceMaxMtu() { + return routerPrivateInterfaceMaxMtu; + } + + public Integer getRouterPublicInterfaceMaxMtu() { + return routerPublicInterfaceMaxMtu; + } + @Override public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { this.resourceIconResponse = resourceIconResponse; diff --git a/api/src/main/java/org/apache/cloudstack/usage/UsageService.java b/api/src/main/java/org/apache/cloudstack/usage/UsageService.java index 73962ba5875..00e8b431f8f 100644 --- a/api/src/main/java/org/apache/cloudstack/usage/UsageService.java +++ b/api/src/main/java/org/apache/cloudstack/usage/UsageService.java @@ -20,7 +20,6 @@ import com.cloud.utils.Pair; import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; -import org.apache.cloudstack.api.response.UsageTypeResponse; import java.util.List; import java.util.TimeZone; @@ -62,6 +61,4 @@ public interface UsageService { TimeZone getUsageTimezone(); boolean removeRawUsageRecords(RemoveRawUsageRecordsCmd cmd); - - List listUsageTypes(); } diff --git a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java index 32ae34056ec..5ad360a8026 100644 --- a/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java +++ b/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java @@ -51,31 +51,31 @@ public class UsageTypes { public static List listUsageTypes() { List responseList = new ArrayList(); - responseList.add(new UsageTypeResponse(RUNNING_VM, "Running Vm Usage")); - responseList.add(new UsageTypeResponse(ALLOCATED_VM, "Allocated Vm Usage")); - responseList.add(new UsageTypeResponse(IP_ADDRESS, "IP Address Usage")); - responseList.add(new UsageTypeResponse(NETWORK_BYTES_SENT, "Network Usage (Bytes Sent)")); - responseList.add(new UsageTypeResponse(NETWORK_BYTES_RECEIVED, "Network Usage (Bytes Received)")); - responseList.add(new UsageTypeResponse(VOLUME, "Volume Usage")); - responseList.add(new UsageTypeResponse(TEMPLATE, "Template Usage")); - responseList.add(new UsageTypeResponse(ISO, "ISO Usage")); - responseList.add(new UsageTypeResponse(SNAPSHOT, "Snapshot Usage")); - responseList.add(new UsageTypeResponse(SECURITY_GROUP, "Security Group Usage")); - responseList.add(new UsageTypeResponse(LOAD_BALANCER_POLICY, "Load Balancer Usage")); - responseList.add(new UsageTypeResponse(PORT_FORWARDING_RULE, "Port Forwarding Usage")); - responseList.add(new UsageTypeResponse(NETWORK_OFFERING, "Network Offering Usage")); - responseList.add(new UsageTypeResponse(VPN_USERS, "VPN users usage")); - responseList.add(new UsageTypeResponse(VM_DISK_IO_READ, "VM Disk usage(I/O Read)")); - responseList.add(new UsageTypeResponse(VM_DISK_IO_WRITE, "VM Disk usage(I/O Write)")); - responseList.add(new UsageTypeResponse(VM_DISK_BYTES_READ, "VM Disk usage(Bytes Read)")); - responseList.add(new UsageTypeResponse(VM_DISK_BYTES_WRITE, "VM Disk usage(Bytes Write)")); - responseList.add(new UsageTypeResponse(VM_SNAPSHOT, "VM Snapshot storage usage")); - responseList.add(new UsageTypeResponse(VOLUME_SECONDARY, "Volume on secondary storage usage")); - responseList.add(new UsageTypeResponse(VM_SNAPSHOT_ON_PRIMARY, "VM Snapshot on primary storage usage")); - responseList.add(new UsageTypeResponse(BACKUP, "Backup storage usage")); - responseList.add(new UsageTypeResponse(BUCKET, "Bucket storage usage")); - responseList.add(new UsageTypeResponse(NETWORK, "Network usage")); - responseList.add(new UsageTypeResponse(VPC, "VPC usage")); + responseList.add(new UsageTypeResponse(RUNNING_VM, "RUNNING_VM", "Running Vm Usage")); + responseList.add(new UsageTypeResponse(ALLOCATED_VM, "ALLOCATED_VM", "Allocated Vm Usage")); + responseList.add(new UsageTypeResponse(IP_ADDRESS, "IP_ADDRESS", "IP Address Usage")); + responseList.add(new UsageTypeResponse(NETWORK_BYTES_SENT, "NETWORK_BYTES_SENT", "Network Usage (Bytes Sent)")); + responseList.add(new UsageTypeResponse(NETWORK_BYTES_RECEIVED, "NETWORK_BYTES_RECEIVED", "Network Usage (Bytes Received)")); + responseList.add(new UsageTypeResponse(VOLUME, "VOLUME", "Volume Usage")); + responseList.add(new UsageTypeResponse(TEMPLATE, "TEMPLATE", "Template Usage")); + responseList.add(new UsageTypeResponse(ISO, "ISO", "ISO Usage")); + responseList.add(new UsageTypeResponse(SNAPSHOT, "SNAPSHOT", "Snapshot Usage")); + responseList.add(new UsageTypeResponse(SECURITY_GROUP, "SECURITY_GROUP", "Security Group Usage")); + responseList.add(new UsageTypeResponse(LOAD_BALANCER_POLICY, "LOAD_BALANCER_POLICY", "Load Balancer Usage")); + responseList.add(new UsageTypeResponse(PORT_FORWARDING_RULE, "PORT_FORWARDING_RULE", "Port Forwarding Usage")); + responseList.add(new UsageTypeResponse(NETWORK_OFFERING, "NETWORK_OFFERING", "Network Offering Usage")); + responseList.add(new UsageTypeResponse(VPN_USERS, "VPN_USERS", "VPN users usage")); + responseList.add(new UsageTypeResponse(VM_DISK_IO_READ, "VM_DISK_IO_READ", "VM Disk usage(I/O Read)")); + responseList.add(new UsageTypeResponse(VM_DISK_IO_WRITE, "VM_DISK_IO_WRITE", "VM Disk usage(I/O Write)")); + responseList.add(new UsageTypeResponse(VM_DISK_BYTES_READ, "VM_DISK_BYTES_READ", "VM Disk usage(Bytes Read)")); + responseList.add(new UsageTypeResponse(VM_DISK_BYTES_WRITE, "VM_DISK_BYTES_WRITE", "VM Disk usage(Bytes Write)")); + responseList.add(new UsageTypeResponse(VM_SNAPSHOT, "VM_SNAPSHOT", "VM Snapshot storage usage")); + responseList.add(new UsageTypeResponse(VOLUME_SECONDARY, "VOLUME_SECONDARY", "Volume on secondary storage usage")); + responseList.add(new UsageTypeResponse(VM_SNAPSHOT_ON_PRIMARY, "VM_SNAPSHOT_ON_PRIMARY", "VM Snapshot on primary storage usage")); + responseList.add(new UsageTypeResponse(BACKUP, "BACKUP", "Backup storage usage")); + responseList.add(new UsageTypeResponse(BUCKET, "BUCKET", "Bucket storage usage")); + responseList.add(new UsageTypeResponse(NETWORK, "NETWORK", "Network usage")); + responseList.add(new UsageTypeResponse(VPC, "VPC", "VPC usage")); return responseList; } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java index 4b9d4fd8974..72ce9593364 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateRoleCmdTest.java @@ -54,6 +54,7 @@ public class CreateRoleCmdTest { when(role.getDescription()).thenReturn("User test"); when(role.getName()).thenReturn("testuser"); when(role.getRoleType()).thenReturn(RoleType.User); + when(role.getState()).thenReturn(Role.State.ENABLED); when(roleService.createRole(createRoleCmd.getRoleName(), createRoleCmd.getRoleType(), createRoleCmd.getRoleDescription(), true)).thenReturn(role); createRoleCmd.execute(); RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject(); @@ -71,6 +72,7 @@ public class CreateRoleCmdTest { when(newRole.getDescription()).thenReturn("User test"); when(newRole.getName()).thenReturn("testuser"); when(newRole.getRoleType()).thenReturn(RoleType.User); + when(newRole.getState()).thenReturn(Role.State.ENABLED); when(roleService.createRole(createRoleCmd.getRoleName(), role, createRoleCmd.getRoleDescription(), true)).thenReturn(newRole); createRoleCmd.execute(); RoleResponse response = (RoleResponse) createRoleCmd.getResponseObject(); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java index 6299c1ed8e2..d2597e5162f 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/ImportRoleCmdTest.java @@ -32,19 +32,13 @@ import org.springframework.test.util.ReflectionTestUtils; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.cloud.exception.InvalidParameterValueException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.regex.Matcher; public class ImportRoleCmdTest { private ImportRoleCmd importRoleCmd; @@ -93,6 +87,7 @@ public class ImportRoleCmdTest { when(role.getDescription()).thenReturn("test user imported"); when(role.getName()).thenReturn("Test User"); when(role.getRoleType()).thenReturn(RoleType.User); + when(role.getState()).thenReturn(Role.State.ENABLED); when(roleService.importRole(anyString(), any(), anyString(), any(), anyBoolean(), anyBoolean())).thenReturn(role); importRoleCmd.execute(); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java index 84b91525742..9a1dae9a480 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateRoleCmdTest.java @@ -62,6 +62,7 @@ public class UpdateRoleCmdTest extends TestCase{ when(role.getId()).thenReturn(1L); when(role.getDescription()).thenReturn("Description Initial"); when(role.getName()).thenReturn("User"); + when(role.getState()).thenReturn(Role.State.ENABLED); updateRoleCmd.execute(); RoleResponse response = (RoleResponse) updateRoleCmd.getResponseObject(); assertEquals((String)ReflectionTestUtils.getField(response, "roleName"),role.getName()); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java index c9eb672c9e9..99bc9d2b3fb 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java @@ -32,8 +32,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.security.InvalidParameterException; - public class CreateVMScheduleCmdTest { @Mock public VMScheduleManager vmScheduleManager; @@ -70,11 +68,11 @@ public class CreateVMScheduleCmdTest { /** * given: "We have a VMScheduleManager and CreateVMScheduleCmd" * when: "CreateVMScheduleCmd is executed with an invalid parameter" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ - @Test(expected = InvalidParameterException.class) - public void testInvalidParameterException() { - Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterException.class); + @Test(expected = InvalidParameterValueException.class) + public void testInvalidParameterValueException() { + Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); createVMScheduleCmd.execute(); } @@ -94,7 +92,7 @@ public class CreateVMScheduleCmdTest { /** * given: "We have an EntityManager and CreateVMScheduleCmd" * when: "CreateVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ @Test(expected = InvalidParameterValueException.class) public void testFailureGetEntityOwnerId() { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java index 9b4decc83aa..1f764a84365 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java @@ -34,8 +34,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.security.InvalidParameterException; - public class DeleteVMScheduleCmdTest { @Mock public VMScheduleManager vmScheduleManager; @@ -89,11 +87,11 @@ public class DeleteVMScheduleCmdTest { /** * given: "We have a VMScheduleManager and DeleteVMScheduleCmd" * when: "DeleteVMScheduleCmd is executed with an invalid parameter" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ - @Test(expected = InvalidParameterException.class) - public void testInvalidParameterException() { - Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterException.class); + @Test(expected = InvalidParameterValueException.class) + public void testInvalidParameterValueException() { + Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); deleteVMScheduleCmd.execute(); } @@ -113,7 +111,7 @@ public class DeleteVMScheduleCmdTest { /** * given: "We have an EntityManager and DeleteVMScheduleCmd" * when: "DeleteVMScheduleCmd.getEntityOwnerId is executed for a VM which doesn't exist" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ @Test(expected = InvalidParameterValueException.class) public void testFailureGetEntityOwnerId() { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java index f9a1d945f03..f5434de3581 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java @@ -18,6 +18,7 @@ */ package org.apache.cloudstack.api.command.user.vm; +import com.cloud.exception.InvalidParameterValueException; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; import org.apache.cloudstack.vm.schedule.VMScheduleManager; @@ -30,7 +31,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collections; @@ -88,11 +88,11 @@ public class ListVMScheduleCmdTest { /** * given: "We have a VMScheduleManager and ListVMScheduleCmd" * when: "ListVMScheduleCmd is executed with an invalid parameter" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ - @Test(expected = InvalidParameterException.class) - public void testInvalidParameterException() { - Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterException.class); + @Test(expected = InvalidParameterValueException.class) + public void testInvalidParameterValueException() { + Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); listVMScheduleCmd.execute(); ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java index 5ce133382f3..2c6c485f25b 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java @@ -33,8 +33,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.security.InvalidParameterException; - public class UpdateVMScheduleCmdTest { @Mock public VMScheduleManager vmScheduleManager; @@ -71,11 +69,11 @@ public class UpdateVMScheduleCmdTest { /** * given: "We have a VMScheduleManager and UpdateVMScheduleCmd" * when: "UpdateVMScheduleCmd is executed with an invalid parameter" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ - @Test(expected = InvalidParameterException.class) - public void testInvalidParameterException() { - Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterException.class); + @Test(expected = InvalidParameterValueException.class) + public void testInvalidParameterValueException() { + Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); updateVMScheduleCmd.execute(); } @@ -99,7 +97,7 @@ public class UpdateVMScheduleCmdTest { /** * given: "We have an EntityManager and UpdateVMScheduleCmd" * when: "UpdateVMScheduleCmd.getEntityOwnerId is executed for a VM Schedule which doesn't exist" - * then: "an InvalidParameterException is thrown" + * then: "an InvalidParameterValueException is thrown" */ @Test(expected = InvalidParameterValueException.class) public void testFailureGetEntityOwnerId() { diff --git a/debian/control b/debian/control index dab7b254b88..ce136ea983a 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: cloudstack Section: libs Priority: extra Maintainer: Wido den Hollander -Build-Depends: debhelper (>= 9), openjdk-11-jdk | java11-sdk | java11-jdk | zulu-11, genisoimage, +Build-Depends: debhelper (>= 9), openjdk-17-jdk | java17-sdk | java17-jdk | zulu-17 | openjdk-11-jdk | java11-sdk | java11-jdk | zulu-11, genisoimage, python-mysql.connector | python3-mysql.connector, maven (>= 3) | maven3, python (>= 2.7) | python2 (>= 2.7), python3 (>= 3), python-setuptools, python3-setuptools, nodejs (>= 12), lsb-release, dh-systemd | debhelper (>= 13) diff --git a/developer/pom.xml b/developer/pom.xml index a70e9153bb4..6cbf483faac 100644 --- a/developer/pom.xml +++ b/developer/pom.xml @@ -28,6 +28,12 @@ 4.20.0.0-SNAPSHOT + + com.mysql + mysql-connector-j + ${cs.mysql.version} + compile + org.apache.commons commons-dbcp2 @@ -108,28 +114,25 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 - - - - com.mysql - mysql-connector-j - ${cs.mysql.version} - - + ${cs.exec-maven-plugin.version} process-resources create-schema - java + exec - com.cloud.upgrade.DatabaseCreator + java + ${basedir}/.. true + --add-opens=java.base/java.lang=ALL-UNNAMED + -classpath + + com.cloud.upgrade.DatabaseCreator ${basedir}/../utils/conf/db.properties ${basedir}/../utils/conf/db.properties.override @@ -177,27 +180,25 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 - - - com.mysql - mysql-connector-j - ${cs.mysql.version} - - + ${cs.exec-maven-plugin.version} process-resources create-schema-simulator - java + exec - com.cloud.upgrade.DatabaseCreator + java + ${basedir}/.. true + --add-opens=java.base/java.lang=ALL-UNNAMED + -classpath + + com.cloud.upgrade.DatabaseCreator ${basedir}/../utils/conf/db.properties ${basedir}/../utils/conf/db.properties.override @@ -235,27 +236,25 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 - - - com.mysql - mysql-connector-j - ${cs.mysql.version} - - + ${cs.exec-maven-plugin.version} process-resources create-schema-simulator - java + exec - com.cloud.upgrade.DatabaseCreator + java + ${basedir}/.. true + --add-opens=java.base/java.lang=ALL-UNNAMED + -classpath + + com.cloud.upgrade.DatabaseCreator ${basedir}/../utils/conf/db.properties ${basedir}/../utils/conf/db.properties.override diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 04ba9a483e1..75211df5291 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -87,6 +87,20 @@ public interface VirtualMachineManager extends Manager { ConfigKey MetadataCustomCloudName = new ConfigKey<>("Advanced", String.class, "metadata.custom.cloud.name", "", "If provided, a custom cloud-name in cloud-init metadata", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataManufacturer = new ConfigKey<>("Advanced", String.class, + "vm.metadata.manufacturer", "Apache Software Foundation", + "If provided, a custom manufacturer will be used in the instance metadata. When an empty" + + "value is set then default manufacturer will be 'Apache Software Foundation'. " + + "A custom manufacturer may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", true, ConfigKey.Scope.Zone); + ConfigKey VmMetadataProductName = new ConfigKey<>("Advanced", String.class, + "vm.metadata.product", "", + "If provided, a custom product name will be used in the instance metadata. When an empty" + + "value is set then default product name will be 'CloudStack Hypervisor'. " + + "A custom product name may break cloud-init functionality with CloudStack datasource. Please " + + "refer documentation", + true, ConfigKey.Scope.Zone); + interface Topics { String VM_POWER_STATE = "vm.powerstate"; } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 41bd74f1192..84098bbc654 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import com.cloud.dc.DataCenter; +import com.cloud.hypervisor.Hypervisor; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; @@ -144,6 +145,8 @@ public interface NetworkOrchestrationService { List getNicProfiles(VirtualMachine vm); + List getNicProfiles(Long vmId, Hypervisor.HypervisorType hypervisorType); + Map getSystemVMAccessDetails(VirtualMachine vm); Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index d52c656f6db..2011b1f08fb 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; import com.cloud.host.Host; +import com.cloud.offering.DiskOffering; import com.cloud.storage.StoragePool; import com.cloud.storage.Volume; import com.cloud.storage.Storage.StoragePoolType; @@ -199,4 +200,9 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver { default long getVolumeSizeRequiredOnPool(long volumeSize, Long templateSize, boolean isEncryptionRequired) { return volumeSize; } + default boolean informStorageForDiskOfferingChange() { + return false; + } + + default void updateStorageWithTheNewDiskOffering(Volume volume, DiskOffering newDiskOffering) {} } diff --git a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java index 3b3537d5488..997ae3985f1 100644 --- a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java +++ b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java @@ -51,6 +51,11 @@ public interface TemplateManager { static final ConfigKey TemplatePreloaderPoolSize = new ConfigKey("Advanced", Integer.class, TemplatePreloaderPoolSizeCK, "8", "Size of the TemplateManager threadpool", false, ConfigKey.Scope.Global); + ConfigKey ValidateUrlIsResolvableBeforeRegisteringTemplate = new ConfigKey<>("Advanced", Boolean.class, + "validate.url.is.resolvable.before.registering.template", "true", "Indicates whether CloudStack " + + "will validate if the provided URL is resolvable during the register of templates/ISOs before persisting them in the database.", + true); + static final String VMWARE_TOOLS_ISO = "vmware-tools.iso"; static final String XS_TOOLS_ISO = "xs-tools.iso"; @@ -139,4 +144,8 @@ public interface TemplateManager { TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones); List getTemplateDisksOnImageStore(Long templateId, DataStoreRole role, String configurationId); + + static Boolean getValidateUrlIsResolvableBeforeRegisteringTemplateValue() { + return ValidateUrlIsResolvableBeforeRegisteringTemplate.value(); + } } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index d207ebc41ce..3fa27e52c09 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -49,15 +49,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; -import com.cloud.configuration.Resource; -import com.cloud.domain.Domain; -import com.cloud.domain.dao.DomainDao; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.dao.AccountDao; -import com.cloud.event.ActionEventUtils; -import com.google.gson.Gson; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -160,6 +151,7 @@ import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.vo.DomainRouterJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.capacity.CapacityManager; +import com.cloud.configuration.Resource; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; @@ -178,6 +170,9 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.DeploymentPlanningManagerImpl; import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.event.UsageEventVO; @@ -189,6 +184,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageAccessException; import com.cloud.exception.StorageUnavailableException; @@ -211,6 +207,8 @@ import com.cloud.network.dao.NetworkDetailsDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.router.VirtualRouter; import com.cloud.network.security.SecurityGroupManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.NetworkOffering; @@ -246,6 +244,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; @@ -281,6 +280,7 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import com.google.gson.Gson; public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { @@ -1101,6 +1101,19 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac markVolumesInPool(vm, answer); } + protected void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm) { + String metadataManufacturer = VmMetadataManufacturer.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataManufacturer = VmMetadataManufacturer.defaultValue(); + } + vmTO.setMetadataManufacturer(metadataManufacturer); + String metadataProduct = VmMetadataProductName.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataProduct = String.format("CloudStack %s Hypervisor", vm.getHypervisorType().toString()); + } + vmTO.setMetadataProductName(metadataProduct); + } + @Override public void orchestrateStart(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -1259,6 +1272,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac vmGuru.finalizeVirtualMachineProfile(vmProfile, dest, ctx); final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + updateVmMetadataManufacturerAndProduct(vmTO, vm); checkAndSetEnterSetupMode(vmTO, params); @@ -4742,7 +4756,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, - AllowExposeDomainInMetadata, MetadataCustomCloudName + AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName }; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index ea34f62ecd5..ce4c6bab94a 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -1072,6 +1072,12 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return null; } + if (isNicAllocatedForNsxPublicNetworkOnVR(network, profile, vm)) { + String guruName = "NsxPublicNetworkGuru"; + NetworkGuru nsxGuru = AdapterBase.getAdapterByName(networkGurus, guruName); + nsxGuru.allocate(network, profile, vm); + } + if (isDefaultNic != null) { profile.setDefaultNic(isDefaultNic); } @@ -1829,6 +1835,19 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return false; } } + if (element instanceof ConfigDriveNetworkElement && (( + _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) && + _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) + ) || ( + _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) && + _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) + ) || ( + _networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) && + _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider()) + ))) { + final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element; + return sp.createConfigDriveIso(profile, vmProfile, dest, null); + } } return true; } @@ -4437,18 +4456,18 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } @Override - public List getNicProfiles(final VirtualMachine vm) { - final List nics = _nicDao.listByVmId(vm.getId()); + public List getNicProfiles(final Long vmId, HypervisorType hypervisorType) { + final List nics = _nicDao.listByVmId(vmId); final List profiles = new ArrayList(); if (nics != null) { for (final Nic nic : nics) { final NetworkVO network = _networksDao.findById(nic.getNetworkId()); - final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); + final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vmId); final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, - _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); + _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(hypervisorType, network)); guru.updateNicProfile(profile, network); profiles.add(profile); } @@ -4456,6 +4475,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return profiles; } + @Override + public List getNicProfiles(final VirtualMachine vm) { + return getNicProfiles(vm.getId(), vm.getHypervisorType()); + } + @Override public Map getSystemVMAccessDetails(final VirtualMachine vm) { final Map accessDetails = new HashMap<>(); diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index 9b32980087c..f2bd1095c81 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -37,26 +37,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.stream.Collectors; -import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.dao.DataCenterDao; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.user.AccountVO; -import com.cloud.user.dao.AccountDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.ScopedConfigStorage; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.MapUtils; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -76,24 +68,34 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.service.ServiceOfferingVO; @@ -115,7 +117,9 @@ import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.user.AccountVO; import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.Journal; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; @@ -220,8 +224,12 @@ public class VirtualMachineManagerImplTest { @Mock protected StateMachine2 _stateMachine; + private ConfigDepotImpl configDepotImpl; + private boolean updatedConfigKeyDepot = false; + @Before public void setup() { + ReflectionTestUtils.getField(VirtualMachineManager.VmMetadataManufacturer, "s_depot"); virtualMachineManagerImpl.setHostAllocators(new ArrayList<>()); when(vmInstanceMock.getId()).thenReturn(vmInstanceVoMockId); @@ -251,6 +259,13 @@ public class VirtualMachineManagerImplTest { virtualMachineManagerImpl.setStoragePoolAllocators(storagePoolAllocators); } + @After + public void cleanup() { + if (updatedConfigKeyDepot) { + ReflectionTestUtils.setField(VirtualMachineManager.VmMetadataManufacturer, "s_depot", configDepotImpl); + } + } + @Test public void testaddHostIpToCertDetailsIfConfigAllows() { Host vmHost = mock(Host.class); @@ -1236,4 +1251,48 @@ public class VirtualMachineManagerImplTest { assertFalse(result.get(1L)); assertTrue(result.get(2L)); } + + private void overrideVmMetadataConfigValue(final String manufacturer, final String product) { + ConfigKey configKey = VirtualMachineManager.VmMetadataManufacturer; + this.configDepotImpl = (ConfigDepotImpl)ReflectionTestUtils.getField(configKey, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + ScopedConfigStorage storage = Mockito.mock(ScopedConfigStorage.class); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(configKey))).thenReturn(manufacturer); + Mockito.when(storage.getConfigValue(Mockito.anyLong(), Mockito.eq(VirtualMachineManager.VmMetadataProductName))) + .thenReturn(product); + Mockito.when(configDepot.findScopedConfigStorage(configKey)).thenReturn(storage); + Mockito.when(configDepot.findScopedConfigStorage(VirtualMachineManager.VmMetadataProductName)).thenReturn(storage); + ReflectionTestUtils.setField(configKey, "s_depot", configDepot); + updatedConfigKeyDepot = true; + } + + private Pair getDummyVmTOAndVm() { + VirtualMachineTO virtualMachineTO = new VirtualMachineTO(1L, "VM", VirtualMachine.Type.User, 1, + 1000, 256, 512, VirtualMachineTemplate.BootloaderType.HVM, "OS", + false, false, "Pass"); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getDataCenterId()).thenReturn(1L); + return new Pair<>(virtualMachineTO, vm); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductDefaultManufacturer() { + overrideVmMetadataConfigValue("", ""); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(VirtualMachineManager.VmMetadataManufacturer.defaultValue(), to.getMetadataManufacturer()); + } + + @Test + public void testUpdateVmMetadataManufacturerAndProductCustomManufacturer() { + String manufacturer = UUID.randomUUID().toString(); + String product = UUID.randomUUID().toString(); + overrideVmMetadataConfigValue(manufacturer, product); + Pair pair = getDummyVmTOAndVm(); + VirtualMachineTO to = pair.first(); + virtualMachineManagerImpl.updateVmMetadataManufacturerAndProduct(to, pair.second()); + Assert.assertEquals(manufacturer, to.getMetadataManufacturer()); + Assert.assertEquals(product, to.getMetadataProductName()); + } } diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index b1868f27837..d3b1f63ca94 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -61,9 +61,16 @@ - org.codehaus.gmaven - gmaven-plugin - 1.5 + org.codehaus.gmavenplus + gmavenplus-plugin + ${cs.gmavenplus.version} + + + org.codehaus.groovy + groovy-all + ${cs.groovy.version} + + setproperty @@ -72,12 +79,14 @@ execute - - def projectVersion = project.properties['project.systemvm.template.version'] - String[] versionParts = projectVersion.tokenize('.') - pom.properties['cs.version'] = versionParts[0] + "." + versionParts[1] - pom.properties['patch.version'] = versionParts[2] - + + + @@ -87,27 +96,29 @@ execute - - def csVersion = pom.properties['cs.version'] - def patch = pom.properties['patch.version'] - def templateList = [] - templateList.add("systemvmtemplate-${csVersion}.${patch}-kvm") - templateList.add("systemvmtemplate-${csVersion}.${patch}-vmware") - templateList.add("systemvmtemplate-${csVersion}.${patch}-xen") - templateList.add("systemvmtemplate-${csVersion}.${patch}-ovm") - templateList.add("systemvmtemplate-${csVersion}.${patch}-hyperv") - File file = new File("./engine/schema/dist/systemvm-templates/md5sum.txt") - def lines = file.readLines() - for (template in templateList) { - def data = lines.findAll { it.contains(template) } - if (data != null) { - if (data.size() > 0) { - def hypervisor = template.tokenize('-')[-1] - pom.properties["$hypervisor" + ".checksum"] = data[0].tokenize(' ')[0] + + + @@ -135,7 +146,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} systemvm-template-metadata diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java index 3d68cc3d9a8..874b05673eb 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java @@ -55,7 +55,7 @@ public class DataCenterIpAddressVO implements InternalIdentity { String reservationId; @Column(name = "nic_id") - private Long instanceId; + private Long nicId; @Column(name = "mac_address") long macAddress; @@ -88,12 +88,12 @@ public class DataCenterIpAddressVO implements InternalIdentity { return id; } - public Long getInstanceId() { - return instanceId; + public Long getNicId() { + return nicId; } - public void setInstanceId(Long instanceId) { - this.instanceId = instanceId; + public void setNicId(Long nicId) { + this.nicId = nicId; } public long getPodId() { diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java index 2776b09c2a1..7719e5adfc7 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java @@ -164,8 +164,8 @@ public class DataCenterDaoImpl extends GenericDaoBase implem } @Override - public void releasePrivateIpAddress(String ipAddress, long dcId, Long instanceId) { - _ipAllocDao.releaseIpAddress(ipAddress, dcId, instanceId); + public void releasePrivateIpAddress(String ipAddress, long dcId, Long nicId) { + _ipAllocDao.releaseIpAddress(ipAddress, dcId, nicId); } @Override @@ -179,8 +179,8 @@ public class DataCenterDaoImpl extends GenericDaoBase implem } @Override - public void releaseLinkLocalIpAddress(String ipAddress, long dcId, Long instanceId) { - _linkLocalIpAllocDao.releaseIpAddress(ipAddress, dcId, instanceId); + public void releaseLinkLocalIpAddress(String ipAddress, long dcId, Long nicId) { + _linkLocalIpAllocDao.releaseIpAddress(ipAddress, dcId, nicId); } @Override @@ -226,9 +226,9 @@ public class DataCenterDaoImpl extends GenericDaoBase implem } @Override - public PrivateAllocationData allocatePrivateIpAddress(long dcId, long podId, long instanceId, String reservationId, boolean forSystemVms) { - _ipAllocDao.releaseIpAddress(instanceId); - DataCenterIpAddressVO vo = _ipAllocDao.takeIpAddress(dcId, podId, instanceId, reservationId, forSystemVms); + public PrivateAllocationData allocatePrivateIpAddress(long dcId, long podId, long nicId, String reservationId, boolean forSystemVms) { + _ipAllocDao.releaseIpAddress(nicId); + DataCenterIpAddressVO vo = _ipAllocDao.takeIpAddress(dcId, podId, nicId, reservationId, forSystemVms); if (vo == null) { return null; } @@ -242,8 +242,8 @@ public class DataCenterDaoImpl extends GenericDaoBase implem } @Override - public String allocateLinkLocalIpAddress(long dcId, long podId, long instanceId, String reservationId) { - DataCenterLinkLocalIpAddressVO vo = _linkLocalIpAllocDao.takeIpAddress(dcId, podId, instanceId, reservationId); + public String allocateLinkLocalIpAddress(long dcId, long podId, long nicId, String reservationId) { + DataCenterLinkLocalIpAddressVO vo = _linkLocalIpAllocDao.takeIpAddress(dcId, podId, nicId, reservationId); if (vo == null) { return null; } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDaoImpl.java index c23137095e6..48b9c83c64c 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDaoImpl.java @@ -51,7 +51,7 @@ public class DataCenterIpAddressDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); sc.setParameters("pod", podId); sc.setParameters("taken", (Date)null); @@ -71,7 +71,7 @@ public class DataCenterIpAddressDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); sc.setParameters("ip", ipAddress); sc.setParameters("dc", dcId); - sc.setParameters("instance", instanceId); + sc.setParameters("nic", nicId); DataCenterIpAddressVO vo = createForUpdate(); vo.setTakenAt(null); - vo.setInstanceId(null); + vo.setNicId(null); vo.setReservationId(null); update(vo, sc); } @@ -186,15 +186,15 @@ public class DataCenterIpAddressDaoImpl extends GenericDaoBase, StateDao findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags); List findClustersThatMatchHostTagRule(String computeOfferingTags); + + List listSsvmHostsWithPendingMigrateJobsOrderedByJobCount(); } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java index 170c6a45fc3..63950294654 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java @@ -1371,6 +1371,31 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao return new ArrayList<>(result); } + @Override + public List listSsvmHostsWithPendingMigrateJobsOrderedByJobCount() { + String query = "SELECT cel.host_id, COUNT(*) " + + "FROM cmd_exec_log cel " + + "JOIN host h ON cel.host_id = h.id " + + "WHERE h.removed IS NULL " + + "GROUP BY cel.host_id " + + "ORDER BY 2"; + + TransactionLegacy txn = TransactionLegacy.currentTxn(); + List result = new ArrayList<>(); + + PreparedStatement pstmt; + try { + pstmt = txn.prepareAutoCloseStatement(query); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + result.add((long) rs.getInt(1)); + } + } catch (SQLException e) { + logger.warn("SQLException caught while listing SSVMs with least migrate jobs.", e); + } + return result; + } + private String getHostIdsByComputeTags(List offeringTags){ List questionMarks = new ArrayList(); offeringTags.forEach((tag) -> { questionMarks.add("?"); }); diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java index 80a890aacad..86e0da53666 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java @@ -29,6 +29,8 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import com.cloud.storage.snapshot.SnapshotSchedule; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; @Entity @Table(name = "snapshot_schedule") @@ -132,4 +134,11 @@ public class SnapshotScheduleVO implements SnapshotSchedule { public void setUuid(String uuid) { this.uuid = uuid; } + + @Override + public String toString() { + ReflectionToStringBuilder reflectionToStringBuilder = new ReflectionToStringBuilder(this, ToStringStyle.JSON_STYLE); + reflectionToStringBuilder.setExcludeFieldNames("id"); + return reflectionToStringBuilder.toString(); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java index 7ca0a3915f5..284a42cf9e1 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java @@ -27,13 +27,11 @@ import com.cloud.utils.db.GenericDao; */ public interface SnapshotScheduleDao extends GenericDao { - List getCoincidingSnapshotSchedules(long volumeId, Date date); - List getSchedulesToExecute(Date currentTimestamp); - SnapshotScheduleVO getCurrentSchedule(Long volumeId, Long policyId, boolean executing); + List getSchedulesAssignedWithAsyncJob(); - SnapshotScheduleVO findOneByVolume(long volumeId); + SnapshotScheduleVO getCurrentSchedule(Long volumeId, Long policyId, boolean executing); SnapshotScheduleVO findOneByVolumePolicy(long volumeId, long policyId); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java index 925d02dd90b..14669ce1d43 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java @@ -32,7 +32,7 @@ import com.cloud.utils.db.SearchCriteria; public class SnapshotScheduleDaoImpl extends GenericDaoBase implements SnapshotScheduleDao { protected final SearchBuilder executableSchedulesSearch; protected final SearchBuilder coincidingSchedulesSearch; - private final SearchBuilder VolumeIdSearch; + protected final SearchBuilder schedulesAssignedWithAsyncJob; private final SearchBuilder VolumeIdPolicyIdSearch; protected SnapshotScheduleDaoImpl() { @@ -48,36 +48,14 @@ public class SnapshotScheduleDaoImpl extends GenericDaoBase getCoincidingSnapshotSchedules(long volumeId, Date date) { - SearchCriteria sc = coincidingSchedulesSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("scheduledTimestamp", date); - // Don't return manual snapshots. They will be executed through another - // code path. - sc.addAnd("policyId", SearchCriteria.Op.NEQ, 1L); - return listBy(sc); - } - - @Override - public SnapshotScheduleVO findOneByVolume(long volumeId) { - SearchCriteria sc = VolumeIdSearch.create(); - sc.setParameters("volumeId", volumeId); - return findOneBy(sc); + schedulesAssignedWithAsyncJob = createSearchBuilder(); + schedulesAssignedWithAsyncJob.and("asyncJobId", schedulesAssignedWithAsyncJob.entity().getAsyncJobId(), SearchCriteria.Op.NNULL); + schedulesAssignedWithAsyncJob.done(); } @Override @@ -98,6 +76,11 @@ public class SnapshotScheduleDaoImpl extends GenericDaoBase getSchedulesAssignedWithAsyncJob() { + return listBy(schedulesAssignedWithAsyncJob.create()); + } + /** * {@inheritDoc} */ diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java index d390a480e41..cb219007325 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java @@ -130,6 +130,7 @@ public class DatabaseUpgradeChecker implements SystemIntegrityChecker { protected static Logger LOGGER = LogManager.getLogger(DatabaseUpgradeChecker.class); private final DatabaseVersionHierarchy hierarchy; private static final String VIEWS_DIRECTORY = Paths.get("META-INF", "db", "views").toString(); + private static final String PROCEDURES_DIRECTORY = Paths.get("META-INF", "db", "procedures").toString(); @Inject VersionDao _dao; @@ -298,83 +299,120 @@ public class DatabaseUpgradeChecker implements SystemIntegrityChecker { } protected void upgrade(CloudStackVersion dbVersion, CloudStackVersion currentVersion) { + executeProcedureScripts(); + final DbUpgrade[] upgrades = executeUpgrades(dbVersion, currentVersion); + + executeViewScripts(); + updateSystemVmTemplates(upgrades); + } + + protected void executeProcedureScripts() { + LOGGER.info(String.format("Executing Stored Procedure scripts that are under resource directory [%s].", PROCEDURES_DIRECTORY)); + List filesPathUnderViewsDirectory = FileUtil.getFilesPathsUnderResourceDirectory(PROCEDURES_DIRECTORY); + + try (TransactionLegacy txn = TransactionLegacy.open("execute-procedure-scripts")) { + Connection conn = txn.getConnection(); + + for (String filePath : filesPathUnderViewsDirectory) { + LOGGER.debug(String.format("Executing PROCEDURE script [%s].", filePath)); + + InputStream viewScript = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); + runScript(conn, viewScript); + } + + LOGGER.info(String.format("Finished execution of PROCEDURE scripts that are under resource directory [%s].", PROCEDURES_DIRECTORY)); + } catch (SQLException e) { + String message = String.format("Unable to execute PROCEDURE scripts due to [%s].", e.getMessage()); + LOGGER.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + + private DbUpgrade[] executeUpgrades(CloudStackVersion dbVersion, CloudStackVersion currentVersion) { LOGGER.info("Database upgrade must be performed from " + dbVersion + " to " + currentVersion); final DbUpgrade[] upgrades = calculateUpgradePath(dbVersion, currentVersion); for (DbUpgrade upgrade : upgrades) { - VersionVO version; - LOGGER.debug("Running upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade - .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); - TransactionLegacy txn = TransactionLegacy.open("Upgrade"); - txn.start(); + VersionVO version = executeUpgrade(upgrade); + executeUpgradeCleanup(upgrade, version); + } + return upgrades; + } + + private VersionVO executeUpgrade(DbUpgrade upgrade) { + VersionVO version; + LOGGER.debug("Running upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade + .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); + TransactionLegacy txn = TransactionLegacy.open("Upgrade"); + txn.start(); + try { + Connection conn; try { - Connection conn; - try { - conn = txn.getConnection(); - } catch (SQLException e) { - String errorMessage = "Unable to upgrade the database"; - LOGGER.error(errorMessage, e); - throw new CloudRuntimeException(errorMessage, e); - } - InputStream[] scripts = upgrade.getPrepareScripts(); - if (scripts != null) { - for (InputStream script : scripts) { - runScript(conn, script); - } - } - - upgrade.performDataMigration(conn); - - version = new VersionVO(upgrade.getUpgradedVersion()); - version = _dao.persist(version); - - txn.commit(); - } catch (CloudRuntimeException e) { + conn = txn.getConnection(); + } catch (SQLException e) { String errorMessage = "Unable to upgrade the database"; LOGGER.error(errorMessage, e); throw new CloudRuntimeException(errorMessage, e); - } finally { - txn.close(); + } + InputStream[] scripts = upgrade.getPrepareScripts(); + if (scripts != null) { + for (InputStream script : scripts) { + runScript(conn, script); + } } - // Run the corresponding '-cleanup.sql' script - txn = TransactionLegacy.open("Cleanup"); - try { - LOGGER.info("Cleanup upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade - .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); + upgrade.performDataMigration(conn); - txn.start(); - Connection conn; - try { - conn = txn.getConnection(); - } catch (SQLException e) { - LOGGER.error("Unable to cleanup the database", e); - throw new CloudRuntimeException("Unable to cleanup the database", e); - } + version = new VersionVO(upgrade.getUpgradedVersion()); + version = _dao.persist(version); - InputStream[] scripts = upgrade.getCleanupScripts(); - if (scripts != null) { - for (InputStream script : scripts) { - runScript(conn, script); - LOGGER.debug("Cleanup script " + upgrade.getClass().getSimpleName() + " is executed successfully"); - } - } - txn.commit(); - - txn.start(); - version.setStep(Step.Complete); - version.setUpdated(new Date()); - _dao.update(version.getId(), version); - txn.commit(); - LOGGER.debug("Upgrade completed for version " + version.getVersion()); - } finally { - txn.close(); - } + txn.commit(); + } catch (CloudRuntimeException e) { + String errorMessage = "Unable to upgrade the database"; + LOGGER.error(errorMessage, e); + throw new CloudRuntimeException(errorMessage, e); + } finally { + txn.close(); } + return version; + } - executeViewScripts(); - updateSystemVmTemplates(upgrades); + private void executeUpgradeCleanup(DbUpgrade upgrade, VersionVO version) { + TransactionLegacy txn; + // Run the corresponding '-cleanup.sql' script + txn = TransactionLegacy.open("Cleanup"); + try { + LOGGER.info("Cleanup upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade + .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); + + txn.start(); + Connection conn; + try { + conn = txn.getConnection(); + } catch (SQLException e) { + LOGGER.error("Unable to cleanup the database", e); + throw new CloudRuntimeException("Unable to cleanup the database", e); + } + + InputStream[] scripts = upgrade.getCleanupScripts(); + if (scripts != null) { + for (InputStream script : scripts) { + runScript(conn, script); + LOGGER.debug("Cleanup script " + upgrade.getClass().getSimpleName() + " is executed successfully"); + } + } + txn.commit(); + + txn.start(); + version.setStep(Step.Complete); + version.setUpdated(new Date()); + _dao.update(version.getId(), version); + txn.commit(); + LOGGER.debug("Upgrade completed for version " + version.getVersion()); + } finally { + txn.close(); + } } protected void executeViewScripts() { diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java index d8c7684f8e3..4c26c6bde50 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java @@ -18,12 +18,16 @@ package com.cloud.upgrade.dao; import java.io.InputStream; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import com.cloud.upgrade.SystemVmTemplateRegistration; import com.cloud.utils.exception.CloudRuntimeException; public class Upgrade41910to42000 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { private SystemVmTemplateRegistration systemVmTemplateRegistration; + private static final int MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4 = 191; @Override public String[] getUpgradableVersionRange() { @@ -53,6 +57,7 @@ public class Upgrade41910to42000 extends DbUpgradeAbstractImpl implements DbUpgr @Override public void performDataMigration(Connection conn) { + checkAndUpdateAffinityGroupNameCharSetToUtf8mb4(conn); } @Override @@ -80,4 +85,32 @@ public class Upgrade41910to42000 extends DbUpgradeAbstractImpl implements DbUpgr throw new CloudRuntimeException("Failed to find / register SystemVM template(s)"); } } + + private void checkAndUpdateAffinityGroupNameCharSetToUtf8mb4(Connection conn) { + logger.debug("Check and update char set for affinity group name to utf8mb4"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT MAX(LENGTH(name)) FROM `cloud`.`affinity_group`"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long maxLengthOfName = rs.getLong(1); + if (maxLengthOfName <= MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4) { + pstmt = conn.prepareStatement(String.format("ALTER TABLE `cloud`.`affinity_group` MODIFY `name` VARCHAR(%d) CHARACTER SET utf8mb4 NOT NULL", MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4)); + pstmt.executeUpdate(); + logger.debug("Successfully updated char set for affinity group name to utf8mb4"); + } else { + logger.warn("Unable to update char set for affinity group name, as there are some names with more than " + MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4 + + " chars (max supported chars for index)"); + } + } + + if (rs != null && !rs.isClosed()) { + rs.close(); + } + if (pstmt != null && !pstmt.isClosed()) { + pstmt.close(); + } + } catch (final SQLException e) { + logger.warn("Exception while updating char set for affinity group name to utf8mb4: " + e.getMessage()); + } + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java index d4647255fc6..084df29fa42 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java @@ -58,11 +58,16 @@ public class RoleVO implements Role { @Column(name = "public_role") private boolean publicRole = true; + @Column(name = "state") + @Enumerated(value = EnumType.STRING) + private State state; + @Column(name = GenericDao.REMOVED_COLUMN) private Date removed; public RoleVO() { this.uuid = UUID.randomUUID().toString(); + this.state = State.ENABLED; } public RoleVO(final String name, final RoleType roleType, final String description) { @@ -131,4 +136,12 @@ public class RoleVO implements Role { public void setPublicRole(boolean publicRole) { this.publicRole = publicRole; } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java index 2d4151afc7d..f4fdb6a2b16 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDao.java @@ -28,15 +28,15 @@ import java.util.List; public interface RoleDao extends GenericDao { List findAllByName(String roleName, boolean showPrivateRole); - Pair, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit, boolean showPrivateRole); + Pair, Integer> findAllByName(final String roleName, String keyword, String state, Long offset, Long limit, boolean showPrivateRole); List findAllByRoleType(RoleType type, boolean showPrivateRole); List findByName(String roleName, boolean showPrivateRole); RoleVO findByNameAndType(String roleName, RoleType type, boolean showPrivateRole); - Pair, Integer> findAllByRoleType(RoleType type, Long offset, Long limit, boolean showPrivateRole); + Pair, Integer> findAllByRoleType(RoleType type, String state, Long offset, Long limit, boolean showPrivateRole); - Pair, Integer> listAllRoles(Long startIndex, Long limit, boolean showPrivateRole); + Pair, Integer> listAllRoles(String state, Long startIndex, Long limit, boolean showPrivateRole); List searchByIds(Long... ids); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java index 2e8fdd5fcc2..48c0d828a41 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RoleDaoImpl.java @@ -50,11 +50,13 @@ public class RoleDaoImpl extends GenericDaoBase implements RoleDao RoleByNameSearch = createSearchBuilder(); RoleByNameSearch.and("roleName", RoleByNameSearch.entity().getName(), SearchCriteria.Op.LIKE); RoleByNameSearch.and("isPublicRole", RoleByNameSearch.entity().isPublicRole(), SearchCriteria.Op.EQ); + RoleByNameSearch.and("state", RoleByNameSearch.entity().getState(), SearchCriteria.Op.EQ); RoleByNameSearch.done(); RoleByTypeSearch = createSearchBuilder(); RoleByTypeSearch.and("roleType", RoleByTypeSearch.entity().getRoleType(), SearchCriteria.Op.EQ); RoleByTypeSearch.and("isPublicRole", RoleByTypeSearch.entity().isPublicRole(), SearchCriteria.Op.EQ); + RoleByTypeSearch.and("state", RoleByTypeSearch.entity().getState(), SearchCriteria.Op.EQ); RoleByTypeSearch.done(); RoleByNameAndTypeSearch = createSearchBuilder(); @@ -65,16 +67,17 @@ public class RoleDaoImpl extends GenericDaoBase implements RoleDao RoleByIsPublicSearch = createSearchBuilder(); RoleByIsPublicSearch.and("isPublicRole", RoleByIsPublicSearch.entity().isPublicRole(), SearchCriteria.Op.EQ); + RoleByIsPublicSearch.and("state", RoleByIsPublicSearch.entity().getState(), SearchCriteria.Op.EQ); RoleByIsPublicSearch.done(); } @Override public List findAllByName(final String roleName, boolean showPrivateRole) { - return findAllByName(roleName, null, null, null, showPrivateRole).first(); + return findAllByName(roleName, null, null, null, null, showPrivateRole).first(); } @Override - public Pair, Integer> findAllByName(final String roleName, String keyword, Long offset, Long limit, boolean showPrivateRole) { + public Pair, Integer> findAllByName(final String roleName, String keyword, String state, Long offset, Long limit, boolean showPrivateRole) { SearchCriteria sc = RoleByNameSearch.create(); filterPrivateRolesIfNeeded(sc, showPrivateRole); if (StringUtils.isNotEmpty(roleName)) { @@ -83,19 +86,25 @@ public class RoleDaoImpl extends GenericDaoBase implements RoleDao if (StringUtils.isNotEmpty(keyword)) { sc.setParameters("roleName", "%" + keyword + "%"); } + if (StringUtils.isNotEmpty(state)) { + sc.setParameters("state", state); + } return searchAndCount(sc, new Filter(RoleVO.class, "id", true, offset, limit)); } @Override public List findAllByRoleType(final RoleType type, boolean showPrivateRole) { - return findAllByRoleType(type, null, null, showPrivateRole).first(); + return findAllByRoleType(type, null, null, null, showPrivateRole).first(); } - public Pair, Integer> findAllByRoleType(final RoleType type, Long offset, Long limit, boolean showPrivateRole) { + public Pair, Integer> findAllByRoleType(final RoleType type, String state, Long offset, Long limit, boolean showPrivateRole) { SearchCriteria sc = RoleByTypeSearch.create(); filterPrivateRolesIfNeeded(sc, showPrivateRole); sc.setParameters("roleType", type); + if (StringUtils.isNotEmpty(state)) { + sc.setParameters("state", state); + } return searchAndCount(sc, new Filter(RoleVO.class, "id", true, offset, limit)); } @@ -117,8 +126,11 @@ public class RoleDaoImpl extends GenericDaoBase implements RoleDao } @Override - public Pair, Integer> listAllRoles(Long startIndex, Long limit, boolean showPrivateRole) { + public Pair, Integer> listAllRoles(String state, Long startIndex, Long limit, boolean showPrivateRole) { SearchCriteria sc = RoleByIsPublicSearch.create(); + if (StringUtils.isNotEmpty(state)) { + sc.setParameters("state", state); + } filterPrivateRolesIfNeeded(sc, showPrivateRole); return searchAndCount(sc, new Filter(RoleVO.class, "id", true, startIndex, limit)); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java index 010720ba33a..859b2921504 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java @@ -38,4 +38,7 @@ public interface AffinityGroupDao extends GenericDao { AffinityGroupVO findByAccountAndType(Long accountId, String string); AffinityGroupVO findDomainLevelGroupByType(Long domainId, String string); + + List listByIds(List ids, boolean exclusive); + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java index 3bd7c6d082b..5bd598f36a0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java @@ -31,6 +31,7 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; public class AffinityGroupDaoImpl extends GenericDaoBase implements AffinityGroupDao { + private SearchBuilder IdsSearch; private SearchBuilder AccountIdSearch; private SearchBuilder AccountIdNameSearch; private SearchBuilder AccountIdNamesSearch; @@ -47,6 +48,10 @@ public class AffinityGroupDaoImpl extends GenericDaoBase @PostConstruct protected void init() { + IdsSearch = createSearchBuilder(); + IdsSearch.and("idIn", IdsSearch.entity().getId(), SearchCriteria.Op.IN); + IdsSearch.done(); + AccountIdSearch = createSearchBuilder(); AccountIdSearch.and("accountId", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AccountIdSearch.done(); @@ -158,4 +163,11 @@ public class AffinityGroupDaoImpl extends GenericDaoBase sc.setJoinParameters("domainTypeSearch", "domainId", domainId); return findOneBy(sc); } + + @Override + public List listByIds(List ids, boolean exclusive) { + SearchCriteria sc = IdsSearch.create(); + sc.setParameters("idIn", ids.toArray()); + return lockRows(sc, null, exclusive); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java index a1dc05fce58..7a466c1f505 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java @@ -86,6 +86,13 @@ public class SnapshotDataStoreVO implements StateObject engine + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + org.eclipse.jetty jetty-maven-plugin diff --git a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java index e1d51120efa..58cc341a87b 100644 --- a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java +++ b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java @@ -22,6 +22,8 @@ import static com.cloud.network.NetworkModel.CONFIGDATA_DIR; import static com.cloud.network.NetworkModel.CONFIGDATA_FILE; import static com.cloud.network.NetworkModel.PASSWORD_FILE; import static com.cloud.network.NetworkModel.USERDATA_FILE; +import static com.cloud.network.NetworkService.DEFAULT_MTU; +import static org.apache.cloudstack.storage.configdrive.ConfigDriveUtils.mergeJsonArraysAndUpdateObject; import java.io.File; import java.io.IOException; @@ -33,6 +35,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.cloud.network.Network; +import com.cloud.vm.NicProfile; +import com.googlecode.ipv6.IPv6Network; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; @@ -81,7 +86,7 @@ public class ConfigDriveBuilder { /** * Read the content of a {@link File} and convert it to a String in base 64. - * We expect the content of the file to be encoded using {@link StandardCharsets#US_ASC} + * We expect the content of the file to be encoded using {@link StandardCharsets#US_ASCII} */ public static String fileToBase64String(File isoFile) throws IOException { byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(isoFile)); @@ -108,9 +113,9 @@ public class ConfigDriveBuilder { * This method will build the metadata files required by OpenStack driver. Then, an ISO is going to be generated and returned as a String in base 64. * If vmData is null, we throw a {@link CloudRuntimeException}. Moreover, {@link IOException} are captured and re-thrown as {@link CloudRuntimeException}. */ - public static String buildConfigDrive(List vmData, String isoFileName, String driveLabel, Map customUserdataParams) { - if (vmData == null) { - throw new CloudRuntimeException("No VM metadata provided"); + public static String buildConfigDrive(List nics, List vmData, String isoFileName, String driveLabel, Map customUserdataParams, Map> supportedServices) { + if (vmData == null && nics == null) { + throw new CloudRuntimeException("No VM metadata and nic profile provided"); } Path tempDir = null; @@ -121,10 +126,19 @@ public class ConfigDriveBuilder { File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName); - writeVendorAndNetworkEmptyJsonFile(openStackFolder); - writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams); + writeVendorEmptyJsonFile(openStackFolder); + writeNetworkData(nics, supportedServices, openStackFolder); + for (NicProfile nic: nics) { + if (supportedServices.get(nic.getId()).contains(Network.Service.UserData)) { + if (vmData == null) { + throw new CloudRuntimeException("No VM metadata provided"); + } + writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams); - linkUserData(tempDirName); + linkUserData(tempDirName); + break; + } + } return generateAndRetrieveIsoAsBase64Iso(isoFileName, driveLabel, tempDirName); } catch (IOException e) { @@ -212,18 +226,36 @@ public class ConfigDriveBuilder { } /** - * Writes the following empty JSON files: - *
    - *
  • vendor_data.json - *
  • network_data.json - *
- * - * If the folder does not exist and we cannot create it, we throw a {@link CloudRuntimeException}. + * First we generate a JSON object using {@link #getNetworkDataJsonObjectForNic(NicProfile, List)}, then we write it to a file called "network_data.json". */ - static void writeVendorAndNetworkEmptyJsonFile(File openStackFolder) { + static void writeNetworkData(List nics, Map> supportedServices, File openStackFolder) { + JsonObject finalNetworkData = new JsonObject(); + if (needForGeneratingNetworkData(supportedServices)) { + for (NicProfile nic : nics) { + List supportedService = supportedServices.get(nic.getId()); + JsonObject networkData = getNetworkDataJsonObjectForNic(nic, supportedService); + + mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "links", "id", "type"); + mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "networks", "id", "type"); + mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "services", "address", "type"); + } + } + + writeFile(openStackFolder, "network_data.json", finalNetworkData.toString()); + } + + static boolean needForGeneratingNetworkData(Map> supportedServices) { + return supportedServices.values().stream().anyMatch(services -> services.contains(Network.Service.Dhcp) || services.contains(Network.Service.Dns)); + } + + /** + * Writes an empty JSON file named vendor_data.json in openStackFolder + * + * If the folder does not exist, and we cannot create it, we throw a {@link CloudRuntimeException}. + */ + static void writeVendorEmptyJsonFile(File openStackFolder) { if (openStackFolder.exists() || openStackFolder.mkdirs()) { writeFile(openStackFolder, "vendor_data.json", "{}"); - writeFile(openStackFolder, "network_data.json", "{}"); } else { throw new CloudRuntimeException("Failed to create folder " + openStackFolder); } @@ -250,6 +282,120 @@ public class ConfigDriveBuilder { return metaData; } + /** + * Creates the {@link JsonObject} using @param nic's metadata. We expect the JSONObject to have the following entries: + *
    + *
  • links
  • + *
  • networks
  • + *
  • services
  • + *
+ */ + static JsonObject getNetworkDataJsonObjectForNic(NicProfile nic, List supportedServices) { + JsonObject networkData = new JsonObject(); + + JsonArray links = getLinksJsonArrayForNic(nic); + JsonArray networks = getNetworksJsonArrayForNic(nic); + if (links.size() > 0) { + networkData.add("links", links); + } + if (networks.size() > 0) { + networkData.add("networks", networks); + } + + JsonArray services = getServicesJsonArrayForNic(nic); + if (services.size() > 0) { + networkData.add("services", services); + } + + return networkData; + } + + static JsonArray getLinksJsonArrayForNic(NicProfile nic) { + JsonArray links = new JsonArray(); + if (StringUtils.isNotBlank(nic.getMacAddress())) { + JsonObject link = new JsonObject(); + link.addProperty("ethernet_mac_address", nic.getMacAddress()); + link.addProperty("id", String.format("eth%d", nic.getDeviceId())); + link.addProperty("mtu", nic.getMtu() != null ? nic.getMtu() : DEFAULT_MTU); + link.addProperty("type", "phy"); + links.add(link); + } + return links; + } + + static JsonArray getNetworksJsonArrayForNic(NicProfile nic) { + JsonArray networks = new JsonArray(); + if (StringUtils.isNotBlank(nic.getIPv4Address())) { + JsonObject ipv4Network = new JsonObject(); + ipv4Network.addProperty("id", String.format("eth%d", nic.getDeviceId())); + ipv4Network.addProperty("ip_address", nic.getIPv4Address()); + ipv4Network.addProperty("link", String.format("eth%d", nic.getDeviceId())); + ipv4Network.addProperty("netmask", nic.getIPv4Netmask()); + ipv4Network.addProperty("network_id", nic.getUuid()); + ipv4Network.addProperty("type", "ipv4"); + + JsonArray ipv4RouteArray = new JsonArray(); + JsonObject ipv4Route = new JsonObject(); + ipv4Route.addProperty("gateway", nic.getIPv4Gateway()); + ipv4Route.addProperty("netmask", "0.0.0.0"); + ipv4Route.addProperty("network", "0.0.0.0"); + ipv4RouteArray.add(ipv4Route); + + ipv4Network.add("routes", ipv4RouteArray); + + networks.add(ipv4Network); + } + + if (StringUtils.isNotBlank(nic.getIPv6Address())) { + JsonObject ipv6Network = new JsonObject(); + ipv6Network.addProperty("id", String.format("eth%d", nic.getDeviceId())); + ipv6Network.addProperty("ip_address", nic.getIPv6Address()); + ipv6Network.addProperty("link", String.format("eth%d", nic.getDeviceId())); + ipv6Network.addProperty("netmask", IPv6Network.fromString(nic.getIPv6Cidr()).getNetmask().toString()); + ipv6Network.addProperty("network_id", nic.getUuid()); + ipv6Network.addProperty("type", "ipv6"); + + JsonArray ipv6RouteArray = new JsonArray(); + JsonObject ipv6Route = new JsonObject(); + ipv6Route.addProperty("gateway", nic.getIPv6Gateway()); + ipv6Route.addProperty("netmask", "0"); + ipv6Route.addProperty("network", "::"); + ipv6RouteArray.add(ipv6Route); + + ipv6Network.add("routes", ipv6RouteArray); + + networks.add(ipv6Network); + } + return networks; + } + + static JsonArray getServicesJsonArrayForNic(NicProfile nic) { + JsonArray services = new JsonArray(); + if (StringUtils.isNotBlank(nic.getIPv4Dns1())) { + services.add(getDnsServiceObject(nic.getIPv4Dns1())); + } + + if (StringUtils.isNotBlank(nic.getIPv4Dns2())) { + services.add(getDnsServiceObject(nic.getIPv4Dns2())); + } + + if (StringUtils.isNotBlank(nic.getIPv6Dns1())) { + services.add(getDnsServiceObject(nic.getIPv6Dns1())); + } + + if (StringUtils.isNotBlank(nic.getIPv6Dns2())) { + services.add(getDnsServiceObject(nic.getIPv6Dns2())); + } + return services; + } + + private static JsonObject getDnsServiceObject(String dnsAddress) { + JsonObject dnsService = new JsonObject(); + dnsService.addProperty("address", dnsAddress); + dnsService.addProperty("type", "dns"); + return dnsService; + } + static void createFileInTempDirAnAppendOpenStackMetadataToJsonObject(String tempDirName, JsonObject metaData, String dataType, String fileName, String content, Map customUserdataParams) { if (StringUtils.isBlank(dataType)) { return; diff --git a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java new file mode 100644 index 00000000000..8847497f193 --- /dev/null +++ b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.configdrive; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class ConfigDriveUtils { + + static void mergeJsonArraysAndUpdateObject(JsonObject finalObject, JsonObject newObj, String memberName, String... keys) { + JsonArray existingMembers = finalObject.has(memberName) ? finalObject.get(memberName).getAsJsonArray() : new JsonArray(); + JsonArray newMembers = newObj.has(memberName) ? newObj.get(memberName).getAsJsonArray() : new JsonArray(); + + if (existingMembers.size() > 0 || newMembers.size() > 0) { + JsonArray finalMembers = new JsonArray(); + Set idSet = new HashSet<>(); + for (JsonElement element : existingMembers.getAsJsonArray()) { + JsonObject elementObject = element.getAsJsonObject(); + String key = Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a, b) -> a + "-" + b).orElse(""); + idSet.add(key); + finalMembers.add(element); + } + for (JsonElement element : newMembers.getAsJsonArray()) { + JsonObject elementObject = element.getAsJsonObject(); + String key = Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a, b) -> a + "-" + b).orElse(""); + if (!idSet.contains(key)) { + finalMembers.add(element); + } + } + finalObject.add(memberName, finalMembers); + } + } + +} diff --git a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java index eff881065c2..3effdb5ba21 100644 --- a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java +++ b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.configdrive; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import java.io.File; @@ -27,14 +28,21 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.cloud.network.Network; +import com.cloud.vm.NicProfile; +import com.google.gson.JsonParser; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.MockedConstruction; @@ -49,6 +57,13 @@ import com.google.gson.JsonObject; @RunWith(MockitoJUnitRunner.class) public class ConfigDriveBuilderTest { + private static Map> supportedServices; + + @BeforeClass + public static void beforeClass() throws Exception { + supportedServices = Map.of(1L, List.of(Network.Service.UserData, Network.Service.Dhcp, Network.Service.Dns)); + } + @Test public void writeFileTest() { try (MockedStatic fileUtilsMocked = Mockito.mockStatic(FileUtils.class)) { @@ -112,16 +127,16 @@ public class ConfigDriveBuilderTest { } @Test(expected = CloudRuntimeException.class) - public void buildConfigDriveTestNoVmData() { - ConfigDriveBuilder.buildConfigDrive(null, "teste", "C:", null); + public void buildConfigDriveTestNoVmDataAndNic() { + ConfigDriveBuilder.buildConfigDrive(null, null, "teste", "C:", null, null); } @Test(expected = CloudRuntimeException.class) public void buildConfigDriveTestIoException() { try (MockedStatic configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) { - configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class); - Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod(); - ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null); + configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class); + Mockito.when(ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null, supportedServices)).thenCallRealMethod(); + ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null, supportedServices); } } @@ -129,22 +144,26 @@ public class ConfigDriveBuilderTest { public void buildConfigDriveTest() { try (MockedStatic configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) { - configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class))).then(invocationOnMock -> null); + configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorEmptyJsonFile(Mockito.any(File.class))).then(invocationOnMock -> null); configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVmMetadata(Mockito.anyList(), Mockito.anyString(), Mockito.any(File.class), anyMap())).then(invocationOnMock -> null); configDriveBuilderMocked.when(() -> ConfigDriveBuilder.linkUserData((Mockito.anyString()))).then(invocationOnMock -> null); configDriveBuilderMocked.when(() -> ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenAnswer(invocation -> "mockIsoDataBase64"); - //force execution of real method - Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod(); - String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null); + NicProfile mockedNicProfile = Mockito.mock(NicProfile.class); + Mockito.when(mockedNicProfile.getId()).thenReturn(1L); + + //force execution of real method + Mockito.when(ConfigDriveBuilder.buildConfigDrive(List.of(mockedNicProfile), new ArrayList<>(), "teste", "C:", null, supportedServices)).thenCallRealMethod(); + + String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(List.of(mockedNicProfile), new ArrayList<>(), "teste", "C:", null, supportedServices); Assert.assertEquals("mockIsoDataBase64", returnedIsoData); configDriveBuilderMocked.verify(() -> { - ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class)); + ConfigDriveBuilder.writeVendorEmptyJsonFile(Mockito.any(File.class)); ConfigDriveBuilder.writeVmMetadata(Mockito.anyList(), Mockito.anyString(), Mockito.any(File.class), anyMap()); ConfigDriveBuilder.linkUserData(Mockito.anyString()); ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); @@ -153,23 +172,23 @@ public class ConfigDriveBuilderTest { } @Test(expected = CloudRuntimeException.class) - public void writeVendorAndNetworkEmptyJsonFileTestCannotCreateOpenStackFolder() { + public void writeVendorEmptyJsonFileTestCannotCreateOpenStackFolder() { File folderFileMock = Mockito.mock(File.class); Mockito.doReturn(false).when(folderFileMock).mkdirs(); - ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock); + ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock); } @Test(expected = CloudRuntimeException.class) - public void writeVendorAndNetworkEmptyJsonFileTest() { + public void writeVendorEmptyJsonFileTest() { File folderFileMock = Mockito.mock(File.class); Mockito.doReturn(false).when(folderFileMock).mkdirs(); - ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock); + ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock); } @Test - public void writeVendorAndNetworkEmptyJsonFileTestCreatingFolder() { + public void writeVendorEmptyJsonFileTestCreatingFolder() { try (MockedStatic configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) { File folderFileMock = Mockito.mock(File.class); @@ -177,9 +196,9 @@ public class ConfigDriveBuilderTest { Mockito.doReturn(true).when(folderFileMock).mkdirs(); //force execution of real method - configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock)).thenCallRealMethod(); + configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock)).thenCallRealMethod(); - ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock); + ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock); Mockito.verify(folderFileMock).exists(); Mockito.verify(folderFileMock).mkdirs(); @@ -501,4 +520,143 @@ public class ConfigDriveBuilderTest { Mockito.verify(mkIsoProgramInMacOsFileMock, Mockito.times(1)).getCanonicalPath(); } } + + @Test + public void testWriteNetworkData() throws Exception { + // Setup + NicProfile nicp = mock(NicProfile.class); + Mockito.when(nicp.getId()).thenReturn(1L); + + Mockito.when(nicp.getMacAddress()).thenReturn("00:00:00:00:00:00"); + Mockito.when(nicp.getMtu()).thenReturn(2000); + + Mockito.when(nicp.getIPv4Address()).thenReturn("172.31.0.10"); + Mockito.when(nicp.getDeviceId()).thenReturn(1); + Mockito.when(nicp.getIPv4Netmask()).thenReturn("255.255.255.0"); + Mockito.when(nicp.getUuid()).thenReturn("NETWORK UUID"); + Mockito.when(nicp.getIPv4Gateway()).thenReturn("172.31.0.1"); + + + Mockito.when(nicp.getIPv6Address()).thenReturn("2001:db8:0:1234:0:567:8:1"); + Mockito.when(nicp.getIPv6Cidr()).thenReturn("2001:db8:0:1234:0:567:8:1/64"); + Mockito.when(nicp.getIPv6Gateway()).thenReturn("2001:db8:0:1234:0:567:8::1"); + + Mockito.when(nicp.getIPv4Dns1()).thenReturn("8.8.8.8"); + Mockito.when(nicp.getIPv4Dns2()).thenReturn("1.1.1.1"); + Mockito.when(nicp.getIPv6Dns1()).thenReturn("2001:4860:4860::8888"); + Mockito.when(nicp.getIPv6Dns2()).thenReturn("2001:4860:4860::8844"); + + + List services1 = Arrays.asList(Network.Service.Dhcp, Network.Service.Dns); + + Map> supportedServices = new HashMap<>(); + supportedServices.put(1L, services1); + + TemporaryFolder folder = new TemporaryFolder(); + folder.create(); + File openStackFolder = folder.newFolder("openStack"); + + // Expected JSON structure + String expectedJson = "{" + + " \"links\": [" + + " {" + + " \"ethernet_mac_address\": \"00:00:00:00:00:00\"," + + " \"id\": \"eth1\"," + + " \"mtu\": 2000," + + " \"type\": \"phy\"" + + " }" + + " ]," + + " \"networks\": [" + + " {" + + " \"id\": \"eth1\"," + + " \"ip_address\": \"172.31.0.10\"," + + " \"link\": \"eth1\"," + + " \"netmask\": \"255.255.255.0\"," + + " \"network_id\": \"NETWORK UUID\"," + + " \"type\": \"ipv4\"," + + " \"routes\": [" + + " {" + + " \"gateway\": \"172.31.0.1\"," + + " \"netmask\": \"0.0.0.0\"," + + " \"network\": \"0.0.0.0\"" + + " }" + + " ]" + + " }," + + " {" + + " \"id\": \"eth1\"," + + " \"ip_address\": \"2001:db8:0:1234:0:567:8:1\"," + + " \"link\": \"eth1\"," + + " \"netmask\": \"64\"," + + " \"network_id\": \"NETWORK UUID\"," + + " \"type\": \"ipv6\"," + + " \"routes\": [" + + " {" + + " \"gateway\": \"2001:db8:0:1234:0:567:8::1\"," + + " \"netmask\": \"0\"," + + " \"network\": \"::\"" + + " }" + + " ]" + + " }" + + " ]," + + " \"services\": [" + + " {" + + " \"address\": \"8.8.8.8\"," + + " \"type\": \"dns\"" + + " }," + + " {" + + " \"address\": \"1.1.1.1\"," + + " \"type\": \"dns\"" + + " }," + + " {" + + " \"address\": \"2001:4860:4860::8888\"," + + " \"type\": \"dns\"" + + " }," + + " {" + + " \"address\": \"2001:4860:4860::8844\"," + + " \"type\": \"dns\"" + + " }" + + " ]" + + "}"; + + // Action + ConfigDriveBuilder.writeNetworkData(Arrays.asList(nicp), supportedServices, openStackFolder); + + // Verify + File networkDataFile = new File(openStackFolder, "network_data.json"); + String content = FileUtils.readFileToString(networkDataFile, StandardCharsets.UTF_8); + JsonObject actualJson = new JsonParser().parse(content).getAsJsonObject(); + JsonObject expectedJsonObject = new JsonParser().parse(expectedJson).getAsJsonObject(); + + Assert.assertEquals(expectedJsonObject, actualJson); + folder.delete(); + } + + @Test + public void testWriteNetworkDataEmptyJson() throws Exception { + // Setup + NicProfile nicp = mock(NicProfile.class); + List services1 = Collections.emptyList(); + + Map> supportedServices = new HashMap<>(); + supportedServices.put(1L, services1); + + TemporaryFolder folder = new TemporaryFolder(); + folder.create(); + File openStackFolder = folder.newFolder("openStack"); + + // Expected JSON structure + String expectedJson = "{}"; + + // Action + ConfigDriveBuilder.writeNetworkData(Arrays.asList(nicp), supportedServices, openStackFolder); + + // Verify + File networkDataFile = new File(openStackFolder, "network_data.json"); + String content = FileUtils.readFileToString(networkDataFile, StandardCharsets.UTF_8); + JsonObject actualJson = new JsonParser().parse(content).getAsJsonObject(); + JsonObject expectedJsonObject = new JsonParser().parse(expectedJson).getAsJsonObject(); + + Assert.assertEquals(expectedJsonObject, actualJson); + folder.delete(); + } } diff --git a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java new file mode 100644 index 00000000000..6e935b951da --- /dev/null +++ b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.configdrive; + +import static org.apache.cloudstack.storage.configdrive.ConfigDriveUtils.mergeJsonArraysAndUpdateObject; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import com.google.gson.JsonObject; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigDriveUtilsTest { + + @Test + public void testMergeJsonArraysAndUpdateObjectWithEmptyObjects() { + JsonObject finalObject = new JsonObject(); + JsonObject newObj = new JsonObject(); + mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", "type"); + Assert.assertEquals("{}", finalObject.toString()); + } + + @Test + public void testMergeJsonArraysAndUpdateObjectWithNewMembersAdded() { + JsonObject finalObject = new JsonObject(); + + JsonObject newObj = new JsonObject(); + JsonArray newMembers = new JsonArray(); + JsonObject newMember = new JsonObject(); + newMember.addProperty("id", "eth0"); + newMember.addProperty("type", "phy"); + newMembers.add(newMember); + newObj.add("links", newMembers); + + mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", "type"); + Assert.assertEquals(1, finalObject.getAsJsonArray("links").size()); + JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 'eth0', 'type': 'phy'}]}").getAsJsonObject(); + Assert.assertEquals(expectedObj, finalObject); + } + + @Test + public void testMergeJsonArraysAndUpdateObjectWithDuplicateMembersIgnored() { + JsonObject finalObject = new JsonObject(); + JsonArray existingMembers = new JsonArray(); + JsonObject existingMember = new JsonObject(); + existingMember.addProperty("id", "eth0"); + existingMember.addProperty("type", "phy"); + existingMembers.add(existingMember); + finalObject.add("links", existingMembers); + + JsonObject newObj = new JsonObject(); + newObj.add("links", existingMembers); // same as existingMembers for duplication + + mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", "type"); + Assert.assertEquals(1, finalObject.getAsJsonArray("links").size()); + JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 'eth0', 'type': 'phy'}]}").getAsJsonObject(); + Assert.assertEquals(expectedObj, finalObject); + } + + @Test + public void testMergeJsonArraysAndUpdateObjectWithDifferentMembers() { + JsonObject finalObject = new JsonObject(); + + JsonArray newMembers = new JsonArray(); + JsonObject newMember = new JsonObject(); + newMember.addProperty("id", "eth0"); + newMember.addProperty("type", "phy"); + newMembers.add(newMember); + finalObject.add("links", newMembers); + + JsonObject newObj = new JsonObject(); + newMembers = new JsonArray(); + newMember = new JsonObject(); + newMember.addProperty("id", "eth1"); + newMember.addProperty("type", "phy"); + newMembers.add(newMember); + newObj.add("links", newMembers); + + mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", "type"); + Assert.assertEquals(2, finalObject.getAsJsonArray("links").size()); + JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 'eth0', 'type': 'phy'}, {'id': 'eth1', 'type': 'phy'}]}").getAsJsonObject(); + Assert.assertEquals(expectedObj, finalObject); + } + + @Test(expected = NullPointerException.class) + public void testMergeJsonArraysAndUpdateObjectWithNullObjects() { + mergeJsonArraysAndUpdateObject(null, null, "services", "id", "type"); + } +} diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java index 56e0948e593..67e3ea844d5 100755 --- a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java +++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java @@ -21,7 +21,6 @@ package org.apache.cloudstack.storage.motion; import static org.mockito.Mockito.when; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.never; @@ -55,8 +54,6 @@ public class AncientDataMotionStrategyTest { @Mock PrimaryDataStoreTO dataStoreTO; @Mock - ConfigKey vmwareKey; - @Mock StorageManager storageManager; @Mock StoragePool storagePool; @@ -66,9 +63,7 @@ public class AncientDataMotionStrategyTest { @Before public void setup() throws Exception { - replaceVmwareCreateCloneFullField(); - - when(vmwareKey.valueIn(POOL_ID)).thenReturn(FULL_CLONE_FLAG); + overrideDefaultConfigValue(StorageManager.VmwareCreateCloneFull, String.valueOf(FULL_CLONE_FLAG)); when(dataTO.getHypervisorType()).thenReturn(HypervisorType.VMware); when(dataTO.getDataStore()).thenReturn(dataStoreTO); @@ -76,14 +71,10 @@ public class AncientDataMotionStrategyTest { when(storageManager.getStoragePool(POOL_ID)).thenReturn(storagePool); } - private void replaceVmwareCreateCloneFullField() throws Exception { - Field field = StorageManager.class.getDeclaredField("VmwareCreateCloneFull"); - field.setAccessible(true); - // remove final modifier from field - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.set(null, vmwareKey); + private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException { + final Field f = ConfigKey.class.getDeclaredField("_defaultValue"); + f.setAccessible(true); + f.set(configKey, value); } @Test diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java index 7e902bc61fe..afc8be1e5f9 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java @@ -100,6 +100,8 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase { @Inject SnapshotZoneDao snapshotZoneDao; + private final List snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error); + public SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) { List snaps = snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image); for (SnapshotDataStoreVO ref : snaps) { @@ -197,9 +199,8 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase { boolean result = false; boolean resultIsSet = false; - final List snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.BackedUp, Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error); try { - while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())) { + do { SnapshotInfo child = snapshot.getChild(); if (child != null) { @@ -245,7 +246,7 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase { } snapshot = parent; - } + } while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())); } catch (Exception e) { logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e); } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index 9606da1e337..db3f798a68a 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -20,9 +20,6 @@ package org.apache.cloudstack.storage.image; import java.net.URI; import java.net.URISyntaxException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -87,7 +84,6 @@ import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.download.DownloadMonitor; import com.cloud.utils.NumbersUtil; -import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Proxy; import com.cloud.vm.VirtualMachineManager; @@ -425,8 +421,9 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { private Answer sendToLeastBusyEndpoint(List eps, CopyCommand cmd) { Answer answer = null; EndPoint endPoint = null; - List epIds = ssvmWithLeastMigrateJobs(); + logger.debug("Picking SSVM from the pool with least commands running on it."); + List epIds = hostDao.listSsvmHostsWithPendingMigrateJobsOrderedByJobCount(); if (epIds.isEmpty()) { Collections.shuffle(eps); endPoint = eps.get(0); @@ -533,23 +530,4 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { private Integer getCopyCmdsCountToSpecificSSVM(Long ssvmId) { return _cmdExecLogDao.getCopyCmdCountForSSVM(ssvmId); } - - private List ssvmWithLeastMigrateJobs() { - logger.debug("Picking ssvm from the pool with least commands running on it"); - String query = "select host_id, count(*) from cmd_exec_log group by host_id order by 2;"; - TransactionLegacy txn = TransactionLegacy.currentTxn(); - - List result = new ArrayList(); - PreparedStatement pstmt = null; - try { - pstmt = txn.prepareAutoCloseStatement(query); - ResultSet rs = pstmt.executeQuery(); - while (rs.next()) { - result.add((long) rs.getInt(1)); - } - } catch (SQLException e) { - logger.debug("SQLException caught", e); - } - return result; - } } diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index b7e4f44cf8c..52a6b204ee8 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -437,9 +437,11 @@ public abstract class GenericDaoBase extends Compone } return result; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to find on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @@ -522,9 +524,11 @@ public abstract class GenericDaoBase extends Compone return results; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to find on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @@ -870,8 +874,9 @@ public abstract class GenericDaoBase extends Compone ub.clear(); return result; } catch (final SQLException e) { + logger.error("DB Exception on: " + pstmt, e); handleEntityExistsException(e); - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to update on DB, due to: " + e.getLocalizedMessage()); } } @@ -1065,7 +1070,8 @@ public abstract class GenericDaoBase extends Compone ResultSet rs = pstmt.executeQuery(); return rs.next() ? toEntityBean(rs, true) : null; } catch (SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to find by id on DB, due to: " + e.getLocalizedMessage()); } } @@ -1180,9 +1186,11 @@ public abstract class GenericDaoBase extends Compone } return result; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to execute on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @@ -1231,7 +1239,8 @@ public abstract class GenericDaoBase extends Compone } return true; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to expunge on DB, due to: " + e.getLocalizedMessage()); } } @@ -1269,9 +1278,11 @@ public abstract class GenericDaoBase extends Compone } return pstmt.executeUpdate(); } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to expunge on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @Override @@ -1550,7 +1561,7 @@ public abstract class GenericDaoBase extends Compone return entity; } - assert false : "Can't call persit if you don't have primary key"; + assert false : "Can't call persist if you don't have primary key"; } ID id = null; @@ -1606,8 +1617,9 @@ public abstract class GenericDaoBase extends Compone } txn.commit(); } catch (final SQLException e) { + logger.error("DB Exception on: " + pstmt, e); handleEntityExistsException(e); - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to persist on DB, due to: " + e.getLocalizedMessage()); } catch (IllegalArgumentException e) { throw new CloudRuntimeException("Problem with getting the ec attribute ", e); } catch (IllegalAccessException e) { @@ -1956,7 +1968,8 @@ public abstract class GenericDaoBase extends Compone pstmt.executeUpdate(); txn.commit(); } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to expunge on DB, due to: " + e.getLocalizedMessage()); } } @@ -1984,7 +1997,8 @@ public abstract class GenericDaoBase extends Compone } return result > 0; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to unremove on DB, due to: " + e.getLocalizedMessage()); } } @@ -2027,7 +2041,8 @@ public abstract class GenericDaoBase extends Compone } return result > 0; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to remove on DB, due to: " + e.getLocalizedMessage()); } } @@ -2175,9 +2190,11 @@ public abstract class GenericDaoBase extends Compone } return 0; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to get count on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @@ -2234,9 +2251,11 @@ public abstract class GenericDaoBase extends Compone } return 0; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception in executing: " + sql, e); + logger.error("DB Exception in executing: " + sql, e); + throw new CloudRuntimeException("Unable to get count on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught exception in : " + sql, e); + logger.error("Caught exception in : " + sql, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } @@ -2309,9 +2328,11 @@ public abstract class GenericDaoBase extends Compone } return 0; } catch (final SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + pstmt, e); + logger.error("DB Exception on: " + pstmt, e); + throw new CloudRuntimeException("Unable to get count on DB, due to: " + e.getLocalizedMessage()); } catch (final Exception e) { - throw new CloudRuntimeException("Caught: " + pstmt, e); + logger.error("Caught: " + pstmt, e); + throw new CloudRuntimeException("Caught error: " + e.getLocalizedMessage()); } } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDao.java index 4f13fb33180..419bb0ad7d2 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDao.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDao.java @@ -28,18 +28,10 @@ public interface QuotaTariffDao extends GenericDao { Pair, Integer> listQuotaTariffs(Date startDate, Date endDate, Integer usageType, String name, String uuid, boolean listAll, Long startIndex, Long pageSize); + Pair, Integer> listQuotaTariffs(Date startDate, Date endDate, Integer usageType, String name, String uuid, boolean listAll, boolean listOnlyRemoved, Long startIndex, Long pageSize, String keyword); + QuotaTariffVO findByName(String name); - QuotaTariffVO findTariffPlanByUsageType(int quotaType, Date onOrBefore); - - Pair, Integer> listAllTariffPlans(); - - Pair, Integer> listAllTariffPlans(final Long startIndex, final Long pageSize); - - Pair, Integer> listAllTariffPlans(Date onOrBefore); - - Pair, Integer> listAllTariffPlans(Date onOrBefore, Long startIndex, Long pageSize); - Boolean updateQuotaTariff(QuotaTariffVO plan); QuotaTariffVO addQuotaTariff(QuotaTariffVO plan); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java index 8cbec8c8598..d36c698f44d 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java @@ -16,12 +16,9 @@ //under the License. package org.apache.cloudstack.quota.dao; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; -import org.apache.cloudstack.quota.constant.QuotaTypes; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -34,7 +31,6 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionLegacy; -import com.cloud.utils.db.TransactionStatus; @Component public class QuotaTariffDaoImpl extends GenericDaoBase implements QuotaTariffDao { @@ -45,7 +41,7 @@ public class QuotaTariffDaoImpl extends GenericDaoBase impl public QuotaTariffDaoImpl() { super(); searchUsageType = createSearchBuilder(); - searchUsageType.and("usage_type", searchUsageType.entity().getUsageType(), SearchCriteria.Op.EQ); + searchUsageType.and("usageType", searchUsageType.entity().getUsageType(), SearchCriteria.Op.EQ); searchUsageType.done(); listAllIncludedUsageType = createSearchBuilder(); @@ -54,111 +50,28 @@ public class QuotaTariffDaoImpl extends GenericDaoBase impl listAllIncludedUsageType.done(); } - @Override - public QuotaTariffVO findTariffPlanByUsageType(final int quotaType, final Date effectiveDate) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaTariffVO doInTransaction(final TransactionStatus status) { - List result = new ArrayList<>(); - final Filter filter = new Filter(QuotaTariffVO.class, "updatedOn", false, 0L, 1L); - final SearchCriteria sc = listAllIncludedUsageType.create(); - sc.setParameters("onorbefore", effectiveDate); - sc.setParameters("quotatype", quotaType); - result = search(sc, filter); - if (result != null && !result.isEmpty()) { - return result.get(0); - } else { - if (logger.isDebugEnabled()) { - logger.debug("QuotaTariffDaoImpl::findTariffPlanByUsageType: Missing quota type " + quotaType); - } - return null; - } - } - }); - } - - @Override - public Pair, Integer> listAllTariffPlans() { - return listAllTariffPlans(null, null); - } - - @Override - public Pair, Integer> listAllTariffPlans(final Long startIndex, final Long pageSize) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback, Integer>>() { - @Override - public Pair, Integer> doInTransaction(final TransactionStatus status) { - return searchAndCount(null, new Filter(QuotaTariffVO.class, "updatedOn", false, startIndex, pageSize)); - } - }); - } - - - private List paginateList(final List list, final Long startIndex, final Long pageSize) { - if (startIndex == null || pageSize == null) { - return list; - } - if (list.size() < startIndex){ - return Collections.emptyList(); - } - return list.subList(startIndex.intValue(), (int) Math.min(startIndex + pageSize, list.size())); - } - - @Override - public Pair, Integer> listAllTariffPlans(final Date effectiveDate) { - return listAllTariffPlans(effectiveDate, null, null); - } - - @Override - public Pair, Integer> listAllTariffPlans(final Date effectiveDate, final Long startIndex, final Long pageSize) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback, Integer>>() { - @Override - public Pair, Integer> doInTransaction(final TransactionStatus status) { - List tariffs = new ArrayList(); - final Filter filter = new Filter(QuotaTariffVO.class, "updatedOn", false, 0L, 1L); - final SearchCriteria sc = listAllIncludedUsageType.create(); - sc.setParameters("onorbefore", effectiveDate); - for (Integer quotaType : QuotaTypes.listQuotaTypes().keySet()) { - sc.setParameters("quotatype", quotaType); - List result = search(sc, filter); - if (result != null && !result.isEmpty()) { - tariffs.add(result.get(0)); - if (logger.isDebugEnabled()) { - logger.debug("ListAllTariffPlans on or before " + effectiveDate + " quota type " + result.get(0).getUsageTypeDescription() + " , effective Date=" - + result.get(0).getEffectiveOn() + " val=" + result.get(0).getCurrencyValue()); - } - } - } - return new Pair<>(paginateList(tariffs, startIndex, pageSize), tariffs.size()); - } - }); - } - @Override public Boolean updateQuotaTariff(final QuotaTariffVO plan) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public Boolean doInTransaction(final TransactionStatus status) { - return update(plan.getId(), plan); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> update(plan.getId(), plan)); } @Override public QuotaTariffVO addQuotaTariff(final QuotaTariffVO plan) { if (plan.getIdObj() != null) { - throw new IllegalStateException("The QuotaTariffVO being added should not have an Id set "); + throw new IllegalStateException("The QuotaTariffVO being added should not have an Id set."); } - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaTariffVO doInTransaction(final TransactionStatus status) { - return persist(plan); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> persist(plan)); } @Override public Pair, Integer> listQuotaTariffs(Date startDate, Date endDate, Integer usageType, String name, String uuid, boolean listAll, Long startIndex, Long pageSize) { - SearchCriteria searchCriteria = createListQuotaTariffsSearchCriteria(startDate, endDate, usageType, name, uuid); + return listQuotaTariffs(startDate, endDate, usageType, name, uuid, listAll, false, startIndex, pageSize, null); + } + + @Override + public Pair, Integer> listQuotaTariffs(Date startDate, Date endDate, Integer usageType, String name, String uuid, boolean listAll, boolean listOnlyRemoved, Long startIndex, Long pageSize, String keyword) { + SearchCriteria searchCriteria = createListQuotaTariffsSearchCriteria(startDate, endDate, usageType, name, uuid, listOnlyRemoved, keyword); + Filter sorter = new Filter(QuotaTariffVO.class, "usageType", false, startIndex, pageSize); sorter.addOrderBy(QuotaTariffVO.class, "effectiveOn", false); sorter.addOrderBy(QuotaTariffVO.class, "updatedOn", false); @@ -166,39 +79,34 @@ public class QuotaTariffDaoImpl extends GenericDaoBase impl return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback, Integer>>) status -> searchAndCount(searchCriteria, sorter, listAll)); } - protected SearchCriteria createListQuotaTariffsSearchCriteria(Date startDate, Date endDate, Integer usageType, String name, String uuid) { - SearchCriteria searchCriteria = createListQuotaTariffsSearchBuilder(startDate, endDate, usageType, name, uuid).create(); + protected SearchCriteria createListQuotaTariffsSearchCriteria(Date startDate, Date endDate, Integer usageType, String name, String uuid, boolean listOnlyRemoved, String keyword) { + SearchCriteria searchCriteria = createListQuotaTariffsSearchBuilder(listOnlyRemoved).create(); - searchCriteria.setParametersIfNotNull("start_date", startDate); - searchCriteria.setParametersIfNotNull("end_date", endDate); - searchCriteria.setParametersIfNotNull("usage_type", usageType); + searchCriteria.setParametersIfNotNull("startDate", startDate); + searchCriteria.setParametersIfNotNull("endDate", endDate); + searchCriteria.setParametersIfNotNull("usageType", usageType); searchCriteria.setParametersIfNotNull("name", name); searchCriteria.setParametersIfNotNull("uuid", uuid); + if (keyword != null) { + searchCriteria.setParameters("nameLike", "%" + keyword + "%"); + } + return searchCriteria; } - protected SearchBuilder createListQuotaTariffsSearchBuilder(Date startDate, Date endDate, Integer usageType, String name, String uuid) { + protected SearchBuilder createListQuotaTariffsSearchBuilder(boolean listOnlyRemoved) { SearchBuilder searchBuilder = createSearchBuilder(); - if (startDate != null) { - searchBuilder.and("start_date", searchBuilder.entity().getEffectiveOn(), SearchCriteria.Op.GTEQ); - } + searchBuilder.and("startDate", searchBuilder.entity().getEffectiveOn(), SearchCriteria.Op.GTEQ); + searchBuilder.and("endDate", searchBuilder.entity().getEndDate(), SearchCriteria.Op.LTEQ); + searchBuilder.and("usageType", searchBuilder.entity().getUsageType(), SearchCriteria.Op.EQ); + searchBuilder.and("name", searchBuilder.entity().getName(), SearchCriteria.Op.EQ); + searchBuilder.and("uuid", searchBuilder.entity().getUuid(), SearchCriteria.Op.EQ); + searchBuilder.and("nameLike", searchBuilder.entity().getName(), SearchCriteria.Op.LIKE); - if (endDate != null) { - searchBuilder.and("end_date", searchBuilder.entity().getEndDate(), SearchCriteria.Op.LTEQ); - } - - if (usageType != null) { - searchBuilder.and("usage_type", searchBuilder.entity().getUsageType(), SearchCriteria.Op.EQ); - } - - if (name != null) { - searchBuilder.and("name", searchBuilder.entity().getName(), SearchCriteria.Op.EQ); - } - - if (uuid != null) { - searchBuilder.and("uuid", searchBuilder.entity().getUuid(), SearchCriteria.Op.EQ); + if (listOnlyRemoved) { + searchBuilder.and("removed", searchBuilder.entity().getRemoved(), SearchCriteria.Op.NNULL); } return searchBuilder; diff --git a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java index c44347c85ef..18b840e7a8c 100644 --- a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java +++ b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.framework.security.keystore; import com.cloud.agent.api.LogLevel; import com.cloud.agent.api.LogLevel.Log4jLevel; +import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; public interface KeystoreManager extends Manager { @@ -59,7 +60,7 @@ public interface KeystoreManager extends Manager { } } - boolean validateCertificate(String certificate, String key, String domainSuffix); + Pair validateCertificate(String certificate, String key, String domainSuffix); void saveCertificate(String name, String certificate, String key, String domainSuffix); diff --git a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java index 3fc2ff3702e..3e01942fb2b 100644 --- a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java +++ b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java @@ -30,6 +30,7 @@ import java.util.regex.Pattern; import javax.inject.Inject; +import com.cloud.utils.Pair; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -45,24 +46,28 @@ public class KeystoreManagerImpl extends ManagerBase implements KeystoreManager private KeystoreDao _ksDao; @Override - public boolean validateCertificate(String certificate, String key, String domainSuffix) { + public Pair validateCertificate(String certificate, String key, String domainSuffix) { + String errMsg = null; if (StringUtils.isAnyEmpty(certificate, key, domainSuffix)) { - logger.error("Invalid parameter found in (certificate, key, domainSuffix) tuple for domain: " + domainSuffix); - return false; + errMsg = String.format("Invalid parameter found in (certificate, key, domainSuffix) tuple for domain: %s", domainSuffix); + logger.error(errMsg); + return new Pair<>(false, errMsg); } try { String ksPassword = "passwordForValidation"; byte[] ksBits = CertificateHelper.buildAndSaveKeystore(domainSuffix, certificate, getKeyContent(key), ksPassword); KeyStore ks = CertificateHelper.loadKeystore(ksBits, ksPassword); - if (ks != null) - return true; - - logger.error("Unabled to construct keystore for domain: " + domainSuffix); + if (ks != null) { + return new Pair<>(true, errMsg); + } + errMsg = String.format("Unable to construct keystore for domain: %s", domainSuffix); + logger.error(errMsg); } catch (Exception e) { - logger.error("Certificate validation failed due to exception for domain: " + domainSuffix, e); + errMsg = String.format("Certificate validation failed due to exception for domain: %s", domainSuffix); + logger.error(errMsg, e); } - return false; + return new Pair<>(false, errMsg); } @Override diff --git a/packaging/README.md b/packaging/README.md index 08e34baeae2..78057b828b2 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -6,9 +6,9 @@ These scripts are also used by the CloudStack team to build packages for the off # Requirements The RPM and DEB packages have dependencies on versions of specific libraries. Due to these dependencies the following distributions and their versions are supported by the packages. -* CentOS / RHEL: 7 and 8 -* Debian 7 (Wheezy) and 8 (Jessy) (untested!) -* Ubuntu: 16.04 (Xenial), 18.04 (Bionic) and 20.04 (Focal) +* CentOS / RHEL: 8 and 9 +* Ubuntu: 20.04, 22.04, 24.04 +* Debian 12 (Bookworm, untested!) # Building Using the scripts in the *packaging* directory the RPM and DEB packages can be build. @@ -38,4 +38,4 @@ The commands above will generate Ubuntu 14.04, 16.04, and 22.04 packages which y ## RPM The *package.sh* script can be used to build RPM packages for CloudStack. In the *packaging* script you can run the following command: -``./package.sh --pack oss --distribution centos7`` +``./package.sh --pack oss --distribution el8`` diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec deleted file mode 100644 index 80b8443e09b..00000000000 --- a/packaging/centos7/cloud.spec +++ /dev/null @@ -1,725 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -%define __os_install_post %{nil} -%global debug_package %{nil} - -# DISABLE the post-percentinstall java repacking and line number stripping -# we need to find a way to just disable the java repacking and line number stripping, but not the autodeps - -Name: cloudstack -Summary: CloudStack IaaS Platform -#http://fedoraproject.org/wiki/PackageNamingGuidelines#Pre-Release_packages -%define _maventag %{_fullver} -Release: %{_rel}%{dist} - -%define __python python3 -%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} - -Version: %{_ver} -License: ASL 2.0 -Vendor: Apache CloudStack -Packager: Apache CloudStack -Group: System Environment/Libraries -# FIXME do groups for every single one of the subpackages -Source0: %{name}-%{_maventag}.tgz -BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build - -BuildRequires: java-11-openjdk-devel -BuildRequires: ws-commons-util -BuildRequires: jpackage-utils -BuildRequires: gcc -BuildRequires: glibc-devel -BuildRequires: /usr/bin/mkisofs -BuildRequires: mysql-connector-python -BuildRequires: maven => 3.0.0 -BuildRequires: python-setuptools -BuildRequires: python3 -BuildRequires: python3-pip -BuildRequires: python3-setuptools -BuildRequires: wget -BuildRequires: nodejs - -%description -CloudStack is a highly-scalable elastic, open source, -intelligent IaaS cloud implementation. - -%package management -Summary: CloudStack management server UI -Requires: java-11-openjdk -Requires: tzdata-java -Requires: python -Requires: python3 -Requires: bash -Requires: gawk -Requires: which -Requires: file -Requires: bzip2 -Requires: gzip -Requires: unzip -Requires: /sbin/mount.nfs -Requires: openssh-clients -Requires: nfs-utils -Requires: iproute -Requires: wget -Requires: mysql -Requires: sudo -Requires: /sbin/service -Requires: /sbin/chkconfig -Requires: /usr/bin/ssh-keygen -Requires: genisoimage -Requires: ipmitool -Requires: %{name}-common = %{_ver} -Requires: iptables-services -Requires: qemu-img -Requires: rng-tools -Requires: python3-pip -Requires: python3-setuptools -Group: System Environment/Libraries -%description management -The CloudStack management server is the central point of coordination, -management, and intelligence in CloudStack. - -%package common -Summary: Apache CloudStack common files and scripts -Requires: python -Requires: python3 -Requires: python3-pip -Group: System Environment/Libraries -%description common -The Apache CloudStack files shared between agent and management server -%global __requires_exclude ^libuuid\\.so\\.1$ - -%package agent -Summary: CloudStack Agent for KVM hypervisors -Requires: openssh-clients -Requires: java-11-openjdk -Requires: tzdata-java -Requires: %{name}-common = %{_ver} -Requires: libvirt -Requires: bridge-utils -Requires: ebtables -Requires: iptables -Requires: ethtool -Requires: net-tools -Requires: iproute -Requires: ipset -Requires: perl -Requires: python36-libvirt -Requires: qemu-img -Requires: qemu-kvm -Requires: cryptsetup -Requires: rng-tools -Provides: cloud-agent -Group: System Environment/Libraries -%description agent -The CloudStack agent for KVM hypervisors - -%package baremetal-agent -Summary: CloudStack baremetal agent -Requires: tftp-server -Requires: xinetd -Requires: syslinux -Requires: chkconfig -Requires: dhcp -Requires: httpd -Group: System Environment/Libraries -%description baremetal-agent -The CloudStack baremetal agent - -%package usage -Summary: CloudStack Usage calculation server -Requires: java-11-openjdk -Requires: tzdata-java -Group: System Environment/Libraries -%description usage -The CloudStack usage calculation service - -%package ui -Summary: CloudStack UI -Group: System Environment/Libraries -%description ui -The CloudStack UI - -%package cli -Summary: Apache CloudStack CLI -Provides: python-marvin -Group: System Environment/Libraries -%description cli -Apache CloudStack command line interface - -%package marvin -Summary: Apache CloudStack Marvin library -Requires: python3 -Requires: python3-devel -Requires: python3-pip -Requires: python-pip -Requires: gcc -Requires: python-devel -Requires: libffi-devel -Requires: openssl-devel -Group: System Environment/Libraries -%description marvin -Apache CloudStack Marvin library - -%package integration-tests -Summary: Apache CloudStack Marvin integration tests -Requires: %{name}-marvin = %{_ver} -Group: System Environment/Libraries -%description integration-tests -Apache CloudStack Marvin integration tests - -%if "%{_ossnoss}" == "noredist" -%package mysql-ha -Summary: Apache CloudStack Balancing Strategy for MySQL -Group: System Environmnet/Libraries -%description mysql-ha -Apache CloudStack Balancing Strategy for MySQL - -%endif - -%prep -echo Doing CloudStack build - -%setup -q -n %{name}-%{_maventag} - -%build - -cp packaging/centos7/replace.properties build/replace.properties -echo VERSION=%{_maventag} >> build/replace.properties -echo PACKAGE=%{name} >> build/replace.properties -touch build/gitrev.txt -echo $(git rev-parse HEAD) > build/gitrev.txt - -if [ "%{_ossnoss}" == "NOREDIST" -o "%{_ossnoss}" == "noredist" ] ; then - echo "Adding noredist flag to the maven build" - FLAGS="$FLAGS -Dnoredist" -fi - -if [ "%{_sim}" == "SIMULATOR" -o "%{_sim}" == "simulator" ] ; then - echo "Adding simulator flag to the maven build" - FLAGS="$FLAGS -Dsimulator" -fi - -if [ \"%{_temp}\" != "" ]; then - echo "Adding flags to package requested templates" - FLAGS="$FLAGS `rpm --eval %{?_temp}`" -fi - -mvn -Psystemvm,developer $FLAGS clean package -cd ui && npm install && npm run build && cd .. - -%install -[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} -# Common directories -mkdir -p ${RPM_BUILD_ROOT}%{_bindir} -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/ipallocator -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management -mkdir -p ${RPM_BUILD_ROOT}%{_initrddir} -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/default -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d - -# Common -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms -mkdir -p ${RPM_BUILD_ROOT}%{python_sitearch}/ -mkdir -p ${RPM_BUILD_ROOT}/usr/bin -cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/ -install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py -cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{python_sitearch}/ -python3 -m py_compile ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py -python3 -m compileall ${RPM_BUILD_ROOT}%{python_sitearch}/cloudutils -cp build/gitrev.txt ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -cp packaging/centos7/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin - -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco -cp -r plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco - -# Management -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/ -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/run -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel - -# Setup Jetty -ln -sf /etc/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/conf -ln -sf /var/log/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/logs - -install -D client/target/utilities/bin/cloud-migrate-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-migrate-databases -install -D client/target/utilities/bin/cloud-set-guest-password ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-password -install -D client/target/utilities/bin/cloud-set-guest-sshkey ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-sshkey -install -D client/target/utilities/bin/cloud-setup-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-databases -install -D client/target/utilities/bin/cloud-setup-encryption ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-encryption -install -D client/target/utilities/bin/cloud-setup-management ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-management -install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal -install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm -install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses -# Bundle cmk in cloudstack-management -wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/6.3.0/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk -chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk - -cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup - -cp -r client/target/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/ -cp -r client/target/classes/META-INF/webapp ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp -cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/ -cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/ -rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json -ln -sf /etc/%{name}/management/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json -mv ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/cloudstack-%{_maventag}.jar -cp client/target/lib/*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/ - -# Don't package the scripts in the management webapp -rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/scripts -rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/vms - -for name in db.properties server.properties log4j-cloud.xml environment.properties java.security.ciphers -do - cp client/target/conf/$name ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/$name -done - -ln -sf log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/log4j2.xml - -install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py -install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar -install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar - -install -D packaging/centos7/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator -install -D packaging/centos7/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud -install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service -install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management -install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management -touch ${RPM_BUILD_ROOT}%{_localstatedir}/run/%{name}-management.pid -#install -D server/target/conf/cloudstack-catalina.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-catalina -install -D server/target/conf/cloudstack-management.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-management - -# SystemVM template -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm -cp -r engine/schema/dist/systemvm-templates/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm -rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm/md5sum.txt - -# UI -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/ -cp -r client/target/classes/META-INF/webapp/WEB-INF ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui -cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui/ -cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/ -rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json -ln -sf /etc/%{name}/ui/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json - -# Package mysql-connector-python -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/ee/ff/48bde5c0f013094d729fe4b0316ba2a24774b3ff1c52d924a8a4cb04078a/six-1.15.0-py2.py3-none-any.whl -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/e9/93/4860cebd5ad3ff2664ad3c966490ccb46e3b88458b2095145bca11727ca4/setuptools-47.3.1-py3-none-any.whl -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/28/05/9867ef8eafd12265267bee138fa2c46ebf34a276ea4cbe184cba4c606e8b/protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl -wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/d1/53/4cf90d2fe81b9cdb55dc180951bcec44ea8685665f1bdb1412501dc362dd/mysql_connector_python-8.0.20-cp36-cp36m-manylinux1_x86_64.whl - -chmod 440 ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management -chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent - -# KVM Agent -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins -install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service -install -D packaging/systemd/cloudstack-rolling-maintenance@.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-rolling-maintenance@.service -install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent -install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties -install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties -install -D agent/target/transformed/log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/log4j-cloud.xml -install -D agent/target/transformed/cloud-setup-agent ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-agent -install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade -install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool -install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook -install -D agent/target/transformed/rolling-maintenance ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/rolling-maintenance -install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh -install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh -install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent -install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar -cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib -cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib -cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib - -# Usage server -mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib -install -D usage/target/cloud-usage-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/cloud-usage-%{_maventag}.jar -install -D usage/target/transformed/db.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/db.properties -install -D usage/target/transformed/log4j-cloud_usage.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/log4j-cloud.xml -cp usage/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/ -cp client/target/lib/mysql*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/ -install -D packaging/systemd/cloudstack-usage.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-usage.service -install -D packaging/systemd/cloudstack-usage.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-usage -mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/usage/ -install -D usage/target/transformed/cloudstack-usage.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-usage - -# CLI -cp -r cloud-cli/cloudtool ${RPM_BUILD_ROOT}%{python_sitearch}/ -install cloud-cli/cloudapis/cloud.py ${RPM_BUILD_ROOT}%{python_sitearch}/cloudapis.py - -# Marvin -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin -cp tools/marvin/dist/Marvin-*.tar.gz ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin/ - -# integration-tests -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests -cp -r test/integration/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests/ - -# MYSQL HA -if [ "x%{_ossnoss}" == "xnoredist" ] ; then - mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib - cp -r plugins/database/mysql-ha/target/cloud-plugin-database-mysqlha-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib -fi - -#License files from whisker -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-cli-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-cli-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE -install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE -install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE - -%clean -[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} - -%preun management -/usr/bin/systemctl stop cloudstack-management || true -/usr/bin/systemctl disable cloudstack-management || true - -%pre management -id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \ - -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud|| true - -rm -rf %{_localstatedir}/cache/cloudstack - -# in case of upgrade to 4.9+ copy commands.properties if not exists in /etc/cloudstack/management/ -if [ "$1" == "2" ] ; then - if [ -f "%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties" ] && [ ! -f "%{_sysconfdir}/%{name}/management/commands.properties" ] ; then - cp -p %{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties %{_sysconfdir}/%{name}/management/commands.properties - fi -fi - -# Remove old tomcat symlinks and env config file -if [ -L "%{_datadir}/%{name}-management/lib" ] -then - rm -f %{_datadir}/%{name}-management/bin - rm -f %{_datadir}/%{name}-management/lib - rm -f %{_datadir}/%{name}-management/temp - rm -f %{_datadir}/%{name}-management/work - rm -f %{_sysconfdir}/default/%{name}-management -fi - -%post management -# Install mysql-connector-python -pip3 install %{_datadir}/%{name}-management/setup/wheel/six-1.15.0-py2.py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/setuptools-47.3.1-py3-none-any.whl %{_datadir}/%{name}-management/setup/wheel/protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.20-cp36-cp36m-manylinux1_x86_64.whl - -pip3 install urllib3 - -/usr/bin/systemctl enable cloudstack-management > /dev/null 2>&1 || true -/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true - -grep -s -q "db.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "db.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "db.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" - -# Update DB properties having master and slave(s), with source and replica(s) respectively (for inclusiveness) -grep -s -q "^db.cloud.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.slaves=/db.cloud.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "^db.cloud.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.secondsBeforeRetryMaster=/db.cloud.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "^db.cloud.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.queriesBeforeRetryMaster=/db.cloud.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "^db.usage.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.slaves=/db.usage.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "^db.usage.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.secondsBeforeRetryMaster=/db.usage.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" -grep -s -q "^db.usage.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.queriesBeforeRetryMaster=/db.usage.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" - -if [ ! -f %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/vhd-util ] ; then - echo Please download vhd-util from http://download.cloudstack.org/tools/vhd-util and put it in - echo %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/ -fi - -if [ -f %{_sysconfdir}/sysconfig/%{name}-management ] ; then - rm -f %{_sysconfdir}/sysconfig/%{name}-management -fi - -chown -R cloud:cloud /var/log/cloudstack/management - -systemctl daemon-reload - -%posttrans management -# Print help message -if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then - sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text - /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text management -fi - -%preun agent -/sbin/service cloudstack-agent stop || true -if [ "$1" == "0" ] ; then - /sbin/chkconfig --del cloudstack-agent > /dev/null 2>&1 || true -fi - -%pre agent - -# save old configs if they exist (for upgrade). Otherwise we may lose them -# when the old packages are erased. There are a lot of properties files here. -if [ -d "%{_sysconfdir}/cloud" ] ; then - mv %{_sysconfdir}/cloud %{_sysconfdir}/cloud.rpmsave -fi - -%post agent -if [ "$1" == "2" ] ; then - echo "Running %{_bindir}/%{name}-agent-upgrade to update bridge name for upgrade from CloudStack 4.0.x (and before) to CloudStack 4.1 (and later)" - %{_bindir}/%{name}-agent-upgrade -fi -if [ ! -d %{_sysconfdir}/libvirt/hooks ] ; then - mkdir %{_sysconfdir}/libvirt/hooks -fi -cp -a ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook %{_sysconfdir}/libvirt/hooks/qemu -mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp -/usr/bin/systemctl restart libvirtd -/usr/bin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true -/usr/bin/systemctl enable cloudstack-rolling-maintenance@p > /dev/null 2>&1 || true -/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true - -# if saved configs from upgrade exist, copy them over -if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then - mv %{_sysconfdir}/%{name}/agent/agent.properties %{_sysconfdir}/%{name}/agent/agent.properties.rpmnew - cp -p %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/%{name}/agent - # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall - mv %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/cloud.rpmsave/agent/agent.properties.rpmsave -fi - -systemctl daemon-reload - -%posttrans agent -# Print help message -if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then - sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text - /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text agent -fi - -%pre usage -id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \ - -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud|| true - -%preun usage -/sbin/service cloudstack-usage stop || true -if [ "$1" == "0" ] ; then - /sbin/chkconfig --del cloudstack-usage > /dev/null 2>&1 || true -fi - -%post usage -if [ -f "%{_sysconfdir}/%{name}/management/db.properties" ]; then - echo "Replacing usage server's db.properties with a link to the management server's db.properties" - rm -f %{_sysconfdir}/%{name}/usage/db.properties - ln -s %{_sysconfdir}/%{name}/management/db.properties %{_sysconfdir}/%{name}/usage/db.properties - /usr/bin/systemctl enable cloudstack-usage > /dev/null 2>&1 || true -fi - -if [ -f "%{_sysconfdir}/%{name}/management/key" ]; then - echo "Replacing usage server's key with a link to the management server's key" - rm -f %{_sysconfdir}/%{name}/usage/key - ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key -fi - -if [ ! -f "%{_sysconfdir}/%{name}/usage/key" ]; then - ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key -fi - -mkdir -p /usr/local/libexec -if [ ! -f "/usr/local/libexec/sanity-check-last-id" ]; then - echo 1 > /usr/local/libexec/sanity-check-last-id -fi -chown cloud:cloud /usr/local/libexec/sanity-check-last-id - -%posttrans usage -# Print help message -if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then - sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text - /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text usage -fi - -%post marvin -pip install --upgrade https://files.pythonhosted.org/packages/ca/ea/1e2553b088bad2f9fa8120c2624f797b2d7450d3b61bb492d29c72e3d3c2/mysql_connector_python-8.0.20-cp27-cp27mu-manylinux1_x86_64.whl -pip install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz -pip3 install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz -pip3 install --upgrade nose -pip3 install --upgrade urllib3 - -#No default permission as the permission setup is complex -%files management -%defattr(-,root,root,-) -%dir %{_datadir}/%{name}-management -%dir %attr(0770,root,cloud) %{_localstatedir}/%{name}/mnt -%dir %attr(0770,cloud,cloud) %{_localstatedir}/%{name}/management -%dir %attr(0770,root,cloud) %{_localstatedir}/cache/%{name}/management -%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/management -%config(noreplace) %{_sysconfdir}/default/%{name}-management -%config(noreplace) %{_sysconfdir}/sudoers.d/%{name}-management -%config(noreplace) %{_sysconfdir}/security/limits.d/cloud -%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/db.properties -%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/server.properties -%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/config.json -%config(noreplace) %{_sysconfdir}/%{name}/management/log4j-cloud.xml -%config(noreplace) %{_sysconfdir}/%{name}/management/log4j2.xml -%config(noreplace) %{_sysconfdir}/%{name}/management/environment.properties -%config(noreplace) %{_sysconfdir}/%{name}/management/java.security.ciphers -%attr(0644,root,root) %{_unitdir}/%{name}-management.service -%attr(0755,cloud,cloud) %{_localstatedir}/run/%{name}-management.pid -%attr(0755,root,root) %{_bindir}/%{name}-setup-management -%attr(0755,root,root) %{_bindir}/%{name}-update-xenserver-licenses -%{_datadir}/%{name}-management/conf -%{_datadir}/%{name}-management/lib/*.jar -%{_datadir}/%{name}-management/logs -%{_datadir}/%{name}-management/templates -%attr(0755,root,root) %{_bindir}/%{name}-setup-databases -%attr(0755,root,root) %{_bindir}/%{name}-migrate-databases -%attr(0755,root,root) %{_bindir}/%{name}-set-guest-password -%attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey -%attr(0755,root,root) %{_bindir}/%{name}-sysvmadm -%attr(0755,root,root) %{_bindir}/%{name}-setup-encryption -%attr(0755,root,root) %{_bindir}/cmk -%{_datadir}/%{name}-management/setup/*.sql -%{_datadir}/%{name}-management/setup/*.sh -%{_datadir}/%{name}-management/setup/server-setup.xml -%{_datadir}/%{name}-management/webapp/* -%attr(0755,root,root) %{_bindir}/%{name}-external-ipallocator.py -%attr(0755,root,root) %{_initrddir}/%{name}-ipallocator -%dir %attr(0770,root,root) %{_localstatedir}/log/%{name}/ipallocator -%{_defaultdocdir}/%{name}-management-%{version}/LICENSE -%{_defaultdocdir}/%{name}-management-%{version}/NOTICE -#%attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-catalina -%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-management -%{_datadir}/%{name}-management/setup/wheel/*.whl - -%files agent -%attr(0755,root,root) %{_bindir}/%{name}-setup-agent -%attr(0755,root,root) %{_bindir}/%{name}-agent-upgrade -%attr(0755,root,root) %{_bindir}/%{name}-guest-tool -%attr(0755,root,root) %{_bindir}/%{name}-ssh -%attr(0644,root,root) %{_unitdir}/%{name}-agent.service -%attr(0644,root,root) %{_unitdir}/%{name}-rolling-maintenance@.service -%config(noreplace) %{_sysconfdir}/default/%{name}-agent -%attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh -%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent -%attr(0755,root,root) %{_datadir}/%{name}-common/scripts/network/cisco -%config(noreplace) %{_sysconfdir}/%{name}/agent -%dir %{_localstatedir}/log/%{name}/agent -%attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar -%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook -%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/rolling-maintenance -%dir %{_datadir}/%{name}-agent/plugins -%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE -%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE - -%files common -%dir %attr(0755,root,root) %{python_sitearch}/cloudutils -%dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms -%attr(0755,root,root) %{_datadir}/%{name}-common/scripts -%attr(0755,root,root) /usr/bin/cloudstack-sccs -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh -%attr(0644,root,root) %{python_sitearch}/cloud_utils.py -%attr(0644,root,root) %{python_sitearch}/__pycache__/* -%attr(0644,root,root) %{python_sitearch}/cloudutils/* -%attr(0644, root, root) %{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar -%attr(0644, root, root) %{_datadir}/%{name}-common/lib/%{name}-utils.jar -%{_defaultdocdir}/%{name}-common-%{version}/LICENSE -%{_defaultdocdir}/%{name}-common-%{version}/NOTICE - -%files ui -%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/ui/config.json -%{_datadir}/%{name}-ui/* -%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE -%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE - -%files usage -%attr(0644,root,root) %{_unitdir}/%{name}-usage.service -%config(noreplace) %{_sysconfdir}/default/%{name}-usage -%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-usage -%attr(0644,root,root) %{_datadir}/%{name}-usage/*.jar -%attr(0644,root,root) %{_datadir}/%{name}-usage/lib/*.jar -%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/usage -%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/db.properties -%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/log4j-cloud.xml -%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE -%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE - -%files cli -%attr(0644,root,root) %{python_sitearch}/cloudapis.py -%attr(0644,root,root) %{python_sitearch}/cloudtool/__init__.py -%attr(0644,root,root) %{python_sitearch}/cloudtool/utils.py -%{_defaultdocdir}/%{name}-cli-%{version}/LICENSE -%{_defaultdocdir}/%{name}-cli-%{version}/NOTICE - -%files marvin -%attr(0644,root,root) %{_datadir}/%{name}-marvin/Marvin*.tar.gz -%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE -%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE - -%files integration-tests -%attr(0755,root,root) %{_datadir}/%{name}-integration-tests/* -%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE -%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE - -%if "%{_ossnoss}" == "noredist" -%files mysql-ha -%defattr(0644,cloud,cloud,0755) -%attr(0644,root,root) %{_datadir}/%{name}-mysql-ha/lib/* -%endif - -%files baremetal-agent -%attr(0755,root,root) %{_bindir}/cloudstack-setup-baremetal - -%changelog -* Fri Oct 14 2022 Daan Hoogland 4.18.0 -- initialising sanity check pointer file - -* Thu Apr 30 2015 Rohit Yadav 4.6.0 -- Remove awsapi package - -* Wed Nov 19 2014 Hugo Trippaers 4.6.0 -- Create a specific spec for CentOS 7 - -* Fri Jul 4 2014 Hugo Trippaers 4.5.0 -- Add a package for the mysql ha module - -* Fri Oct 5 2012 Hugo Trippaers 4.1.0 -- new style spec file diff --git a/packaging/centos7/replace.properties b/packaging/centos7/replace.properties deleted file mode 100644 index 18b4a770a88..00000000000 --- a/packaging/centos7/replace.properties +++ /dev/null @@ -1,59 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -DBUSER=cloud -DBPW=cloud -DBROOTPW= -MSLOG=vmops.log -APISERVERLOG=api.log -DBHOST=localhost -DBDRIVER=jdbc:mysql -COMPONENTS-SPEC=components-premium.xml -REMOTEHOST=localhost -AGENTCLASSPATH= -AGENTLOG=/var/log/cloudstack/agent/agent.log -AGENTLOGDIR=/var/log/cloudstack/agent/ -AGENTSYSCONFDIR=/etc/cloudstack/agent -APISERVERLOG=/var/log/cloudstack/management/apilog.log -BINDIR=/usr/bin -COMMONLIBDIR=/usr/share/cloudstack-common -CONFIGUREVARS= -DEPSCLASSPATH= -DOCDIR= -IPALOCATORLOG=/var/log/cloudstack/management/ipallocator.log -JAVADIR=/usr/share/java -LIBEXECDIR=/usr/libexec -LOCKDIR=/var/lock -MSCLASSPATH= -MSCONF=/etc/cloudstack/management -MSENVIRON=/usr/share/cloudstack-management -MSLOG=/var/log/cloudstack/management/management-server.log -MSLOGDIR=/var/log/cloudstack/management/ -MSMNTDIR=/var/cloudstack/mnt -MSUSER=cloud -PIDDIR=/var/run -PLUGINJAVADIR=/usr/share/cloudstack-management/plugin -PREMIUMJAVADIR=/usr/share/cloudstack-management/premium -PYTHONDIR=/usr/lib/python3.6/site-packages/ -SERVERSYSCONFDIR=/etc/sysconfig -SETUPDATADIR=/usr/share/cloudstack-management/setup -SYSCONFDIR=/etc/sysconfig -SYSTEMCLASSPATH= -SYSTEMJARS= -USAGECLASSPATH= -USAGELOG=/var/log/cloudstack/usage/usage.log -USAGESYSCONFDIR=/etc/sysconfig diff --git a/packaging/centos8 b/packaging/centos8 new file mode 120000 index 00000000000..4dad90d45e0 --- /dev/null +++ b/packaging/centos8 @@ -0,0 +1 @@ +el8 \ No newline at end of file diff --git a/packaging/centos8/cloud-ipallocator.rc b/packaging/centos8/cloud-ipallocator.rc deleted file mode 100755 index 255725b94d0..00000000000 --- a/packaging/centos8/cloud-ipallocator.rc +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# chkconfig: 35 99 10 -# description: Cloud Agent - -# WARNING: if this script is changed, then all other initscripts MUST BE changed to match it as well - -. /etc/rc.d/init.d/functions - -# set environment variables - -SHORTNAME="$(basename $(readlink -f $0))" -PIDFILE=/var/run/"$SHORTNAME".pid -LOCKFILE=/var/lock/subsys/"$SHORTNAME" -LOGFILE=/var/log/cloudstack/ipallocator/ipallocator.log -PROGNAME="External IPAllocator" - -unset OPTIONS -[ -r /etc/sysconfig/"$SHORTNAME" ] && source /etc/sysconfig/"$SHORTNAME" -DAEMONIZE=/usr/bin/cloud-daemonize -PROG=/usr/bin/cloud-external-ipallocator.py -OPTIONS=8083 - -start() { - echo -n $"Starting $PROGNAME: " - if hostname --fqdn >/dev/null 2>&1 ; then - daemon --check=$SHORTNAME --pidfile=${PIDFILE} "$DAEMONIZE" \ - -n "$SHORTNAME" -p "$PIDFILE" -l "$LOGFILE" "$PROG" $OPTIONS - RETVAL=$? - echo - else - failure - echo - echo The host name does not resolve properly to an IP address. Cannot start "$PROGNAME". > /dev/stderr - RETVAL=9 - fi - [ $RETVAL = 0 ] && touch ${LOCKFILE} - return $RETVAL -} - -stop() { - echo -n $"Stopping $PROGNAME: " - killproc -p ${PIDFILE} $SHORTNAME # -d 10 $SHORTNAME - RETVAL=$? - echo - [ $RETVAL = 0 ] && rm -f ${LOCKFILE} ${PIDFILE} -} - - -# See how we were called. -case "$1" in - start) - start - ;; - stop) - stop - ;; - status) - status -p ${PIDFILE} $SHORTNAME - RETVAL=$? - ;; - restart) - stop - sleep 3 - start - ;; - condrestart) - if status -p ${PIDFILE} $SHORTNAME >&/dev/null; then - stop - sleep 3 - start - fi - ;; - *) - echo $"Usage: $SHORTNAME {start|stop|restart|condrestart|status|help}" - RETVAL=3 -esac - -exit $RETVAL diff --git a/packaging/centos8/cloud.limits b/packaging/centos8/cloud.limits deleted file mode 100644 index 7debeb29018..00000000000 --- a/packaging/centos8/cloud.limits +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -cloud hard nofile 4096 -cloud soft nofile 4096 diff --git a/packaging/centos8/cloudstack-agent.te b/packaging/centos8/cloudstack-agent.te deleted file mode 100644 index 4259e173a46..00000000000 --- a/packaging/centos8/cloudstack-agent.te +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -module cloudstack-agent 1.0; - -require { - type nfs_t; - type system_conf_t; - type mount_t; - type qemu_t; - class file unlink; - class filesystem getattr; -} - -#============= mount_t ============== -allow mount_t system_conf_t:file unlink; - -#============= qemu_t ============== -allow qemu_t nfs_t:filesystem getattr; diff --git a/packaging/centos8/cloudstack-sccs b/packaging/centos8/cloudstack-sccs deleted file mode 100644 index e05d372c7fe..00000000000 --- a/packaging/centos8/cloudstack-sccs +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -cat /usr/share/cloudstack-common/scripts/gitrev.txt diff --git a/packaging/centos7/cloud-ipallocator.rc b/packaging/el8/cloud-ipallocator.rc similarity index 100% rename from packaging/centos7/cloud-ipallocator.rc rename to packaging/el8/cloud-ipallocator.rc diff --git a/packaging/centos7/cloud.limits b/packaging/el8/cloud.limits similarity index 100% rename from packaging/centos7/cloud.limits rename to packaging/el8/cloud.limits diff --git a/packaging/centos8/cloud.spec b/packaging/el8/cloud.spec similarity index 98% rename from packaging/centos8/cloud.spec rename to packaging/el8/cloud.spec index 3b801f0f1f3..adc8c2e2b35 100644 --- a/packaging/centos8/cloud.spec +++ b/packaging/el8/cloud.spec @@ -36,7 +36,7 @@ Group: System Environment/Libraries Source0: %{name}-%{_maventag}.tgz BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build -BuildRequires: java-11-openjdk-devel +BuildRequires: (java-11-openjdk-devel or java-17-openjdk-devel) #BuildRequires: ws-commons-util BuildRequires: jpackage-utils BuildRequires: gcc @@ -181,7 +181,7 @@ echo Doing CloudStack build %build -cp packaging/centos8/replace.properties build/replace.properties +cp packaging/el8/replace.properties build/replace.properties echo VERSION=%{_maventag} >> build/replace.properties echo PACKAGE=%{name} >> build/replace.properties touch build/gitrev.txt @@ -232,7 +232,7 @@ cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-s python3 -m py_compile ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py python3 -m compileall ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloudutils cp build/gitrev.txt ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -cp packaging/centos8/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin +cp packaging/el8/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco cp -r plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco @@ -290,8 +290,8 @@ install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/ install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar -install -D packaging/centos8/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator -install -D packaging/centos8/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud +install -D packaging/el8/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator +install -D packaging/el8/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management diff --git a/packaging/centos7/cloudstack-agent.te b/packaging/el8/cloudstack-agent.te similarity index 100% rename from packaging/centos7/cloudstack-agent.te rename to packaging/el8/cloudstack-agent.te diff --git a/packaging/centos7/cloudstack-sccs b/packaging/el8/cloudstack-sccs similarity index 100% rename from packaging/centos7/cloudstack-sccs rename to packaging/el8/cloudstack-sccs diff --git a/packaging/centos8/replace.properties b/packaging/el8/replace.properties similarity index 100% rename from packaging/centos8/replace.properties rename to packaging/el8/replace.properties diff --git a/packaging/el9 b/packaging/el9 index 575742e7ff2..4dad90d45e0 120000 --- a/packaging/el9 +++ b/packaging/el9 @@ -1 +1 @@ -centos8 \ No newline at end of file +el8 \ No newline at end of file diff --git a/packaging/package.sh b/packaging/package.sh index bf95f84a11a..ecffaace48b 100755 --- a/packaging/package.sh +++ b/packaging/package.sh @@ -22,11 +22,11 @@ Usage: ./package.sh -d DISTRO [OPTIONS]... Package CloudStack for specific distribution and provided options. If there's a "branding" string in the POM version (e.g. x.y.z.a-NAME[-SNAPSHOT]), the branding name will -be used in the final generated package like: cloudstack-management-x.y.z.a-NAME.NUMBER.el7.centos.x86_64 +be used in the final generated package like: cloudstack-management-x.y.z.a-NAME.NUMBER.el.x86_64 note that you can override/provide "branding" string with "-b, --brand" flag as well. Mandatory arguments: - -d, --distribution string Build package for specified distribution ("centos7") + -d, --distribution string Build package for specified distribution ("el8") Optional arguments: -p, --pack string Define which type of libraries to package ("oss"|"OSS"|"noredist"|"NOREDIST") (default "oss") @@ -42,12 +42,12 @@ Other arguments: -h, --help Display this help message and exit Examples: - package.sh --distribution centos7 - package.sh --distribution centos7 --pack oss - package.sh --distribution centos7 --pack noredist - package.sh --distribution centos7 --pack noredist -t "kvm,xen" - package.sh --distribution centos7 --release 42 - package.sh --distribution centos7 --pack noredist --release 42 + package.sh --distribution el8 + package.sh --distribution el8 --pack oss + package.sh --distribution el8 --pack noredist + package.sh --distribution el8 --pack noredist -t "kvm,xen" + package.sh --distribution el8 --release 42 + package.sh --distribution el8 --pack noredist --release 42 USAGE exit 0 diff --git a/packaging/suse15 b/packaging/suse15 index 575742e7ff2..4dad90d45e0 120000 --- a/packaging/suse15 +++ b/packaging/suse15 @@ -1 +1 @@ -centos8 \ No newline at end of file +el8 \ No newline at end of file diff --git a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java index 7f316fe7a91..4a5bbe8e787 100644 --- a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java @@ -23,9 +23,14 @@ import java.util.Map; import java.util.Set; import java.util.HashSet; import java.util.ArrayList; +import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; import org.apache.commons.collections.CollectionUtils; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; @@ -54,9 +59,16 @@ public class HostAffinityProcessor extends AffinityProcessorBase implements Affi VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); if (CollectionUtils.isNotEmpty(vmGroupMappings)) { - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - processAffinityGroup(vmGroupMapping, plan, vm, vmList); - } + List affinityGroupIdList = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIdList, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + processAffinityGroup(vmGroupMapping, plan, vm, vmList); + } + } + }); } } @@ -130,16 +142,23 @@ public class HostAffinityProcessor extends AffinityProcessorBase implements Affi long plannedHostId = plannedDestination.getHost().getId(); VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); - - if (CollectionUtils.isNotEmpty(vmGroupMappings)) { - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { - return false; - } - } + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return true; } + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { + return false; + } - return true; + } + return true; + } + }); } /** diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java index 9feeeed2b6d..e724f02fe98 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -19,10 +19,12 @@ package org.apache.cloudstack.affinity; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.commons.collections.CollectionUtils; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; @@ -37,6 +39,10 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.exception.AffinityConflictException; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; @@ -65,40 +71,54 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - if (vmGroupMapping != null) { - AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); - - if (logger.isDebugEnabled()) { - logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return; + } + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + processAffinityGroup(vmGroupMapping, avoid, vm); } + } + }); - List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); - groupVMIds.remove(vm.getId()); + } - for (Long groupVMId : groupVMIds) { - VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); - if (groupVM != null && !groupVM.isRemoved()) { - if (groupVM.getHostId() != null) { - avoid.addHost(groupVM.getHostId()); - if (logger.isDebugEnabled()) { - logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host"); - } - } else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) { - long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; - if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { - avoid.addHost(groupVM.getLastHostId()); - if (logger.isDebugEnabled()) { - logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() + - " is present on the host, in Stopped state but has reserved capacity"); - } - } + protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, ExcludeList avoid, VirtualMachine vm) { + if (vmGroupMapping != null) { + AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); + + if (logger.isDebugEnabled()) { + logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + } + + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); + if (groupVM != null && !groupVM.isRemoved()) { + if (groupVM.getHostId() != null) { + avoid.addHost(groupVM.getHostId()); + if (logger.isDebugEnabled()) { + logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host"); + } + } + } else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { + avoid.addHost(groupVM.getLastHostId()); + if (logger.isDebugEnabled()) { + logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() + + " is present on the host, in Stopped state but has reserved capacity"); } } } } } - } @Override @@ -119,6 +139,9 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return true; + } for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { // if more than 1 VM's are present in the group then check for @@ -137,7 +160,32 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements } } } - return true; + + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + // if more than 1 VM's are present in the group then check for + // conflict due to parallel deployment + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId); + if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) { + if (logger.isDebugEnabled()) { + logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() + + " reserved on the same host " + plannedHostId); + } + return false; + } + } + } + return true; + } + }); } } diff --git a/plugins/api/rate-limit/pom.xml b/plugins/api/rate-limit/pom.xml index 73bdd0697d1..2449a23f2d0 100644 --- a/plugins/api/rate-limit/pom.xml +++ b/plugins/api/rate-limit/pom.xml @@ -34,7 +34,7 @@ maven-surefire-plugin always - @{argLine} -Xmx2048m -XX:MaxPermSize=1024m + @{argLine} -Xmx2048m -XX:MaxMetaspaceSize=1024m org/apache/cloudstack/ratelimit/integration/* diff --git a/plugins/database/quota/pom.xml b/plugins/database/quota/pom.xml index 9dada4128a5..b574b263020 100644 --- a/plugins/database/quota/pom.xml +++ b/plugins/database/quota/pom.xml @@ -62,5 +62,10 @@ joda-time ${cs.joda-time.version}
+ + org.apache.cloudstack + cloud-plugin-api-discovery + ${project.version} +
diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java index 137f42536df..f1fd4b4afe1 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java @@ -54,10 +54,7 @@ public class QuotaTariffCreateCmd extends BaseCmd { @Parameter(name = "value", type = CommandType.DOUBLE, required = true, description = "The quota tariff value of the resource as per the default unit.") private Double value; - @Parameter(name = ApiConstants.ACTIVATION_RULE, type = CommandType.STRING, description = "Quota tariff's activation rule. It can receive a JS script that results in either " + - "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + - "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + - "value will be applied.", length = 65535) + @Parameter(name = ApiConstants.ACTIVATION_RULE, type = CommandType.STRING, description = ApiConstants.PARAMETER_DESCRIPTION_ACTIVATION_RULE, length = 65535) private String activationRule; @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "The effective start date on/after which the quota tariff is effective. Inform null to " + @@ -80,7 +77,7 @@ public class QuotaTariffCreateCmd extends BaseCmd { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create new quota tariff."); } - QuotaTariffResponse response = responseBuilder.createQuotaTariffResponse(result); + QuotaTariffResponse response = responseBuilder.createQuotaTariffResponse(result, true); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java index b4e8c868e40..d054d545931 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java @@ -17,15 +17,18 @@ package org.apache.cloudstack.api.command; import com.cloud.user.Account; +import com.cloud.user.User; import com.cloud.utils.Pair; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.QuotaResponseBuilder; import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -59,20 +62,29 @@ public class QuotaTariffListCmd extends BaseListCmd { + "list all, including the removed ones. The default is false.", since = "4.18.0.0") private boolean listAll = false; - public QuotaTariffListCmd() { - super(); - } + @Parameter(name = ApiConstants.LIST_ONLY_REMOVED, type = CommandType.BOOLEAN, description = "If set to true, we will list only the removed tariffs." + + " The default is false.") + private boolean listOnlyRemoved = false; + + @Parameter(name = ApiConstants.ID, type = CommandType.STRING, description = "The quota tariff's id.", validations = {ApiArgValidator.UuidString}) + private String id; @Override public void execute() { final Pair, Integer> result = _responseBuilder.listQuotaTariffPlans(this); + User user = CallContext.current().getCallingUser(); + boolean returnActivationRules = _responseBuilder.isUserAllowedToSeeActivationRules(user); + if (!returnActivationRules) { + logger.debug("User [{}] does not have permission to create or update quota tariffs, therefore we will not return the activation rules.", user.getUuid()); + } + final List responses = new ArrayList<>(); - logger.trace(String.format("Adding quota tariffs [%s] to response of API quotaTariffList.", ReflectionToStringBuilderUtils.reflectCollection(responses))); + logger.trace("Adding quota tariffs [{}] to response of API quotaTariffList.", ReflectionToStringBuilderUtils.reflectCollection(responses)); for (final QuotaTariffVO resource : result.first()) { - responses.add(_responseBuilder.createQuotaTariffResponse(resource)); + responses.add(_responseBuilder.createQuotaTariffResponse(resource, returnActivationRules)); } final ListResponse response = new ListResponse<>(); @@ -106,4 +118,15 @@ public class QuotaTariffListCmd extends BaseListCmd { return listAll; } + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public boolean isListOnlyRemoved() { + return listOnlyRemoved; + } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java index 6370cc57e4e..b5766875507 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java @@ -63,10 +63,8 @@ public class QuotaTariffUpdateCmd extends BaseCmd { since = "4.18.0.0") private String description; - @Parameter(name = ApiConstants.ACTIVATION_RULE, type = CommandType.STRING, description = "Quota tariff's activation rule. It can receive a JS script that results in either " + - "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + - "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + - "value will be applied. Inform empty to remove the activation rule.", length = 65535, since = "4.18.0.0") + @Parameter(name = ApiConstants.ACTIVATION_RULE, type = CommandType.STRING, description = ApiConstants.PARAMETER_DESCRIPTION_ACTIVATION_RULE + + " Inform empty to remove the activation rule.", length = 65535, since = "4.18.0.0") private String activationRule; @Parameter(name = ApiConstants.POSITION, type = CommandType.INTEGER, description = "Position in the execution sequence for tariffs of the same type", since = "4.20.0.0") @@ -119,7 +117,7 @@ public class QuotaTariffUpdateCmd extends BaseCmd { if (result == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update quota tariff plan"); } - final QuotaTariffResponse response = _responseBuilder.createQuotaTariffResponse(result); + final QuotaTariffResponse response = _responseBuilder.createQuotaTariffResponse(result, true); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java index ecbb809b60b..c635551aeb5 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java @@ -16,6 +16,7 @@ //under the License. package org.apache.cloudstack.api.response; +import com.cloud.user.User; import org.apache.cloudstack.api.command.QuotaBalanceCmd; import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; @@ -41,7 +42,9 @@ public interface QuotaResponseBuilder { Pair, Integer> listQuotaTariffPlans(QuotaTariffListCmd cmd); - QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO configuration); + QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO quotaTariff, boolean returnActivationRule); + + boolean isUserAllowedToSeeActivationRules(User user); QuotaStatementResponse createQuotaStatementResponse(List quotaUsage); diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java index 88e90cc9ba9..1c486759e43 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java @@ -52,6 +52,7 @@ import org.apache.cloudstack.api.command.QuotaTariffCreateCmd; import org.apache.cloudstack.api.command.QuotaTariffListCmd; import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.discovery.ApiDiscoveryService; import org.apache.cloudstack.quota.QuotaManager; import org.apache.cloudstack.quota.QuotaManagerImpl; import org.apache.cloudstack.quota.QuotaService; @@ -135,8 +136,11 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class}; + @Inject + private ApiDiscoveryService apiDiscoveryService; + @Override - public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff) { + public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff, boolean returnActivationRule) { final QuotaTariffResponse response = new QuotaTariffResponse(); response.setUsageType(tariff.getUsageType()); response.setUsageName(tariff.getUsageName()); @@ -146,13 +150,15 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { response.setEffectiveOn(tariff.getEffectiveOn()); response.setUsageTypeDescription(tariff.getUsageTypeDescription()); response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); - response.setActivationRule(tariff.getActivationRule()); response.setName(tariff.getName()); response.setEndDate(tariff.getEndDate()); response.setDescription(tariff.getDescription()); response.setId(tariff.getUuid()); response.setRemoved(tariff.getRemoved()); response.setPosition(tariff.getPosition()); + if (returnActivationRule) { + response.setActivationRule(tariff.getActivationRule()); + } return response; } @@ -228,6 +234,11 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { } } + public boolean isUserAllowedToSeeActivationRules(User user) { + List apiList = (List) apiDiscoveryService.listApis(user, null).getResponses(); + return apiList.stream().anyMatch(response -> StringUtils.equalsAny(response.getName(), "quotaTariffCreate", "quotaTariffUpdate")); + } + @Override public QuotaBalanceResponse createQuotaBalanceResponse(List quotaBalance, Date startDate, Date endDate) { if (quotaBalance == null || quotaBalance.isEmpty()) { @@ -400,11 +411,14 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { boolean listAll = cmd.isListAll(); Long startIndex = cmd.getStartIndex(); Long pageSize = cmd.getPageSizeVal(); + String uuid = cmd.getId(); + boolean listOnlyRemoved = cmd.isListOnlyRemoved(); + String keyword = cmd.getKeyword(); - logger.debug(String.format("Listing quota tariffs for parameters [%s].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "effectiveDate", - "endDate", "listAll", "name", "page", "pageSize", "usageType"))); + logger.debug("Listing quota tariffs for parameters [{}].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "effectiveDate", + "endDate", "listAll", "name", "page", "pageSize", "usageType", "uuid", "listOnlyRemoved", "keyword")); - return _quotaTariffDao.listQuotaTariffs(startDate, endDate, usageType, name, null, listAll, startIndex, pageSize); + return _quotaTariffDao.listQuotaTariffs(startDate, endDate, usageType, name, uuid, listAll, listOnlyRemoved, startIndex, pageSize, keyword); } @Override diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java index f5ce92ae014..a98d3d611de 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java @@ -16,15 +16,18 @@ // under the License. package org.apache.cloudstack.api.command; +import com.cloud.user.User; import junit.framework.TestCase; import org.apache.cloudstack.api.response.QuotaResponseBuilder; import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.quota.constant.QuotaTypes; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; @@ -40,6 +43,12 @@ public class QuotaTariffListCmdTest extends TestCase { @Mock QuotaResponseBuilder responseBuilder; + @Mock + User userMock; + + @Mock + CallContext callContextMock; + @Test public void testQuotaTariffListCmd() throws NoSuchFieldException, IllegalAccessException { QuotaTariffListCmd cmd = new QuotaTariffListCmd(); @@ -48,17 +57,24 @@ public class QuotaTariffListCmdTest extends TestCase { rbField.setAccessible(true); rbField.set(cmd, responseBuilder); - List quotaTariffVOList = new ArrayList(); + List quotaTariffVOList = new ArrayList<>(); QuotaTariffVO tariff = new QuotaTariffVO(); tariff.setEffectiveOn(new Date()); tariff.setCurrencyValue(new BigDecimal(100)); tariff.setUsageType(QuotaTypes.VOLUME); quotaTariffVOList.add(new QuotaTariffVO()); - Mockito.when(responseBuilder.listQuotaTariffPlans(Mockito.eq(cmd))).thenReturn(new Pair<>(quotaTariffVOList, quotaTariffVOList.size())); - Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class))).thenReturn(new QuotaTariffResponse()); - cmd.execute(); - Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class)); + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + Mockito.when(responseBuilder.listQuotaTariffPlans(Mockito.eq(cmd))).thenReturn(new Pair<>(quotaTariffVOList, quotaTariffVOList.size())); + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(callContextMock.getCallingUser()).thenReturn(userMock); + Mockito.when(responseBuilder.isUserAllowedToSeeActivationRules(userMock)).thenReturn(true); + Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class), Mockito.eq(true))).thenReturn(new QuotaTariffResponse()); + + cmd.execute(); + } + + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class), Mockito.eq(true)); } } diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java index 22d78d6794e..7a4d1a75356 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java @@ -60,8 +60,8 @@ public class QuotaTariffUpdateCmdTest extends TestCase { } Mockito.when(responseBuilder.updateQuotaTariffPlan(Mockito.eq(cmd))).thenReturn(tariff); - Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.eq(tariff))).thenReturn(new QuotaTariffResponse()); + Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.eq(tariff), Mockito.eq(true))).thenReturn(new QuotaTariffResponse()); cmd.execute(); - Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.eq(tariff)); + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.eq(tariff), Mockito.eq(true)); } } diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java index 71e38a5ab8c..fd359525893 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java @@ -55,7 +55,10 @@ import org.apache.cloudstack.quota.vo.QuotaCreditsVO; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.discovery.ApiDiscoveryService; + import org.apache.commons.lang3.time.DateUtils; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -69,6 +72,7 @@ import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; +import com.cloud.user.User; import junit.framework.TestCase; import org.mockito.junit.MockitoJUnitRunner; @@ -91,6 +95,12 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock UserDao userDaoMock; + @Mock + User userMock; + + @Mock + ApiDiscoveryService discoveryServiceMock; + @Mock QuotaService quotaServiceMock; @@ -164,11 +174,29 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Test public void testQuotaResponse() { QuotaTariffVO tariffVO = makeTariffTestData(); - QuotaTariffResponse response = quotaResponseBuilderSpy.createQuotaTariffResponse(tariffVO); + QuotaTariffResponse response = quotaResponseBuilderSpy.createQuotaTariffResponse(tariffVO, true); assertTrue(tariffVO.getUsageType() == response.getUsageType()); assertTrue(tariffVO.getCurrencyValue().equals(response.getTariffValue())); } + @Test + public void createQuotaTariffResponseTestIfReturnsActivationRuleWithPermission() { + QuotaTariffVO tariff = makeTariffTestData(); + tariff.setActivationRule("x === 10"); + + QuotaTariffResponse tariffResponse = quotaResponseBuilderSpy.createQuotaTariffResponse(tariff, true); + assertEquals("x === 10", tariffResponse.getActivationRule()); + } + + @Test + public void createQuotaTariffResponseTestIfReturnsActivationRuleWithoutPermission() { + QuotaTariffVO tariff = makeTariffTestData(); + tariff.setActivationRule("x === 10"); + + QuotaTariffResponse tariffResponse = quotaResponseBuilderSpy.createQuotaTariffResponse(tariff, false); + assertNull(tariffResponse.getActivationRule()); + } + @Test public void testAddQuotaCredits() { final long accountId = 2L; @@ -569,4 +597,52 @@ public class QuotaResponseBuilderImplTest extends TestCase { Mockito.verify(quotaTariffVoMock).setPosition(position); } + + @Test + public void isUserAllowedToSeeActivationRulesTestWithPermissionToCreateTariff() { + ApiDiscoveryResponse response = new ApiDiscoveryResponse(); + response.setName("quotaTariffCreate"); + + List cmdList = new ArrayList<>(); + cmdList.add(response); + + ListResponse responseList = new ListResponse<>(); + responseList.setResponses(cmdList); + + Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null); + + assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock)); + } + + @Test + public void isUserAllowedToSeeActivationRulesTestWithPermissionToUpdateTariff() { + ApiDiscoveryResponse response = new ApiDiscoveryResponse(); + response.setName("quotaTariffUpdate"); + + List cmdList = new ArrayList<>(); + cmdList.add(response); + + ListResponse responseList = new ListResponse<>(); + responseList.setResponses(cmdList); + + Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null); + + assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock)); + } + + @Test + public void isUserAllowedToSeeActivationRulesTestWithNoPermission() { + ApiDiscoveryResponse response = new ApiDiscoveryResponse(); + response.setName("testCmd"); + + List cmdList = new ArrayList<>(); + cmdList.add(response); + + ListResponse responseList = new ListResponse<>(); + responseList.setResponses(cmdList); + + Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null); + + assertFalse(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock)); + } } diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/response/WebhookResponse.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/response/WebhookResponse.java index 161b8c5796a..5e1cca36138 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/response/WebhookResponse.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/response/WebhookResponse.java @@ -54,6 +54,10 @@ public class WebhookResponse extends BaseResponse implements ControlledViewEntit @Param(description = "The name of the domain in which the Webhook exists") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the Webhook belongs") + private String domainPath; + @SerializedName(ApiConstants.ACCOUNT) @Param(description = "The account associated with the Webhook") private String accountName; @@ -107,6 +111,11 @@ public class WebhookResponse extends BaseResponse implements ControlledViewEntit this.domainId = domainId; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + @Override public void setDomainName(String domainName) { this.domainName = domainName; diff --git a/plugins/hypervisors/hyperv/pom.xml b/plugins/hypervisors/hyperv/pom.xml index b24c4c8a847..56b2b6d1503 100644 --- a/plugins/hypervisors/hyperv/pom.xml +++ b/plugins/hypervisors/hyperv/pom.xml @@ -55,7 +55,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} java com.cloud.agent.AgentShell @@ -137,6 +137,7 @@ org.codehaus.mojo exec-maven-plugin + ${cs.exec-maven-plugin.version} compile 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 dec7f70e62f..5d964509215 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 @@ -2838,6 +2838,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv GuestDef guest = new GuestDef(); configureGuestAndVMHypervisorType(vmTO, vm, guest); + guest.setManufacturer(vmTO.getMetadataManufacturer()); + guest.setProduct(vmTO.getMetadataProductName()); guest.setGuestArch(guestCpuArch != null ? guestCpuArch : vmTO.getArch()); guest.setMachineType(isGuestAarch64() ? VIRT : PC); guest.setBootType(GuestDef.BootType.BIOS); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index a0394430643..c1ea3e99717 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -95,6 +95,8 @@ public class LibvirtVMDef { } private GuestType _type; + private String manufacturer; + private String product; private BootType _boottype; private BootMode _bootmode; private String _arch; @@ -124,6 +126,22 @@ public class LibvirtVMDef { return _type; } + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + public void setNvram(String nvram) { _nvram = nvram; } public void setNvramTemplate(String nvramTemplate) { _nvramTemplate = nvramTemplate; } @@ -182,8 +200,8 @@ public class LibvirtVMDef { guestDef.append("\n"); guestDef.append("\n"); - guestDef.append("Apache Software Foundation\n"); - guestDef.append("CloudStack " + _type.toString() + " Hypervisor\n"); + guestDef.append("" + getManufacturer() +"\n"); + guestDef.append("" + getProduct() + "\n"); guestDef.append("" + _uuid + "\n"); guestDef.append("\n"); guestDef.append("\n"); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index 6b9e52c3e26..c0f6e44cddd 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -773,6 +773,42 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { } } + private boolean destroyStoragePool(Connect conn, String uuid) throws LibvirtException { + StoragePool sp; + try { + sp = conn.storagePoolLookupByUUIDString(uuid); + } catch (LibvirtException exc) { + logger.warn("Storage pool " + uuid + " doesn't exist in libvirt. Assuming it is already removed"); + logger.warn(exc.getStackTrace()); + return true; + } + + if (sp != null) { + if (sp.isPersistent() == 1) { + sp.destroy(); + sp.undefine(); + } else { + sp.destroy(); + } + sp.free(); + + return true; + } else { + logger.warn("Storage pool " + uuid + " doesn't exist in libvirt. Assuming it is already removed"); + return false; + } + } + + private boolean destroyStoragePoolHandleException(Connect conn, String uuid) + { + try { + return destroyStoragePool(conn, uuid); + } catch (LibvirtException e) { + logger.error(String.format("Failed to destroy libvirt pool %s: %s", uuid, e)); + } + return false; + } + @Override public boolean deleteStoragePool(String uuid) { logger.info("Attempting to remove storage pool " + uuid + " from libvirt"); @@ -783,16 +819,8 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { throw new CloudRuntimeException(e.toString()); } - StoragePool sp = null; Secret s = null; - try { - sp = conn.storagePoolLookupByUUIDString(uuid); - } catch (LibvirtException e) { - logger.warn("Storage pool " + uuid + " doesn't exist in libvirt. Assuming it is already removed"); - return true; - } - /* * Some storage pools, like RBD also have 'secret' information stored in libvirt * Destroy them if they exist @@ -804,13 +832,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { } try { - if (sp.isPersistent() == 1) { - sp.destroy(); - sp.undefine(); - } else { - sp.destroy(); - } - sp.free(); + destroyStoragePool(conn, uuid); if (s != null) { s.undefine(); s.free(); @@ -828,6 +850,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { String result = Script.runSimpleBashScript("sleep 5 && umount " + targetPath); if (result == null) { logger.info("Succeeded in unmounting " + targetPath); + destroyStoragePoolHandleException(conn, uuid); return true; } logger.error("Failed to unmount " + targetPath); diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java index e061f1e8952..81daabf59d7 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.kvm.ha; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor; @@ -36,7 +37,6 @@ import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; import org.joda.time.DateTime; import javax.inject.Inject; -import java.security.InvalidParameterException; public final class KVMHAProvider extends HAAbstractHostProvider implements HAProvider, Configurable { @@ -129,7 +129,7 @@ public final class KVMHAProvider extends HAAbstractHostProvider implements HAPro case MaxDegradedWaitTimeout: return KVMHAConfig.KvmHADegradedMaxPeriod.valueIn(clusterId); default: - throw new InvalidParameterException("Unknown HAProviderConfig " + name.toString()); + throw new InvalidParameterValueException("Unknown HAProviderConfig " + name.toString()); } } diff --git a/plugins/hypervisors/simulator/src/main/java/org/apache/cloudstack/ha/SimulatorHAProvider.java b/plugins/hypervisors/simulator/src/main/java/org/apache/cloudstack/ha/SimulatorHAProvider.java index 7d35cfb8899..386b32fabf0 100644 --- a/plugins/hypervisors/simulator/src/main/java/org/apache/cloudstack/ha/SimulatorHAProvider.java +++ b/plugins/hypervisors/simulator/src/main/java/org/apache/cloudstack/ha/SimulatorHAProvider.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.ha; import com.cloud.api.response.SimulatorHAStateResponse; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.fsm.StateListener; @@ -30,7 +31,6 @@ import org.apache.cloudstack.ha.provider.host.HAAbstractHostProvider; import org.joda.time.DateTime; import javax.inject.Inject; -import java.security.InvalidParameterException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -129,7 +129,7 @@ public class SimulatorHAProvider extends HAAbstractHostProvider implements HAPro case RecoveryWaitTimeout: return 1L; default: - throw new InvalidParameterException("Unknown HAProviderConfig " + name.toString()); + throw new InvalidParameterValueException("Unknown HAProviderConfig " + name.toString()); } } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 6f6ae6f41e7..987c7d198d7 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -55,6 +55,7 @@ import com.vmware.vim25.FileQueryFlags; import com.vmware.vim25.FolderFileInfo; import com.vmware.vim25.HostDatastoreBrowserSearchResults; import com.vmware.vim25.HostDatastoreBrowserSearchSpec; +import com.vmware.vim25.VirtualCdromIsoBackingInfo; import com.vmware.vim25.VirtualMachineConfigSummary; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.backup.PrepareForBackupRestorationCommand; @@ -2737,8 +2738,9 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes private DiskTO[] getDisks(DiskTO[] sortedDisks) { return Arrays.stream(sortedDisks).filter(vol -> ((vol.getPath() != null && - vol.getPath().contains("configdrive"))) || (vol.getType() != Volume.Type.ISO)).toArray(DiskTO[]::new); + vol.getPath().contains(ConfigDrive.CONFIGDRIVEDIR))) || (vol.getType() != Volume.Type.ISO)).toArray(DiskTO[]::new); } + private void configureIso(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo, DiskTO vol, VirtualDeviceConfigSpec[] deviceConfigSpecArray, int ideUnitNumber, int i) throws Exception { TemplateObjectTO iso = (TemplateObjectTO) vol.getData(); @@ -4447,6 +4449,8 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes msg = "Have problem in powering off VM " + cmd.getVmName() + ", let the process continue"; logger.warn(msg); } + + disconnectConfigDriveIsoIfExists(vmMo); return new StopAnswer(cmd, msg, true); } @@ -4465,6 +4469,30 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes } } + private void disconnectConfigDriveIsoIfExists(VirtualMachineMO vmMo) { + try { + List isoDevices = vmMo.getIsoDevices(); + if (CollectionUtils.isEmpty(isoDevices)) { + return; + } + + for (VirtualDevice isoDevice : isoDevices) { + if (!(isoDevice.getBacking() instanceof VirtualCdromIsoBackingInfo)) { + continue; + } + String isoFilePath = ((VirtualCdromIsoBackingInfo)isoDevice.getBacking()).getFileName(); + if (!isoFilePath.contains(ConfigDrive.CONFIGDRIVEDIR)) { + continue; + } + logger.info(String.format("Disconnecting config drive at location: %s", isoFilePath)); + vmMo.detachIso(isoFilePath, true); + return; + } + } catch (Exception e) { + logger.warn(String.format("Couldn't check/disconnect config drive, error: %s", e.getMessage()), e); + } + } + protected Answer execute(RebootRouterCommand cmd) { RebootAnswer answer = (RebootAnswer) execute((RebootCommand) cmd); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 151344575dc..06f92181506 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -582,6 +582,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne Domain domain = ApiDBUtils.findDomainById(kubernetesCluster.getDomainId()); response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); response.setKeypair(kubernetesCluster.getKeyPair()); response.setState(kubernetesCluster.getState().toString()); response.setCores(String.valueOf(kubernetesCluster.getCores())); @@ -1626,8 +1627,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne private void updateNodeCount(KubernetesClusterVO kubernetesCluster) { List nodeList = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId()); kubernetesCluster.setControlNodeCount(nodeList.stream().filter(KubernetesClusterVmMapVO::isControlNode).count()); - kubernetesCluster.setNodeCount(nodeList.size()); - kubernetesCluster.setNodeCount(nodeList.size()); + kubernetesCluster.setNodeCount(nodeList.size() - kubernetesCluster.getControlNodeCount()); kubernetesClusterDao.persist(kubernetesCluster); } diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java index 56d7b0c5838..721cb47867b 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.kubernetes.cluster; -import java.security.InvalidParameterException; - import javax.inject.Inject; import com.cloud.exception.InvalidParameterValueException; @@ -227,7 +225,7 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { public Long getNodeRootDiskSize() { if (nodeRootDiskSize != null) { if (nodeRootDiskSize < DEFAULT_NODE_ROOT_DISK_SIZE) { - throw new InvalidParameterException("Provided node root disk size is lesser than default size of " + DEFAULT_NODE_ROOT_DISK_SIZE +"GB"); + throw new InvalidParameterValueException("Provided node root disk size is lesser than default size of " + DEFAULT_NODE_ROOT_DISK_SIZE +"GB"); } return nodeRootDiskSize; } else { diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java index 168dfaf6091..8074aef9eff 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java @@ -98,6 +98,10 @@ public class KubernetesClusterResponse extends BaseResponseWithAnnotations imple @Param(description = "the name of the domain in which the Kubernetes cluster exists") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the domain to which the Kubernetes cluster belongs", since = "4.19.2.0") + private String domainPath; + @SerializedName(ApiConstants.SSH_KEYPAIR) @Param(description = "keypair details") private String keypair; @@ -279,6 +283,10 @@ public class KubernetesClusterResponse extends BaseResponseWithAnnotations imple this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } public String getKeypair() { return keypair; } diff --git a/plugins/network-elements/brocade-vcs/pom.xml b/plugins/network-elements/brocade-vcs/pom.xml index 255c8e6bdfc..1363658d179 100644 --- a/plugins/network-elements/brocade-vcs/pom.xml +++ b/plugins/network-elements/brocade-vcs/pom.xml @@ -32,7 +32,7 @@ org.jvnet.jaxb2.maven2 maven-jaxb2-plugin - 0.14.0 + 0.15.3 interface diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/api/response/ServiceInstanceResponse.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/api/response/ServiceInstanceResponse.java index 00eb7f3098b..a4fa713ee00 100644 --- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/api/response/ServiceInstanceResponse.java +++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/api/response/ServiceInstanceResponse.java @@ -59,6 +59,10 @@ public class ServiceInstanceResponse extends BaseResponse implements ControlledE @Param(description = "the name of the domain in which the virtual machine exists") private String domainName; + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "path of the Domain in which the virtual machine exists", since = "4.19.2.0") + private String domainPath; + public void setId(String id) { this.id = id; } @@ -88,4 +92,8 @@ public class ServiceInstanceResponse extends BaseResponse implements ControlledE this.domainName = domainName; } + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } } 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 610f4aa82aa..5d2efa0dc9a 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 @@ -514,4 +514,9 @@ public class MockAccountManager extends ManagerBase implements AccountManager { public ConfigKey[] getConfigKeys() { return null; } + + @Override + public void checkApiAccess(Account account, String command) throws PermissionDeniedException { + + } } diff --git a/plugins/network-elements/opendaylight/src/main/java/org/apache/cloudstack/network/opendaylight/agent/OpenDaylightControllerResource.java b/plugins/network-elements/opendaylight/src/main/java/org/apache/cloudstack/network/opendaylight/agent/OpenDaylightControllerResource.java index 8ea65f4e729..6b81f8dc38a 100644 --- a/plugins/network-elements/opendaylight/src/main/java/org/apache/cloudstack/network/opendaylight/agent/OpenDaylightControllerResource.java +++ b/plugins/network-elements/opendaylight/src/main/java/org/apache/cloudstack/network/opendaylight/agent/OpenDaylightControllerResource.java @@ -21,7 +21,6 @@ package org.apache.cloudstack.network.opendaylight.agent; import java.net.MalformedURLException; import java.net.URL; -import java.security.InvalidParameterException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -69,6 +68,7 @@ import com.cloud.agent.api.ReadyCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.host.Host; import com.cloud.host.Host.Type; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.resource.ServerResource; public class OpenDaylightControllerResource implements ServerResource { @@ -332,11 +332,11 @@ public class OpenDaylightControllerResource implements ServerResource { private void updateConfiguration() { if (!configuration.containsKey("url") || !configuration.containsKey("username") || !configuration.containsKey("password")) - throw new InvalidParameterException("OpenDaylightControllerResource needs a url, username and password."); + throw new InvalidParameterValueException("OpenDaylightControllerResource needs a url, username and password."); try { controllerUrl = new URL((String)configuration.get("url")); } catch (MalformedURLException e) { - throw new InvalidParameterException("OpenDaylightControllerResource found an invalid controller url"); + throw new InvalidParameterValueException("OpenDaylightControllerResource found an invalid controller url"); } controllerUsername = (String)configuration.get("username"); controllerPassword = (String)configuration.get("password"); diff --git a/plugins/network-elements/tungsten/src/main/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImpl.java b/plugins/network-elements/tungsten/src/main/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImpl.java index cb366959327..60b5b7290a9 100644 --- a/plugins/network-elements/tungsten/src/main/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImpl.java +++ b/plugins/network-elements/tungsten/src/main/java/org/apache/cloudstack/network/tungsten/service/TungstenServiceImpl.java @@ -32,6 +32,7 @@ import com.cloud.dc.dao.DataCenterIpAddressDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -219,7 +220,6 @@ import org.apache.commons.lang3.StringUtils; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -2466,7 +2466,7 @@ public class TungstenServiceImpl extends ManagerBase implements TungstenService boolean success = true; LoadBalancerVO loadBalancer = loadBalancerDao.findById(lbId); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer Id:" + lbId); + throw new InvalidParameterValueException("Invalid Load balancer Id:" + lbId); } if (loadBalancer.getState() == FirewallRule.State.Active) { diff --git a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java index 570ac377c20..6650dad7677 100644 --- a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java +++ b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/util/ElastistorUtil.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.datastore.util; import com.cloud.agent.api.Answer; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.google.gson.Gson; @@ -47,7 +48,6 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import java.net.ConnectException; -import java.security.InvalidParameterException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -196,8 +196,8 @@ public class ElastistorUtil { } catch (InvalidCredentialsException e) { throw new CloudRuntimeException("InvalidCredentialsException:" + e.getMessage(), e); - } catch (InvalidParameterException e) { - throw new CloudRuntimeException("InvalidParameterException:" + e.getMessage(), e); + } catch (InvalidParameterValueException e) { + throw new CloudRuntimeException("InvalidParameterValueException:" + e.getMessage(), e); } catch (SSLHandshakeException e) { throw new CloudRuntimeException("SSLHandshakeException:" + e.getMessage(), e); } catch (ServiceUnavailableException e) { @@ -1050,22 +1050,22 @@ public class ElastistorUtil { private String queryparamapikey = "apikey"; private String queryparamresponse = "response"; - public ElastiCenterClient(String address, String key) throws InvalidCredentialsException, InvalidParameterException, SSLHandshakeException, ServiceUnavailableException { + public ElastiCenterClient(String address, String key) throws InvalidCredentialsException, InvalidParameterValueException, SSLHandshakeException, ServiceUnavailableException { elastiCenterAddress = address; apiKey = key; initialize(); } - public void initialize() throws InvalidParameterException, SSLHandshakeException, InvalidCredentialsException, ServiceUnavailableException { + public void initialize() throws InvalidParameterValueException, SSLHandshakeException, InvalidCredentialsException, ServiceUnavailableException { if (apiKey == null || apiKey.trim().isEmpty()) { - throw new InvalidParameterException("Unable to initialize. Please specify a valid API Key."); + throw new InvalidParameterValueException("Unable to initialize. Please specify a valid API Key."); } if (elastiCenterAddress == null || elastiCenterAddress.trim().isEmpty()) { // TODO : Validate the format, like valid IP address or // hostname. - throw new InvalidParameterException("Unable to initialize. Please specify a valid ElastiCenter IP Address or Hostname."); + throw new InvalidParameterValueException("Unable to initialize. Please specify a valid ElastiCenter IP Address or Hostname."); } if (ignoreSSLCertificate) { @@ -1143,7 +1143,7 @@ public class ElastistorUtil { } if (command == null || command.trim().isEmpty()) { - throw new InvalidParameterException("No command to execute."); + throw new InvalidParameterValueException("No command to execute."); } try { @@ -1175,9 +1175,9 @@ public class ElastistorUtil { if (401 == response.getStatus()) { throw new InvalidCredentialsException("Please specify a valid API Key."); } else if (431 == response.getStatus()) { - throw new InvalidParameterException(response.getHeaders().getFirst("X-Description")); + throw new InvalidParameterValueException(response.getHeaders().getFirst("X-Description")); } else if (432 == response.getStatus()) { - throw new InvalidParameterException(command + " does not exist on the ElastiCenter server. Please specify a valid command or contact your ElastiCenter Administrator."); + throw new InvalidParameterValueException(command + " does not exist on the ElastiCenter server. Please specify a valid command or contact your ElastiCenter Administrator."); } else { throw new ServiceUnavailableException("Internal Error. Please contact your ElastiCenter Administrator."); } diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 97b813669a6..5d037d63433 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -50,6 +50,7 @@ import com.linbit.linstor.api.model.ResourceGroupSpawn; import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.ResourceWithVolumes; import com.linbit.linstor.api.model.StoragePool; +import com.linbit.linstor.api.model.Volume; import com.linbit.linstor.api.model.VolumeDefinition; public class LinstorStorageAdaptor implements StorageAdaptor { @@ -453,6 +454,40 @@ public class LinstorStorageAdaptor implements StorageAdaptor { return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null); } + /** + * Checks if all diskful resource are on a zeroed block device. + * @param destPool Linstor pool to use + * @param resName Linstor resource name + * @return true if all resources are on a provider with zeroed blocks. + */ + private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) { + final DevelopersApi api = getLinstorAPI(destPool); + + try { + List resWithVols = api.viewResources( + Collections.emptyList(), + Collections.singletonList(resName), + Collections.emptyList(), + Collections.emptyList(), + null, + null); + + if (resWithVols != null) { + return resWithVols.stream() + .allMatch(res -> { + Volume vol0 = res.getVolumes().get(0); + return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN || + vol0.getProviderKind() == ProviderKind.ZFS || + vol0.getProviderKind() == ProviderKind.ZFS_THIN || + vol0.getProviderKind() == ProviderKind.DISKLESS); + } ); + } + } catch (ApiException apiExc) { + logger.error(apiExc.getMessage()); + } + return false; + } + @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType) { @@ -470,8 +505,10 @@ public class LinstorStorageAdaptor implements StorageAdaptor { destFile.setFormat(dstDisk.getFormat()); destFile.setSize(disk.getVirtualSize()); + boolean zeroedDevice = resourceSupportZeroBlocks(destPools, LinstorUtil.RSC_PREFIX + name); + try { - final QemuImg qemu = new QemuImg(timeout); + final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true); qemu.convert(srcFile, destFile); } catch (QemuImgException | LibvirtException e) { logger.error(e); diff --git a/plugins/storage/volume/storpool/README.md b/plugins/storage/volume/storpool/README.md index e5b84786c7c..5cad0069621 100644 --- a/plugins/storage/volume/storpool/README.md +++ b/plugins/storage/volume/storpool/README.md @@ -345,6 +345,46 @@ corresponding system disk offering. CloudStack has no way to specify max BW. Do they want to be able to specify max BW only is sufficient. +================================================================================ + +StorPool provides the ‘storpool_qos’ service ([QoS user guide](https://kb.storpool.com/storpool_misc/qos.html#storpool-qos-user-guide)) that tracks and configures the storage tier for all volumes based on a specifically provided `qc` tag specifying the storage tier for each volume. + +To manage the QoS limits with a `qc` tag, you have to add a `qc` tag resource detail to each disk offering to which a tier should be applied, with a key `SP_QOSCLASS` and the value from the configuration file for the `storpool_qos` service: + + add resourcedetail resourceid={diskofferingid} details[0].key=SP_QOSCLASS details[0].value={the name of the tier from the config} resourcetype=DiskOffering + +To change the tier via CloudStack, you can use the CloudStack API call `changeOfferingForVolume`. The size is required, but the user could use the current volume size. Example: + + change offeringforvolume id={The UUID of the Volume} diskofferingid={The UUID of the disk offering} size={The current or a new size for the volume} + +Users who were using the offerings to change the StorPool template via the `SP_TEMPLATE` detail, will continue to have this functionality but should use `changeOfferingForVolume` API call instead of: + - `resizeVolume` API call for DATA disk + - `scaleVirtualMachine` API call for ROOT disk + + +If the disk offering has both `SP_TEMPLATE` and `SP_QOSCLASS` defined, the `SP_QOSCLASS` detail will be prioritised, setting the volume’s QoS using the respective ‘qc’ tag value. In case the QoS for a volume is changed manually, the ‘storpool_qos’ service will automatically reset the QoS limits following the ‘qc’ tag value once per minute. + +

Usage

+ +Creating Disk Offering for each tier. + +Go to Service Offerings > Disk Offering > Add disk offering. + +Add disk offering detail with API call in CloudStack CLI. + + add resourcedetail resourcetype=diskoffering resourceid=$UUID details[0].key=SP_QOSCLASS details[0].value=$Tier Name + + +Creating VM with QoS + +Deploy virtual machine: Go to Compute> Instances> Add Instances. + - For the ROOT volume, choose the option `Override disk offering`. This will set the required `qc` tag from the disk offering (DO) detail. + +Creating DATA disk with QoS + - Create volume via GUI/CLI and choose a disk offering which has the required `SP_QOSCLASS` detail + +To update the tier of a ROOT/DATA volume go to Storage> Volumes and select the Volume and click on the Change disk offering for the volume in the upper right corner. + ## Supported operations for Volume encryption Supported Virtual machine operations - live migration of VM to another host, virtual machine snapshots (group snapshot without memory), revert VM snapshot, delete VM snapshot diff --git a/plugins/storage/volume/storpool/pom.xml b/plugins/storage/volume/storpool/pom.xml index 8a7fda0ce79..e1555dc8958 100644 --- a/plugins/storage/volume/storpool/pom.xml +++ b/plugins/storage/volume/storpool/pom.xml @@ -68,9 +68,16 @@ - org.codehaus.gmaven - gmaven-plugin - 1.5 + org.codehaus.gmavenplus + gmavenplus-plugin + ${cs.gmavenplus.version} + + + org.codehaus.groovy + groovy-all + ${cs.groovy.version} + + set-properties @@ -79,15 +86,16 @@ execute - 1.7 - - File git = new File("./.git") - if (!git.exists()) { - pom.properties['storpool.skip.git.properties'] = 'true' - } else { - pom.properties['storpool.skip.git.properties'] = 'false' - } - + + + diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java new file mode 100644 index 00000000000..456f5b90639 --- /dev/null +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.datastore.api; + +import java.io.Serializable; +import java.util.Map; + +public class StorPoolVolumeDef implements Serializable { + + private static final long serialVersionUID = 1L; + private transient String name; + private Long size; + private Map tags; + private String parent; + private Long iops; + private String template; + private String baseOn; + private String rename; + private Boolean shrinkOk; + + public StorPoolVolumeDef() { + } + + public StorPoolVolumeDef(String name, Long size, Map tags, String parent, Long iops, String template, + String baseOn, String rename, Boolean shrinkOk) { + super(); + this.name = name; + this.size = size; + this.tags = tags; + this.parent = parent; + this.iops = iops; + this.template = template; + this.baseOn = baseOn; + this.rename = rename; + this.shrinkOk = shrinkOk; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Long getSize() { + return size; + } + public void setSize(Long size) { + this.size = size; + } + public Map getTags() { + return tags; + } + public void setTags(Map tags) { + this.tags = tags; + } + public String getParent() { + return parent; + } + public void setParent(String parent) { + this.parent = parent; + } + public Long getIops() { + return iops; + } + public void setIops(Long iops) { + this.iops = iops; + } + public String getTemplate() { + return template; + } + public void setTemplate(String template) { + this.template = template; + } + public String getBaseOn() { + return baseOn; + } + public void setBaseOn(String baseOn) { + this.baseOn = baseOn; + } + public String getRename() { + return rename; + } + public void setRename(String rename) { + this.rename = rename; + } + + public Boolean getShrinkOk() { + return shrinkOk; + } + + public void setShrinkOk(Boolean shrinkOk) { + this.shrinkOk = shrinkOk; + } +} diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index b9c0b73f78d..631186636ca 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -39,12 +39,15 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.apache.cloudstack.storage.RemoteHostEndPoint; import org.apache.cloudstack.storage.command.CommandResult; import org.apache.cloudstack.storage.command.CopyCmdAnswer; import org.apache.cloudstack.storage.command.CreateObjectAnswer; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef; +import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; @@ -81,12 +84,18 @@ import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.dc.dao.ClusterDao; +import com.cloud.exception.StorageUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; +import com.cloud.offering.DiskOffering; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ResizeVolumePayload; import com.cloud.storage.Snapshot; @@ -156,6 +165,12 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { private StoragePoolHostDao storagePoolHostDao; @Inject DataStoreManager dataStoreManager; + @Inject + private DiskOfferingDetailsDao diskOfferingDetailsDao; + @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailDao; + @Inject + private ServiceOfferingDao serviceOfferingDao; private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) { List snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image); @@ -259,15 +274,25 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { String path = null; Answer answer; + String tier = null; + String template = null; if (data.getType() == DataObjectType.VOLUME) { try { VolumeInfo vinfo = (VolumeInfo)data; String name = vinfo.getUuid(); Long size = vinfo.getPassphraseId() == null ? vinfo.getSize() : vinfo.getSize() + 2097152; + Long vmId = vinfo.getInstanceId(); + SpConnectionDesc conn = StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), storagePoolDetailsDao, primaryStoreDao); - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.createAsync volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", vinfo.getName(), vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), vinfo.getpayload(), conn.getTemplateName()); - SpApiResponse resp = StorPoolUtil.volumeCreate(name, null, size, getVMInstanceUUID(vinfo.getInstanceId()), null, "volume", vinfo.getMaxIops(), conn); + if (vinfo.getDiskOfferingId() != null) { + tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId()); + if (tier == null) { + template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId()); + } + } + + SpApiResponse resp = createStorPoolVolume(template, tier, vinfo, name, size, vmId, conn); if (resp.getError() == null) { String volumeName = StorPoolUtil.getNameFromResponse(resp, false); path = StorPoolUtil.devPath(volumeName); @@ -298,6 +323,26 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } } + private SpApiResponse createStorPoolVolume(String template, String tier, VolumeInfo vinfo, String name, Long size, + Long vmId, SpConnectionDesc conn) { + SpApiResponse resp = new SpApiResponse(); + Map tags = StorPoolHelper.addStorPoolTags(name, getVMInstanceUUID(vmId), "volume", getVcPolicyTag(vmId), tier); + if (tier != null || template != null) { + StorPoolUtil.spLog( + "Creating volume [%s] with template [%s] or tier tags [%s] described in disk/service offerings details", + vinfo.getUuid(), template, tier); + resp = StorPoolUtil.volumeCreate(size, null, template, tags, conn); + } else { + StorPoolUtil.spLog( + "StorpoolPrimaryDataStoreDriver.createAsync volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", + vinfo.getName(), vinfo.getUuid(), vinfo.isAttachedVM(), vinfo.getAttachedVmName(), + vinfo.getpayload(), conn.getTemplateName()); + resp = StorPoolUtil.volumeCreate(name, null, size, getVMInstanceUUID(vinfo.getInstanceId()), null, + "volume", vinfo.getMaxIops(), conn); + } + return resp; + } + private void updateVolume(DataStore dataStore, String path, VolumeInfo vinfo) { VolumeVO volume = volumeDao.findById(vinfo.getId()); volume.setPoolId(dataStore.getId()); @@ -336,68 +381,111 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { public void resize(DataObject data, AsyncCompletionCallback callback) { String path = null; String err = null; - ResizeVolumeAnswer answer = null; if (data.getType() == DataObjectType.VOLUME) { VolumeObject vol = (VolumeObject)data; - StoragePool pool = (StoragePool)data.getDataStore(); - ResizeVolumePayload payload = (ResizeVolumePayload)vol.getpayload(); + path = vol.getPath(); - final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true); - final long oldSize = vol.getSize(); - Long oldMaxIops = vol.getMaxIops(); - - try { - SpConnectionDesc conn = StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - - long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : payload.newMaxIops; - - StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s", name, vol.getUuid(), oldSize, payload.newSize, payload.shrinkOk, maxIops); - - SpApiResponse resp = StorPoolUtil.volumeUpdate(name, payload.newSize, payload.shrinkOk, maxIops, conn); - if (resp.getError() != null) { - err = String.format("Could not resize StorPool volume %s. Error: %s", name, resp.getError()); - } else { - StorPoolResizeVolumeCommand resizeCmd = new StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), payload.newSize, payload.shrinkOk, - payload.instanceName, payload.hosts == null ? false : true); - answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, payload.hosts, resizeCmd); - - if (answer == null || !answer.getResult()) { - err = answer != null ? answer.getDetails() : "return a null answer, resize failed for unknown reason"; - } else { - path = StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)); - - vol.setSize(payload.newSize); - vol.update(); - if (payload.newMaxIops != null) { - VolumeVO volume = volumeDao.findById(vol.getId()); - volume.setMaxIops(payload.newMaxIops); - volumeDao.update(volume.getId(), volume); - } - - updateStoragePool(vol.getPoolId(), payload.newSize - oldSize); - } - } - if (err != null) { - // try restoring volume to its initial size - resp = StorPoolUtil.volumeUpdate(name, oldSize, true, oldMaxIops, conn); - if (resp.getError() != null) { - logger.debug(String.format("Could not resize StorPool volume %s back to its original size. Error: %s", name, resp.getError())); - } - } - } catch (Exception e) { - logger.debug("sending resize command failed", e); - err = e.toString(); - } + err = resizeVolume(data, path, vol); } else { err = String.format("Invalid object type \"%s\" passed to resize", data.getType()); } - CreateCmdResult res = new CreateCmdResult(path, answer); + CreateCmdResult res = new CreateCmdResult(path, new Answer(null, err != null, err)); res.setResult(err); callback.complete(res); } + private String resizeVolume(DataObject data, String path, VolumeObject vol) { + String err = null; + ResizeVolumePayload payload = (ResizeVolumePayload)vol.getpayload(); + boolean needResize = vol.getSize() != payload.newSize; + + final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(path, true); + final long oldSize = vol.getSize(); + Long oldMaxIops = vol.getMaxIops(); + + try { + SpConnectionDesc conn = StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); + + err = updateStorPoolVolume(vol, payload, conn); + if (err == null && needResize) { + err = notifyQemuForTheNewSize(data, err, vol, payload); + } + + if (err != null) { + // try restoring volume to its initial size + SpApiResponse response = StorPoolUtil.volumeUpdate(name, oldSize, true, oldMaxIops, conn); + if (response.getError() != null) { + logger.debug(String.format("Could not resize StorPool volume %s back to its original size. Error: %s", name, response.getError())); + } + } + } catch (Exception e) { + logger.debug("sending resize command failed", e); + err = e.toString(); + } + return err; + } + + private String notifyQemuForTheNewSize(DataObject data, String err, VolumeObject vol, ResizeVolumePayload payload) + throws StorageUnavailableException { + StoragePool pool = (StoragePool)data.getDataStore(); + + StorPoolResizeVolumeCommand resizeCmd = new StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), payload.newSize, payload.shrinkOk, + payload.instanceName, payload.hosts == null ? false : true); + ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, payload.hosts, resizeCmd); + + if (answer == null || !answer.getResult()) { + err = answer != null ? answer.getDetails() : "return a null answer, resize failed for unknown reason"; + } + return err; + } + + private String updateStorPoolVolume(VolumeObject vol, ResizeVolumePayload payload, SpConnectionDesc conn) { + String err = null; + String name = StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true); + Long newDiskOfferingId = payload.getNewDiskOfferingId(); + String tier = null; + String template = null; + if (newDiskOfferingId != null) { + tier = getTierFromOfferingDetail(newDiskOfferingId); + if (tier == null) { + template = getTemplateFromOfferingDetail(newDiskOfferingId); + } + } + SpApiResponse resp = new SpApiResponse(); + if (tier != null || template != null) { + Map tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier); + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, tags, null, null, template, null, null, + payload.shrinkOk); + resp = StorPoolUtil.volumeUpdate(spVolume, conn); + } else { + long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : payload.newMaxIops; + + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, null, null, maxIops, null, null, null, + payload.shrinkOk); + StorPoolUtil.spLog( + "StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s", + name, vol.getUuid(), vol.getSize(), payload.newSize, payload.shrinkOk, maxIops); + + resp = StorPoolUtil.volumeUpdate(spVolume, conn); + } + if (resp.getError() != null) { + err = String.format("Could not resize StorPool volume %s. Error: %s", name, resp.getError()); + } else { + vol.setSize(payload.newSize); + vol.update(); + if (payload.newMaxIops != null) { + VolumeVO volume = volumeDao.findById(vol.getId()); + volume.setMaxIops(payload.newMaxIops); + volumeDao.update(volume.getId(), volume); + } + + updateStoragePool(vol.getPoolId(), payload.newSize - vol.getSize()); + } + return err; + } + @Override public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { String err = null; @@ -772,8 +860,30 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } StorPoolUtil.spLog(String.format("volume size is: %d", size)); Long vmId = vinfo.getInstanceId(); - SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId), getVcPolicyTag(vmId), - "volume", vinfo.getMaxIops(), conn); + + String template = null; + String tier = null; + SpApiResponse resp = new SpApiResponse(); + + if (vinfo.getDiskOfferingId() != null) { + tier = getTierFromOfferingDetail(vinfo.getDiskOfferingId()); + if (tier == null) { + template = getTemplateFromOfferingDetail(vinfo.getDiskOfferingId()); + } + } + + if (tier != null || template != null) { + Map tags = StorPoolHelper.addStorPoolTags(name, getVMInstanceUUID(vmId), "volume", getVcPolicyTag(vmId), tier); + + StorPoolUtil.spLog( + "Creating volume [%s] with template [%s] or tier tags [%s] described in disk/service offerings details", + vinfo.getUuid(), template, tier); + resp = StorPoolUtil.volumeCreate(size, parentName, template, tags, conn); + } else { + resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId), + getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn); + } + if (resp.getError() == null) { updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize()); updateVolumePoolType(vinfo); @@ -1255,4 +1365,67 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { StorPoolUtil.spLog("The volume [%s] is detach from all clusters [%s]", volName, resp); } } + + @Override + public boolean informStorageForDiskOfferingChange() { + return true; + } + + @Override + public void updateStorageWithTheNewDiskOffering(Volume volume, DiskOffering newDiskOffering) { + if (newDiskOffering == null) { + return; + } + + StoragePoolVO pool = primaryStoreDao.findById(volume.getPoolId()); + if (pool == null) { + return; + } + + String tier = getTierFromOfferingDetail(newDiskOffering.getId()); + String template = null; + if (tier == null) { + template = getTemplateFromOfferingDetail(newDiskOffering.getId()); + } + if (tier == null && template == null) { + return; + } + SpConnectionDesc conn = StorPoolUtil.getSpConnection(pool.getUuid(), pool.getId(), storagePoolDetailsDao, primaryStoreDao); + StorPoolUtil.spLog("Updating volume [%s] with tier tag [%s] or template [%s] from Disk offering", volume.getId(), tier, template); + String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true); + Map tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier); + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volumeName, null, tags, null, null, template, null, null, null); + SpApiResponse response = StorPoolUtil.volumeUpdate(spVolume, conn); + if (response.getError() != null) { + StorPoolUtil.spLog("Could not update volume [%s] with tier tag [%s] or template [%s] from Disk offering due to [%s]", volume.getId(), tier, template, response.getError()); + } + } + + private String getTemplateFromOfferingDetail(Long diskOfferingId) { + String template = null; + DiskOfferingDetailVO diskOfferingDetail = diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TEMPLATE); + if (diskOfferingDetail == null ) { + ServiceOfferingVO serviceOffering = serviceOfferingDao.findServiceOfferingByComputeOnlyDiskOffering(diskOfferingId, true); + if (serviceOffering != null) { + ServiceOfferingDetailsVO serviceOfferingDetail = serviceOfferingDetailDao.findDetail(serviceOffering.getId(), StorPoolUtil.SP_TEMPLATE); + if (serviceOfferingDetail != null) { + template = serviceOfferingDetail.getValue(); + } + } + } else { + template = diskOfferingDetail.getValue(); + } + return template; + } + + private String getTierFromOfferingDetail(Long diskOfferingId) { + String tier = null; + DiskOfferingDetailVO diskOfferingDetail = diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TIER); + if (diskOfferingDetail == null ) { + return tier; + } else { + tier = diskOfferingDetail.getValue(); + } + return tier; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java index 5a84e699f52..3113ae8fdaa 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java @@ -163,11 +163,12 @@ public class StorPoolHelper { return null; } - public static Map addStorPoolTags(String name, String vmUuid, String csTag, String vcPolicy) { + public static Map addStorPoolTags(String name, String vmUuid, String csTag, String vcPolicy, String qcTier) { Map tags = new HashMap<>(); tags.put("uuid", name); tags.put("cvm", vmUuid); tags.put(StorPoolUtil.SP_VC_POLICY, vcPolicy); + tags.put("qc", qcTier); if (csTag != null) { tags.put("cs", csTag); } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java index 42809daec57..97f4e2fe155 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java @@ -30,6 +30,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef; +import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; @@ -135,6 +136,7 @@ public class StorPoolUtil { public static final String DELAY_DELETE = "delayDelete"; + public static final String SP_TIER = "SP_QOSCLASS"; public static enum StorpoolRights { RO("ro"), RW("rw"), DETACH("detach"); @@ -499,7 +501,19 @@ public class StorPoolUtil { json.put("parent", parentName); json.put("size", size); json.put("template", conn.getTemplateName()); - Map tags = StorPoolHelper.addStorPoolTags(name, vmUuid, csTag, vcPolicy); + Map tags = StorPoolHelper.addStorPoolTags(name, vmUuid, csTag, vcPolicy, null); + json.put("tags", tags); + return POST("MultiCluster/VolumeCreate", json, conn); + } + + public static SpApiResponse volumeCreate(Long size, String parentName, String template, Map tags, SpConnectionDesc conn) { + template = template != null ? template : conn.getTemplateName(); + + Map json = new LinkedHashMap<>(); + json.put("name", ""); + json.put("parent", parentName); + json.put("size", size); + json.put("template", template); json.put("tags", tags); return POST("MultiCluster/VolumeCreate", json, conn); } @@ -523,7 +537,7 @@ public class StorPoolUtil { json.put("iops", iops); } json.put("template", conn.getTemplateName()); - Map tags = StorPoolHelper.addStorPoolTags(name, cvmTag, csTag, vcPolicyTag); + Map tags = StorPoolHelper.addStorPoolTags(name, cvmTag, csTag, vcPolicyTag, null); json.put("tags", tags); return POST("MultiCluster/VolumeCreate", json, conn); } @@ -551,7 +565,7 @@ public class StorPoolUtil { public static SpApiResponse volumeRemoveTags(String name, SpConnectionDesc conn) { Map json = new HashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(null, "", null, ""); + Map tags = StorPoolHelper.addStorPoolTags(null, "", null, "", null); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } @@ -559,7 +573,7 @@ public class StorPoolUtil { public static SpApiResponse volumeUpdateIopsAndTags(final String name, final String uuid, Long iops, SpConnectionDesc conn, String vcPolicy) { Map json = new HashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy); + Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy, null); json.put("iops", iops); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); @@ -567,14 +581,14 @@ public class StorPoolUtil { public static SpApiResponse volumeUpdateCvmTags(final String name, final String uuid, SpConnectionDesc conn) { Map json = new HashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null); + Map tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null, null); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } public static SpApiResponse volumeUpdateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) { Map json = new HashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy); + Map tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy, null); json.put("tags", tags); return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } @@ -585,10 +599,14 @@ public class StorPoolUtil { return POST("MultiCluster/VolumeUpdate/" + name, json, conn); } + public static SpApiResponse volumeUpdate(StorPoolVolumeDef volume, SpConnectionDesc conn) { + return POST("MultiCluster/VolumeUpdate/" + volume.getName(), volume, conn); + } + public static SpApiResponse volumeSnapshot(final String volumeName, final String snapshotName, String vmUuid, String csTag, String vcPolicy, SpConnectionDesc conn) { Map json = new HashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy); + Map tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy, null); json.put("name", ""); json.put("tags", tags); @@ -602,7 +620,7 @@ public class StorPoolUtil { public static SpApiResponse volumesGroupSnapshot(final List volumeTOs, final String vmUuid, final String snapshotName, String csTag, SpConnectionDesc conn) { Map json = new LinkedHashMap<>(); - Map tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null); + Map tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null, null); List> volumes = new ArrayList<>(); for (VolumeObjectTO volumeTO : volumeTOs) { Map vol = new LinkedHashMap<>(); diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index 134dfb5efdb..010284e3766 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -30,7 +30,6 @@ 2.0.0.AM25 - 1.5 1.3.2 1.1.3 1.1-groovy-2.4 @@ -41,18 +40,21 @@ - org.codehaus.gmaven - gmaven-plugin - ${gmaven.version} - - 1.7 - - + org.codehaus.gmavenplus + gmavenplus-plugin + ${cs.gmavenplus.version} + + + org.codehaus.groovy + groovy-all + ${cs.groovy.version} + + compile - testCompile + compileTests @@ -66,29 +68,12 @@ - - - org.codehaus.gmaven.runtime - gmaven-runtime-1.7 - ${gmaven.version} - - - org.codehaus.groovy - groovy-all - - - - - org.codehaus.groovy - groovy-all - ${cs.groovy.version} - - org.apache.maven.plugins maven-surefire-plugin + @{argLine} --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED **/*Spec.groovy **/*Test.java diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapTestConfigTool.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapTestConfigTool.java index 47f201de479..4197bad4f2d 100644 --- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapTestConfigTool.java +++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapTestConfigTool.java @@ -19,7 +19,6 @@ package org.apache.cloudstack.ldap; import org.apache.cloudstack.framework.config.ConfigKey; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; public class LdapTestConfigTool { public LdapTestConfigTool() { @@ -31,18 +30,12 @@ public class LdapTestConfigTool { ConfigKey key = (ConfigKey)configKey.get(ldapConfiguration); - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(configKey, configKey.getModifiers() & ~Modifier.FINAL); - Field f = ConfigKey.class.getDeclaredField("_value"); f.setAccessible(true); - modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); f.set(key, o); Field dynamic = ConfigKey.class.getDeclaredField("_isDynamic"); dynamic.setAccessible(true); - modifiersField.setInt(dynamic, dynamic.getModifiers() & ~Modifier.FINAL); dynamic.setBoolean(key, false); } } diff --git a/pom.xml b/pom.xml index 727ac2f7c5b..ed3fa73506e 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 11 target build/replace.properties - -Djava.security.egd=file:/dev/./urandom -noverify + -Djava.security.egd=file:/dev/./urandom -noverify --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/javax.net.ssl=ALL-UNNAMED 1.8 @@ -79,6 +79,7 @@ 3.8.2 2.22.2 4.4.1 + 3.2.0 2.19.0 @@ -137,6 +138,7 @@ 3.2.14 2.6.11 0.0.27 + 3.0.2 1.42.3 2.4.17 1.7.2 @@ -185,7 +187,6 @@ 1.4.20 5.3.26 0.5.4 - 1.12.0 @@ -933,20 +934,6 @@ - - - org.codehaus.gmaven - gmaven-plugin - [1.3,) - - compile - testCompile - - - - - - org.apache.maven.plugins diff --git a/server/pom.xml b/server/pom.xml index e18dcb5fe28..269583c381a 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -218,7 +218,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} -Xmx2048m -XX:MaxPermSize=512m -Djava.security.egd=file:/dev/./urandom + @{argLine} -Xmx2048m -XX:MaxMetaspaceSize=512m -Djava.security.egd=file:/dev/./urandom %regex[.*[0-9]*To[0-9]*.*Test.*] com/cloud/upgrade/AdvanceZone223To224UpgradeTest diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 729c7a9e43a..e9f60ea7aa1 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -206,7 +206,7 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { return true; } else if (entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) { - _networkMgr.checkNetworkPermissions(caller, (Network)entity); + _networkMgr.checkNetworkPermissions(caller, (Network) entity); } else if (entity instanceof Network && accessType != null && accessType == AccessType.OperateEntry) { _networkMgr.checkNetworkOperatePermissions(caller, (Network)entity); } else if (entity instanceof VirtualRouter) { @@ -214,30 +214,58 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { } else if (entity instanceof AffinityGroup) { return false; } else { - if (_accountService.isNormalUser(caller.getId())) { - Account account = _accountDao.findById(entity.getAccountId()); - String errorMessage = String.format("%s does not have permission to operate with resource", caller); - if (account != null && account.getType() == Account.Type.PROJECT) { - //only project owner can delete/modify the project - if (accessType != null && accessType == AccessType.ModifyProject) { - if (!_projectMgr.canModifyProjectAccount(caller, account.getId())) { - throw new PermissionDeniedException(errorMessage); - } - } else if (!_projectMgr.canAccessProjectAccount(caller, account.getId())) { - throw new PermissionDeniedException(errorMessage); - } - checkOperationPermitted(caller, entity); - } else { - if (caller.getId() != entity.getAccountId()) { - throw new PermissionDeniedException(errorMessage); - } - } - } + validateCallerHasAccessToEntityOwner(caller, entity, accessType); } return true; } - private boolean checkOperationPermitted(Account caller, ControlledEntity entity) { + protected void validateCallerHasAccessToEntityOwner(Account caller, ControlledEntity entity, AccessType accessType) { + PermissionDeniedException exception = new PermissionDeniedException("Caller does not have permission to operate with provided resource."); + String entityLog = String.format("entity [owner ID: %d, type: %s]", entity.getAccountId(), + entity.getEntityType().getSimpleName()); + + if (_accountService.isRootAdmin(caller.getId())) { + return; + } + + if (caller.getId() == entity.getAccountId()) { + return; + } + + Account owner = _accountDao.findById(entity.getAccountId()); + if (owner == null) { + logger.error(String.format("Owner not found for %s", entityLog)); + throw exception; + } + + Account.Type callerAccountType = caller.getType(); + if ((callerAccountType == Account.Type.DOMAIN_ADMIN || callerAccountType == Account.Type.RESOURCE_DOMAIN_ADMIN) && + _domainDao.isChildDomain(caller.getDomainId(), owner.getDomainId())) { + return; + } + + if (owner.getType() == Account.Type.PROJECT) { + // only project owner can delete/modify the project + if (accessType == AccessType.ModifyProject) { + if (!_projectMgr.canModifyProjectAccount(caller, owner.getId())) { + logger.error(String.format("Caller ID: %d does not have permission to modify project with " + + "owner ID: %d", caller.getId(), owner.getId())); + throw exception; + } + } else if (!_projectMgr.canAccessProjectAccount(caller, owner.getId())) { + logger.error(String.format("Caller ID: %d does not have permission to access project with " + + "owner ID: %d", caller.getId(), owner.getId())); + throw exception; + } + checkOperationPermitted(caller, entity); + return; + } + + logger.error(String.format("Caller ID: %d does not have permission to access %s", caller.getId(), entityLog)); + throw exception; + } + + protected boolean checkOperationPermitted(Account caller, ControlledEntity entity) { User user = CallContext.current().getCallingUser(); Project project = projectDao.findByProjectAccountId(entity.getAccountId()); if (project == null) { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index d247aaf5328..01334c5d4c5 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -372,7 +372,6 @@ import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotVO; import com.cloud.storage.StoragePool; import com.cloud.storage.Upload; -import com.cloud.storage.UploadVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; @@ -796,6 +795,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (domain != null) { vmSnapshotResponse.setDomainId(domain.getUuid()); vmSnapshotResponse.setDomainName(domain.getName()); + vmSnapshotResponse.setDomainPath(domain.getPath()); } List tags = _resourceTagDao.listBy(vmSnapshot.getId(), ResourceObjectType.VMSnapshot); @@ -1525,7 +1525,7 @@ public class ApiResponseHelper implements ResponseGenerator { } // Do it for stats as well. capacityResponses.addAll(getStatsCapacityresponse(null, cluster.getId(), pod.getId(), pod.getDataCenterId())); - clusterResponse.setCapacitites(new ArrayList(capacityResponses)); + clusterResponse.setCapacities(new ArrayList(capacityResponses)); } clusterResponse.setHasAnnotation(annotationDao.hasAnnotations(cluster.getUuid(), AnnotationService.EntityType.CLUSTER.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); @@ -1914,58 +1914,49 @@ public class ApiResponseHelper implements ResponseGenerator { return listSgs.get(0); } - //TODO: we need to deprecate uploadVO, since extract is done in a synchronous fashion - @Override - public ExtractResponse createExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url) { - + private ExtractResponse createExtractResponse (Long zoneId, Long accountId, String url) { ExtractResponse response = new ExtractResponse(); - response.setObjectName("template"); - VMTemplateVO template = ApiDBUtils.findTemplateById(id); - response.setId(template.getUuid()); - response.setName(template.getName()); if (zoneId != null) { DataCenter zone = ApiDBUtils.findZoneById(zoneId); response.setZoneId(zone.getUuid()); response.setZoneName(zone.getName()); } - response.setMode(mode); response.setUrl(url); response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString()); Account account = ApiDBUtils.findAccountById(accountId); response.setAccountId(account.getUuid()); - return response; } @Override - public ExtractResponse createExtractResponse(Long uploadId, Long id, Long zoneId, Long accountId, String mode, String url) { + public ExtractResponse createVolumeExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url) { + ExtractResponse response = createExtractResponse(zoneId, accountId, url); + response.setObjectName("volume"); + response.setMode(mode); + Volume volume = ApiDBUtils.findVolumeById(id); + response.setId(volume.getUuid()); + response.setName(volume.getName()); + return response; + } - ExtractResponse response = new ExtractResponse(); - response.setObjectName("template"); + @Override + public ExtractResponse createSnapshotExtractResponse(Long id, Long zoneId, Long accountId, String url) { + ExtractResponse response = createExtractResponse(zoneId, accountId, url); + response.setObjectName("snapshot"); + Snapshot snapshot = ApiDBUtils.findSnapshotById(id); + response.setId(snapshot.getUuid()); + response.setName(snapshot.getName()); + return response; + } + + @Override + public ExtractResponse createImageExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url) { + ExtractResponse response = createExtractResponse(zoneId, accountId, url); + response.setMode(mode); VMTemplateVO template = ApiDBUtils.findTemplateById(id); response.setId(template.getUuid()); response.setName(template.getName()); - if (zoneId != null) { - DataCenter zone = ApiDBUtils.findZoneById(zoneId); - response.setZoneId(zone.getUuid()); - response.setZoneName(zone.getName()); - } - response.setMode(mode); - if (uploadId == null) { - // region-wide image store - response.setUrl(url); - response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString()); - } else { - UploadVO uploadInfo = ApiDBUtils.findUploadById(uploadId); - response.setUploadId(uploadInfo.getUuid()); - response.setState(uploadInfo.getUploadState().toString()); - response.setUrl(uploadInfo.getUploadUrl()); - } - Account account = ApiDBUtils.findAccountById(accountId); - response.setAccountId(account.getUuid()); - return response; - } @Override @@ -2253,6 +2244,7 @@ public class ApiResponseHelper implements ResponseGenerator { response.setDomainId(securityGroup.getDomainUuid()); response.setDomainName(securityGroup.getDomainName()); + response.setDomainPath(securityGroup.getDomainPath()); for (SecurityRule securityRule : securityRules) { SecurityGroupRuleResponse securityGroupData = new SecurityGroupRuleResponse(); @@ -2657,6 +2649,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (domain != null) { response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); } } @@ -2905,6 +2898,7 @@ public class ApiResponseHelper implements ResponseGenerator { Domain domain = ApiDBUtils.findDomainById(object.getDomainId()); response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); } private void populateOwner(ControlledViewEntityResponse response, ControlledEntity object) { @@ -2922,6 +2916,7 @@ public class ApiResponseHelper implements ResponseGenerator { Domain domain = ApiDBUtils.findDomainById(object.getDomainId()); response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); } public static void populateOwner(ControlledViewEntityResponse response, ControlledViewEntity object) { @@ -2935,6 +2930,7 @@ public class ApiResponseHelper implements ResponseGenerator { response.setDomainId(object.getDomainUuid()); response.setDomainName(object.getDomainName()); + response.setDomainPath(object.getDomainPath()); } private void populateAccount(ControlledEntityResponse response, long accountId) { @@ -2961,6 +2957,7 @@ public class ApiResponseHelper implements ResponseGenerator { response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); } @Override @@ -3899,6 +3896,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (domain != null) { usageRecResponse.setDomainId(domain.getUuid()); usageRecResponse.setDomainName(domain.getName()); + usageRecResponse.setDomainPath(domain.getPath()); } if (usageRecord.getZoneId() != null) { @@ -4696,6 +4694,7 @@ public class ApiResponseHelper implements ResponseGenerator { if (domain != null) { response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setDomainPath(domain.getPath()); } response.setObjectName("affinitygroup"); diff --git a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java index bdba8dcace2..314b83acdb5 100644 --- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java @@ -60,6 +60,7 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.UuidUtils; import com.cloud.utils.db.EntityManager; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; public class ParamProcessWorker implements DispatchWorker { @@ -117,8 +118,21 @@ public class ParamProcessWorker implements DispatchWorker { } } + private void validateNameForRFCCompliance(final Object param, final String argName) { + String value = String.valueOf(param); + if (StringUtils.isBlank(value) || !NetUtils.verifyDomainNameLabel(value, true)) { + String msg = "it can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit"; + throwInvalidParameterValueException(argName, msg); + } + } + protected void throwInvalidParameterValueException(String argName) { - throw new InvalidParameterValueException(String.format("Invalid value provided for API arg: %s", argName)); + throwInvalidParameterValueException(argName, null); + } + + protected void throwInvalidParameterValueException(String argName, String customMsg) { + throw new InvalidParameterValueException(String.format("Invalid value provided for API arg: %s%s", argName, StringUtils.isBlank(customMsg)? "" : " - " + customMsg)); } private void validateField(final Object paramObj, final Parameter annotation) throws ServerApiException { @@ -155,6 +169,12 @@ public class ParamProcessWorker implements DispatchWorker { break; } break; + case RFCComplianceDomainName: + switch (annotation.type()) { + case STRING: + validateNameForRFCCompliance(paramObj, argName); + break; + } } } } @@ -165,14 +185,18 @@ public class ParamProcessWorker implements DispatchWorker { final List cmdFields = cmd.getParamFields(); + String commandName = cmd.getCommandName(); + if (commandName.endsWith(BaseCmd.RESPONSE_SUFFIX)) { + commandName = cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8); + } + for (final Field field : cmdFields) { final Parameter parameterAnnotation = field.getAnnotation(Parameter.class); final Object paramObj = params.get(parameterAnnotation.name()); if (paramObj == null) { if (parameterAnnotation.required()) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " + - cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + - " due to missing parameter " + parameterAnnotation.name()); + commandName + " due to missing parameter " + parameterAnnotation.name()); } continue; } @@ -186,29 +210,28 @@ public class ParamProcessWorker implements DispatchWorker { setFieldValue(field, cmd, paramObj, parameterAnnotation); } catch (final IllegalArgumentException argEx) { if (logger.isDebugEnabled()) { - logger.debug("Unable to execute API command " + cmd.getCommandName() + " due to invalid value " + paramObj + " for parameter " + + logger.debug("Unable to execute API command " + commandName + " due to invalid value " + paramObj + " for parameter " + parameterAnnotation.name()); } throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " + - cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value " + paramObj + " for parameter " + + commandName + " due to invalid value " + paramObj + " for parameter " + parameterAnnotation.name()); } catch (final ParseException parseEx) { if (logger.isDebugEnabled()) { - logger.debug("Invalid date parameter " + paramObj + " passed to command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8)); + logger.debug("Invalid date parameter " + paramObj + " passed to command " + commandName); } throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to parse date " + paramObj + " for command " + - cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + ", please pass dates in the format mentioned in the api documentation"); + commandName + ", please pass dates in the format mentioned in the api documentation"); } catch (final InvalidParameterValueException invEx) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " + - cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value. " + invEx.getMessage()); + commandName + " due to invalid value. " + invEx.getMessage()); } catch (final CloudRuntimeException cloudEx) { logger.error("CloudRuntimeException", cloudEx); // FIXME: Better error message? This only happens if the API command is not executable, which typically //means // there was // and IllegalAccessException setting one of the parameters. - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Internal error executing API command " + - cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8)); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Internal error executing API command " + commandName); } //check access on the resource this field points to diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java index 2bfbb3b9d67..902c9c9bb25 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java @@ -90,7 +90,7 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase implements Map hostDetails = hostDetailsDao.findDetails(host.getId()); if (hostDetails != null) { if (hostDetails.containsKey(Host.HOST_UEFI_ENABLE)) { - hostResponse.setUefiCapabilty(Boolean.parseBoolean((String) hostDetails.get(Host.HOST_UEFI_ENABLE))); + hostResponse.setUefiCapability(Boolean.parseBoolean((String) hostDetails.get(Host.HOST_UEFI_ENABLE))); } else { - hostResponse.setUefiCapabilty(new Boolean(false)); + hostResponse.setUefiCapability(new Boolean(false)); } } if (details.contains(HostDetails.all) && (host.getHypervisorType() == Hypervisor.HypervisorType.KVM || diff --git a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java index 3bd689025d4..7030d32b5fd 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java @@ -59,6 +59,7 @@ public class ProjectAccountJoinDaoImpl extends GenericDaoBase resourceDetails = getResourceDetails(resourceId, resourceType, type); + CallContext ctx = CallContext.current(); + accountId = getOwnerAccountId(ctx, type, accountId); Event event = persistActionEvent(userId, accountId, null, null, type, Event.State.Scheduled, eventDisplayEnabled, description, resourceDetails.first(), resourceDetails.third(), startEventId); publishOnEventBus(event, userId, accountId, EventCategory.ACTION_EVENT.getName(), type, @@ -127,7 +130,7 @@ public class ActionEventUtils { public static void onStartedActionEventFromContext(String eventType, String eventDescription, Long resourceId, String resourceType, boolean eventDisplayEnabled) { CallContext ctx = CallContext.current(); long userId = ctx.getCallingUserId(); - long accountId = ctx.getProject() != null ? ctx.getProject().getProjectAccountId() : ctx.getCallingAccountId(); //This should be the entity owner id rather than the Calling User Account Id. + long accountId = getOwnerAccountId(ctx, eventType, ctx.getCallingAccountId()); long startEventId = ctx.getStartEventId(); if (!eventType.equals("")) @@ -413,7 +416,11 @@ public class ActionEventUtils { LOGGER.trace("Caught exception while populating first class entities for event bus, moving on"); } } - } + public static long getOwnerAccountId(CallContext ctx, String eventType, long callingAccountId) { + List mainProjectEvents = List.of(EventTypes.EVENT_PROJECT_CREATE, EventTypes.EVENT_PROJECT_UPDATE, EventTypes.EVENT_PROJECT_DELETE); + long accountId = ctx.getProject() != null && !mainProjectEvents.stream().anyMatch(eventType::equalsIgnoreCase) ? ctx.getProject().getProjectAccountId() : callingAccountId; //This should be the entity owner id rather than the Calling User Account Id. + return accountId; + } } diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 770c919bbf5..9dbc478f9c0 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -1261,7 +1261,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage AcquirePodIpCmdResponse ret = new AcquirePodIpCmdResponse(); ret.setCidrAddress(pod_vo.getCidrAddress()); ret.setGateway(pod_vo.getGateway()); - ret.setInstanceId(vo.getInstanceId()); + ret.setNicId(vo.getNicId()); ret.setIpAddress(vo.getIpAddress()); ret.setMacAddress(vo.getMacAddress()); ret.setPodId(vo.getPodId()); diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index aadce946193..0c6e826a589 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -18,7 +18,6 @@ package com.cloud.network; import java.math.BigInteger; -import java.security.InvalidParameterException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -417,10 +416,10 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi } // Since it's non-conserve mode, only one service should used for IP if (services.size() != 1) { - throw new InvalidParameterException("There are multiple services used ip " + ip.getAddress() + "."); + throw new InvalidParameterValueException("There are multiple services used ip " + ip.getAddress() + "."); } if (service != null && !((Service)services.toArray()[0] == service || service.equals(Service.Firewall))) { - throw new InvalidParameterException("The IP " + ip.getAddress() + " is already used as " + ((Service)services.toArray()[0]).getName() + " rather than " + + throw new InvalidParameterValueException("The IP " + ip.getAddress() + " is already used as " + ((Service)services.toArray()[0]).getName() + " rather than " + service.getName()); } return true; @@ -458,7 +457,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi // Since IP already has service to bind with, the oldProvider can't be null Set newProviders = serviceToProviders.get(service); if (newProviders == null || newProviders.isEmpty()) { - throw new InvalidParameterException("There is no new provider for IP " + publicIp.getAddress() + " of service " + service.getName() + "!"); + throw new InvalidParameterValueException("There is no new provider for IP " + publicIp.getAddress() + " of service " + service.getName() + "!"); } Provider newProvider = (Provider)newProviders.toArray()[0]; Set oldProviders = serviceToProviders.get(services.toArray()[0]); @@ -471,7 +470,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi IpDeployer newIpDeployer = ((IpDeployingRequester)newElement).getIpDeployer(network); // FIXME: I ignored this check } else { - throw new InvalidParameterException("Ip cannot be applied for new provider!"); + throw new InvalidParameterValueException("Ip cannot be applied for new provider!"); } return true; } @@ -2174,7 +2173,6 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi NetworkVO network = _networksDao.findById(networkId); Integer networkRate = getNetworkRate(network.getId(), vm.getId()); -// NetworkGuru guru = _networkGurus.get(network.getGuruName()); NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, isSecurityGroupSupportedInNetwork(network), getNetworkTag( vm.getHypervisorType(), network)); @@ -2184,7 +2182,17 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi if (network.getTrafficType() == TrafficType.Guest && network.getPrivateMtu() != null) { profile.setMtu(network.getPrivateMtu()); } -// guru.updateNicProfile(profile, network); + + DataCenter dc = _dcDao.findById(network.getDataCenterId()); + + Pair ip4Dns = getNetworkIp4Dns(network, dc); + profile.setIPv4Dns1(ip4Dns.first()); + profile.setIPv4Dns2(ip4Dns.second()); + + Pair ip6Dns = getNetworkIp6Dns(network, dc); + profile.setIPv6Dns1(ip6Dns.first()); + profile.setIPv6Dns2(ip6Dns.second()); + return profile; } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index dd788aee5d2..8da4185ac55 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -20,7 +20,6 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; -import java.security.InvalidParameterException; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -37,6 +36,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -550,10 +550,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } // Since it's non-conserve mode, only one service should be used for IP if (services.size() != 1) { - throw new InvalidParameterException("There are multiple services used ip " + ip.getAddress() + "."); + throw new InvalidParameterValueException("There are multiple services used ip " + ip.getAddress() + "."); } if (service != null && !((Service)services.toArray()[0] == service || service.equals(Service.Firewall))) { - throw new InvalidParameterException("The IP " + ip.getAddress() + " is already used as " + ((Service)services.toArray()[0]).getName() + " rather than " + service.getName()); + throw new InvalidParameterValueException("The IP " + ip.getAddress() + " is already used as " + ((Service)services.toArray()[0]).getName() + " rather than " + service.getName()); } return true; } @@ -599,7 +599,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } // We don't support multiple providers for one service now if (!provider.equals(curProvider)) { - throw new InvalidParameterException("There would be multiple providers for IP " + ip.getAddress() + " with the new network offering!"); + throw new InvalidParameterValueException("There would be multiple providers for IP " + ip.getAddress() + " with the new network offering!"); } } } @@ -2280,9 +2280,6 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C Long associatedNetworkId = cmd.getAssociatedNetworkId(); String networkFilterStr = cmd.getNetworkFilter(); - boolean applyManualPagination = CollectionUtils.isNotEmpty(supportedServicesStr) || - Boolean.TRUE.equals(canUseForDeploy); - String vlanId = null; if (cmd instanceof ListNetworksCmdByAdmin) { vlanId = ((ListNetworksCmdByAdmin)cmd).getVlan(); @@ -2368,13 +2365,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C isRecursive = true; } - Long offset = cmd.getStartIndex(); - Long limit = cmd.getPageSizeVal(); - if (applyManualPagination) { - offset = null; - limit = null; - } - Filter searchFilter = new Filter(NetworkVO.class, "id", false, offset, limit); + Filter searchFilter = new Filter(NetworkVO.class, "id", false, null, null); SearchBuilder sb = _networksDao.createSearchBuilder(); if (forVpc != null) { @@ -2429,123 +2420,113 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C sb.join("associatedNetworkSearch", associatedNetworkSearch, sb.entity().getId(), associatedNetworkSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER); } - SearchCriteria mainSearchCriteria = createNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, - guestIpType, trafficType, physicalNetworkId, networkOfferingId, aclType, restartRequired, - specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId); - SearchCriteria additionalSearchCriteria = _networksDao.createSearchCriteria(); + List networksToReturn = new ArrayList(); if (isSystem == null || !isSystem) { if (!permittedAccounts.isEmpty()) { if (Arrays.asList(Network.NetworkFilter.Account, Network.NetworkFilter.AccountDomain, Network.NetworkFilter.All).contains(networkFilter)) { //get account level networks - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, - getAccountSpecificNetworksSearchCriteria(sb, permittedAccounts, skipProjectNetworks)); + networksToReturn.addAll(listAccountSpecificNetworks(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, skipProjectNetworks, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, permittedAccounts)); } if (domainId != null && Arrays.asList(Network.NetworkFilter.Domain, Network.NetworkFilter.AccountDomain, Network.NetworkFilter.All).contains(networkFilter)) { //get domain level networks - SearchCriteria domainLevelSC = getDomainLevelNetworksSearchCriteria(sb, domainId, false); - if (domainLevelSC != null) { - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, domainLevelSC); - } + networksToReturn.addAll(listDomainLevelNetworks(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, domainId, false)); } if (Arrays.asList(Network.NetworkFilter.Shared, Network.NetworkFilter.All).contains(networkFilter)) { // get shared networks - SearchCriteria sharedNetworksSC = getSharedNetworksSearchCriteria(sb, permittedAccounts); - if (sharedNetworksSC != null) { - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, sharedNetworksSC); - } + List sharedNetworks = listSharedNetworks(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, permittedAccounts); + addNetworksToReturnIfNotExist(networksToReturn, sharedNetworks); + } } else { if (Arrays.asList(Network.NetworkFilter.Account, Network.NetworkFilter.AccountDomain, Network.NetworkFilter.All).contains(networkFilter)) { //add account specific networks - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, - getAccountSpecificNetworksByDomainPathSearchCriteria(sb, path, isRecursive, - skipProjectNetworks)); + networksToReturn.addAll(listAccountSpecificNetworksByDomainPath(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, skipProjectNetworks, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, path, isRecursive)); } if (Arrays.asList(Network.NetworkFilter.Domain, Network.NetworkFilter.AccountDomain, Network.NetworkFilter.All).contains(networkFilter)) { //add domain specific networks of domain + parent domains - SearchCriteria domainSpecificNetworksByDomainPathSC = - getDomainSpecificNetworksByDomainPathSearchCriteria(sb, path, isRecursive); - if (domainSpecificNetworksByDomainPathSC != null) { - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, domainSpecificNetworksByDomainPathSC); - } + networksToReturn.addAll(listDomainSpecificNetworksByDomainPath(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, path, isRecursive)); //add networks of subdomains if (domainId == null) { - SearchCriteria domainLevelSC = getDomainLevelNetworksSearchCriteria(sb, caller.getDomainId(), true); - if (domainLevelSC != null) { - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, domainLevelSC); - } + networksToReturn.addAll(listDomainLevelNetworks(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, caller.getDomainId(), true)); } } if (Arrays.asList(Network.NetworkFilter.Shared, Network.NetworkFilter.All).contains(networkFilter)) { // get shared networks - SearchCriteria sharedNetworksSC = getSharedNetworksByDomainPathSearchCriteria(sb, path, isRecursive); - if (sharedNetworksSC != null) { - additionalSearchCriteria.addOr("id", SearchCriteria.Op.SC, sharedNetworksSC); - } + List sharedNetworks = listSharedNetworksByDomainPath(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + aclType, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter, path, isRecursive); + addNetworksToReturnIfNotExist(networksToReturn, sharedNetworks); } } - if (CollectionUtils.isNotEmpty(additionalSearchCriteria.getValues())) { - mainSearchCriteria.addAnd("id", SearchCriteria.Op.SC, additionalSearchCriteria); - } } else { - if (skipProjectNetworks) { - mainSearchCriteria.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); - } else { - mainSearchCriteria.setJoinParameters("accountSearch", "typeEQ", Account.Type.PROJECT); - } + networksToReturn = _networksDao.search(buildNetworkSearchCriteria(sb, keyword, id, isSystem, zoneId, guestIpType, trafficType, physicalNetworkId, networkOfferingId, + null, true, restartRequired, specifyIpRanges, vpcId, tags, display, vlanId, associatedNetworkId), searchFilter); } - Pair, Integer> result = _networksDao.searchAndCount(mainSearchCriteria, searchFilter); - List networksToReturn = result.first(); if (supportedServicesStr != null && !supportedServicesStr.isEmpty() && !networksToReturn.isEmpty()) { - List supportedNetworks = new ArrayList<>(); - Service[] supportedServices = new Service[supportedServicesStr.size()]; + List supportedNetworks = new ArrayList(); + Service[] suppportedServices = new Service[supportedServicesStr.size()]; int i = 0; for (String supportedServiceStr : supportedServicesStr) { Service service = Service.getService(supportedServiceStr); if (service == null) { throw new InvalidParameterValueException("Invalid service specified " + supportedServiceStr); } else { - supportedServices[i] = service; + suppportedServices[i] = service; } i++; } + for (NetworkVO network : networksToReturn) { - if (areServicesSupportedInNetwork(network.getId(), supportedServices)) { + if (areServicesSupportedInNetwork(network.getId(), suppportedServices)) { supportedNetworks.add(network); } } + networksToReturn = supportedNetworks; } if (canUseForDeploy != null) { - List networksForDeploy = new ArrayList<>(); + List networksForDeploy = new ArrayList(); for (NetworkVO network : networksToReturn) { if (_networkModel.canUseForDeploy(network) == canUseForDeploy) { networksForDeploy.add(network); } } + networksToReturn = networksForDeploy; } - if (applyManualPagination) { - //Now apply pagination - List wPagination = com.cloud.utils.StringUtils.applyPagination(networksToReturn, cmd.getStartIndex(), cmd.getPageSizeVal()); - if (wPagination != null) { - Pair, Integer> listWPagination = new Pair<>(wPagination, networksToReturn.size()); - return listWPagination; - } - return new Pair<>(networksToReturn, networksToReturn.size()); + //Now apply pagination + List wPagination = com.cloud.utils.StringUtils.applyPagination(networksToReturn, cmd.getStartIndex(), cmd.getPageSizeVal()); + if (wPagination != null) { + Pair, Integer> listWPagination = new Pair, Integer>(wPagination, networksToReturn.size()); + return listWPagination; } - return new Pair<>(result.first(), result.second()); + return new Pair, Integer>(networksToReturn, networksToReturn.size()); } - private SearchCriteria createNetworkSearchCriteria(SearchBuilder sb, String keyword, Long id, - Boolean isSystem, Long zoneId, String guestIpType, String trafficType, Long physicalNetworkId, - Long networkOfferingId, String aclType, Boolean restartRequired, - Boolean specifyIpRanges, Long vpcId, Map tags, Boolean display, String vlanId, Long associatedNetworkId) { + private void addNetworksToReturnIfNotExist(final List networksToReturn, final List sharedNetworks) { + Set networkIds = networksToReturn.stream() + .map(NetworkVO::getId) + .collect(Collectors.toSet()); + List sharedNetworksToReturn = sharedNetworks.stream() + .filter(network -> ! networkIds.contains(network.getId())) + .collect(Collectors.toList()); + networksToReturn.addAll(sharedNetworksToReturn); + } + + private SearchCriteria buildNetworkSearchCriteria(SearchBuilder sb, String keyword, Long id, + Boolean isSystem, Long zoneId, String guestIpType, String trafficType, Long physicalNetworkId, + Long networkOfferingId, String aclType, boolean skipProjectNetworks, Boolean restartRequired, + Boolean specifyIpRanges, Long vpcId, Map tags, Boolean display, String vlanId, Long associatedNetworkId) { SearchCriteria sc = sb.create(); @@ -2587,6 +2568,12 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C sc.addAnd("physicalNetworkId", SearchCriteria.Op.EQ, physicalNetworkId); } + if (skipProjectNetworks) { + sc.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); + } else { + sc.setJoinParameters("accountSearch", "typeEQ", Account.Type.PROJECT); + } + if (restartRequired != null) { sc.addAnd("restartRequired", SearchCriteria.Op.EQ, restartRequired); } @@ -2627,8 +2614,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C return sc; } - private SearchCriteria getDomainLevelNetworksSearchCriteria(SearchBuilder sb, long domainId, boolean parentDomainsOnly) { - List networkIds = new ArrayList<>(); + private List listDomainLevelNetworks(SearchCriteria sc, Filter searchFilter, long domainId, boolean parentDomainsOnly) { + List networkIds = new ArrayList(); Set allowedDomains = _domainMgr.getDomainParentIds(domainId); List maps = _networkDomainDao.listDomainNetworkMapByDomain(allowedDomains.toArray()); @@ -2643,55 +2630,48 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } if (!networkIds.isEmpty()) { - SearchCriteria domainSC = sb.create(); - domainSC.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); + SearchCriteria domainSC = _networksDao.createSearchCriteria(); domainSC.addAnd("id", SearchCriteria.Op.IN, networkIds.toArray()); domainSC.addAnd("aclType", SearchCriteria.Op.EQ, ACLType.Domain.toString()); - return domainSC; + + sc.addAnd("id", SearchCriteria.Op.SC, domainSC); + return _networksDao.search(sc, searchFilter); + } else { + return new ArrayList(); } - return null; } - private SearchCriteria getAccountSpecificNetworksSearchCriteria(SearchBuilder sb, - List permittedAccounts, boolean skipProjectNetworks) { - SearchCriteria accountSC = sb.create(); - if (skipProjectNetworks) { - accountSC.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); - } else { - accountSC.setJoinParameters("accountSearch", "typeEQ", Account.Type.PROJECT); - } + private List listAccountSpecificNetworks(SearchCriteria sc, Filter searchFilter, List permittedAccounts) { + SearchCriteria accountSC = _networksDao.createSearchCriteria(); if (!permittedAccounts.isEmpty()) { accountSC.addAnd("accountId", SearchCriteria.Op.IN, permittedAccounts.toArray()); } + accountSC.addAnd("aclType", SearchCriteria.Op.EQ, ACLType.Account.toString()); - return accountSC; + + sc.addAnd("id", SearchCriteria.Op.SC, accountSC); + return _networksDao.search(sc, searchFilter); } - private SearchCriteria getAccountSpecificNetworksByDomainPathSearchCriteria(SearchBuilder sb, - String path, boolean isRecursive, boolean skipProjectNetworks) { - SearchCriteria accountSC = sb.create(); - if (skipProjectNetworks) { - accountSC.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); - } else { - accountSC.setJoinParameters("accountSearch", "typeEQ", Account.Type.PROJECT); - } + private List listAccountSpecificNetworksByDomainPath(SearchCriteria sc, Filter searchFilter, String path, boolean isRecursive) { + SearchCriteria accountSC = _networksDao.createSearchCriteria(); accountSC.addAnd("aclType", SearchCriteria.Op.EQ, ACLType.Account.toString()); if (path != null) { if (isRecursive) { - accountSC.setJoinParameters("domainSearch", "path", path + "%"); + sc.setJoinParameters("domainSearch", "path", path + "%"); } else { - accountSC.setJoinParameters("domainSearch", "path", path); + sc.setJoinParameters("domainSearch", "path", path); } } - return accountSC; + sc.addAnd("id", SearchCriteria.Op.SC, accountSC); + return _networksDao.search(sc, searchFilter); } - private SearchCriteria getDomainSpecificNetworksByDomainPathSearchCriteria(SearchBuilder sb, - String path, boolean isRecursive) { + private List listDomainSpecificNetworksByDomainPath(SearchCriteria sc, Filter searchFilter, String path, boolean isRecursive) { - Set allowedDomains = new HashSet<>(); + Set allowedDomains = new HashSet(); if (path != null) { if (isRecursive) { allowedDomains = _domainMgr.getDomainChildrenIds(path); @@ -2701,7 +2681,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } - List networkIds = new ArrayList<>(); + List networkIds = new ArrayList(); List maps = _networkDomainDao.listDomainNetworkMapByDomain(allowedDomains.toArray()); @@ -2710,28 +2690,30 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } if (!networkIds.isEmpty()) { - SearchCriteria domainSC = sb.create(); - domainSC.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); + SearchCriteria domainSC = _networksDao.createSearchCriteria(); domainSC.addAnd("id", SearchCriteria.Op.IN, networkIds.toArray()); domainSC.addAnd("aclType", SearchCriteria.Op.EQ, ACLType.Domain.toString()); - return domainSC; + + sc.addAnd("id", SearchCriteria.Op.SC, domainSC); + return _networksDao.search(sc, searchFilter); + } else { + return new ArrayList(); } - return null; } - private SearchCriteria getSharedNetworksSearchCriteria(SearchBuilder sb, List permittedAccounts) { + private List listSharedNetworks(SearchCriteria sc, Filter searchFilter, List permittedAccounts) { List sharedNetworkIds = _networkPermissionDao.listPermittedNetworkIdsByAccounts(permittedAccounts); if (!sharedNetworkIds.isEmpty()) { - SearchCriteria ssc = sb.create(); - ssc.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); + SearchCriteria ssc = _networksDao.createSearchCriteria(); ssc.addAnd("id", SearchCriteria.Op.IN, sharedNetworkIds.toArray()); - return ssc; + sc.addAnd("id", SearchCriteria.Op.SC, ssc); + return _networksDao.search(sc, searchFilter); } - return null; + return new ArrayList(); } - private SearchCriteria getSharedNetworksByDomainPathSearchCriteria(SearchBuilder sb, String path, boolean isRecursive) { - Set allowedDomains = new HashSet<>(); + private List listSharedNetworksByDomainPath(SearchCriteria sc, Filter searchFilter, String path, boolean isRecursive) { + Set allowedDomains = new HashSet(); if (path != null) { if (isRecursive) { allowedDomains = _domainMgr.getDomainChildrenIds(path); @@ -2753,13 +2735,13 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C List sharedNetworkIds = _networkPermissionDao.listPermittedNetworkIdsByAccounts(allowedAccountsList); if (!sharedNetworkIds.isEmpty()) { - SearchCriteria ssc = sb.create(); - ssc.setJoinParameters("accountSearch", "typeNEQ", Account.Type.PROJECT); + SearchCriteria ssc = _networksDao.createSearchCriteria(); ssc.addAnd("id", SearchCriteria.Op.IN, sharedNetworkIds.toArray()); - return ssc; + sc.addAnd("id", SearchCriteria.Op.SC, ssc); + return _networksDao.search(sc, searchFilter); } } - return null; + return new ArrayList(); } @Override @@ -2827,7 +2809,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C * TODO Restarting a SDN based network requires updating the nics and the configuration * in the controller. This requires a non-trivial rewrite of the restart procedure. */ - throw new InvalidParameterException("Unable to restart a running SDN network."); + throw new InvalidParameterValueException("Unable to restart a running SDN network."); } Account callerAccount = _accountMgr.getActiveAccountById(user.getAccountId()); @@ -4072,11 +4054,11 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } if (tags != null && tags.size() > 1) { - throw new InvalidParameterException("Only one tag can be specified for a physical network at this time"); + throw new InvalidParameterValueException("Only one tag can be specified for a physical network at this time"); } if (isolationMethods != null && isolationMethods.size() > 1) { - throw new InvalidParameterException("Only one isolationMethod can be specified for a physical network at this time"); + throw new InvalidParameterValueException("Only one isolationMethod can be specified for a physical network at this time"); } if (vnetRange != null && zoneType == NetworkType.Basic) { @@ -4215,7 +4197,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } if (tags != null && tags.size() > 1) { - throw new InvalidParameterException("Unable to support more than one tag on network yet"); + throw new InvalidParameterValueException("Unable to support more than one tag on network yet"); } // If tags are null, then check if there are any other networks with null tags diff --git a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java index a9fa3e95275..3449f1f5d00 100644 --- a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.network.element; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -24,6 +25,7 @@ import java.util.Set; import javax.inject.Inject; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; @@ -90,7 +92,8 @@ import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; -public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement, UserDataServiceProvider, +public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement, + UserDataServiceProvider, DhcpServiceProvider, DnsServiceProvider, StateListener, NetworkMigrationResponder { private static final Map> capabilities = setCapabilities(); @@ -110,6 +113,8 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle @Inject NetworkModel _networkModel; @Inject + NetworkOrchestrationService _networkOrchestrationService; + @Inject GuestOSCategoryDao _guestOSCategoryDao; @Inject GuestOSDao _guestOSDao; @@ -197,6 +202,8 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle private static Map> setCapabilities() { Map> capabilities = new HashMap<>(); capabilities.put(Service.UserData, null); + capabilities.put(Service.Dhcp, new HashMap<>()); + capabilities.put(Service.Dns, new HashMap<>()); return capabilities; } @@ -224,8 +231,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { return (canHandle(network.getTrafficType()) - && configureConfigDriveData(profile, nic, dest)) - && createConfigDriveIso(profile, dest, null); + && configureConfigDriveData(profile, nic, dest)); } @Override @@ -342,10 +348,13 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle configureConfigDriveData(vm, nic, dest); // Create the config drive on dest host cache - createConfigDriveIsoOnHostCache(vm, dest.getHost().getId()); + createConfigDriveIsoOnHostCache(nic, vm, dest.getHost().getId()); } else { vm.setConfigDriveLocation(getConfigDriveLocation(vm.getId())); - addPasswordAndUserdata(network, nic, vm, dest, context); + boolean result = addPasswordAndUserdata(network, nic, vm, dest, context); + if (result) { + createConfigDriveIso(nic, vm, dest, null); + } } } catch (InsufficientCapacityException | ResourceUnavailableException e) { logger.error("Failed to add config disk drive due to: ", e); @@ -398,7 +407,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle vm.getUuid(), nic.getMacAddress(), userVm.getDetail("SSH.PublicKey"), (String) vm.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows, VirtualMachineManager.getHypervisorHostname(dest.getHost() != null ? dest.getHost().getName() : "")); vm.setVmData(vmData); vm.setConfigDriveLabel(VirtualMachineManager.VmConfigDriveLabel.value()); - createConfigDriveIso(vm, dest, diskToUse); + createConfigDriveIso(nic, vm, dest, diskToUse); } } } @@ -528,7 +537,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle return false; } - private boolean createConfigDriveIsoOnHostCache(VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException { + private boolean createConfigDriveIsoOnHostCache(NicProfile nic, VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException { if (hostId == null) { throw new ResourceUnavailableException("Config drive iso creation failed, dest host not available", ConfigDriveNetworkElement.class, 0L); @@ -540,7 +549,9 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName()); final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName()); - final String isoData = ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap); + List nicProfiles = _networkOrchestrationService.getNicProfiles(nic.getVirtualMachineId(), profile.getHypervisorType()); + final Map> supportedServices = getSupportedServicesByElementForNetwork(nicProfiles); + final String isoData = ConfigDriveBuilder.buildConfigDrive(nicProfiles, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap, supportedServices); final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, null, false, true, true); final HandleConfigDriveIsoAnswer answer = (HandleConfigDriveIsoAnswer) agentManager.easySend(hostId, configDriveIsoCommand); @@ -590,7 +601,27 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle return true; } - private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestination dest, DiskTO disk) throws ResourceUnavailableException { + private Map> getSupportedServicesByElementForNetwork(List nics) { + + Map> supportedServices = new HashMap<>(); + for (NicProfile nic: nics) { + ArrayList serviceList = new ArrayList<>(); + if (_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), Service.Dns, getProvider())) { + serviceList.add(Service.Dns); + } + if (_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), Service.UserData, getProvider())) { + serviceList.add(Service.UserData); + } + if (_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), Service.Dhcp, getProvider())) { + serviceList.add(Service.Dhcp); + } + supportedServices.put(nic.getId(), serviceList); + } + + return supportedServices; + } + + public boolean createConfigDriveIso(NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, DiskTO disk) throws ResourceUnavailableException { DataStore dataStore = getDatastoreForConfigDriveIso(disk, profile, dest); final Long agentId = findAgentId(profile, dest, dataStore); @@ -605,7 +636,10 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName()); final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName()); - final String isoData = ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap); + List nicProfiles = _networkOrchestrationService.getNicProfiles(nic.getVirtualMachineId(), profile.getHypervisorType()); + final Map> supportedServices = getSupportedServicesByElementForNetwork(nicProfiles); + final String isoData = ConfigDriveBuilder.buildConfigDrive( + nicProfiles, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap, supportedServices); boolean useHostCacheOnUnsupportedPool = VirtualMachineManager.VmConfigDriveUseHostCacheOnUnsupportedPool.valueIn(dest.getDataCenter().getId()); boolean preferHostCache = VirtualMachineManager.VmConfigDriveForceHostCacheUse.valueIn(dest.getDataCenter().getId()); final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, dataStore.getTO(), useHostCacheOnUnsupportedPool, preferHostCache, true); @@ -758,4 +792,52 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle return true; } + @Override + public boolean addDhcpEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, + ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + // Update nic profile with required information. + // Add network checks + return true; + } + + @Override + public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm, + DeployDestination dest, + ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + return false; + } + + @Override + public boolean removeDhcpSupportForSubnet(Network network) throws ResourceUnavailableException { + return true; + } + + @Override + public boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions) { + return false; + } + + @Override + public boolean removeDhcpEntry(Network network, NicProfile nic, + VirtualMachineProfile vmProfile) throws ResourceUnavailableException { + return true; + } + + @Override + public boolean addDnsEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, + ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + return true; + } + + @Override + public boolean configDnsSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm, + DeployDestination dest, + ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + return true; + } + + @Override + public boolean removeDnsSupportForSubnet(Network network) throws ResourceUnavailableException { + return true; + } } diff --git a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java index 6edb9c4f0af..320f064a31e 100644 --- a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java +++ b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java @@ -16,7 +16,6 @@ // under the License. package com.cloud.network.lb; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -733,7 +732,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(cmd.getLbRuleId()); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer Id:" + cmd.getLbRuleId()); + throw new InvalidParameterValueException("Invalid Load balancer Id:" + cmd.getLbRuleId()); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, loadBalancer); @@ -790,7 +789,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(cmd.getLbRuleId()); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer Id:" + cmd.getLbRuleId()); + throw new InvalidParameterValueException("Invalid Load balancer Id:" + cmd.getLbRuleId()); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, loadBalancer); FirewallRule.State backupState = loadBalancer.getState(); @@ -820,11 +819,11 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LBStickinessPolicyVO stickinessPolicy = _lb2stickinesspoliciesDao.findById(stickinessPolicyId); if (stickinessPolicy == null) { - throw new InvalidParameterException("Invalid Stickiness policy id value: " + stickinessPolicyId); + throw new InvalidParameterValueException("Invalid Stickiness policy id value: " + stickinessPolicyId); } LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(stickinessPolicy.getLoadBalancerId())); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer : " + stickinessPolicy.getLoadBalancerId() + " for Stickiness policy id: " + stickinessPolicyId); + throw new InvalidParameterValueException("Invalid Load balancer : " + stickinessPolicy.getLoadBalancerId() + " for Stickiness policy id: " + stickinessPolicyId); } long loadBalancerId = loadBalancer.getId(); FirewallRule.State backupState = loadBalancer.getState(); @@ -873,11 +872,11 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LBHealthCheckPolicyVO healthCheckPolicy = _lb2healthcheckDao.findById(healthCheckPolicyId); if (healthCheckPolicy == null) { - throw new InvalidParameterException("Invalid HealthCheck policy id value: " + healthCheckPolicyId); + throw new InvalidParameterValueException("Invalid HealthCheck policy id value: " + healthCheckPolicyId); } LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(healthCheckPolicy.getLoadBalancerId())); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer : " + healthCheckPolicy.getLoadBalancerId() + " for HealthCheck policy id: " + healthCheckPolicyId); + throw new InvalidParameterValueException("Invalid Load balancer : " + healthCheckPolicy.getLoadBalancerId() + " for HealthCheck policy id: " + healthCheckPolicyId); } final long loadBalancerId = loadBalancer.getId(); FirewallRule.State backupState = loadBalancer.getState(); @@ -1268,12 +1267,12 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(lbRuleId)); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid load balancer id: " + lbRuleId); + throw new InvalidParameterValueException("Invalid load balancer id: " + lbRuleId); } SslCertVO certVO = _entityMgr.findById(SslCertVO.class, certId); if (certVO == null) { - throw new InvalidParameterException("Invalid certificate id: " + certId); + throw new InvalidParameterValueException("Invalid certificate id: " + certId); } _accountMgr.checkAccess(caller.getCallingAccount(), null, true, loadBalancer); @@ -1332,11 +1331,11 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerCertMapVO lbCertMap = _lbCertMapDao.findByLbRuleId(lbRuleId); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid load balancer value: " + lbRuleId); + throw new InvalidParameterValueException("Invalid load balancer value: " + lbRuleId); } if (lbCertMap == null) { - throw new InvalidParameterException("No certificate is bound to lb with id: " + lbRuleId); + throw new InvalidParameterValueException("No certificate is bound to lb with id: " + lbRuleId); } _accountMgr.checkAccess(caller.getCallingAccount(), null, true, loadBalancer); @@ -1380,7 +1379,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(loadBalancerId)); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid load balancer value: " + loadBalancerId); + throw new InvalidParameterValueException("Invalid load balancer value: " + loadBalancerId); } _accountMgr.checkAccess(caller.getCallingAccount(), null, true, loadBalancer); @@ -2692,7 +2691,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(policy.getLoadBalancerId())); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer : " + policy.getLoadBalancerId() + " for Stickiness policy id: " + id); + throw new InvalidParameterValueException("Invalid Load balancer : " + policy.getLoadBalancerId() + " for Stickiness policy id: " + id); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, loadBalancer); @@ -2719,7 +2718,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements LoadBalancerVO loadBalancer = _lbDao.findById(Long.valueOf(policy.getLoadBalancerId())); if (loadBalancer == null) { - throw new InvalidParameterException("Invalid Load balancer : " + policy.getLoadBalancerId() + " for Stickiness policy id: " + id); + throw new InvalidParameterValueException("Invalid Load balancer : " + policy.getLoadBalancerId() + " for Stickiness policy id: " + id); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, loadBalancer); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index fb0adda9baf..16e3925330d 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -293,16 +293,16 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C assignAccountToProject(project, ownerFinal.getId(), ProjectAccount.Role.Admin, Optional.ofNullable(finalUser).map(User::getId).orElse(null), null); - if (project != null) { - CallContext.current().setEventDetails("Project id=" + project.getId()); - CallContext.current().putContextParameter(Project.class, project.getUuid()); - } + if (project != null) { + CallContext.current().setEventDetails("Project id=" + project.getId()); + CallContext.current().putContextParameter(Project.class, project.getUuid()); + } - //Increment resource count + //Increment resource count _resourceLimitMgr.incrementResourceCount(ownerFinal.getId(), ResourceType.project); - return project; - } + return project; + } }); messageBus.publish(_name, ProjectManager.MESSAGE_CREATE_TUNGSTEN_PROJECT_EVENT, PublishScope.LOCAL, project); @@ -1290,7 +1290,7 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager, C } @Override - @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACTIVATE, eventDescription = "activating project") + @ActionEvent(eventType = EventTypes.EVENT_PROJECT_ACTIVATE, eventDescription = "activating project", async = true) @DB public Project activateProject(final long projectId) { Account caller = CallContext.current().getCallingAccount(); diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index fb73ac577dd..1c76aa2bf72 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -2716,7 +2716,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, } if (existingPrivateIPs.size() == 1) { final DataCenterIpAddressVO vo = existingPrivateIPs.get(0); - if (vo.getInstanceId() != null) { + if (vo.getNicId() != null) { throw new IllegalArgumentException("The private ip address of the server (" + serverPrivateIP + ") is already in use in pod: " + pod.getName() + " and zone: " + dc.getName()); } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index a485cfe0c66..fb3cf9bf193 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -17,6 +17,7 @@ package com.cloud.server; import java.lang.reflect.Field; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -43,6 +44,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.security.CertificateHelper; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroupProcessor; @@ -495,6 +497,7 @@ import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnaps import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.cloudstack.api.command.user.snapshot.RevertSnapshotCmd; @@ -3755,6 +3758,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(CreateSnapshotFromVMSnapshotCmd.class); cmdList.add(CopySnapshotCmd.class); cmdList.add(DeleteSnapshotCmd.class); + cmdList.add(ExtractSnapshotCmd.class); cmdList.add(ArchiveSnapshotCmd.class); cmdList.add(CreateSnapshotPolicyCmd.class); cmdList.add(UpdateSnapshotPolicyCmd.class); @@ -4570,13 +4574,12 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe final String certificate = cmd.getCertificate(); final String key = cmd.getPrivateKey(); + String domainSuffix = cmd.getDomainSuffix(); - if (cmd.getPrivateKey() != null && !_ksMgr.validateCertificate(certificate, key, cmd.getDomainSuffix())) { - throw new InvalidParameterValueException("Failed to pass certificate validation check"); - } + validateCertificate(certificate, key, domainSuffix); if (cmd.getPrivateKey() != null) { - _ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, certificate, key, cmd.getDomainSuffix()); + _ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, certificate, key, domainSuffix); // Reboot ssvm here since private key is present - meaning server cert being passed final List alreadyRunning = _secStorageVmDao.getSecStorageVmListInStates(null, State.Running, State.Migrating, State.Starting); @@ -4593,6 +4596,24 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe + "please give a few minutes for console access and storage services service to be up and working again"; } + private void validateCertificate(String certificate, String key, String domainSuffix) { + if (key != null) { + Pair result = _ksMgr.validateCertificate(certificate, key, domainSuffix); + if (!result.first()) { + throw new InvalidParameterValueException(String.format("Failed to pass certificate validation check with error: %s", result.second())); + } + } else { + try { + logger.debug(String.format("Trying to validate the root certificate format")); + CertificateHelper.buildCertificate(certificate); + } catch (CertificateException e) { + String errorMsg = String.format("Failed to pass certificate validation check with error: Certificate validation failed due to exception: %s", e.getMessage()); + logger.error(errorMsg); + throw new InvalidParameterValueException(errorMsg); + } + } + } + @Override public List getHypervisors(final Long zoneId) { final List result = new ArrayList<>(); diff --git a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java index 84dcd302bdd..32aa09be8b6 100644 --- a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java +++ b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java @@ -46,4 +46,12 @@ public class ResizeVolumePayload { this(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged); this.newDiskOfferingId = newDiskOfferingId; } + + public Long getNewDiskOfferingId() { + return newDiskOfferingId; + } + + public void setNewDiskOfferingId(Long newDiskOfferingId) { + this.newDiskOfferingId = newDiskOfferingId; + } } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 102997c0950..a106465bbce 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1411,7 +1411,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C } }); } else { - throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated " + "non-destroyed vols for this pool"); + logger.debug("Cannot delete storage pool {} as the following non-destroyed volumes are on it: {}.", sPool::getName, () -> getStoragePoolNonDestroyedVolumesLog(sPool.getId())); + throw new CloudRuntimeException(String.format("Cannot delete pool %s as there are non-destroyed volumes associated to this pool.", sPool.getName())); } } return deleteDataStoreInternal(sPool, forced); @@ -1472,7 +1473,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C if (vlms.first() > 0) { Pair nonDstrdVlms = volumeDao.getNonDestroyedCountAndTotalByPool(sPool.getId()); if (nonDstrdVlms.first() > 0) { - throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated " + "non-destroyed vols for this pool"); + logger.debug("Cannot delete storage pool {} as the following non-destroyed volumes are on it: {}.", sPool::getName, () -> getStoragePoolNonDestroyedVolumesLog(sPool.getId())); + throw new CloudRuntimeException(String.format("Cannot delete pool %s as there are non-destroyed volumes associated to this pool.", sPool.getName())); } // force expunge non-destroyed volumes List vols = volumeDao.listVolumesToBeDestroyed(); @@ -1480,9 +1482,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C AsyncCallFuture future = volService.expungeVolumeAsync(volFactory.getVolume(vol.getId())); try { future.get(); - } catch (InterruptedException e) { - logger.debug("expunge volume failed:" + vol.getId(), e); - } catch (ExecutionException e) { + } catch (InterruptedException | ExecutionException e) { logger.debug("expunge volume failed:" + vol.getId(), e); } } @@ -1491,7 +1491,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C // Check if the pool has associated volumes in the volumes table // If it does , then you cannot delete the pool if (vlms.first() > 0) { - throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated volumes for this pool"); + logger.debug("Cannot delete storage pool {} as the following non-destroyed volumes are on it: {}.", sPool::getName, () -> getStoragePoolNonDestroyedVolumesLog(sPool.getId())); + throw new CloudRuntimeException(String.format("Cannot delete pool %s as there are non-destroyed volumes associated to this pool.", sPool.getName())); } } @@ -1514,6 +1515,23 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C return lifeCycle.deleteDataStore(store); } + protected String getStoragePoolNonDestroyedVolumesLog(long storagePoolId) { + StringBuilder sb = new StringBuilder(); + List nonDestroyedVols = volumeDao.findByPoolId(storagePoolId, null).stream().filter(vol -> vol.getState() != Volume.State.Destroy).collect(Collectors.toList()); + VMInstanceVO volInstance; + List logMessageInfo = new ArrayList<>(); + + sb.append("["); + for (VolumeVO vol : nonDestroyedVols) { + volInstance = _vmInstanceDao.findById(vol.getInstanceId()); + logMessageInfo.add(String.format("Volume [%s] (attached to VM [%s])", vol.getUuid(), volInstance.getUuid())); + } + sb.append(String.join(", ", logMessageInfo)); + sb.append("]"); + + return sb.toString(); + } + @Override public boolean connectHostToSharedPool(long hostId, long poolId) throws StorageUnavailableException, StorageConflictException { StoragePool pool = (StoragePool)_dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 73a7f7ab546..a925f5cecf4 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1391,7 +1391,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Integer newHypervisorSnapshotReserve, Long newDiskOfferingId, boolean shrinkOk) { - final VolumeVO volume = _volsDao.findById(volumeId); + VolumeVO volume = _volsDao.findById(volumeId); UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); Long currentDiskOfferingId = volume.getDiskOfferingId(); @@ -1499,12 +1499,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } // Update size if volume has same size as before, else it is already updated - final VolumeVO volumeNow = _volsDao.findById(volumeId); - if (currentSize == volumeNow.getSize() && currentSize != newSize) { + volume = _volsDao.findById(volumeId); + if (currentSize == volume.getSize() && currentSize != newSize) { volume.setSize(newSize); - } else if (volumeNow.getSize() != newSize) { + } else if (volume.getSize() != newSize) { // consider the updated size as the new size - newSize = volumeNow.getSize(); + newSize = volume.getSize(); } _volsDao.update(volume.getId(), volume); @@ -2062,6 +2062,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic if (!volumeMigrateRequired && !volumeResizeRequired) { _volsDao.updateDiskOffering(volume.getId(), newDiskOffering.getId()); volume = _volsDao.findById(volume.getId()); + updateStorageWithTheNewDiskOffering(volume, newDiskOffering); + return volume; } @@ -2098,6 +2100,18 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return volume; } + private void updateStorageWithTheNewDiskOffering(VolumeVO volume, DiskOfferingVO newDiskOffering) { + DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary); + DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null; + + if (dataStoreDriver instanceof PrimaryDataStoreDriver) { + PrimaryDataStoreDriver storageDriver = (PrimaryDataStoreDriver)dataStoreDriver; + if (storageDriver.informStorageForDiskOfferingChange()) { + storageDriver.updateStorageWithTheNewDiskOffering(volume, newDiskOffering); + } + } + } + /** * This method is to compare long values, in miniops and maxiops a or b can be null or 0. * Use this method to treat 0 and null as same @@ -2331,7 +2345,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic * the actual disk size. */ if (currentSize > newSize) { - if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState())) { + if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState()) && !StoragePoolType.StorPool.equals(volume.getPoolType())) { String message = "Unable to shrink volumes of type QCOW2"; logger.warn(message); throw new InvalidParameterValueException(message); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 56981cfe55c..51634adfa4c 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -33,6 +33,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.SecurityChecker; +import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -40,6 +41,7 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd; @@ -72,10 +74,12 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.springframework.stereotype.Component; @@ -466,6 +470,74 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement return snapshot; } + @Override + @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_EXTRACT, eventDescription = "extracting snapshot", async = true) + public String extractSnapshot(ExtractSnapshotCmd cmd) { + Account caller = CallContext.current().getCallingAccount(); + Long snapshotId = cmd.getId(); + Long zoneId = cmd.getZoneId(); + + if (!_accountMgr.isRootAdmin(caller.getId()) && ApiDBUtils.isExtractionDisabled()) { + logger.error("Extraction is disabled through [{}].", Config.DisableExtraction); + throw new PermissionDeniedException("Extraction could not be completed."); + } + + SnapshotVO snapshot = _snapshotDao.findById(snapshotId); + if (snapshot == null || snapshot.getRemoved() != null) { + logger.error("Unable to find active [{}].", snapshot); + throw new InvalidParameterValueException("Unable to find active snapshot."); + } + + if (zoneId != null && dataCenterDao.findById(zoneId) == null) { + logger.error("Invalid zone id [{}].", zoneId); + throw new IllegalArgumentException("Please specify a valid zone."); + } + + _accountMgr.checkAccess(caller, null, true, snapshot); + + List imageStores = dataStoreMgr.getImageStoresByScope(new ZoneScope(zoneId)); + + if (CollectionUtils.isEmpty(imageStores)) { + logger.error("Could not find any zone storages."); + throw new InvalidParameterValueException("Extraction could not be completed"); + } + + SnapshotDataStoreVO snapshotDataStoreReference = null; + ImageStoreEntity chosenStore = null; + + for (DataStore store : imageStores) { + snapshotDataStoreReference = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshotId); + if (snapshotDataStoreReference == null) { + logger.trace("Snapshot [{}] not in store [{}].", snapshotId, store.getId()); + continue; + } + String existingExtractUrl = snapshotDataStoreReference.getExtractUrl(); + if (existingExtractUrl != null) { + logger.debug("Extract URL already exists: [{}].", existingExtractUrl); + return existingExtractUrl; + } + chosenStore = (ImageStoreEntity) store; + logger.debug("Snapshot [{}] found in store [{}].", snapshotId, chosenStore.getId()); + break; + } + + if (ObjectUtils.anyNull(chosenStore, snapshotDataStoreReference)) { + logger.error("Snapshot [{}] not found in any secondary storage.", snapshotId); + throw new InvalidParameterValueException("Snapshot not found."); + } + + snapshotSrv.syncVolumeSnapshotsToRegionStore(snapshot.getVolumeId(), chosenStore); + + SnapshotInfo snapshotObject = snapshotFactory.getSnapshot(snapshotId, chosenStore); + String extractUrl = chosenStore.createEntityExtractUrl(snapshotObject.getPath(), snapshotObject.getBaseVolume().getFormat(), snapshotObject); + logger.debug("Extract URL [{}] created for snapshot [{}].", extractUrl, snapshot); + snapshotDataStoreReference.setExtractUrl(extractUrl); + snapshotDataStoreReference.setExtractUrlCreated(DateUtil.now()); + _snapshotStoreDao.update(snapshotDataStoreReference.getId(), snapshotDataStoreReference); + + return extractUrl; + } + @Override public Snapshot archiveSnapshot(Long snapshotId) { SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshotOnPrimaryStore(snapshotId); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java index 29955066062..2a53021636c 100644 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.springframework.stereotype.Component; @@ -47,7 +48,6 @@ import com.cloud.server.TaggedResourceService; import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotScheduleVO; -import com.cloud.storage.SnapshotVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.SnapshotPolicyDao; @@ -64,7 +64,6 @@ import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.TestClock; import com.cloud.utils.db.DB; import com.cloud.utils.db.GlobalLock; -import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; @@ -144,7 +143,7 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu try { if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { try { - checkStatusOfCurrentlyExecutingSnapshots(); + scheduleNextSnapshotJobsIfNecessary(); } finally { scanLock.unlock(); } @@ -174,70 +173,39 @@ public class SnapshotSchedulerImpl extends ManagerBase implements SnapshotSchedu } } - private void checkStatusOfCurrentlyExecutingSnapshots() { - final SearchCriteria sc = _snapshotScheduleDao.createSearchCriteria(); - sc.addAnd("asyncJobId", SearchCriteria.Op.NNULL); - final List snapshotSchedules = _snapshotScheduleDao.search(sc, null); - for (final SnapshotScheduleVO snapshotSchedule : snapshotSchedules) { - final Long asyncJobId = snapshotSchedule.getAsyncJobId(); - final AsyncJobVO asyncJob = _asyncJobDao.findByIdIncludingRemoved(asyncJobId); - switch (asyncJob.getStatus()) { - case SUCCEEDED: - // The snapshot has been successfully backed up. - // The snapshot state has also been cleaned up. - // We can schedule the next job for this snapshot. - // Remove the existing entry in the snapshot_schedule table. - scheduleNextSnapshotJob(snapshotSchedule); - break; - case FAILED: - // Check the snapshot status. - final Long snapshotId = snapshotSchedule.getSnapshotId(); - if (snapshotId == null) { - // createSnapshotAsync exited, successfully or unsuccessfully, - // even before creating a snapshot record - // No cleanup needs to be done. - // Schedule the next snapshot. - scheduleNextSnapshotJob(snapshotSchedule); - } else { - final SnapshotVO snapshot = _snapshotDao.findById(snapshotId); - if (snapshot == null || snapshot.getRemoved() != null) { - // This snapshot has been deleted successfully from the primary storage - // Again no cleanup needs to be done. - // Schedule the next snapshot. - // There's very little probability that the code reaches this point. - // The snapshotId is a foreign key for the snapshot_schedule table - // set to ON DELETE CASCADE. So if the snapshot entry is deleted, the snapshot_schedule entry will be too. - // But what if it has only been marked as removed? - scheduleNextSnapshotJob(snapshotSchedule); - } else { - // The management server executing this snapshot job appears to have crashed - // while creating the snapshot on primary storage/or backing it up. - // We have no idea whether the snapshot was successfully taken on the primary or not. - // Schedule the next snapshot job. - // The ValidatePreviousSnapshotCommand will take appropriate action on this snapshot - // If the snapshot was taken successfully on primary, it will retry backing it up. - // and cleanup the previous snapshot - // Set the userId to that of system. - //_snapshotManager.validateSnapshot(1L, snapshot); - // In all cases, schedule the next snapshot job - scheduleNextSnapshotJob(snapshotSchedule); - } - } - - break; - case IN_PROGRESS: - // There is no way of knowing from here whether - // 1) Another management server is processing this snapshot job - // 2) The management server has crashed and this snapshot is lying - // around in an inconsistent state. - // Hopefully, this can be resolved at the backend when the current snapshot gets executed. - // But if it remains in this state, the current snapshot will not get executed. - // And it will remain in stasis. - break; - } + private void scheduleNextSnapshotJobsIfNecessary() { + List snapshotSchedules = _snapshotScheduleDao.getSchedulesAssignedWithAsyncJob(); + logger.info("Verifying the current state of [{}] snapshot schedules and scheduling next jobs, if necessary.", snapshotSchedules.size()); + for (SnapshotScheduleVO snapshotSchedule : snapshotSchedules) { + scheduleNextSnapshotJobIfNecessary(snapshotSchedule); } } + protected void scheduleNextSnapshotJobIfNecessary(SnapshotScheduleVO snapshotSchedule) { + Long asyncJobId = snapshotSchedule.getAsyncJobId(); + AsyncJobVO asyncJob = _asyncJobDao.findByIdIncludingRemoved(asyncJobId); + + if (asyncJob == null) { + logger.debug("The async job [{}] of snapshot schedule [{}] does not exist anymore. Considering it as finished and scheduling the next snapshot job.", + asyncJobId, snapshotSchedule); + scheduleNextSnapshotJob(snapshotSchedule); + return; + } + + JobInfo.Status status = asyncJob.getStatus(); + + if (JobInfo.Status.SUCCEEDED.equals(status)) { + logger.debug("Last job of schedule [{}] succeeded; scheduling the next snapshot job.", snapshotSchedule); + } else if (JobInfo.Status.FAILED.equals(status)) { + logger.debug("Last job of schedule [{}] failed with [{}]; scheduling a new snapshot job.", snapshotSchedule, asyncJob.getResult()); + } else { + logger.debug("Schedule [{}] is still in progress, skipping next job scheduling.", snapshotSchedule); + return; + } + + scheduleNextSnapshotJob(snapshotSchedule); + } + @DB protected void deleteExpiredVMSnapshots() { Date now = new Date(); diff --git a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java index 365f0202c87..026a9350f33 100644 --- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java @@ -35,12 +35,14 @@ import org.apache.cloudstack.agent.directdownload.CheckUrlAnswer; import org.apache.cloudstack.agent.directdownload.CheckUrlCommand; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.direct.download.DirectDownloadManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -202,7 +204,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { public TemplateProfile prepare(RegisterIsoCmd cmd) throws ResourceAllocationException { TemplateProfile profile = super.prepare(cmd); String url = profile.getUrl(); - UriUtils.validateUrl(ImageFormat.ISO.getFileExtension(), url); + UriUtils.validateUrl(ImageFormat.ISO.getFileExtension(), url, !TemplateManager.getValidateUrlIsResolvableBeforeRegisteringTemplateValue(), false); boolean followRedirects = StorageManager.DataStoreDownloadFollowRedirects.value(); if (cmd.isDirectDownload()) { DigestHelper.validateChecksumString(cmd.getChecksum()); @@ -236,7 +238,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocationException { TemplateProfile profile = super.prepare(cmd); String url = profile.getUrl(); - UriUtils.validateUrl(cmd.getFormat(), url, cmd.isDirectDownload()); + UriUtils.validateUrl(cmd.getFormat(), url, !TemplateManager.getValidateUrlIsResolvableBeforeRegisteringTemplateValue(), cmd.isDirectDownload()); Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.getType(cmd.getHypervisor()); boolean followRedirects = StorageManager.DataStoreDownloadFollowRedirects.value(); if (cmd.isDirectDownload()) { @@ -419,6 +421,16 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { if (zoneIdList.size() > 1) throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time."); + // Set Event Details for Template/ISO Upload + String eventType = template.getFormat().equals(ImageFormat.ISO) ? "Iso" : "Template"; + String eventResourceId = template.getUuid(); + CallContext.current().setEventDetails(String.format("%s Id: %s", eventType, eventResourceId)); + CallContext.current().putContextParameter(eventType.equals("Iso") ? eventType : VirtualMachineTemplate.class, eventResourceId); + if (template.getFormat().equals(ImageFormat.ISO)) { + CallContext.current().setEventResourceType(ApiCommandResourceType.Iso); + CallContext.current().setEventResourceId(template.getId()); + } + Long zoneId = zoneIdList.get(0); DataStore imageStore = verifyHeuristicRulesForZone(template, zoneId); List payloads = new LinkedList<>(); @@ -752,8 +764,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { public TemplateProfile prepareDelete(DeleteTemplateCmd cmd) { TemplateProfile profile = super.prepareDelete(cmd); VMTemplateVO template = profile.getTemplate(); - if (template.getTemplateType() == TemplateType.SYSTEM) { - throw new InvalidParameterValueException("The DomR template cannot be deleted."); + if (template.getTemplateType() == TemplateType.SYSTEM && !cmd.getIsSystem()) { + throw new InvalidParameterValueException("Could not delete template as it is a SYSTEM template and isSystem is set to false."); } checkZoneImageStores(profile.getTemplate(), profile.getZoneIdList()); return profile; diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 11254afbaad..af558304981 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -298,7 +298,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Inject private HypervisorGuruManager _hvGuruMgr; - private boolean _disableExtraction = false; private List _adapters; ExecutorService _preloadExecutor; @@ -539,7 +538,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, if (isISO) { desc = Upload.Type.ISO.toString(); } - if (!_accountMgr.isRootAdmin(caller.getId()) && _disableExtraction) { + if (!_accountMgr.isRootAdmin(caller.getId()) && ApiDBUtils.isExtractionDisabled()) { throw new PermissionDeniedException("Extraction has been disabled by admin"); } @@ -1112,10 +1111,6 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Override public boolean configure(String name, Map params) throws ConfigurationException { - - String disableExtraction = _configDao.getValue(Config.DisableExtraction.toString()); - _disableExtraction = (disableExtraction == null) ? false : Boolean.parseBoolean(disableExtraction); - _preloadExecutor = Executors.newFixedThreadPool(TemplatePreloaderPoolSize.value(), new NamedThreadFactory("Template-Preloader")); return true; @@ -2354,7 +2349,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {AllowPublicUserTemplates, TemplatePreloaderPoolSize}; + return new ConfigKey[] {AllowPublicUserTemplates, TemplatePreloaderPoolSize, ValidateUrlIsResolvableBeforeRegisteringTemplate}; } public List getTemplateAdapters() { diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index 170ef1fdbbc..421d2587441 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -31,7 +31,6 @@ import com.cloud.utils.DateUtil; import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; -import org.apache.cloudstack.api.response.UsageTypeResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.usage.Usage; @@ -485,10 +484,4 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag } return true; } - - @Override - public List listUsageTypes() { - return UsageTypes.listUsageTypes(); - } - } diff --git a/server/src/main/java/com/cloud/user/AccountManager.java b/server/src/main/java/com/cloud/user/AccountManager.java index 6d2d1db5668..72235a808a4 100644 --- a/server/src/main/java/com/cloud/user/AccountManager.java +++ b/server/src/main/java/com/cloud/user/AccountManager.java @@ -199,4 +199,6 @@ public interface AccountManager extends AccountService, Configurable { UserTwoFactorAuthenticationSetupResponse setupUserTwoFactorAuthentication(SetupUserTwoFactorAuthenticationCmd cmd); List getApiNameList(); + + void checkApiAccess(Account caller, String command); } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 15121aa0a14..07d06fbd2f7 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1369,6 +1369,12 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M } } + @Override + public void checkApiAccess(Account caller, String command) { + List apiCheckers = getEnabledApiCheckers(); + checkApiAccess(apiCheckers, caller, command); + } + @NotNull private List getEnabledApiCheckers() { // we are really only interested in the dynamic access checker @@ -1836,7 +1842,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M // If the user is a System user, return an error. We do not allow this AccountVO account = _accountDao.findById(accountId); - if (! isDeleteNeeded(account, accountId, caller)) { + if (caller.getId() == accountId) { + Domain domain = _domainDao.findById(account.getDomainId()); + throw new InvalidParameterValueException(String.format("Deletion of your own account is not allowed. To delete account %s (ID: %s, Domain: %s), " + + "request to another user with permissions to perform the operation.", + account.getAccountName(), account.getUuid(), domain.getUuid())); + } + + if (!isDeleteNeeded(account, accountId, caller)) { return true; } @@ -1856,7 +1869,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M return deleteAccount(account, callerUserId, caller); } - private boolean isDeleteNeeded(AccountVO account, long accountId, Account caller) { + protected boolean isDeleteNeeded(AccountVO account, long accountId, Account caller) { if (account == null) { logger.info(String.format("The account, identified by id %d, doesn't exist", accountId )); return false; @@ -2769,7 +2782,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M throw new InvalidParameterValueException("Unable to find user by id"); } final ControlledEntity account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user. - checkAccess(CallContext.current().getCallingUser(), account); + User caller = CallContext.current().getCallingUser(); + preventRootDomainAdminAccessToRootAdminKeys(caller, account); + checkAccess(caller, account); Map keys = new HashMap(); keys.put("apikey", user.getApiKey()); @@ -2778,6 +2793,19 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M return keys; } + protected void preventRootDomainAdminAccessToRootAdminKeys(User caller, ControlledEntity account) { + if (isDomainAdminForRootDomain(caller) && isRootAdmin(account.getAccountId())) { + String msg = String.format("Caller Username %s does not have access to root admin keys", caller.getUsername()); + logger.error(msg); + throw new PermissionDeniedException(msg); + } + } + + protected boolean isDomainAdminForRootDomain(User callingUser) { + AccountVO caller = _accountDao.findById(callingUser.getAccountId()); + return caller.getType() == Account.Type.DOMAIN_ADMIN && caller.getDomainId() == Domain.ROOT_DOMAIN; + } + @Override public List listUserTwoFactorAuthenticationProviders() { return userTwoFactorAuthenticationProviders; @@ -2812,6 +2840,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M } Account account = _accountDao.findById(user.getAccountId()); + preventRootDomainAdminAccessToRootAdminKeys(user, account); checkAccess(caller, null, true, account); // don't allow updating system user diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 958b81e8351..22ef809e5da 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -65,9 +65,11 @@ import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin; +import org.apache.cloudstack.api.command.admin.vm.ExpungeVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; @@ -3328,6 +3330,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return null; } + /** + * Encapsulates AllowUserExpungeRecoverVm so we can unit test checkExpungeVmPermission. + */ + protected boolean getConfigAllowUserExpungeRecoverVm(Long accountId) { + return AllowUserExpungeRecoverVm.valueIn(accountId); + } + + protected void checkExpungeVmPermission (Account callingAccount) { + logger.debug(String.format("Checking if [%s] has permission for expunging VMs.", callingAccount)); + if (!_accountMgr.isAdmin(callingAccount.getId()) && !getConfigAllowUserExpungeRecoverVm(callingAccount.getId())) { + logger.error(String.format("Parameter [%s] can only be passed by Admin accounts or when the allow.user.expunge.recover.vm key is true.", ApiConstants.EXPUNGE)); + throw new PermissionDeniedException("Account does not have permission for expunging."); + } + try { + _accountMgr.checkApiAccess(callingAccount, BaseCmd.getCommandNameByClass(ExpungeVMCmd.class)); + } catch (PermissionDeniedException ex) { + logger.error(String.format("Role [%s] of [%s] does not have permission for expunging VMs.", callingAccount.getRoleId(), callingAccount)); + throw new PermissionDeniedException("Account does not have permission for expunging."); + } + } + protected void checkPluginsIfVmCanBeDestroyed(UserVm vm) { try { KubernetesServiceHelper kubernetesServiceHelper = @@ -3345,10 +3368,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir long vmId = cmd.getId(); boolean expunge = cmd.getExpunge(); - // When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVm is false for the caller. - if (expunge && !_accountMgr.isAdmin(ctx.getCallingAccount().getId()) && !AllowUserExpungeRecoverVm.valueIn(cmd.getEntityOwnerId())) { - throw new PermissionDeniedException("Parameter " + ApiConstants.EXPUNGE + " can be passed by Admin only. Or when the allow.user.expunge.recover.vm key is set."); + if (expunge) { + checkExpungeVmPermission(ctx.getCallingAccount()); } + // check if VM exists UserVmVO vm = _vmDao.findById(vmId); @@ -4753,17 +4776,24 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, dataDiskControllerSetting); } - String controllerSetting = StringUtils.defaultIfEmpty(_configDao.getValue(Config.VmwareRootDiskControllerType.key()), - Config.VmwareRootDiskControllerType.getDefaultValue()); - // Don't override if VM already has root/data disk controller detail if (vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER) == null) { - vm.setDetail(VmDetailConstants.ROOT_DISK_CONTROLLER, controllerSetting); + String vmwareRootDiskControllerTypeFromSetting = StringUtils.defaultIfEmpty(_configDao.getValue(Config.VmwareRootDiskControllerType.key()), + Config.VmwareRootDiskControllerType.getDefaultValue()); + vm.setDetail(VmDetailConstants.ROOT_DISK_CONTROLLER, vmwareRootDiskControllerTypeFromSetting); } + if (vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER) == null) { - if (controllerSetting.equalsIgnoreCase("scsi")) { - vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, "scsi"); + String finalRootDiskController = vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER); + // Set the data disk controller detail same as the final scsi root disk controller if VM doesn't have data disk controller detail + // This is to ensure the disk controller is available for the data disks, as all the SCSI controllers are created with same controller type + String scsiControllerPattern = "(?i)\\b(scsi|lsilogic|lsilogicsas|lsisas1068|buslogic|pvscsi)\\b"; + if (finalRootDiskController.matches(scsiControllerPattern)) { + logger.info(String.format("Data disk controller was not defined, but root disk is using SCSI controller [%s]." + + "To ensure disk controllers are available for the data disks, the data disk controller is updated to match the root disk controller.", finalRootDiskController)); + vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, finalRootDiskController); } else { + logger.info("Data disk controller was not defined; defaulting to 'osdefault'."); vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, "osdefault"); } } diff --git a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java index bab286bb8f9..60e7093c48b 100644 --- a/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java @@ -36,6 +36,8 @@ import org.apache.cloudstack.api.command.admin.acl.CreateRoleCmd; import org.apache.cloudstack.api.command.admin.acl.CreateRolePermissionCmd; import org.apache.cloudstack.api.command.admin.acl.DeleteRoleCmd; import org.apache.cloudstack.api.command.admin.acl.DeleteRolePermissionCmd; +import org.apache.cloudstack.api.command.admin.acl.DisableRoleCmd; +import org.apache.cloudstack.api.command.admin.acl.EnableRoleCmd; import org.apache.cloudstack.api.command.admin.acl.ImportRoleCmd; import org.apache.cloudstack.api.command.admin.acl.ListRolePermissionsCmd; import org.apache.cloudstack.api.command.admin.acl.ListRolesCmd; @@ -349,6 +351,36 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu throw new PermissionDeniedException("Found accounts that have role in use, won't allow to delete role"); } + protected boolean updateRoleState(Role role, Role.State state) { + checkCallerAccess(); + if (role == null) { + return false; + } + if (role.getState().equals(state)) { + throw new PermissionDeniedException(String.format("Role is already %s", state)); + } + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + RoleVO roleVO = roleDao.findById(role.getId()); + roleVO.setState(state); + return roleDao.update(role.getId(), roleVO); + } + }); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ROLE_ENABLE, eventDescription = "enabling Role") + public boolean enableRole(Role role) { + return updateRoleState(role, Role.State.ENABLED); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_ROLE_DISABLE, eventDescription = "disabling Role") + public boolean disableRole(Role role) { + return updateRoleState(role, Role.State.DISABLED); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ROLE_PERMISSION_CREATE, eventDescription = "creating Role Permission") public RolePermission createRolePermission(final Role role, final Rule rule, final Permission permission, final String description) { @@ -401,13 +433,13 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu @Override public List findRolesByName(String name) { - return findRolesByName(name, null, null, null).first(); + return findRolesByName(name, null, null, null, null).first(); } @Override - public Pair, Integer> findRolesByName(String name, String keyword, Long startIndex, Long limit) { + public Pair, Integer> findRolesByName(String name, String keyword, String state, Long startIndex, Long limit) { if (StringUtils.isNotBlank(name) || StringUtils.isNotBlank(keyword)) { - Pair, Integer> data = roleDao.findAllByName(name, keyword, startIndex, limit, isCallerRootAdmin()); + Pair, Integer> data = roleDao.findAllByName(name, keyword, state, startIndex, limit, isCallerRootAdmin()); int removed = removeRolesIfNeeded(data.first()); return new Pair,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed)); } @@ -504,15 +536,15 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu @Override public List findRolesByType(RoleType roleType) { - return findRolesByType(roleType, null, null).first(); + return findRolesByType(roleType, null, null, null).first(); } @Override - public Pair, Integer> findRolesByType(RoleType roleType, Long startIndex, Long limit) { + public Pair, Integer> findRolesByType(RoleType roleType, String state, Long startIndex, Long limit) { if (roleType == null || RoleType.Admin == roleType && !isCallerRootAdmin()) { return new Pair, Integer>(Collections.emptyList(), 0); } - Pair, Integer> data = roleDao.findAllByRoleType(roleType, startIndex, limit, isCallerRootAdmin()); + Pair, Integer> data = roleDao.findAllByRoleType(roleType, state, startIndex, limit, isCallerRootAdmin()); return new Pair,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second())); } @@ -524,8 +556,8 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu } @Override - public Pair, Integer> listRoles(Long startIndex, Long limit) { - Pair, Integer> data = roleDao.listAllRoles(startIndex, limit, isCallerRootAdmin()); + public Pair, Integer> listRoles(String state, Long startIndex, Long limit) { + Pair, Integer> data = roleDao.listAllRoles(state, startIndex, limit, isCallerRootAdmin()); int removed = removeRolesIfNeeded(data.first()); return new Pair,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed)); } @@ -577,6 +609,8 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu cmdList.add(ListRolePermissionsCmd.class); cmdList.add(UpdateRolePermissionCmd.class); cmdList.add(DeleteRolePermissionCmd.class); + cmdList.add(EnableRoleCmd.class); + cmdList.add(DisableRoleCmd.class); return cmdList; } } diff --git a/server/src/test/java/com/cloud/acl/DomainCheckerTest.java b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java new file mode 100644 index 00000000000..a5ec41306d8 --- /dev/null +++ b/server/src/test/java/com/cloud/acl/DomainCheckerTest.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.acl; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.SecurityChecker; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.ProjectManager; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Ternary; + +@RunWith(MockitoJUnitRunner.class) +public class DomainCheckerTest { + + @Mock + AccountService _accountService; + @Mock + AccountDao _accountDao; + @Mock + DomainDao _domainDao; + @Mock + ProjectManager _projectMgr; + + @Spy + @InjectMocks + DomainChecker domainChecker; + + private ControlledEntity getMockedEntity(long accountId) { + ControlledEntity entity = Mockito.mock(Account.class); + Mockito.when(entity.getAccountId()).thenReturn(accountId); + Mockito.when(entity.getEntityType()).thenReturn((Class)Account.class); + return entity; + } + + @Test + public void testRootAdminHasAccess() { + Account rootAdmin = Mockito.mock(Account.class); + Mockito.when(rootAdmin.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(2L); + Mockito.when(_accountService.isRootAdmin(rootAdmin.getId())).thenReturn(true); + + domainChecker.validateCallerHasAccessToEntityOwner(rootAdmin, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testCallerIsOwner() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(1L); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test(expected = PermissionDeniedException.class) + public void testOwnerNotFound() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + ControlledEntity entity = getMockedEntity(2L); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(null); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testDomainAdminHasAccess() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + Mockito.when(caller.getDomainId()).thenReturn(100L); + Mockito.when(caller.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + ControlledEntity entity = getMockedEntity(2L); + AccountVO owner = Mockito.mock(AccountVO.class); + Mockito.when(owner.getDomainId()).thenReturn(101L); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(owner); + Mockito.when(_domainDao.isChildDomain(100L, 101L)).thenReturn(true); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + private Ternary getProjectAccessCheckResources() { + Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(100L); + Mockito.when(caller.getType()).thenReturn(Account.Type.PROJECT); + ControlledEntity entity = getMockedEntity(2L); + AccountVO projectAccount = Mockito.mock(AccountVO.class); + Mockito.when(projectAccount.getId()).thenReturn(2L); + Mockito.when(projectAccount.getType()).thenReturn(Account.Type.PROJECT); + return new Ternary<>(caller, entity, projectAccount); + } + + @Test + public void testProjectOwnerCanModify() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canModifyProjectAccount(caller, projectAccount.getId())).thenReturn(true); + Mockito.doReturn(true).when(domainChecker).checkOperationPermitted(caller, entity); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test(expected = PermissionDeniedException.class) + public void testProjectOwnerCannotModify() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canModifyProjectAccount(caller, projectAccount.getId())).thenReturn(false); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ModifyProject); + } + + @Test + public void testProjectOwnerCanAccess() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canAccessProjectAccount(caller, projectAccount.getId())).thenReturn(true); + Mockito.doReturn(true).when(domainChecker).checkOperationPermitted(caller, entity); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ListEntry); + } + + @Test(expected = PermissionDeniedException.class) + public void testProjectOwnerCannotAccess() { + Ternary resources = getProjectAccessCheckResources(); + Account caller = resources.first(); + ControlledEntity entity = resources.second(); + AccountVO projectAccount = resources.third(); + Mockito.when(_accountDao.findById(entity.getAccountId())).thenReturn(projectAccount); + Mockito.when(_projectMgr.canAccessProjectAccount(caller, projectAccount.getId())).thenReturn(false); + + domainChecker.validateCallerHasAccessToEntityOwner(caller, entity, SecurityChecker.AccessType.ListEntry); + } + +} diff --git a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java index 22c0ba5a795..da70bc1c1bf 100644 --- a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java +++ b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java @@ -26,6 +26,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.User; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; @@ -63,6 +64,9 @@ public class ParamProcessWorkerTest { @Parameter(name = "doubleparam1", type = CommandType.DOUBLE) double doubleparam1; + @Parameter(name = "vmHostNameParam", type = CommandType.STRING, validations = {ApiArgValidator.RFCComplianceDomainName}) + String vmHostNameParam; + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { @@ -100,11 +104,44 @@ public class ParamProcessWorkerTest { params.put("intparam1", "100"); params.put("boolparam1", "true"); params.put("doubleparam1", "11.89"); + params.put("vmHostNameParam", "test-host-name-123"); final TestCmd cmd = new TestCmd(); paramProcessWorker.processParameters(cmd, params); Assert.assertEquals("foo", cmd.strparam1); Assert.assertEquals(100, cmd.intparam1); Assert.assertTrue(Double.compare(cmd.doubleparam1, 11.89) == 0); + Assert.assertEquals("test-host-name-123", cmd.vmHostNameParam); } + @Test(expected = ServerApiException.class) + public void processVmHostNameParameter_CannotStartWithDigit() { + final HashMap params = new HashMap(); + params.put("vmHostNameParam", "123test"); + final TestCmd cmd = new TestCmd(); + paramProcessWorker.processParameters(cmd, params); + } + + @Test(expected = ServerApiException.class) + public void processVmHostNameParameter_CannotStartWithHypen() { + final HashMap params = new HashMap(); + params.put("vmHostNameParam", "-test"); + final TestCmd cmd = new TestCmd(); + paramProcessWorker.processParameters(cmd, params); + } + + @Test(expected = ServerApiException.class) + public void processVmHostNameParameter_CannotEndWithHypen() { + final HashMap params = new HashMap(); + params.put("vmHostNameParam", "test-"); + final TestCmd cmd = new TestCmd(); + paramProcessWorker.processParameters(cmd, params); + } + + @Test(expected = ServerApiException.class) + public void processVmHostNameParameter_NotMoreThan63Chars() { + final HashMap params = new HashMap(); + params.put("vmHostNameParam", "test-f2405112-d5a1-47c1-9f00-976909e3a6d3-1e6f3264-955ee76011a99"); + final TestCmd cmd = new TestCmd(); + paramProcessWorker.processParameters(cmd, params); + } } diff --git a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java index d83120d75f3..8c8dc33d7ec 100644 --- a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java +++ b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java @@ -61,6 +61,7 @@ import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Maps; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; @@ -83,6 +84,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -148,6 +150,7 @@ public class ConfigDriveNetworkElementTest { @Mock private AgentManager agentManager; @Mock private CallContext callContextMock; @Mock private DomainVO domainVO; + @Mock private NetworkOrchestrationService _networkOrchestrationService; @Spy @InjectMocks private ConfigDriveNetworkElement _configDrivesNetworkElement = new ConfigDriveNetworkElement(); @@ -264,13 +267,9 @@ public class ConfigDriveNetworkElementTest { try (MockedStatic ignored1 = Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic ignored2 = Mockito.mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount(); - Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyList(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn("content"); final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class); final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class); - when(agentManager.easySend(Mockito.anyLong(), Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); - when(answer.getResult()).thenReturn(true); - when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY); when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest); when(virtualMachine.getUuid()).thenReturn("vm-uuid"); when(userVmDetailVO.getValue()).thenReturn(PUBLIC_KEY); @@ -288,6 +287,28 @@ public class ConfigDriveNetworkElementTest { profile.setConfigDriveLabel("testlabel"); assertTrue(_configDrivesNetworkElement.addPasswordAndUserdata( network, nicp, profile, deployDestination, null)); + } + } + + @Test + public void testCreateConfigDriveIso() throws Exception { + try (MockedStatic ignored1 = Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic ignored2 = Mockito.mockStatic(CallContext.class)) { + Mockito.when(CallContext.current()).thenReturn(callContextMock); + Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyList(), Mockito.anyList(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), Mockito.anyMap())).thenReturn("content"); + + final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class); + when(agentManager.easySend(Mockito.anyLong(), Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); + when(answer.getResult()).thenReturn(true); + when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY); + when(virtualMachine.getUuid()).thenReturn("vm-uuid"); + + Map parms = Maps.newHashMap(); + parms.put(VirtualMachineProfile.Param.VmPassword, PASSWORD); + parms.put(VirtualMachineProfile.Param.VmSshPubKey, PUBLIC_KEY); + VirtualMachineProfile profile = new VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, parms); + profile.setConfigDriveLabel("testlabel"); + profile.setVmData(Collections.emptyList()); + assertTrue(_configDrivesNetworkElement.createConfigDriveIso(nicp, profile, deployDestination, null)); ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class); verify(agentManager, times(1)).easySend(Mockito.anyLong(), commandCaptor.capture()); diff --git a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java index 4c893121fd9..fcbae4f339c 100644 --- a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java +++ b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java @@ -110,6 +110,18 @@ public class StorageManagerImplTest { @InjectMocks private StorageManagerImpl storageManagerImpl; + @Mock + private StoragePoolVO storagePoolVOMock; + + @Mock + private VolumeVO volume1VOMock; + + @Mock + private VolumeVO volume2VOMock; + + @Mock + private VMInstanceVO vmInstanceVOMock; + @Test public void createLocalStoragePoolName() { String hostMockName = "host1"; @@ -515,6 +527,24 @@ public class StorageManagerImplTest { .update(StorageManager.DataStoreDownloadFollowRedirects.key(),StorageManager.DataStoreDownloadFollowRedirects.defaultValue()); } + @Test + public void getStoragePoolNonDestroyedVolumesLogTestNonDestroyedVolumesReturnLog() { + Mockito.doReturn(1L).when(storagePoolVOMock).getId(); + Mockito.doReturn(1L).when(volume1VOMock).getInstanceId(); + Mockito.doReturn("786633d1-a942-4374-9d56-322dd4b0d202").when(volume1VOMock).getUuid(); + Mockito.doReturn(1L).when(volume2VOMock).getInstanceId(); + Mockito.doReturn("ffb46333-e983-4c21-b5f0-51c5877a3805").when(volume2VOMock).getUuid(); + Mockito.doReturn("58760044-928f-4c4e-9fef-d0e48423595e").when(vmInstanceVOMock).getUuid(); + + Mockito.when(_volumeDao.findByPoolId(storagePoolVOMock.getId(), null)).thenReturn(List.of(volume1VOMock, volume2VOMock)); + Mockito.doReturn(vmInstanceVOMock).when(vmInstanceDao).findById(Mockito.anyLong()); + + String log = storageManagerImpl.getStoragePoolNonDestroyedVolumesLog(storagePoolVOMock.getId()); + String expected = String.format("[Volume [%s] (attached to VM [%s]), Volume [%s] (attached to VM [%s])]", volume1VOMock.getUuid(), vmInstanceVOMock.getUuid(), volume2VOMock.getUuid(), vmInstanceVOMock.getUuid()); + + Assert.assertEquals(expected, log); + } + private ChangeStoragePoolScopeCmd mockChangeStoragePooolScopeCmd(String newScope) { ChangeStoragePoolScopeCmd cmd = new ChangeStoragePoolScopeCmd(); ReflectionTestUtils.setField(cmd, "id", 1L); diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java index 74b31283d9d..28903c72cc3 100755 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java @@ -27,13 +27,20 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import com.cloud.api.ApiDBUtils; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.storage.Storage; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; @@ -49,6 +56,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -176,6 +184,16 @@ public class SnapshotManagerTest { @Mock DataCenterDao dataCenterDao; + MockedStatic apiDBUtilsMock; + @Mock + ExtractSnapshotCmd extractSnapshotCmdMock; + @Mock + DataCenterVO dataCenterVOMock; + @Mock + ImageStoreEntity imageStoreEntityMock; + @Mock + DataStoreManager dataStoreManagerMock; + SnapshotPolicyVO snapshotPolicyVoInstance; List listIntervalTypes = Arrays.asList(DateUtil.IntervalType.values()); @@ -191,6 +209,11 @@ public class SnapshotManagerTest { private static final int TEST_SNAPSHOT_POLICY_MAX_SNAPS = 1; private static final boolean TEST_SNAPSHOT_POLICY_DISPLAY = true; private static final boolean TEST_SNAPSHOT_POLICY_ACTIVE = true; + private static final long TEST_ZONE_ID = 7L; + private static final long TEST_SNAPSHOTDATASTORE_ID = 7L; + private static final String TEST_EXTRACT_URL = "extractUrl"; + private static final String TEST_SNAPSHOT_PATH = "path"; + private static final Storage.ImageFormat TEST_VOLUME_FORMAT = Storage.ImageFormat.RAW; @Before public void setup() throws ResourceAllocationException { @@ -228,10 +251,13 @@ public class SnapshotManagerTest { snapshotPolicyVoInstance = new SnapshotPolicyVO(TEST_VOLUME_ID, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY); + + apiDBUtilsMock = Mockito.mockStatic(ApiDBUtils.class); } @After public void tearDown() throws Exception { + apiDBUtilsMock.close(); CallContext.unregister(); } @@ -533,4 +559,108 @@ public class SnapshotManagerTest { mockForBackupSnapshotToSecondaryZoneTest(true, DataCenter.Type.Edge); Assert.assertFalse(_snapshotMgr.isBackupSnapshotToSecondaryForZone(1L)); } + + private void mockForExtractSnapshotTests() { + Mockito.doReturn(TEST_SNAPSHOT_ID).when(extractSnapshotCmdMock).getId(); + Mockito.doReturn(TEST_ZONE_ID).when(extractSnapshotCmdMock).getZoneId(); + Mockito.doReturn(false).when(_accountMgr).isRootAdmin(Mockito.anyLong()); + Mockito.when(ApiDBUtils.isExtractionDisabled()).thenReturn(false); + + Mockito.doReturn(dataCenterVOMock).when(dataCenterDao).findById(TEST_ZONE_ID); + + List dataStores = new ArrayList<>(); + dataStores.add(imageStoreEntityMock); + Mockito.doReturn(dataStores).when(dataStoreManagerMock).getImageStoresByScope(Mockito.any()); + Mockito.doReturn(TEST_STORAGE_POOL_ID).when(imageStoreEntityMock).getId(); + + Mockito.doReturn(snapshotStoreMock).when(snapshotStoreDao).findByStoreSnapshot(DataStoreRole.Image, TEST_STORAGE_POOL_ID, TEST_SNAPSHOT_ID); + + Mockito.doReturn(snapshotInfoMock).when(snapshotFactory).getSnapshot(TEST_SNAPSHOT_ID, imageStoreEntityMock); + Mockito.doReturn(TEST_SNAPSHOT_PATH).when(snapshotInfoMock).getPath(); + Mockito.doReturn(volumeInfoMock).when(snapshotInfoMock).getBaseVolume(); + Mockito.doReturn(TEST_VOLUME_FORMAT).when(volumeInfoMock).getFormat(); + + Mockito.doReturn(TEST_SNAPSHOTDATASTORE_ID).when(snapshotStoreMock).getId(); + Mockito.doReturn(TEST_EXTRACT_URL).when(imageStoreEntityMock).createEntityExtractUrl(TEST_SNAPSHOT_PATH, TEST_VOLUME_FORMAT, snapshotInfoMock); + } + + @Test(expected = PermissionDeniedException.class) + public void extractSnapshotTestNotRootAdminDisabledExtractionReturnException() { + mockForExtractSnapshotTests(); + Mockito.when(ApiDBUtils.isExtractionDisabled()).thenReturn(true); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test(expected = InvalidParameterValueException.class) + public void extractSnapshotTestNullSnapshotReturnException() { + mockForExtractSnapshotTests(); + Mockito.doReturn(null).when(_snapshotDao).findById(TEST_SNAPSHOT_ID); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test(expected = InvalidParameterValueException.class) + public void extractSnapshotTestRemovedSnapshotReturnException() { + mockForExtractSnapshotTests(); + Mockito.doReturn(Mockito.mock(Date.class)).when(snapshotMock).getRemoved(); + Mockito.doReturn(snapshotMock).when(_snapshotDao).findById(TEST_SNAPSHOT_ID); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test(expected = IllegalArgumentException.class) + public void extractSnapshotTestNullDataCenterReturnException() { + mockForExtractSnapshotTests(); + Mockito.doReturn(null).when(dataCenterDao).findById(TEST_ZONE_ID); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test(expected = InvalidParameterValueException.class) + public void extractSnapshotTestNoZoneStoragesReturnException() { + mockForExtractSnapshotTests(); + Mockito.doReturn(Collections.emptyList()).when(dataStoreManagerMock).getImageStoresByScope(Mockito.any()); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test() + public void extractSnapshotTestExistingExtractUrlReturnUrl() { + mockForExtractSnapshotTests(); + String extractUrl = "extractUrl"; + Mockito.doReturn(extractUrl).when(snapshotStoreMock).getExtractUrl(); + + Assert.assertEquals(extractUrl, _snapshotMgr.extractSnapshot(extractSnapshotCmdMock)); + Mockito.verify(snapshotSrv, Mockito.never()).syncVolumeSnapshotsToRegionStore(Mockito.anyLong(), Mockito.any()); + Mockito.verify(snapshotStoreDao, Mockito.never()).update(Mockito.anyLong(), Mockito.any()); + } + + @Test(expected = InvalidParameterValueException.class) + public void extractSnapshotTestNullSnapshotStoreReturnException() { + mockForExtractSnapshotTests(); + Mockito.doReturn(null).when(snapshotStoreDao).findByStoreSnapshot(DataStoreRole.Image, TEST_STORAGE_POOL_ID, TEST_SNAPSHOT_ID); + + _snapshotMgr.extractSnapshot(extractSnapshotCmdMock); + } + + @Test() + public void extractSnapshotTestCreateExtractUrlReturnUrl() { + mockForExtractSnapshotTests(); + + Assert.assertEquals(TEST_EXTRACT_URL, _snapshotMgr.extractSnapshot(extractSnapshotCmdMock)); + Mockito.verify(snapshotSrv).syncVolumeSnapshotsToRegionStore(TEST_VOLUME_ID, imageStoreEntityMock); + Mockito.verify(snapshotStoreDao).update(TEST_SNAPSHOTDATASTORE_ID, snapshotStoreMock); + } + + @Test() + public void extractSnapshotTestRootAdminDisabledExtractionCreateExtractUrlReturnUrl() { + mockForExtractSnapshotTests(); + Mockito.doReturn(true).when(_accountMgr).isRootAdmin(Mockito.anyLong()); + Mockito.when(ApiDBUtils.isExtractionDisabled()).thenReturn(true); + + Assert.assertEquals(TEST_EXTRACT_URL, _snapshotMgr.extractSnapshot(extractSnapshotCmdMock)); + Mockito.verify(snapshotSrv).syncVolumeSnapshotsToRegionStore(TEST_VOLUME_ID, imageStoreEntityMock); + Mockito.verify(snapshotStoreDao).update(TEST_SNAPSHOTDATASTORE_ID, snapshotStoreMock); + } } diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java index 971af289ef7..3827531891f 100644 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotSchedulerImplTest.java @@ -26,6 +26,9 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; +import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.jobs.JobInfo; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,6 +68,16 @@ public class SnapshotSchedulerImplTest { @Mock AccountVO accountVoMock; + @Mock + private SnapshotScheduleVO snapshotScheduleVoMock; + + @Mock + private AsyncJobDao asyncJobDaoMock; + + @Mock + private AsyncJobVO asyncJobVoMock; + + @Test public void scheduleNextSnapshotJobTestParameterIsNullReturnNull() { SnapshotScheduleVO snapshotScheduleVO = null; @@ -215,4 +228,50 @@ public class SnapshotSchedulerImplTest { Mockito.verify(snapshotScheduleDaoMock, Mockito.never()).remove(Mockito.anyLong()); } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobIsNullThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(null).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobSucceededThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.SUCCEEDED).when(asyncJobVoMock).getStatus(); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobFailedThenScheduleNextSnapshot() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.FAILED).when(asyncJobVoMock).getStatus(); + Mockito.doReturn(new Date()).when(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } + + @Test + public void scheduleNextSnapshotJobIfNecessaryTestAsyncJobInProgressThenDoNothing() { + Mockito.doReturn(1L).when(snapshotScheduleVoMock).getAsyncJobId(); + Mockito.doReturn(asyncJobVoMock).when(asyncJobDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(JobInfo.Status.IN_PROGRESS).when(asyncJobVoMock).getStatus(); + + snapshotSchedulerImplSpy.scheduleNextSnapshotJobIfNecessary(snapshotScheduleVoMock); + + Mockito.verify(snapshotSchedulerImplSpy, Mockito.never()).scheduleNextSnapshotJob(Mockito.any(SnapshotScheduleVO.class)); + } } diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index e5c623ca6df..db4fbed5320 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -28,6 +28,8 @@ import java.util.Map; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; + +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse; @@ -204,6 +206,39 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase { Mockito.verify(_accountDao, Mockito.atLeastOnce()).markForCleanup(Mockito.eq(42l)); } + @Test (expected = InvalidParameterValueException.class) + public void deleteUserAccountTestIfAccountIdIsEqualToCallerIdShouldThrowException() { + try (MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { + CallContext callContextMock = Mockito.mock(CallContext.class); + callContextMocked.when(CallContext::current).thenReturn(callContextMock); + long accountId = 1L; + + Mockito.doReturn(accountVoMock).when(callContextMock).getCallingAccount(); + Mockito.doReturn(accountVoMock).when(_accountDao).findById(Mockito.anyLong()); + Mockito.doReturn(domainVoMock).when(_domainDao).findById(Mockito.anyLong()); + Mockito.doReturn(1L).when(accountVoMock).getId(); + + accountManagerImpl.deleteUserAccount(accountId); + } + } + + @Test + public void deleteUserAccountTestIfAccountIdIsNotEqualToCallerAccountIdShouldNotThrowException() { + try (MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { + CallContext callContextMock = Mockito.mock(CallContext.class); + callContextMocked.when(CallContext::current).thenReturn(callContextMock); + long accountId = 1L; + + Mockito.doReturn(accountVoMock).when(callContextMock).getCallingAccount(); + Mockito.doReturn(accountVoMock).when(_accountDao).findById(Mockito.anyLong()); + Mockito.doReturn(2L).when(accountVoMock).getId(); + Mockito.doReturn(true).when(accountManagerImpl).isDeleteNeeded(Mockito.any(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(new ArrayList()).when(_projectAccountDao).listAdministratedProjectIds(Mockito.anyLong()); + + accountManagerImpl.deleteUserAccount(accountId); + } + } + @Test (expected = InvalidParameterValueException.class) public void deleteUserTestIfUserIdIsEqualToCallerIdShouldThrowException() { try (MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { @@ -291,6 +326,63 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase { accountManagerImpl.getKeys(_listkeyscmd); } + @Test(expected = PermissionDeniedException.class) + public void testGetUserKeysCmdDomainAdminRootAdminUser() { + CallContext.register(callingUser, callingAccount); + Mockito.when(_listkeyscmd.getID()).thenReturn(2L); + Mockito.when(accountManagerImpl.getActiveUser(2L)).thenReturn(userVoMock); + Mockito.when(userAccountDaoMock.findById(2L)).thenReturn(userAccountVO); + Mockito.when(userAccountVO.getAccountId()).thenReturn(2L); + Mockito.when(userDetailsDaoMock.listDetailsKeyPairs(Mockito.anyLong())).thenReturn(null); + + // Queried account - admin account + AccountVO adminAccountMock = Mockito.mock(AccountVO.class); + Mockito.when(adminAccountMock.getAccountId()).thenReturn(2L); + Mockito.when(_accountDao.findByIdIncludingRemoved(2L)).thenReturn(adminAccountMock); + Mockito.lenient().when(accountService.isRootAdmin(2L)).thenReturn(true); + Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), + Mockito.nullable(ControlledEntity.class), Mockito.nullable(AccessType.class), Mockito.anyString())).thenReturn(true); + + // Calling account is domain admin of the ROOT domain + Mockito.lenient().when(callingAccount.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + Mockito.lenient().when(callingAccount.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + + Mockito.lenient().when(callingUser.getAccountId()).thenReturn(2L); + Mockito.lenient().when(_accountDao.findById(2L)).thenReturn(callingAccount); + + Mockito.lenient().when(accountService.isDomainAdmin(Mockito.anyLong())).thenReturn(Boolean.TRUE); + Mockito.lenient().when(accountMock.getAccountId()).thenReturn(2L); + + accountManagerImpl.getKeys(_listkeyscmd); + } + + @Test + public void testPreventRootDomainAdminAccessToRootAdminKeysNormalUser() { + User user = Mockito.mock(User.class); + ControlledEntity entity = Mockito.mock(ControlledEntity.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + AccountVO account = Mockito.mock(AccountVO.class); + Mockito.when(account.getType()).thenReturn(Account.Type.NORMAL); + Mockito.when(_accountDao.findById(1L)).thenReturn(account); + accountManagerImpl.preventRootDomainAdminAccessToRootAdminKeys(user, entity); + Mockito.verify(accountManagerImpl, Mockito.never()).isRootAdmin(Mockito.anyLong()); + } + + @Test(expected = PermissionDeniedException.class) + public void testPreventRootDomainAdminAccessToRootAdminKeysRootDomainAdminUser() { + User user = Mockito.mock(User.class); + ControlledEntity entity = Mockito.mock(ControlledEntity.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + AccountVO account = Mockito.mock(AccountVO.class); + Mockito.when(account.getType()).thenReturn(Account.Type.DOMAIN_ADMIN); + Mockito.when(account.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + Mockito.when(_accountDao.findById(1L)).thenReturn(account); + Mockito.when(entity.getAccountId()).thenReturn(1L); + Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), + Mockito.nullable(ControlledEntity.class), Mockito.nullable(AccessType.class), Mockito.anyString())).thenReturn(true); + accountManagerImpl.preventRootDomainAdminAccessToRootAdminKeys(user, entity); + } + @Test public void updateUserTestTimeZoneAndEmailNull() { prepareMockAndExecuteUpdateUserTest(0); diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java index 334e1f33481..b4c2dafd664 100644 --- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java +++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java @@ -464,6 +464,10 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco return null; } + @Override + public void checkApiAccess(Account account, String command) throws PermissionDeniedException { + + } @Override public void checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException { diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 39128f21c87..90d857b5816 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -1595,4 +1595,40 @@ public class UserVmManagerImplTest { Long actualSize = userVmManagerImpl.getRootVolumeSizeForVmRestore(null, template, userVm, diskOffering, details, false); Assert.assertEquals(20 * GiB_TO_BYTES, actualSize.longValue()); } + + @Test + public void checkExpungeVMPermissionTestAccountIsNotAdminConfigFalseThrowsPermissionDeniedException () { + Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong()); + Mockito.doReturn(false).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong()); + + Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock)); + } + @Test + public void checkExpungeVmPermissionTestAccountIsNotAdminConfigTrueNoApiAccessThrowsPermissionDeniedException () { + Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong()); + Mockito.doReturn(true).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong()); + Mockito.doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine"); + + Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock)); + } + @Test + public void checkExpungeVmPermissionTestAccountIsNotAdminConfigTrueHasApiAccessReturnNothing () { + Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong()); + Mockito.doReturn(true).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong()); + + userVmManagerImpl.checkExpungeVmPermission(accountMock); + } + @Test + public void checkExpungeVmPermissionTestAccountIsAdminNoApiAccessThrowsPermissionDeniedException () { + Mockito.doReturn(true).when(accountManager).isAdmin(Mockito.anyLong()); + Mockito.doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine"); + + Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock)); + } + @Test + public void checkExpungeVmPermissionTestAccountIsAdminHasApiAccessReturnNothing () { + Mockito.doReturn(true).when(accountManager).isAdmin(Mockito.anyLong()); + + userVmManagerImpl.checkExpungeVmPermission(accountMock); + } } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 8355648ad1d..68ad250a95e 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.dc.DataCenter; +import com.cloud.hypervisor.Hypervisor; import com.cloud.network.PublicIpQuarantine; import com.cloud.network.VirtualRouterProvider; import com.cloud.utils.fsm.NoTransitionException; @@ -640,6 +641,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches return null; } + @Override + public List getNicProfiles(Long vmId, Hypervisor.HypervisorType hypervisorType) { + return List.of(); + } + @Override public Map getSystemVMAccessDetails(VirtualMachine vm) { return null; diff --git a/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java index e596601325e..5d9ee268d8b 100644 --- a/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/acl/RoleManagerImplTest.java @@ -214,7 +214,7 @@ public class RoleManagerImplTest { String roleName = "roleName"; List roles = new ArrayList<>(); Pair, Integer> toBeReturned = new Pair(roles, 0); - Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByName(roleName, null, null, null, false); + Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByName(roleName, null, null, null, null, false); roleManagerImpl.findRolesByName(roleName); Mockito.verify(roleManagerImpl).removeRolesIfNeeded(roles); @@ -345,7 +345,7 @@ public class RoleManagerImplTest { List roles = new ArrayList<>(); roles.add(Mockito.mock(Role.class)); Pair, Integer> toBeReturned = new Pair(roles, 1); - Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.Admin, null, null, true); + Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.Admin, null, null, null, true); List returnedRoles = roleManagerImpl.findRolesByType(RoleType.Admin); Assert.assertEquals(1, returnedRoles.size()); @@ -360,7 +360,7 @@ public class RoleManagerImplTest { List roles = new ArrayList<>(); roles.add(Mockito.mock(Role.class)); Pair, Integer> toBeReturned = new Pair(roles, 1); - Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.User, null, null, true); + Mockito.doReturn(toBeReturned).when(roleDaoMock).findAllByRoleType(RoleType.User, null, null, null, true); List returnedRoles = roleManagerImpl.findRolesByType(RoleType.User); Assert.assertEquals(1, returnedRoles.size()); diff --git a/services/secondary-storage/server/pom.xml b/services/secondary-storage/server/pom.xml index 058aae05a48..0e72754561c 100644 --- a/services/secondary-storage/server/pom.xml +++ b/services/secondary-storage/server/pom.xml @@ -83,7 +83,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} diff --git a/systemvm/pom.xml b/systemvm/pom.xml index ea91ed49283..8fd0b59e703 100644 --- a/systemvm/pom.xml +++ b/systemvm/pom.xml @@ -186,7 +186,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} diff --git a/test/integration/component/test_acl_isolatednetwork.py b/test/integration/component/test_acl_isolatednetwork.py index 038fd358972..8397171bcc6 100644 --- a/test/integration/component/test_acl_isolatednetwork.py +++ b/test/integration/component/test_acl_isolatednetwork.py @@ -799,7 +799,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Domain admin is allowed to deploy vm for users not in hos domain ") except Exception as e: self.debug("When Domain admin tries to deploy vm for users in their sub domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Domain admin tries to deploy vm for users not in hos domain ") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -876,7 +876,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Regular user is allowed to deploy vm for other users in their domain ") except Exception as e: self.debug("When user tries to deploy vm for users in their domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to deploy vm for other users in their domain ") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -903,7 +903,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Regular user is allowed to deploy vm for users not in their domain ") except Exception as e: self.debug("When user tries to deploy vm for users n different domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to deploy vm for users not in their domain ") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -1030,7 +1030,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Domain admin is allowed to restart network for users not in their domain ") except Exception as e: self.debug("When Domain admin tries to restart network for users in their sub domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Domain admin tries to restart network for users not in their domain ") ## Test cases relating restart network as regular user @@ -1061,7 +1061,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Regular user is allowed to restart network for users in their domain ") except Exception as e: self.debug("When user tries to restart network for users in their domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to restart network for users in their domain ") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -1077,7 +1077,7 @@ class TestIsolatedNetwork(cloudstackTestCase): self.fail("Regular user is allowed to restart network for users not in their domain ") except Exception as e: self.debug("When user tries to restart network for users in other domain %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user is allowed to restart network for users not in their domain ") @staticmethod diff --git a/test/integration/component/test_acl_isolatednetwork_delete.py b/test/integration/component/test_acl_isolatednetwork_delete.py index a8c4b67557e..6781dccad66 100644 --- a/test/integration/component/test_acl_isolatednetwork_delete.py +++ b/test/integration/component/test_acl_isolatednetwork_delete.py @@ -455,7 +455,7 @@ class TestIsolatedNetworkDelete(cloudstackTestCase): self.fail("Domain admin is allowed to delete network for users not in their domain ") except Exception as e: self.debug ("When Domain admin tries to delete network for user in a different domain %s" %e) - if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Domain admin tries to delete network for users not in their domain ") ## Test cases relating deleting network as regular user @@ -490,7 +490,7 @@ class TestIsolatedNetworkDelete(cloudstackTestCase): self.fail("Regular user is allowed to delete network for users in their domain ") except Exception as e: self.debug ("When user tries to delete network for users in their domain %s" %e) - if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Regular user is allowed to delete network for users in their domain ") @attr("simulator_only",tags=["advanced"],required_hardware="false") @@ -508,7 +508,7 @@ class TestIsolatedNetworkDelete(cloudstackTestCase): self.fail("Regular user is allowed to delete network for users not in their domain ") except Exception as e: self.debug ("When user tries to delete network for users in other domain %s" %e) - if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e,CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to delete network for users not in their domain ") @staticmethod diff --git a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py index 609af80b66c..212320c2c2f 100644 --- a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py +++ b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py @@ -1171,7 +1171,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=all") except Exception as e: self.debug("When a Domain admin user deploys a VM for ROOT user in a shared network with scope=all %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=all") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -1199,7 +1199,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin user is able to Deploy VM for a domain user, but there is no access to in a shared network with scope=domain with no subdomain access ") except Exception as e: self.debug("When a Domain admin user deploys a VM for a domain user, but there is no access to in a shared network with scope=domain with no subdomain access %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail( "Error mesage validation failed when Domain admin user tries to Deploy VM for a domain user, but there is no access to in a shared network with scope=domain with no subdomain access ") @@ -1405,7 +1405,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin is able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and no subdomain access") except Exception as e: self.debug("When a regular user from ROOT domain deploys a VM in a shared network with scope=domain with no subdomain access %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail( "Error message validation failed when Domain admin tries to deploy a VM for user in ROOT domain in a shared network with scope=Domain and no subdomain access") @@ -1601,7 +1601,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin is able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and subdomain access") except Exception as e: self.debug("When a user from ROOT domain deploys a VM in a shared network with scope=domain with subdomain access %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail( "Error message validation failed when Domain admin tries to deploy a VM for user in ROOT domain in a shared network with scope=Domain and subdomain access") @@ -1717,7 +1717,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin is able to deploy a VM for an regular user from a differnt domain in a shared network with scope=account") except Exception as e: self.debug("When a user from different domain deploys a VM in a shared network with scope=account %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail( "Error message validation failed when Domain admin tries to deploy a VM for an regular user from a differnt domain in a shared network with scope=account") @@ -1746,7 +1746,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Domain admin is able to deploy a VM for an regular user in ROOT domain in a shared network with scope=account") except Exception as e: self.debug("When a user from ROOT domain deploys a VM in a shared network with scope=account %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_DOMAIN): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Domain admin tries to deploy a VM for an regular user in ROOT domain in a shared network with scope=account") ## Test cases relating to deploying Virtual Machine as Regular user for other users in shared network with scope=all @@ -1776,7 +1776,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Regular user is allowed to deploy a VM for another user in the same domain in a shared network with scope=all") except Exception as e: self.debug("When a regular user deploys a VM for another user in the same domain in a shared network with scope=all %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to deploy a VM for another user in the same domain in a shared network with scope=all") @attr("simulator_only", tags=["advanced"], required_hardware="false") @@ -1804,7 +1804,7 @@ class TestSharedNetworkImpersonation(cloudstackTestCase): self.fail("Regular user is allowed to deploy a VM for another user in the same domain in a shared network with scope=all") except Exception as e: self.debug("When a regular user deploys a VM for another user in the same domain in a shared network with scope=all %s" % e) - if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_ACCOUNT): + if not CloudstackAclException.verifyMsginException(e, CloudstackAclException.NO_PERMISSION_TO_OPERATE_SOURCE): self.fail("Error message validation failed when Regular user tries to deploy a VM for another user in the same domain in a shared network with scope=all") @staticmethod diff --git a/test/integration/plugins/storpool/sp_util.py b/test/integration/plugins/storpool/sp_util.py index eaea3d4934d..70f36609af5 100644 --- a/test/integration/plugins/storpool/sp_util.py +++ b/test/integration/plugins/storpool/sp_util.py @@ -79,6 +79,11 @@ class TestData(): diskOfferingEncrypted2 = "diskOfferingEncrypted2" cephDiskOffering = "cephDiskOffering" nfsDiskOffering = "nfsDiskOffering" + diskOfferingTier1Tag = "diskOfferingTier1Tag" + diskOfferingTier2Tag = "diskOfferingTier2Tag" + diskOfferingTier1Template = "diskOfferingTier1Template" + diskOfferingTier2Template = "diskOfferingTier2Template" + diskOfferingWithTagsAndTempl = "diskOfferingWithTagsAndTempl" domainId = "domainId" hypervisor = "hypervisor" login = "login" @@ -278,6 +283,46 @@ class TestData(): TestData.tags: "nfs", "storagetype": "shared" }, + TestData.diskOfferingTier1Template: { + "name": "tier1-template", + "displaytext": "Tier1 using different StorPool template", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingTier2Template: { + "name": "tier2-template", + "displaytext": "Tier2 using different StorPool template", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingTier1Tag: { + "name": "tier1-tag", + "displaytext": "Tier1 using QOS tags", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingTier2Tag: { + "name": "tier2-tag", + "displaytext": "Tier2 using QOS tags", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingWithTagsAndTempl: { + "name": "tier2-tag-template", + "displaytext": "Tier2 using QOS tags and template", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, TestData.volume_1: { TestData.diskName: "test-volume-1", }, diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py b/test/integration/plugins/storpool/test_storpool_tiers.py new file mode 100644 index 00000000000..71758c24bed --- /dev/null +++ b/test/integration/plugins/storpool/test_storpool_tiers.py @@ -0,0 +1,544 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import pprint +import uuid + +from marvin.cloudstackAPI import (listResourceDetails, addResourceDetail, changeOfferingForVolume) +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.codes import FAILED +from marvin.lib.base import (DiskOffering, + ServiceOffering, + StoragePool, + VirtualMachine, + SecurityGroup, + ResourceDetails + ) +from marvin.lib.common import (get_domain, + get_template, + list_disk_offering, + list_storage_pools, + list_volumes, + list_service_offering, + list_zones) +from marvin.lib.utils import random_gen, cleanup_resources +from nose.plugins.attrib import attr +from storpool import spapi + +from sp_util import (TestData, StorPoolHelper) + + +class TestStorPoolTiers(cloudstackTestCase): + @classmethod + def setUpClass(cls): + super(TestStorPoolTiers, cls).setUpClass() + try: + cls.setUpCloudStack() + except Exception: + raise + + @classmethod + def setUpCloudStack(cls): + config = cls.getClsConfig() + StorPoolHelper.logger = cls + + zone = config.zones[0] + assert zone is not None + + cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True) + testClient = super(TestStorPoolTiers, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.unsupportedHypervisor = False + cls.hypervisor = testClient.getHypervisorInfo() + if cls.hypervisor.lower() in ("hyperv", "lxc"): + cls.unsupportedHypervisor = True + return + + cls._cleanup = [] + + cls.services = testClient.getParsedTestDataConfig() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = list_zones(cls.apiclient, name=zone.name)[0] + + td = TestData() + cls.testdata = td.testdata + cls.helper = StorPoolHelper() + + disk_offerings_tier1_tags = cls.testdata[TestData.diskOfferingTier1Tag] + disk_offerings_tier2_tags = cls.testdata[TestData.diskOfferingTier2Tag] + disk_offerings_tier1_template = cls.testdata[TestData.diskOfferingTier1Template] + disk_offerings_tier2_template = cls.testdata[TestData.diskOfferingTier2Template] + disk_offerings_tier2_tags_template = cls.testdata[TestData.diskOfferingWithTagsAndTempl] + + cls.qos = "SP_QOSCLASS" + cls.spTemplate = "SP_TEMPLATE" + + cls.disk_offerings_tier1_tags = cls.getDiskOffering(disk_offerings_tier1_tags, cls.qos, "ssd") + + cls.disk_offerings_tier2_tags = cls.getDiskOffering(disk_offerings_tier2_tags, cls.qos, "virtual") + + cls.disk_offerings_tier1_template = cls.getDiskOffering(disk_offerings_tier1_template, cls.spTemplate, "ssd") + + cls.disk_offerings_tier2_template = cls.getDiskOffering(disk_offerings_tier2_template, cls.spTemplate, + "virtual") + cls.disk_offerings_tier2_tags_template = cls.getDiskOffering(disk_offerings_tier2_tags_template, cls.spTemplate, + "virtual") + cls.resourceDetails(cls.qos, cls.disk_offerings_tier2_tags_template.id, "virtual") + + cls.account = cls.helper.create_account( + cls.apiclient, + cls.services["account"], + accounttype=1, + domainid=cls.domain.id, + roleid=1 + ) + cls._cleanup.append(cls.account) + + securitygroup = SecurityGroup.list(cls.apiclient, account=cls.account.name, domainid=cls.account.domainid)[0] + cls.helper.set_securityGroups(cls.apiclient, account=cls.account.name, domainid=cls.account.domainid, + id=securitygroup.id) + + storpool_primary_storage = cls.testdata[TestData.primaryStorage] + + storpool_service_offerings = cls.testdata[TestData.serviceOffering] + + cls.template_name = storpool_primary_storage.get("name") + + storage_pool = list_storage_pools( + cls.apiclient, + name=cls.template_name + ) + + service_offerings = list_service_offering( + cls.apiclient, + name=cls.template_name + ) + + disk_offerings = list_disk_offering( + cls.apiclient, + name="ssd" + ) + + if storage_pool is None: + storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) + else: + storage_pool = storage_pool[0] + cls.storage_pool = storage_pool + cls.debug(pprint.pformat(storage_pool)) + if service_offerings is None: + service_offerings = ServiceOffering.create(cls.apiclient, storpool_service_offerings) + else: + service_offerings = service_offerings[0] + # The version of CentOS has to be supported + template = get_template( + cls.apiclient, + cls.zone.id, + account="system" + ) + + if template == FAILED: + assert False, "get_template() failed to return template\ + with description %s" % cls.services["ostype"] + + cls.services["domainid"] = cls.domain.id + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["templates"]["ostypeid"] = template.ostypeid + cls.services["zoneid"] = cls.zone.id + + cls.service_offering = service_offerings + cls.debug(pprint.pformat(cls.service_offering)) + + cls.template = template + cls.random_data_0 = random_gen(size=100) + cls.test_dir = "/tmp" + cls.random_data = "random.data" + return + + @classmethod + def getDiskOffering(cls, dataDiskOffering, qos, resValue): + disk_offerings = list_disk_offering(cls.apiclient, name=dataDiskOffering.get("name")) + if disk_offerings is None: + disk_offerings = DiskOffering.create(cls.apiclient, services=dataDiskOffering, custom=True) + cls.resourceDetails(qos, disk_offerings.id, resValue) + else: + disk_offerings = disk_offerings[0] + cls.resourceDetails(qos, disk_offerings.id, ) + return disk_offerings + + @classmethod + def tearDownClass(cls): + super(TestStorPoolTiers, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + + if self.unsupportedHypervisor: + self.skipTest("Skipping test because unsupported hypervisor\ + %s" % self.hypervisor) + return + + def tearDown(self): + super(TestStorPoolTiers, self).tearDown() + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_01_check_tags_on_deployed_vm_and_datadisk(self): + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_02_change_offering_on_attached_root_disk(self): + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, root_volume[0].size) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + def test_03_change_offering_on_attached_data_disk(self): + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, root_volume[0].size) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_04_check_templates_on_deployed_vm_and_datadisk(self): + virtual_machine_template_tier1 = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=self.disk_offerings_tier1_template.id, + diskofferingid=self.disk_offerings_tier1_template.id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_template_tier1.id, listall=True) + for v in volumes: + self.check_storpool_template(v, self.disk_offerings_tier1_template.id, self.spTemplate) + virtual_machine_template_tier1.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self): + virtual_machine_template_tier2 = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=self.disk_offerings_tier2_template.id, + diskofferingid=self.disk_offerings_tier2_template.id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_template_tier2.id, listall=True) + for v in volumes: + self.check_storpool_template(v, self.disk_offerings_tier2_template.id, self.spTemplate) + virtual_machine_template_tier2.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_06_change_offerings_with_tags_detached_volume(self): + disk_off_id = self.disk_offerings_tier2_tags.id + virtual_machine_tier2_tag = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=disk_off_id, + diskofferingid=disk_off_id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + virtual_machine_tier2_tag.stop(self.apiclient, forced=True) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier2_tag.id, type="DATADISK", + listall=True) + + virtual_machine_tier2_tag.detach_volume( + self.apiclient, + volumes[0] + ) + + self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, qos_or_template=self.qos, + disk_offering_id=disk_off_id, attached=True) + + self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) + self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_07_change_offerings_with_template_detached_volume(self): + disk_off_id = self.disk_offerings_tier2_template.id + virtual_machine_tier2_template = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=disk_off_id, + diskofferingid=disk_off_id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + virtual_machine_tier2_template.stop(self.apiclient, forced=True) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier2_template.id, type="DATADISK", + listall=True) + + virtual_machine_tier2_template.detach_volume( + self.apiclient, + volumes[0] + ) + + self.check_storpool_template(volume=volumes[0], disk_offering_id=disk_off_id, qos_or_template=self.spTemplate) + + self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_template.id, volumes[0].size) + self.check_storpool_template(volume=volumes[0], disk_offering_id=self.disk_offerings_tier1_template.id, + qos_or_template=self.spTemplate) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_08_deploy_vm_with_tags_and_template_in_offerings(self): + """ + Deploy virtual machine with disk offering on which resource details is set tier2 template and tier2 qos tags + """ + disk_off_id = self.disk_offerings_tier2_tags_template.id + virtual_machine_tier2_template = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=disk_off_id, + diskofferingid=disk_off_id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + virtual_machine_tier2_template.stop(self.apiclient, forced=True) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier2_template.id, type="DATADISK", + listall=True) + + virtual_machine_tier2_template.detach_volume( + self.apiclient, + volumes[0] + ) + + self.check_storpool_template(volume=volumes[0], disk_offering_id=disk_off_id, qos_or_template=self.spTemplate, + diff_template=True) + self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_template, qos_or_template=self.qos, + disk_offering_id=disk_off_id, attached=True) + + self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) + self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_template, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_09_resize_root_volume(self): + ''' + Resize Root volume with changeOfferingForVolume + ''' + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, (root_volume[0].size + 1024)) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_10_shrink_root_volume(self): + ''' + Shrink Root volume with changeOfferingForVolume + ''' + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, (root_volume[0].size - 1024), + True) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_11_resize_data_volume(self): + ''' + Resize DATADISK volume with changeOfferingForVolume + ''' + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, (root_volume[0].size + 1024)) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_12_shrink_data_volume(self): + ''' + Shrink DATADISK volume with changeOfferingForVolume + ''' + virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() + + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.changeOfferingForVolume(root_volume[0].id, self.disk_offerings_tier2_tags.id, (root_volume[0].size - 1024), + True) + root_volume = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + + def deploy_vm_and_check_tier_tag(self): + virtual_machine_tier1_tag = VirtualMachine.create( + self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + overridediskofferingid=self.disk_offerings_tier1_tags.id, + diskofferingid=self.disk_offerings_tier1_tags.id, + size=2, + hypervisor=self.hypervisor, + rootdisksize=10 + ) + volumes = list_volumes(self.apiclient, virtualmachineid=virtual_machine_tier1_tag.id, listall=True) + self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + return virtual_machine_tier1_tag + + @classmethod + def resourceDetails(cls, qos, id, resValue=None): + listResourceDetailCmd = listResourceDetails.listResourceDetailsCmd() + listResourceDetailCmd.resourceid = id + listResourceDetailCmd.resourcetype = "DiskOffering" + listResourceDetailCmd.key = qos + details = cls.apiclient.listResourceDetails(listResourceDetailCmd) + + if details is None: + resource = addResourceDetail.addResourceDetailCmd() + resource.resourceid = id + resource.resourcetype = "DiskOffering" + resDet = {'key': qos, 'value': resValue} + resource.details = [resDet] + + resource.fordisplay = True + details = cls.apiclient.addResourceDetail(resource) + + @classmethod + def getZone(cls): + zones = list_zones(cls.apiclient) + for z in zones: + if z.name == cls.getClsConfig().mgtSvr[0].zone: + cls.zone = z + assert cls.zone is not None + + def vc_policy_tags(self, volumes, vm, qos_or_template, disk_offering_id, should_tags_exists=None, vm_tags=None, + attached=None): + vc_policy_tag = False + cvm_tag = False + qs_tag = False + id = vm.id + for v in volumes: + name = v.path.split("/")[3] + volume = self.spapi.volumeList(volumeName="~" + name) + tags = volume[0].tags + resource_details_value = ResourceDetails.list(self.apiclient, resourcetype="DiskOffering", + resourceid=disk_offering_id, key=qos_or_template) + for t in tags: + self.debug("TAGS are %s" % t) + if vm_tags: + for vm_tag in vm_tags: + if t == vm_tag.key: + vc_policy_tag = True + self.assertEqual(tags[t], vm_tag.value, "Tags are not equal") + if t == 'cvm': + self.debug("CVM tag %s is not the same as vm UUID %s" % (tags[t], id)) + self.debug(type(tags[t])) + self.debug(len(tags[t])) + self.debug(type(id)) + self.debug(len(id)) + cvm_tag = True + self.assertEqual(tags[t], id, "CVM tag is not the same as vm UUID ") + if t == 'qc': + qs_tag = True + self.assertEqual(tags[t], resource_details_value[0].value, "QOS tags should be the same") + if should_tags_exists: + self.assertTrue(vc_policy_tag, "There aren't volumes with vm tags") + self.assertTrue(cvm_tag, "There aren't volumes with vm tags") + if attached: + self.assertTrue(qs_tag, "The QOS tag isn't set") + else: + self.assertFalse(vc_policy_tag, "The tags should be removed") + self.assertFalse(cvm_tag, "The tags should be removed") + + def check_storpool_template(self, volume, disk_offering_id, qos_or_template, diff_template=None): + name = volume.path.split("/")[3] + sp_volume = self.spapi.volumeList(volumeName="~" + name) + template = sp_volume[0].templateName + resource_details_value = ResourceDetails.list(self.apiclient, resourcetype="DiskOffering", + resourceid=disk_offering_id, key=qos_or_template) + if diff_template: + self.assertNotEqual(template, resource_details_value[0].value, "The templates should not be the same") + else: + self.assertEqual(template, resource_details_value[0].value) + + def changeOfferingForVolume(self, volume_id, disk_offering_id, size, shrinkok=None): + size = int(size / 1024 / 1024 / 1024) + change_offering_for_volume_cmd = changeOfferingForVolume.changeOfferingForVolumeCmd() + change_offering_for_volume_cmd.id = volume_id + change_offering_for_volume_cmd.diskofferingid = disk_offering_id + change_offering_for_volume_cmd.size = size + change_offering_for_volume_cmd.shrinkok = shrinkok + + return self.apiclient.changeOfferingForVolume(change_offering_for_volume_cmd) diff --git a/test/integration/smoke/test_account_access.py b/test/integration/smoke/test_account_access.py new file mode 100644 index 00000000000..97eeced6386 --- /dev/null +++ b/test/integration/smoke/test_account_access.py @@ -0,0 +1,198 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for Account User Access +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import * +from marvin.lib.base import (Account, + User, + Domain) +from marvin.lib.common import (get_domain) +from marvin.cloudstackAPI import (getUserKeys) +from marvin.cloudstackException import CloudstackAPIException +from nose.plugins.attrib import attr + +_multiprocess_shared_ = True + +class TestAccountAccess(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestAccountAccess, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + + cls.domains = [] + cls.domain_admins = {} + cls.domain_users = {} + cls.account_users = {} + + domain_data = { + "name": "domain_1" + } + cls.domain_1 = Domain.create( + cls.apiclient, + domain_data, + ) + cls._cleanup.append(cls.domain_1) + cls.domains.append(cls.domain_1) + domain_data["name"] = "domain_11" + cls.domain_11 = Domain.create( + cls.apiclient, + domain_data, + parentdomainid=cls.domain_1.id + ) + cls._cleanup.append(cls.domain_11) + cls.domains.append(cls.domain_11) + domain_data["name"] = "domain_12" + cls.domain_12 = Domain.create( + cls.apiclient, + domain_data, + parentdomainid=cls.domain_1.id + ) + cls._cleanup.append(cls.domain_12) + cls.domains.append(cls.domain_12) + domain_data["name"] = "domain_2" + cls.domain_2 = Domain.create( + cls.apiclient, + domain_data, + ) + cls._cleanup.append(cls.domain_2) + cls.domains.append(cls.domain_2) + + + for d in cls.domains: + cls.create_domainadmin_and_user(d) + + @classmethod + def tearDownClass(cls): + super(TestAccountAccess, cls).tearDownClass() + + @classmethod + def create_account(cls, domain, is_admin): + cls.debug(f"Creating account for domain {domain.name}, admin: {is_admin}") + data = { + "email": "admin-" + domain.name + "@test.com", + "firstname": "Admin", + "lastname": domain.name, + "username": "admin-" + domain.name, + "password": "password" + } + if is_admin == False: + data["email"] = "user-" + domain.name + "@test.com" + data["firstname"] = "User" + data["username"] = "user-" + domain.name + account = Account.create( + cls.apiclient, + data, + admin=is_admin, + domainid=domain.id + ) + cls._cleanup.append(account) + if is_admin == True: + cls.domain_admins[domain.id] = account + else: + cls.domain_users[domain.id] = account + + user = User.create( + cls.apiclient, + data, + account=account.name, + domainid=account.domainid) + cls._cleanup.append(user) + cls.account_users[account.id] = user + + @classmethod + def create_domainadmin_and_user(cls, domain): + cls.debug(f"Creating accounts for domain #{domain.id} {domain.name}") + cls.create_account(domain, True) + cls.create_account(domain, False) + + def get_user_keys(self, api_client, user_id): + getUserKeysCmd = getUserKeys.getUserKeysCmd() + getUserKeysCmd.id = user_id + return api_client.getUserKeys(getUserKeysCmd) + + def is_child_domain(self, parent_domain, child_domain): + if not parent_domain or not child_domain: + return False + parent_domain_prefix = parent_domain.split('-')[0] + child_domain_prefix = child_domain.split('-')[0] + if not parent_domain_prefix or not child_domain_prefix: + return False + return child_domain_prefix.startswith(parent_domain_prefix) + + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") + def test_01_user_access(self): + """ + Test user account is not accessing any other account + """ + + domain_user_accounts = [value for value in self.domain_users.values()] + all_account_users = [value for value in self.account_users.values()] + for user_account in domain_user_accounts: + current_account_user = self.account_users[user_account.id] + self.debug(f"Check for account {user_account.name} with user {current_account_user.username}") + user_api_client = self.testClient.getUserApiClient( + UserName=user_account.name, + DomainName=user_account.domain + ) + for user in all_account_users: + self.debug(f"Checking access for user {user.username} associated with account {user.account}") + try: + self.get_user_keys(user_api_client, user.id) + self.debug(f"API successful") + if user.id != current_account_user.id: + self.fail(f"User account #{user_account.id} was able to access another account #{user.id}") + except CloudstackAPIException as e: + self.debug(f"Exception occurred: {e}") + if user.id == current_account_user.id: + self.fail(f"User account #{user_account.id} not able to access own account") + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="false") + def test_02_domain_admin_access(self): + """ + Test domain admin account is not accessing any other account from unauthorized domain + """ + + domain_admin_accounts = [value for value in self.domain_admins.values()] + all_account_users = [value for value in self.account_users.values()] + for admin_account in domain_admin_accounts: + current_account_user = self.account_users[admin_account.id] + self.debug(f"Check for domain admin {admin_account.name} with user {current_account_user.username}, {current_account_user.domain}") + admin_api_client = self.testClient.getUserApiClient( + UserName=admin_account.name, + DomainName=admin_account.domain + ) + for user in all_account_users: + self.debug(f"Checking access for user {user.username}, {user.domain} associated with account {user.account}") + try: + self.get_user_keys(admin_api_client, user.id) + self.debug(f"API successful") + if self.is_child_domain(current_account_user.domain, user.domain) == False: + self.fail(f"User account #{admin_account.id} was able to access another account #{user.id}") + except CloudstackAPIException as e: + self.debug(f"Exception occurred: {e}") + if self.is_child_domain(current_account_user.domain, user.domain) == True: + self.fail(f"User account #{admin_account.id} not able to access own account") diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index 8f3f4f533dd..b3e7fd3e42f 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -17,6 +17,8 @@ # under the License. """ BVT tests for Network Life Cycle """ +import json + # Import Local Modules from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE, NAT_RULE, PASS) @@ -24,7 +26,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.cloudstackException import CloudstackAPIException from marvin.cloudstackAPI import rebootRouter from marvin.sshClient import SshClient -from marvin.lib.utils import cleanup_resources, get_process_status, get_host_credentials +from marvin.lib.utils import cleanup_resources, get_process_status, get_host_credentials, random_gen from marvin.lib.base import (Account, VirtualMachine, ServiceOffering, @@ -37,7 +39,9 @@ from marvin.lib.base import (Account, LoadBalancerRule, Router, NIC, - Cluster) + Template, + Cluster, + SSHKeyPair) from marvin.lib.common import (get_domain, get_free_vlan, get_zone, @@ -58,9 +62,11 @@ from marvin.lib.decoratorGenerators import skipTestIf from ddt import ddt, data import unittest # Import System modules +import os import time import logging import random +import tempfile _multiprocess_shared_ = True @@ -2113,3 +2119,313 @@ class TestSharedNetwork(cloudstackTestCase): 0, "Failed to find the placeholder IP" ) + + +class TestSharedNetworkWithConfigDrive(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestSharedNetworkWithConfigDrive, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + + cls.services = cls.testClient.getParsedTestDataConfig() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.hv = cls.testClient.getHypervisorInfo() + + if cls.hv.lower() == 'simulator': + cls.skip = True + return + else: + cls.skip = False + + cls._cleanup = [] + + template = Template.register( + cls.apiclient, + cls.services["test_templates_cloud_init"][cls.hv.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hv, + ) + template.download(cls.apiclient) + cls._cleanup.append(template) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = template.id + cls.services["virtual_machine"]["username"] = "ubuntu" + # Create Network Offering + cls.services["shared_network_offering_configdrive"]["specifyVlan"] = "True" + cls.services["shared_network_offering_configdrive"]["specifyIpRanges"] = "True" + cls.shared_network_offering = NetworkOffering.create(cls.apiclient, + cls.services["shared_network_offering_configdrive"], + conservemode=True) + + cls.isolated_network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["isolated_network_offering"], + conservemode=True + ) + + # Update network offering state from disabled to enabled. + NetworkOffering.update( + cls.isolated_network_offering, + cls.apiclient, + id=cls.isolated_network_offering.id, + state="enabled" + ) + + # Update network offering state from disabled to enabled. + NetworkOffering.update(cls.shared_network_offering, cls.apiclient, state="enabled") + + cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offering"]) + physical_network, vlan = get_free_vlan(cls.apiclient, cls.zone.id) + # create network using the shared network offering created + + cls.services["shared_network"]["acltype"] = "domain" + cls.services["shared_network"]["vlan"] = vlan + cls.services["shared_network"]["networkofferingid"] = cls.shared_network_offering.id + cls.services["shared_network"]["physicalnetworkid"] = physical_network.id + + cls.setSharedNetworkParams("shared_network") + cls.shared_network = Network.create(cls.apiclient, + cls.services["shared_network"], + networkofferingid=cls.shared_network_offering.id, + zoneid=cls.zone.id) + + cls.isolated_network = Network.create( + cls.apiclient, + cls.services["isolated_network"], + networkofferingid=cls.isolated_network_offering.id, + zoneid=cls.zone.id + ) + + cls._cleanup.extend([ + cls.service_offering, + cls.shared_network, + cls.shared_network_offering, + cls.isolated_network, + cls.isolated_network_offering, + ]) + cls.tmp_files = [] + cls.keypair = cls.generate_ssh_keys() + return + + @classmethod + def generate_ssh_keys(cls): + """Generates ssh key pair + + Writes the private key into a temp file and returns the file name + + :returns: generated keypair + :rtype: MySSHKeyPair + """ + cls.keypair = SSHKeyPair.create( + cls.apiclient, + name=random_gen() + ".pem") + + cls._cleanup.append(SSHKeyPair(cls.keypair.__dict__, None)) + cls.debug("Created keypair with name: %s" % cls.keypair.name) + cls.debug("Writing the private key to local file") + pkfile = tempfile.gettempdir() + os.sep + cls.keypair.name + cls.keypair.private_key_file = pkfile + cls.tmp_files.append(pkfile) + cls.debug("File path: %s" % pkfile) + with open(pkfile, "w+") as f: + f.write(cls.keypair.privatekey) + os.chmod(pkfile, 0o400) + + return cls.keypair + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + if self.skip: + self.skipTest("Hypervisor is simulator - skipping Test..") + self.cleanup = [] + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.apiclient, cls._cleanup) + for tmp_file in cls.tmp_files: + os.remove(tmp_file) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + return + + @classmethod + def setSharedNetworkParams(cls, network, range=20): + + # @range: range decides the endip. Pass the range as "x" if you want the difference between the startip + # and endip as "x" + # Set the subnet number of shared networks randomly prior to execution + # of each test case to avoid overlapping of ip addresses + shared_network_subnet_number = random.randrange(1, 254) + cls.services[network]["gateway"] = "172.16." + str(shared_network_subnet_number) + ".1" + cls.services[network]["startip"] = "172.16." + str(shared_network_subnet_number) + ".2" + cls.services[network]["endip"] = "172.16." + str(shared_network_subnet_number) + "." + str(range + 1) + cls.services[network]["netmask"] = "255.255.255.0" + logger.debug("Executing command '%s'" % cls.services[network]) + + def _mount_config_drive(self, ssh): + """ + This method is to verify whether configdrive iso + is attached to vm or not + Returns mount path if config drive is attached else None + """ + mountdir = "/root/iso" + cmd = "sudo blkid -t LABEL='config-2' " \ + "/dev/sr? /dev/hd? /dev/sd? /dev/xvd? -o device" + tmp_cmd = [ + 'sudo bash -c "if [ ! -d {0} ]; then mkdir {0}; fi"'.format(mountdir), + "sudo umount %s" % mountdir] + self.debug("Unmounting drive from %s" % mountdir) + for tcmd in tmp_cmd: + ssh.execute(tcmd) + + self.debug("Trying to find ConfigDrive device") + configDrive = ssh.execute(cmd) + if not configDrive: + self.warn("ConfigDrive is not attached") + return None + + res = ssh.execute("sudo mount {} {}".format(str(configDrive[0]), mountdir)) + if str(res).lower().find("read-only") > -1: + self.debug("ConfigDrive iso is mounted at location %s" % mountdir) + return mountdir + else: + return None + + def _umount_config_drive(self, ssh, mount_path): + """unmount config drive inside guest vm + + :param ssh: SSH connection to the VM + :type ssh: marvin.sshClient.SshClient + :type mount_path: str + """ + ssh.execute("sudo umount -d %s" % mount_path) + # Give the VM time to unlock the iso device + time.sleep(0.5) + # Verify umount + result = ssh.execute("sudo ls %s" % mount_path) + self.assertTrue(len(result) == 0, + "After umount directory should be empty " + "but contains: %s" % result) + + def _get_config_drive_data(self, ssh, file, name, fail_on_missing=True): + """Fetches the content of a file file on the config drive + + :param ssh: SSH connection to the VM + :param file: path to the file to fetch + :param name: description of the file + :param fail_on_missing: + whether the test should fail if the file is missing + :type ssh: marvin.sshClient.SshClient + :type file: str + :type name: str + :type fail_on_missing: bool + :returns: the content of the file + :rtype: str + """ + cmd = "sudo cat %s" % file + res = ssh.execute(cmd) + content = '\n'.join(res) + + if fail_on_missing and "No such file or directory" in content: + self.debug("{} is not found".format(name)) + self.fail("{} is not found".format(name)) + + return content + + def _get_ip_address_output(self, ssh): + cmd = "ip address" + res = ssh.execute(cmd) + return '\n'.join(res) + + @attr(tags=["advanced", "shared"], required_hardware="true") + def test_01_deployVMInSharedNetwork(self): + try: + self.virtual_machine = VirtualMachine.create(self.apiclient, self.services["virtual_machine"], + networkids=[self.shared_network.id, self.isolated_network.id], + serviceofferingid=self.service_offering.id, + keypair=self.keypair.name + ) + self.cleanup.append(self.virtual_machine) + except Exception as e: + self.fail("Exception while deploying virtual machine: %s" % e) + + public_ips = list_publicIP( + self.apiclient, + associatednetworkid=self.isolated_network.id + ) + public_ip = public_ips[0] + FireWallRule.create( + self.apiclient, + ipaddressid=public_ip.id, + protocol=self.services["natrule"]["protocol"], + cidrlist=['0.0.0.0/0'], + startport=self.services["natrule"]["publicport"], + endport=self.services["natrule"]["publicport"] + ) + + nat_rule = NATRule.create( + self.apiclient, + self.virtual_machine, + self.services["natrule"], + public_ip.id + ) + + private_key_file_location = self.keypair.private_key_file if self.keypair else None + ssh = self.virtual_machine.get_ssh_client(ipaddress=nat_rule.ipaddress, + keyPairFileLocation=private_key_file_location, retries=5) + + mount_path = self._mount_config_drive(ssh) + + network_data_content = self._get_config_drive_data(ssh, mount_path + "/openstack/latest/network_data.json", + "network_data") + + network_data = json.loads(network_data_content) + + self._umount_config_drive(ssh, mount_path) + + ip_address_output = self._get_ip_address_output(ssh) + + self.assertTrue('links' in network_data, "network_data.json doesn't contain links") + self.assertTrue('networks' in network_data, "network_data.json doesn't contain networks") + self.assertTrue('services' in network_data, "network_data.json doesn't contain services") + + for x in ['links', 'networks', 'services']: + self.assertTrue(x in network_data, "network_data.json doesn't contain " + x) + self.assertEqual(len(network_data[x]), 2, "network_data.json doesn't contain 2 " + x) + + self.assertIn(network_data['links'][0]['ethernet_mac_address'], + [self.virtual_machine.nic[0].macaddress, self.virtual_machine.nic[1].macaddress], + "macaddress doesn't match") + self.assertIn(network_data['links'][1]['ethernet_mac_address'], + [self.virtual_machine.nic[0].macaddress, self.virtual_machine.nic[1].macaddress], + "macaddress doesn't match") + + self.assertIn(network_data['networks'][0]['ip_address'], + [self.virtual_machine.nic[0].ipaddress, self.virtual_machine.nic[1].ipaddress], + "ip address doesn't match") + self.assertIn(network_data['networks'][1]['ip_address'], + [self.virtual_machine.nic[0].ipaddress, self.virtual_machine.nic[1].ipaddress], + "ip address doesn't match") + self.assertIn(network_data['networks'][0]['netmask'], + [self.virtual_machine.nic[0].netmask, self.virtual_machine.nic[1].netmask], + "netmask doesn't match") + self.assertIn(network_data['networks'][1]['netmask'], + [self.virtual_machine.nic[0].netmask, self.virtual_machine.nic[1].netmask], + "netmask doesn't match") + + self.assertEqual(network_data['services'][0]['type'], 'dns', "network_data.json doesn't contain dns service") + self.assertEqual(network_data['services'][1]['type'], 'dns', "network_data.json doesn't contain dns service") + + self.assertTrue(self.virtual_machine.nic[0].ipaddress in ip_address_output, "ip address doesn't match") + self.assertTrue(self.virtual_machine.nic[1].ipaddress in ip_address_output, "ip address doesn't match") diff --git a/test/integration/smoke/test_resource_names.py b/test/integration/smoke/test_resource_names.py new file mode 100644 index 00000000000..46fa445f1b1 --- /dev/null +++ b/test/integration/smoke/test_resource_names.py @@ -0,0 +1,299 @@ +# -- coding: utf-8 -- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +""" BVT tests for resource names with emojis / unicode +""" +from marvin.cloudstackTestCase import cloudstackTestCase + +from marvin.lib.base import (Account, + ServiceOffering, + VirtualMachine, + Template, + Iso, + Volume, + DiskOffering) +from marvin.lib.common import (get_domain, + get_zone, + get_suitable_test_template, + get_builtin_template_info) +from marvin.codes import FAILED +from nose.plugins.attrib import attr +# Import System modules +import time + +_multiprocess_shared_ = True + +class TestResourceNames(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestResourceNames, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + cls._cleanup = [] + + template = get_suitable_test_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"], + cls.hypervisor + ) + if template == FAILED: + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services[ + "ostype"] + + # Set Zones and disk offerings + cls.services["domainid"] = cls.domain.id + cls.services["zoneid"] = cls.zone.id + cls.services["template"] = template.id + cls.services["iso1"]["zoneid"] = cls.zone.id + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + + cls.services["account"]["firstname"] = "test🎉" + cls.services["account"]["lastname"] = "account🙂" + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.services["service_offerings"]["tiny"]["name"] = "test🎉svcoffering🙂" + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls._cleanup.append(cls.service_offering) + + cls.services["disk_offering"]["name"] = "test🎉diskoffering🙂" + cls.disk_offering = DiskOffering.create( + cls.apiclient, + cls.services["disk_offering"] + ) + cls._cleanup.append(cls.disk_offering) + + cls.services["small"]["displayname"] = "test🎉vm🙂" + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services['mode'] + ) + + @classmethod + def tearDownClass(cls): + super(TestResourceNames, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + super(TestResourceNames, self).tearDown() + + @attr(tags=["advanced", "smoke", "basic"], required_hardware="false") + def test_01_deploy_vm(self): + """Test for deploy virtual machine + """ + # Validate the following: + # 1. listVirtualMachines returns accurate information, and check name + list_vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.debug( + "Verify listVirtualMachines response for virtual machine: %s" \ + % self.virtual_machine.id + ) + self.assertEqual( + isinstance(list_vm_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vm_response), + 0, + "Check VM available in List Virtual Machines" + ) + + vm_response = list_vm_response[0] + self.assertEqual( + vm_response.id, + self.virtual_machine.id, + "Check virtual machine id in listVirtualMachines" + ) + self.assertEqual( + vm_response.name, + self.virtual_machine.name, + "Check virtual machine name in listVirtualMachines" + ) + self.assertEqual( + vm_response.displayname, + self.virtual_machine.displayname, + "Check virtual machine display name in listVirtualMachines" + ) + self.assertEqual( + vm_response.state, + 'Running', + msg="VM is not in Running state" + ) + return + + @attr(tags=["advanced", "smoke", "basic"], required_hardware="true") + def test_02_create_volume(self): + """Test for create volume + """ + # Validate the following: + # 1. Create volume and check name + + self.services["diskname"] = "test🎉data🙂volume" + self.volume = Volume.create( + self.apiclient, + self.services, + account=self.account.name, + domainid=self.account.domainid, + diskofferingid=self.disk_offering.id + ) + # self.cleanup.append(self.volume) + self.virtual_machine.attach_volume(self.apiclient, self.volume) + list_volume_response = Volume.list( + self.apiclient, + id=self.volume.id + ) + self.assertEqual( + isinstance(list_volume_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_volume_response, + None, + "Check if volume exists in ListVolumes" + ) + + volume_response = list_volume_response[0] + self.assertNotEqual( + volume_response.virtualmachineid, + None, + "Check if volume state (attached) is reflected" + ) + self.assertEqual( + volume_response.name, + self.volume.name, + "Check virtual machine display name in listVirtualMachines" + ) + + @attr(tags=["advanced", "smoke", "basic"], required_hardware="true") + def test_03_register_template(self): + """Test for register template + """ + # Validate the following: + # 1. Register template and check name + + if self.hypervisor.lower() in ["lxc"]: + self.skipTest("Skipping test, unsupported hypervisor %s" % self.hypervisor) + + builtin_info = get_builtin_template_info(self.apiclient, self.zone.id) + self.services["template_2"]["url"] = builtin_info[0] + self.services["template_2"]["hypervisor"] = builtin_info[1] + self.services["template_2"]["format"] = builtin_info[2] + self.services["template_2"]["name"] = "test🎉tmpl🙂" + self.services["template_2"]["displaytext"] = "test🎉tmpl🙂" + + template = Template.register(self.apiclient, + self.services["template_2"], + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + self.debug("Successfully registered template with ID: %s" % template.id) + self.cleanup.append(template) + + # Get template response + timeout = 600 + list_template_response = None + while timeout >= 0: + list_template_response = Template.list(self.apiclient, + templatefilter=self.services["template_2"]["templatefilter"], + id=template.id) + + if list_template_response is not None and list_template_response[0].isready: + break + + time.sleep(30) + timeout -= 30 + + template_response = list_template_response[0] + self.assertEqual( + template_response.displaytext, + template.displaytext, + "Check template displaytext in response" + ) + + @attr(tags=["advanced", "smoke", "basic"], required_hardware="true") + def test_04_register_iso(self): + """Test for register ISO + """ + # Validate the following: + # 1. Register ISO and check name + + if self.hypervisor.lower() in ["lxc"]: + self.skipTest("Skipping test, unsupported hypervisor %s" % self.hypervisor) + + self.services["iso1"]["displaytext"] = "test🎉iso🙂" + self.services["iso1"]["name"] = "test🎉iso🙂" + iso = Iso.create( + self.apiclient, + self.services["iso1"], + account=self.account.name, + domainid=self.account.domainid + ) + self.debug("Successfully registered ISO with ID: %s" % iso.id) + self.cleanup.append(iso) + + # Get ISO response + timeout = 600 + list_iso_response = None + while timeout >= 0: + list_iso_response = Iso.list( + self.apiclient, + isofilter="self", + id=iso.id + ) + + if list_iso_response is not None and list_iso_response[0].isready: + break + + time.sleep(30) + timeout -= 30 + + iso_response = list_iso_response[0] + self.assertEqual( + iso_response.displaytext, + iso.displaytext, + "Check ISO displaytext in response" + ) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index aaffa63978a..c05ae2ad42e 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -31,6 +31,7 @@ from marvin.cloudstackAPI import (recoverVirtualMachine, from marvin.lib.utils import * from marvin.lib.base import (Account, + Role, ServiceOffering, VirtualMachine, Host, @@ -94,17 +95,21 @@ class TestDeployVM(cloudstackTestCase): cls.services["iso1"]["zoneid"] = cls.zone.id + cls._cleanup = [] + cls.account = Account.create( cls.apiclient, cls.services["account"], domainid=cls.domain.id ) + cls._cleanup.append(cls.account) cls.debug(cls.account.id) cls.service_offering = ServiceOffering.create( cls.apiclient, cls.services["service_offerings"]["tiny"] ) + cls._cleanup.append(cls.service_offering) cls.virtual_machine = VirtualMachine.create( cls.apiclient, @@ -115,17 +120,9 @@ class TestDeployVM(cloudstackTestCase): mode=cls.services['mode'] ) - cls.cleanup = [ - cls.service_offering, - cls.account - ] - @classmethod def tearDownClass(cls): - try: - cleanup_resources(cls.apiclient, cls.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) + super(TestDeployVM, cls).tearDownClass() def setUp(self): self.apiclient = self.testClient.getApiClient() @@ -262,11 +259,7 @@ class TestDeployVM(cloudstackTestCase): ) def tearDown(self): - try: - # Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) + super(TestDeployVM, self).tearDown() class TestVMLifeCycle(cloudstackTestCase): @@ -279,7 +272,7 @@ class TestVMLifeCycle(cloudstackTestCase): cls.hypervisor = testClient.getHypervisorInfo() # Get Zone, Domain and templates - domain = get_domain(cls.apiclient) + cls.domain = get_domain(cls.apiclient) cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.services['mode'] = cls.zone.networktype @@ -309,7 +302,7 @@ class TestVMLifeCycle(cloudstackTestCase): cls.account = Account.create( cls.apiclient, cls.services["account"], - domainid=domain.id + domainid=cls.domain.id ) cls.small_offering = ServiceOffering.create( @@ -362,6 +355,7 @@ class TestVMLifeCycle(cloudstackTestCase): self.cleanup = [] def tearDown(self): + # This should be a super call instead (like tearDownClass), which reverses cleanup order. Kept for now since fixing requires adjusting test 12. try: # Clean up, terminate the created ISOs cleanup_resources(self.apiclient, self.cleanup) @@ -929,7 +923,7 @@ class TestVMLifeCycle(cloudstackTestCase): domainid=self.account.domainid, diskofferingid=custom_disk_offering.id ) - self.cleanup.append(volume) + self.cleanup.append(volume) # Needs adjusting when changing tearDown to a super call, since it will try to delete an attached volume. VirtualMachine.attach_volume(vm, self.apiclient, volume) # Start the VM @@ -955,6 +949,92 @@ class TestVMLifeCycle(cloudstackTestCase): "Check virtual machine is in running state" ) + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_13_destroy_and_expunge_vm(self): + """Test destroy virtual machine with expunge parameter depending on whether the caller's role has expunge permission. + """ + # Setup steps: + # 1. Create role with DENY expunge permission. + # 2. Create account with said role. + # 3. Create a VM of said account. + # 4. Create a VM of cls.account + # Validation steps: + # 1. Destroy the VM with the created account and verify it was not destroyed. + # 1. Destroy the other VM with cls.account and verify it was expunged. + + role = Role.importRole( + self.apiclient, + { + "name": "MarvinFake Import Role ", + "type": "DomainAdmin", + "description": "Fake Import Domain Admin Role created by Marvin test", + "rules" : [{"rule":"list*", "permission":"allow","description":"Listing apis"}, + {"rule":"get*", "permission":"allow","description":"Get apis"}, + {"rule":"update*", "permission":"allow","description":"Update apis"}, + {"rule":"queryAsyncJobResult", "permission":"allow","description":"Query async job result"}, + {"rule":"deployVirtualMachine", "permission":"allow","description":"Deploy virtual machine"}, + {"rule":"destroyVirtualMachine", "permission":"allow","description":"Destroy virtual machine"}, + {"rule":"expungeVirtualMachine", "permission":"deny","description":"Expunge virtual machine"}] + }, + ) + self.cleanup.append(role) + + domadm = Account.create( + self.apiclient, + self.services["account"], + admin=True, + roleid=role.id, + domainid=self.domain.id + ) + self.cleanup[-1]=domadm # Hacky way to reverse cleanup order to avoid deleting the role before account. Remove this line when tearDown is changed to call super(). + self.cleanup.append(role) # Should be self.cleanup.append(domadm) when tearDown is changed to call super(). + + domadm_apiclient = self.testClient.getUserApiClient(UserName=domadm.name, DomainName=self.domain.name, type=1) + + vm1 = VirtualMachine.create( + self.apiclient, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + ) + + vm2 = VirtualMachine.create( + domadm_apiclient, + self.services["small"], + accountid=domadm.name, + domainid=domadm.domainid, + serviceofferingid=self.small_offering.id, + ) + + self.debug("Expunge VM-ID: %s" % vm1.id) + + cmd = destroyVirtualMachine.destroyVirtualMachineCmd() + cmd.id = vm1.id + cmd.expunge = True + response = self.apiclient.destroyVirtualMachine(cmd) + + self.debug("response: %s" % response) + self.debug("response: %s" % response.id) + self.assertEqual( + response.id, + None, + "Check if VM was expunged.", + ) + + self.debug("Expunge VM-ID: %s" % vm2.id) + + cmd = destroyVirtualMachine.destroyVirtualMachineCmd() + cmd.id = vm2.id + cmd.expunge = True + try: + domadm_apiclient.destroyVirtualMachine(cmd) + self.failed("Destroy VM with expunge should have raised an exception.") + except: + self.debug("Expected exception! Keep going.") + + return + class TestSecuredVmMigration(cloudstackTestCase): diff --git a/tools/apidoc/pom.xml b/tools/apidoc/pom.xml index c16795065f0..f2321df6788 100644 --- a/tools/apidoc/pom.xml +++ b/tools/apidoc/pom.xml @@ -51,7 +51,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} compile diff --git a/tools/appliance/systemvmtemplate/scripts/install_systemvm_packages.sh b/tools/appliance/systemvmtemplate/scripts/install_systemvm_packages.sh index 686661a72f9..d1f17454777 100644 --- a/tools/appliance/systemvmtemplate/scripts/install_systemvm_packages.sh +++ b/tools/appliance/systemvmtemplate/scripts/install_systemvm_packages.sh @@ -83,7 +83,7 @@ function install_packages() { apt_clean # 32 bit architecture support for vhd-util - if [[ "${arch}" != "i386" && "${arch}" != "arm64" ]]; then + if [[ "${arch}" != "i386" && "${arch}" == "amd64" ]]; then dpkg --add-architecture i386 apt-get update ${apt_get} install libuuid1:i386 libc6:i386 @@ -96,6 +96,8 @@ function install_packages() { add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" elif [ "${arch}" == "amd64" ]; then add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" + elif [ "${arch}" == "s390x" ]; then + add-apt-repository "deb [arch=s390x] https://download.docker.com/linux/debian $(lsb_release -cs) stable" else add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" fi @@ -104,7 +106,7 @@ function install_packages() { apt_clean - if [ "${arch}" != "arm64" ]; then + if [ "${arch}" == "amd64" ]; then install_vhd_util # Install xenserver guest utilities as debian repos don't have it wget --no-check-certificate https://download.cloudstack.org/systemvm/debian/xe-guest-utilities_7.20.2-0ubuntu1_amd64.deb diff --git a/tools/devcloud-kvm/pom.xml b/tools/devcloud-kvm/pom.xml index 2cc0fde262b..d6266fad7c2 100644 --- a/tools/devcloud-kvm/pom.xml +++ b/tools/devcloud-kvm/pom.xml @@ -124,7 +124,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} package diff --git a/tools/marvin/marvin/cloudstackException.py b/tools/marvin/marvin/cloudstackException.py index 477a61829fb..cfd11d0678c 100644 --- a/tools/marvin/marvin/cloudstackException.py +++ b/tools/marvin/marvin/cloudstackException.py @@ -77,6 +77,7 @@ class CloudstackAclException(): UNABLE_TO_LIST_NETWORK_ACCOUNT = "Can't create/list resources for account" NO_PERMISSION_TO_ACCESS_ACCOUNT = "does not have permission to access resource Acct" NOT_AVAILABLE_IN_DOMAIN = "not available in domain" + NO_PERMISSION_TO_OPERATE_SOURCE = "does not have permission to operate with provided resource" @staticmethod def verifyMsginException(e,message): diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py index e96dba1c4d5..3485eeb8b18 100644 --- a/tools/marvin/marvin/config/test_data.py +++ b/tools/marvin/marvin/config/test_data.py @@ -450,6 +450,21 @@ test_data = { "UserData": "VirtualRouter" } }, + "shared_network_offering_configdrive": { + "name": "MySharedOfferingWithConfigDrive-shared", + "displaytext": "MySharedOfferingWithConfigDrive", + "guestiptype": "Shared", + "supportedservices": "Dhcp,Dns,UserData", + "specifyVlan": "False", + "specifyIpRanges": "False", + "traffictype": "GUEST", + "tags": "native", + "serviceProviderList": { + "Dhcp": "ConfigDrive", + "Dns": "ConfigDrive", + "UserData": "ConfigDrive" + } + }, "shared_network_offering_all_services": { "name": "shared network offering with services enabled", "displaytext": "Shared network offering", @@ -1047,6 +1062,41 @@ test_data = { "isextractable": "True" }, }, + "test_templates_cloud_init": { + "kvm": { + "name": "ubuntu 22.04 kvm", + "displaytext": "ubuntu 22.04 kvm", + "format": "raw", + "hypervisor": "kvm", + "ostype": "Other Linux (64-bit)", + "url": "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "False" + }, + "xenserver": { + "name": "ubuntu 22.04 xen", + "displaytext": "ubuntu 22.04 xen", + "format": "vhd", + "hypervisor": "xenserver", + "ostype": "Other Linux (64-bit)", + "url": "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64-azure.vhd.tar.gz", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True" + }, + "vmware": { + "name": "ubuntu 22.04 vmware", + "displaytext": "ubuntu 22.04 vmware", + "format": "ova", + "hypervisor": "vmware", + "ostype": "Other Linux (64-bit)", + "url": "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.ova", + "requireshvm": "True", + "ispublic": "True", + "deployasis": "True" + }, + }, "test_ovf_templates": [ { "name": "test-ovf", diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 9c69d33a4ca..7e06ededa1d 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -527,7 +527,7 @@ class VirtualMachine: customcpuspeed=None, custommemory=None, rootdisksize=None, rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={}, properties=None, nicnetworklist=None, bootmode=None, boottype=None, dynamicscalingenabled=None, - userdataid=None, userdatadetails=None, extraconfig=None, size=None): + userdataid=None, userdatadetails=None, extraconfig=None, size=None, overridediskofferingid=None): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -537,6 +537,9 @@ class VirtualMachine: elif "serviceoffering" in services: cmd.serviceofferingid = services["serviceoffering"] + if overridediskofferingid: + cmd.overridediskofferingid = overridediskofferingid + if zoneid: cmd.zoneid = zoneid elif "zoneid" in services: diff --git a/tools/marvin/pom.xml b/tools/marvin/pom.xml index df1186d18b7..ea963fc8da7 100644 --- a/tools/marvin/pom.xml +++ b/tools/marvin/pom.xml @@ -59,7 +59,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} generate-sources @@ -113,7 +113,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} generate-sources @@ -177,9 +177,16 @@ - org.codehaus.gmaven - gmaven-plugin - 1.5 + org.codehaus.gmavenplus + gmavenplus-plugin + ${cs.gmavenplus.version} + + + org.codehaus.groovy + groovy-all + ${cs.groovy.version} + + setproperty @@ -188,10 +195,12 @@ execute - - pom.properties['resolved.basedir']=project.basedir.absolutePath.replace('\','/').replace('D:','/cyg/d'); - pom.properties['resolved.userdir']='${user.dir}'.replace('\','/').replace('D:','/cyg/d'); - + + + @@ -199,7 +208,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} pre-integration-test @@ -234,9 +243,16 @@ - org.codehaus.gmaven - gmaven-plugin - 1.5 + org.codehaus.gmavenplus + gmavenplus-plugin + ${cs.gmavenplus.version} + + + org.codehaus.groovy + groovy-all + ${cs.groovy.version} + + setproperty @@ -249,10 +265,12 @@ ${user.dir} ${marvin.config} - - project.properties['resolved.user.dir']='${user.dir}'.replace('\','/').replace('D:','/cyg/d'); - project.properties['resolved.marvin.config']='${marvin.config}'.replace('\','/').replace('D:','/cyg/d'); - + + +
@@ -260,7 +278,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${cs.exec-maven-plugin.version} integration-test diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 8f2c9fa6d15..2e5412bf737 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -109,12 +109,14 @@ "label.action.disable.disk.offering": "Disable disk offering", "label.action.disable.physical.network": "Disable physical Network", "label.action.disable.pod": "Disable pod", +"label.action.disable.role": "Disable Role", "label.action.disable.static.nat": "Disable static NAT", "label.action.disable.service.offering": "Disable service offering", "label.action.disable.system.service.offering": "Disable system service offering", "label.action.disable.user": "Disable User", "label.action.disable.zone": "Disable zone", "label.action.download.iso": "Download ISO", +"label.action.download.snapshot": "Download Snapshot", "label.action.download.template": "Download Template", "label.action.download.volume": "Download volume", "label.action.edit.account": "Edit Account", @@ -132,6 +134,7 @@ "label.action.enable.maintenance.mode": "Enable maintenance mode", "label.action.enable.physical.network": "Enable physical Network", "label.action.enable.pod": "Enable pod", +"label.action.enable.role": "Enable Role", "label.action.enable.service.offering": "Enable service offering", "label.action.enable.system.service.offering": "Enable system service offering", "label.action.enable.static.nat": "Enable static NAT", @@ -161,6 +164,9 @@ "label.action.patch.systemvm.processing": "Patching System VM....", "label.action.project.add.account": "Add Account to project", "label.action.project.add.user": "Add User to project", +"label.action.quota.tariff.create": "Create Quota Tariff", +"label.action.quota.tariff.edit": "Edit Quota Tariff", +"label.action.quota.tariff.remove": "Remove Quota Tariff", "label.action.reboot.instance": "Reboot Instance", "label.action.reboot.router": "Reboot router", "label.action.reboot.systemvm": "Reboot System VM", @@ -377,14 +383,14 @@ "label.authentication.method": "Authentication Method", "label.authentication.sshkey": "System SSH Key", "label.autoscale": "AutoScale", -"label.autoscalevmgroupname": "AutoScale Instance Group", +"label.autoscalevmgroupname": "AutoScaling Group", "label.author.email": "Author e-mail", "label.author.name": "Author name", "label.auto.assign": "Automatically assign", "label.auto.assign.diskoffering.disk.size": "Automatically assign offering matching the disk size", "label.auto.assign.random.ip": "Automatically assign a random IP address", "label.automigrate.volume": "Auto migrate volume to another storage pool if required", -"label.autoscale.vm.groups": "AutoScale Instance Groups", +"label.autoscale.vm.groups": "AutoScaling Groups", "label.autoscale.vm.profile": "AutoScale Instance Profile", "label.autoscalingenabled": "Auto scaling", "label.availability": "Availability", @@ -644,7 +650,7 @@ "label.delete.acl.list": "Delete ACL list", "label.delete.affinity.group": "Delete affinity group", "label.delete.alerts": "Delete alerts", -"label.delete.autoscale.vmgroup": "Delete AutoScale Instance Group", +"label.delete.autoscale.vmgroup": "Delete AutoScaling Group", "label.delete.backup": "Delete backup", "label.delete.bigswitchbcf": "Remove BigSwitch BCF controller", "label.delete.brocadevcs": "Remove Brocade Vcs switch", @@ -742,7 +748,7 @@ "label.direct.ips": "Shared Network IPs", "label.directdownload": "Direct download", "label.direction": "Direction", -"label.disable.autoscale.vmgroup": "Disable AutoScale Instance Group", +"label.disable.autoscale.vmgroup": "Disable AutoScaling Group", "label.disable.host": "Disable host", "label.disable.network.offering": "Disable Network offering", "label.disable.provider": "Disable provider", @@ -852,7 +858,7 @@ "label.egressdefaultpolicy": "Default egress policy", "label.elastic": "Elastic", "label.email": "Email", -"label.enable.autoscale.vmgroup": "Enable AutoScale Instance Group", +"label.enable.autoscale.vmgroup": "Enable AutoScaling Group", "label.enable.host": "Enable Host", "label.enable.network.offering": "Enable Network offering", "label.enable.oauth": "Enable OAuth Login", @@ -865,6 +871,7 @@ "label.encrypt": "Encrypt", "label.encryptroot": "Encrypt Root Disk", "label.end": "End", +"label.end.date": "End date", "label.end.date.and.time": "End date and time", "label.end.ip": "End IP", "label.end.reserved.system.ip": "End reserved system IP", @@ -1162,6 +1169,7 @@ "label.isdedicated": "Dedicated", "label.isdefault": "Is default", "label.isdynamicallyscalable": "Dynamically scalable", +"label.isencrypted": "Encrypted", "label.istagarule": "Tag as JS rule", "label.isextractable": "Extractable", "label.isfeatured": "Featured", @@ -1461,7 +1469,7 @@ "label.networktype": "Network type", "label.networkwrite": "Network write", "label.new": "New", -"label.new.autoscale.vmgroup": "New AutoScale Instance Group", +"label.new.autoscale.vmgroup": "New AutoScaling Group", "label.new.instance.group": "New Instance group", "label.new.password": "New password", "label.new.project": "New project", @@ -1727,6 +1735,8 @@ "label.quota.summary": "Summary", "label.quota.tariff": "Tariff", "label.quota.tariff.effectivedate": "Effective date", +"label.quota.tariff.position": "Position", +"label.quota.tariff.value": "Tariff value", "label.quota.total": "Total", "label.quota.type.name": "Usage Type", "label.quota.type.unit": "Usage unit", @@ -2060,6 +2070,7 @@ "label.sslverification": "SSL verification", "label.standard.us.keyboard": "Standard (US) keyboard", "label.start": "Start", +"label.start.date": "Start date", "label.start.date.and.time": "Start date and time", "label.start.ip": "Start IP", "label.start.lb.vm": "Start LB Instance", @@ -2273,7 +2284,7 @@ "label.untagged": "Untagged", "label.up": "Up", "label.updateinsequence": "Update in sequence", -"label.update.autoscale.vmgroup": "Update AutoScale Instance group", +"label.update.autoscale.vmgroup": "Update AutoScaling Group", "label.update.condition": "Update condition", "label.update.instance.group": "Update Instance group", "label.update.ip.range": "Update IP range", @@ -2497,7 +2508,7 @@ "label.xenservertoolsversion61plus": "Original XS Version is 6.1+", "label.yes": "Yes", "label.yourinstance": "Your Instance", -"label.your.autoscale.vmgroup": "Your autoscale Instance group", +"label.your.autoscale.vmgroup": "Your autoscaling group", "label.zone": "Zone", "label.zone.dedicated": "Zone dedicated", "label.zone.details": "Zone details", @@ -2525,7 +2536,7 @@ "message.action.cancel.maintenance": "Your host has been successfully canceled for maintenance. This process can take up to several minutes.", "message.action.cancel.maintenance.mode": "Please confirm that you want to cancel this maintenance.", "message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot", -"message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscale Instance group.", +"message.action.delete.autoscale.vmgroup": "Please confirm that you want to delete this autoscaling group.", "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?", "message.action.delete.cluster": "Please confirm that you want to delete this cluster.", "message.action.delete.domain": "Please confirm that you want to delete this domain.", @@ -2567,6 +2578,7 @@ "message.action.disable.static.nat": "Please confirm that you want to disable static NAT.", "message.action.disable.zone": "Please confirm that you want to disable this zone.", "message.action.download.iso": "Please confirm that you want to download this ISO.", +"message.action.download.snapshot": "Please confirm that you want to download this Snapshot.", "message.action.download.template": "Please confirm that you want to download this Template.", "message.action.edit.nfs.mount.options": "Changes to NFS mount options will only take affect on cancelling maintenance mode which will cause the storage pool to be remounted on all KVM hosts with the new mount options.", "message.action.enable.cluster": "Please confirm that you want to enable this cluster.", @@ -2586,6 +2598,10 @@ "message.action.primary.storage.scope.cluster": "Please confirm that you want to change the scope from zone to the specified cluster.
This operation will update the database and disconnect the storage pool from all hosts that were previously connected to the primary storage and are not part of the specified cluster.", "message.action.primary.storage.scope.zone": "Please confirm that you want to change the scope from cluster to zone.
This operation will update the database and connect the storage pool to all hosts of the zone running the same hypervisor as set on the storage pool.", "message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped. Do you want to continue?", +"message.action.quota.tariff.create.error.namerequired": "Please, inform a name for the quota tariff.", +"message.action.quota.tariff.create.error.usagetyperequired": "Please, select the usage type of the quota tariff.", +"message.action.quota.tariff.create.error.valuerequired": "Please, inform a value for the quota tariff.", +"message.action.quota.tariff.remove": "Please confirm that you want to remove this Quota Tariff.", "message.action.reboot.instance": "Please confirm that you want to reboot this Instance.", "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.", "message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.", @@ -2689,10 +2705,10 @@ "message.attach.volume.progress": "Attaching volume", "message.attach.volume.success": "Successfully attached the volume to the instance", "message.authorization.failed": "Session expired, authorization verification failed.", -"message.autoscale.loadbalancer.update": "The load balancer rule can be updated only when autoscale Instance group is DISABLED.", -"message.autoscale.policies.update": "The scale up/down policies can be updated only when autoscale Instance group is DISABLED.", -"message.autoscale.vm.networks": "Please choose at least one Network for Instances in the autoscale Instance group. The default Network must be an Isolated Network or VPC Network Tier which supports Instance AutoScaling and has load balancing rules.", -"message.autoscale.vmprofile.update": "The autoscale Instance profile can be updated only when autoscale Instance group is DISABLED.", +"message.autoscale.loadbalancer.update": "The load balancer rule can be updated only when autoscaling group is DISABLED.", +"message.autoscale.policies.update": "The scale up/down policies can be updated only when autoscaling group is DISABLED.", +"message.autoscale.vm.networks": "Please choose at least one Network for Instances in the autoscaling group. The default Network must be an Isolated Network or VPC Network Tier which supports Instance AutoScaling and has load balancing rules.", +"message.autoscale.vmprofile.update": "The autoscale Instance profile can be updated only when autoscaling group is DISABLED.", "message.backup.attach.restore": "Please confirm that you want to restore and attach the volume from the backup?", "message.backup.create": "Are you sure you want create an Instance backup?", "message.backup.offering.remove": "Are you sure you want to remove Instance from backup offering and delete the backup chain?", @@ -2740,14 +2756,14 @@ "message.confirm.delete.srx": "Please confirm that you would like to delete SRX.", "message.confirm.delete.traffic.type": "Please confirm that you would like to delete traffic type.", "message.confirm.destroy.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to stop this router. Please confirm that you would like to destroy this router.", -"message.confirm.disable.autoscale.vmgroup": "Please confirm that you want to disable this autoscale Instance group.", +"message.confirm.disable.autoscale.vmgroup": "Please confirm that you want to disable this autoscaling group.", "message.confirm.disable.host": "Please confirm that you want to disable the host.", "message.confirm.disable.network.offering": "Are you sure you want to disable this Network offering?", "message.confirm.disable.provider": "Please confirm that you would like to disable this provider.", "message.confirm.disable.storage": "Please confirm that you want to disable the storage pool.", "message.confirm.disable.vpc.offering": "Are you sure you want to disable this VPC offering?", "message.confirm.disable.webhook": "Please confirm that you want to disable this webhook.", -"message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscale Instance group.", +"message.confirm.enable.autoscale.vmgroup": "Please confirm that you want to enable this autoscaling group.", "message.confirm.enable.host": "Please confirm that you want to enable the host.", "message.confirm.enable.network.offering": "Are you sure you want to enable this Network offering?", "message.confirm.enable.provider": "Please confirm that you would like to enable this provider.", @@ -2788,7 +2804,7 @@ "message.create.volume.processing": "Volume creation in progress", "message.create.vpc.offering": "VPC offering created.", "message.create.vpn.customer.gateway.failed": "VPN customer gateway creation failed.", -"message.creating.autoscale.vmgroup": "Creating AutoScale Instance group", +"message.creating.autoscale.vmgroup": "Creating AutoScaling Group", "message.creating.autoscale.vmprofile": "Creating AutoScale Instance profile", "message.creating.autoscale.scaledown.conditions": "Creating ScaleDown conditions", "message.creating.autoscale.scaledown.policy": "Creating ScaleDown policy", @@ -2870,6 +2886,7 @@ "message.detach.disk": "Are you sure you want to detach this disk?", "message.detach.iso.confirm": "Please confirm that you want to detach the ISO from this virtual Instance.", "message.disable.account": "Please confirm that you want to disable this Account. By disabling the Account, all Users for this Account will no longer have access to their cloud resources. All running Instances will be immediately shut down.", +"message.disable.role": "Please confirm that you would like to disable this Role", "message.disable.user": "Please confirm that you would like to disable this User.", "message.disable.vpn": "Are you sure you want to disable VPN?", "message.disable.vpn.failed": "Failed to disable VPN.", @@ -2891,6 +2908,7 @@ "message.egress.rules.info.for.network": "The default egress policy of this Network is %x.
Outgoing traffic matching the following egress rules will be %y", "message.enable.account": "Please confirm that you want to enable this Account.", "message.enable.netsacler.provider.failed": "failed to enable Netscaler provider", +"message.enable.role": "Please confirm that you want to enable this Role", "message.enable.securitygroup.provider.failed": "failed to enable security group provider", "message.enable.user": "Please confirm that you would like to enable this User.", "message.enable.vpn": "Please confirm that you want remote access VPN enabled for this IP address.", @@ -2949,7 +2967,7 @@ "message.error.internallb.instance.port": "Please specify a Instance port.", "message.error.internallb.name": "Please specify a name for the internal LB.", "message.error.internallb.source.port": "Please specify a source port.", -"message.error.invalid.autoscale.vmgroup.name": "Invalid AutoScale Instance group name. It can contain the ASCII letters 'a' through 'z', 'A' through 'Z', the digits '0' through '9' and the hyphen ('-'), must be between 1 and 255 characters long.", +"message.error.invalid.autoscale.vmgroup.name": "Invalid AutoScaling Group name. It can contain the ASCII letters 'a' through 'z', 'A' through 'Z', the digits '0' through '9' and the hyphen ('-'), must be between 1 and 255 characters long.", "message.error.ip.range": "Please enter valid range.", "message.error.ipv4.address": "Please enter a valid IPv4 address.", "message.error.ipv4.dns1": "Please enter IpV4 DNS 1", @@ -3058,6 +3076,7 @@ "message.failed.to.remove": "Failed to remove", "message.generate.keys": "Please confirm that you would like to generate new API/Secret keys for this User.", "message.chart.statistic.info": "The shown charts are self-adjustable, that means, if the value gets close to the limit or overpass it, it will grow to adjust the shown value", +"message.chart.statistic.info.hypervisor.additionals": "The metrics data depend on the hypervisor plugin used for each hypervisor. The behavior can vary across different hypervisors. For instance, with KVM, metrics are real-time statistics provided by libvirt. In contrast, with VMware, the metrics are averaged data for a given time interval controlled by configuration.", "message.guest.traffic.in.advanced.zone": "Guest Network traffic is communication between end-user Instances. Specify a range of VLAN IDs or VXLAN Network identifiers (VNIs) to carry guest traffic for each physical Network.", "message.guest.traffic.in.basic.zone": "Guest Network traffic is communication between end-user Instances. Specify a range of IP addresses that CloudStack can assign to guest Instances. Make sure this range does not overlap the reserved system IP range.", "message.host.controlstate": "The Control Plane Status of this Instance is ", @@ -3183,7 +3202,7 @@ "message.please.confirm.remove.user.data": "Please confirm that you want to remove this Userdata", "message.please.enter.valid.value": "Please enter a valid value.", "message.please.enter.value": "Please enter values.", -"message.please.wait.while.autoscale.vmgroup.is.being.created": "Please wait while your AutoScale Instance group is being created; this may take a while...", +"message.please.wait.while.autoscale.vmgroup.is.being.created": "Please wait while your AutoScaling Group is being created; this may take a while...", "message.please.wait.while.zone.is.being.created": "Please wait while your zone is being created; this may take a while...", "message.pod.dedicated": "Pod dedicated.", "message.pod.dedication.released": "Pod dedication released.", @@ -3193,6 +3212,8 @@ "message.protocol.description": "For XenServer, choose NFS, iSCSI, or PreSetup. For KVM, choose NFS, SharedMountPoint, RDB, CLVM or Gluster. For vSphere, choose NFS, PreSetup (VMFS or iSCSI or FiberChannel or vSAN or vVols) or DatastoreCluster. For Hyper-V, choose SMB/CIFS. For LXC, choose NFS or SharedMountPoint. For OVM, choose NFS or OCFS2.", "message.public.traffic.in.advanced.zone": "Public traffic is generated when Instances in the cloud access the internet. Publicly-accessible IPs must be allocated for this purpose. End Users can use the CloudStack UI to acquire these IPs to implement NAT between their guest Network and their public Network.

Provide at least one range of IP addresses for internet traffic.", "message.public.traffic.in.basic.zone": "Public traffic is generated when Instances in the cloud access the Internet or provide services to clients over the Internet. Publicly accessible IPs must be allocated for this purpose. When a Instance is created, an IP from this set of Public IPs will be allocated to the Instance in addition to the guest IP address. Static 1-1 NAT will be set up automatically between the public IP and the guest IP. End Users can also use the CloudStack UI to acquire additional IPs to implement static NAT between their Instances and the public IP.", +"message.quota.tariff.create.success": "Successfully created quota tariff \"{quotaTariff}\"", +"message.quota.tariff.update.success": "Successfully updated quota tariff \"{quotaTariff}\"", "message.read.accept.license.agreements": "Please read and accept the terms for the license agreements.", "message.read.admin.guide.scaling.up": "Please read the dynamic scaling section in the admin guide before scaling up.", "message.recover.vm": "Please confirm that you would like to recover this Instance.", @@ -3236,11 +3257,11 @@ "message.restart.vpc": "Please confirm that you want to restart the VPC.", "message.restart.vpc.remark": "Please confirm that you want to restart the VPC

Remark: making a non-redundant VPC redundant will force a clean up. The Networks will not be available for a couple of minutes.

", "message.scale.processing": "Scale in progress", -"message.scaledown.policies": "Please add at least a ScaleDown policy. The AutoScale Instance group will be scaled down when all conditions in a ScaleDown policy are matched. ScaleDown policies will be checked after ScaleUp policies.", +"message.scaledown.policies": "Please add at least a ScaleDown policy. The AutoScale Group will be scaled down when all conditions in a ScaleDown policy are matched. ScaleDown policies will be checked after ScaleUp policies.", "message.scaledown.policy.continue": "Please add at least condition to ScaleDown policy to continue", "message.scaledown.policy.duration.continue": "Please input a valid duration to ScaleDown policy to continue", "message.scaledown.policy.name.continue": "Please input a name to ScaleDown policy to continue", -"message.scaleup.policies": "Please add at least a ScaleUp policy. The AutoScale Instance group will be scaled up when all conditions in a ScaleUp policy are matched. ScaleUp policies will be checked before ScaleDown policies.", +"message.scaleup.policies": "Please add at least a ScaleUp policy. The AutoScale Group will be scaled up when all conditions in a ScaleUp policy are matched. ScaleUp policies will be checked before ScaleDown policies.", "message.scaleup.policy.continue": "Please add at least a condition to ScaleUp policy to continue", "message.scaleup.policy.duration.continue": "Please input a valid duration to ScaleUp policy to continue", "message.scaleup.policy.name.continue": "Please input a name to ScaleUp policy to continue", @@ -3252,7 +3273,7 @@ "message.select.disk.offering": "Please select a disk offering for disk", "message.select.end.date.and.time": "Select an end date & time.", "message.select.kvm.host.instance.conversion": "(Optional) Select a KVM host in the cluster to perform the instance conversion through virt-v2v", -"message.select.load.balancer.rule": "Please select a load balancer rule for your AutoScale Instance group.", +"message.select.load.balancer.rule": "Please select a load balancer rule for your AutoScale Group.", "message.select.migration.policy": "Please select a migration policy.", "message.select.nic.network": "Please select a Network for NIC", "message.select.security.groups": "Please select security group(s) for your new Instance.", @@ -3303,7 +3324,8 @@ "message.success.add.tag": "Successfully added new tag", "message.success.add.tungsten.router.table": "Successfully added Router Table", "message.success.add.tungsten.routing.policy": "Successfully added Tungsten-Fabric routing policy", -"message.success.add.vpc.network": "Successfully added VPC Network", +"message.success.add.vpc": "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.gateway": "Successfully added VPN gateway", "message.success.assign.vm": "Successfully assigned Instance", @@ -3416,7 +3438,7 @@ "message.trigger.shutdown": "Please confirm that you would like to trigger a shutdown on this Management server. It will not accept any new Async Jobs and will terminate after there are no pending jobs.", "message.type.values.to.add": "Please add additional values by typing them in", "message.update.autoscale.policy.failed": "Failed to update autoscale policy", -"message.update.autoscale.vmgroup.failed": "Failed to update autoscale Instance group", +"message.update.autoscale.vmgroup.failed": "Failed to update autoscale group", "message.update.autoscale.vm.profile.failed": "Failed to update autoscale Instance profile", "message.update.condition.failed": "Failed to update condition", "message.update.condition.processing": "Updating condition...", @@ -3520,6 +3542,13 @@ "migrate.from": "Migrate from", "migrate.to": "Migrate to", "migrationPolicy": "Migration policy", +"placeholder.quota.tariff.description": "Quota tariff's description", +"placeholder.quota.tariff.enddate": "Quota tariff's end date", +"placeholder.quota.tariff.name": "Quota tariff's name", +"placeholder.quota.tariff.position": "Quota tariff's position in the execution sequence", +"placeholder.quota.tariff.startdate": "Quota tariff's start date", +"placeholder.quota.tariff.usagetype": "Quota tariff's usage type", +"placeholder.quota.tariff.value": "Quota tariff's value", "router.health.checks": "Health check", "side.by.side": "Side by Side", "state.completed": "Completed", @@ -3541,5 +3570,32 @@ "state.stopping": "Stopping", "state.suspended": "Suspended", "user.login": "Login", -"user.logout": "Logout" +"user.logout": "Logout", +"ALLOCATED_VM": "Allocated VM", +"BACKUP": "Backup", +"BACKUP_OBJECT": "Backup Object", +"IP_ADDRESS": "IP Address", +"LOAD_BALANCER_POLICY": "Load Balancer Policy", +"NETWORK": "Network", +"NETWORK_BYTES_RECEIVED": "Network Bytes Received", +"NETWORK_BYTES_SENT": "Network Bytes Sent", +"NETWORK_OFFERING": "Network Offering", +"RUNNING_VM": "Running VM", +"PORT_FORWARDING_RULE": "Port Forwarding Rule", +"SECURITY_GROUP": "Security Group", +"SNAPSHOT": "Snapshot", +"TEMPLATE": "Template", +"VM_DISK_BYTES_READ": "VM Disk (Bytes Read)", +"VM_DISK_BYTES_WRITE": "VM Disk (Bytes Write)", +"VM_DISK_IO_READ": "VM Disk (IO Read)", +"VM_DISK_IO_WRITE": "VM Disk (IO Write)", +"VM_SNAPSHOT": "VM Snapshot", +"VM_SNAPSHOT_ON_PRIMARY": "VM Snapshot on Primary", +"VOLUME": "Volume", +"VOLUME_SECONDARY": "Volume on Secondary", +"VPN_USERS": "VPN Users", +"Compute*Month": "Compute * Month", +"GB*Month": "GB * Month", +"IP*Month": "IP * Month", +"Policy*Month": "Policy * Month" } diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index fec66ba4cef..79333c100d3 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -95,6 +95,7 @@ "label.action.disable.user": "Desativar usu\u00e1rio", "label.action.disable.zone": "Desativar zona", "label.action.download.iso": "Baixar ISO", +"label.action.download.snapshot": "Baixar snapshot", "label.action.download.template": "Baixar template", "label.action.download.volume": "Baixar disco", "label.action.edit.account": "Editar conta", @@ -162,6 +163,7 @@ "label.action.vmsnapshot.revert": "Reverter snapshot de VM", "label.action.vmstoragesnapshot.create": "Criar snapshot de volume da VM", "label.actions": "A\u00e7\u00f5es", +"label.active": "Ativo", "label.activate.project": "Ativar projeto", "label.activeviewersessions": "Sess\u00f5es ativas", "label.add": "Adicionar", @@ -625,6 +627,7 @@ "label.enable.vpc.offering": "Habilitar oferta VPC", "label.enable.vpn": "Habilitar VPN", "label.end": "Fim", +"label.end.date": "Data de término", "label.end.date.and.time": "Data e hor\u00e1rio final", "label.end.ip": "IP final", "label.end.reserved.system.ip": "Fim dos IPs reservados para o sistema", @@ -1279,7 +1282,12 @@ "label.quotastate": "Estado da cota", "label.summary": "Sum\u00e1rio", "label.quota.tariff": "Tarifa", +"label.action.quota.tariff.create": "Criar tarifa", +"label.action.quota.tariff.edit": "Editar tarifa", +"label.action.quota.tariff.remove": "Remover tarifa", "label.quota.tariff.effectivedate": "Data efetiva", +"label.quota.tariff.position": "Posi\u00e7\u00e3o", +"label.quota.tariff.value": "Valor", "label.quota.total": "Total", "label.quota.type.name": "Tipo de uso", "label.quota.type.unit": "Unidade do uso", @@ -1338,6 +1346,7 @@ "label.remove.vmware.datacenter": "Remover datacenter VMware", "label.remove.vpc": "Remover VPC", "label.remove.vpc.offering": "Remover oferta VPC", +"label.removed": "Removido", "label.removing": "Removendo", "label.replace.acl": "Substituir ACL", "label.replace.acl.list": "Substituir lista ACL", @@ -1518,6 +1527,7 @@ "label.standard.us.keyboard": "Teclado padr\u00e3o (EUA)", "label.sslcertificates": "Certificados SSL", "label.start": "Iniciar", +"label.start.date": "Data de in\u00edcio", "label.start.date.and.time": "Data e hor\u00e1rio inicial", "label.start.ip": "IP do in\u00edcio", "label.start.lb.vm": "Iniciar VM LB", @@ -1674,7 +1684,7 @@ "label.upload.volume.from.url": "Carregar volume por URL", "label.url": "URL", "label.usageinterface": "Interface de uso", -"label.usagename": "Tipo", +"label.usagetype": "Tipo", "label.usageunit": "Unidade", "label.use.kubectl.access.cluster": "os arquivos kubectl e kubeconfig para acessar o cluster", "label.use.local.timezone": "Use o fuso hor\u00e1rio local", @@ -1847,6 +1857,7 @@ "message.action.disable.static.nat": "Confirme que voc\u00ea deseja desativar o NAT est\u00e1tico.", "message.action.disable.zone": "Confirma a desativa\u00e7\u00e3o da zona.", "message.action.download.iso": "Por favor confirme que voc\u00ea deseja baixar esta ISO.", +"message.action.download.snapshot": "Por favor confirme que voc\u00ea deseja baixar esta snapshot.", "message.action.download.template": "Por favor confirme que voc\u00ea deseja baixar este template.", "message.action.enable.cluster": "Confirma a ativa\u00e7\u00e3o do cluster.", "message.action.enable.physical.network": "Por favor confirme que voc\u00ea deseja habilitar esta rede f\u00edsica.", @@ -1858,6 +1869,10 @@ "message.action.instance.reset.password": "Por favor confirme que voc\u00ea deseja alterar a senha de root para est\u00e1 m\u00e1quina virtual.", "message.action.manage.cluster": "Confirma a vincula\u00e7\u00e3o do cluster.", "message.action.primarystorage.enable.maintenance.mode": "Aviso: colocar o armazenamento prim\u00e1rio em modo de manuten\u00e7\u00e3o ir\u00e1 causar a parada de todas as VMs hospedadas nesta unidade. Deseja continuar?", +"message.action.quota.tariff.create.error.namerequired": "Por favor, informe o nome da tarifa.", +"message.action.quota.tariff.create.error.usagetyperequired": "Por favor, selecione o tipo da tarifa.", +"message.action.quota.tariff.create.error.valuerequired": "Por favor, informe o valor da tarifa.", +"message.action.quota.tariff.remove": "Por favor, confirme que voc\u00ea deseja remover a tarifa.", "message.action.reboot.instance": "Por favor, confirme que voc\u00ea deseja reiniciar esta inst\u00e2ncia.", "message.action.reboot.router": "Confirme que voc\ufffd deseja reiniciar este roteador.", "message.action.reboot.systemvm": "Confirme que voc\u00ea deseja reiniciar esta VM de sistema.", @@ -2280,6 +2295,8 @@ "message.protocol.description": "Para XenServer, escolha NFS, iSCSI, ou PreSetup. para KVM, escolha NFS, SharedMountPoint, RDB, CLVM ou Gluster. para vSphere, escolha NFS, PreSetup (VMFS, iSCSI, fiberChannel, vSAN ou vVols) ou datastoreCluster. para Hyper-V, escolha SMB/CIFS. para LXC, escolha NFS ou SharedMountPoint. para OVM, escolha NFS ou ocfs2.", "message.public.traffic.in.advanced.zone": "O tr\u00e1fego p\u00fablico \u00e9 gerado quando as VMs na nuvem acessam a internet. Os IPs acess\u00edveis ao p\u00fablico devem ser alocados para essa finalidade. Os usu\u00e1rios finais podem usar a interface do usu\u00e1rio CloudStack para adquirir esses IPs afim de implementar NAT entre a sua rede de guests e sua rede p\u00fablica.

Forne\u00e7a pelo menos um intervalo de endere\u00e7os IP para o tr\u00e1fego de internet.", "message.public.traffic.in.basic.zone": "O tr\u00e1fego p\u00fablico \u00e9 gerado quando as VMs na nuvem acessam a internet ou prestam servi\u00e7os aos clientes atrav\u00e9s da internet. Os IPs acess\u00edveis ao p\u00fablico devem ser alocados para essa finalidade. Quando uma inst\u00e2ncia \u00e9 criada, um IP a partir deste conjunto de IPs P\u00fablicos ser\u00e3o destinados \u00e0 inst\u00e2ncia, al\u00e9m do endere\u00e7o IP guest. Um NAT est\u00e1tico 1-1 ser\u00e1 criada automaticamente entre o IP p\u00fablico e IP guest. Os usu\u00e1rios finais tamb\u00e9m podem usar a interface de usu\u00e1rio CloudStack para adquirir IPs adicionais afim de se implementar NAT est\u00e1tico entre suas inst\u00e2ncias e o IP p\u00fablico.", +"message.quota.tariff.create.success": "Tarifa \"{quotaTariff}\" criada com sucesso", +"message.quota.tariff.update.success": "Tarifa \"{quotaTariff}\" atualizada com sucesso", "message.read.accept.license.agreements": "Leia e aceite os termos dos contratos de licen\u00e7a.", "message.read.admin.guide.scaling.up": "Por favor leia a sess\u00e3o sobre escalonamento din\u00e2mico no guia do administrador antes de escalonar.", "message.recover.vm": "Por favor, confirme a recupera\u00e7\u00e3o desta VM.", @@ -2495,6 +2512,13 @@ "migrate.from": "Migrar de", "migrate.to": "Migrar para", "migrationPolicy": "Pol\u00edtica de migra\u00e7\u00e3o", +"placeholder.quota.tariff.description": "Descri\u00e7\u00e3o", +"placeholder.quota.tariff.enddate": "Data de t\u00e9rmino", +"placeholder.quota.tariff.name": "Nome", +"placeholder.quota.tariff.position": "Posi\u00e7\u00e3o da tarifa do Quota na sequ\u00eancia de execu\u00e7\u00e3o", +"placeholder.quota.tariff.startdate": "Data de in\u00edcio", +"placeholder.quota.tariff.usagetype": "Tipo", +"placeholder.quota.tariff.value": "Valor", "router.health.checks": "Checagem de sa\u00fade", "side.by.side": "Lado a lado", "state.completed": "Completo", @@ -2516,5 +2540,32 @@ "state.stopping": "Parando", "state.suspended": "Suspendido", "user.login": "Entrar", -"user.logout": "Sair" +"user.logout": "Sair", +"ALLOCATED_VM": "VM alocada", +"BACKUP": "Backup", +"BACKUP_OBJECT": "Objeto backup", +"IP_ADDRESS": "Endere\u00e7o IP", +"LOAD_BALANCER_POLICY": "Pol\u00edtica de balanceamento de carga", +"NETWORK": "Rede", +"NETWORK_BYTES_RECEIVED": "Bytes recebidos na rede", +"NETWORK_BYTES_SENT": "Bytes enviados na rede", +"NETWORK_OFFERING": "Oferta de rede", +"RUNNING_VM": "VM rodando", +"PORT_FORWARDING_RULE": "Regra de redirecionamento de porta", +"SECURITY_GROUP": "Grupo de seguran\u00e7a", +"SNAPSHOT": "Snapshot", +"TEMPLATE": "Template", +"VM_DISK_BYTES_READ": "Leitura do disco da VM (bytes)", +"VM_DISK_BYTES_WRITE": "Escrita no disco da VM (bytes)", +"VM_DISK_IO_READ": "Leitura do disco da VM (IO)", +"VM_DISK_IO_WRITE": "Escrita no disco da VM (IO)", +"VM_SNAPSHOT": "Snapshot de VM", +"VM_SNAPSHOT_ON_PRIMARY": "Snapshot de VM no armazenamento prim\u00e1rio", +"VOLUME": "Volume", +"VOLUME_SECONDARY": "Volume no armazenamento secund\u00e1rio", +"VPN_USERS": "Usu\u00e1rios de VPN", +"Compute*Month": "Recurso * M\u00eas", +"GB*Month": "GB * M\u00eas", +"IP*Month": "IP * M\u00eas", +"Policy*Month": "Pol\u00edticas de Rede * M\u00eas" } diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index c9ab6b89ec8..017d304e39b 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -38,8 +38,8 @@ :dataSource="fetchDetails()"> - @@ -174,7 +171,10 @@ {{ text }} {{ text }} -