diff --git a/api/src/main/java/com/cloud/offering/DiskOffering.java b/api/src/main/java/com/cloud/offering/DiskOffering.java index e1c41f77cbf5..9407acfdec10 100644 --- a/api/src/main/java/com/cloud/offering/DiskOffering.java +++ b/api/src/main/java/com/cloud/offering/DiskOffering.java @@ -37,7 +37,7 @@ enum State { State getState(); enum DiskCacheMode { - NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"); + NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"), HYPERVISOR_DEFAULT("hypervisor_default"); private final String _diskCacheMode; 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 f5861c257a1d..475b845af9b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -27,6 +27,7 @@ public class ApiConstants { public static final String ACTIVATION_RULE = "activationrule"; public static final String ACTIVITY = "activity"; public static final String ADAPTER_TYPE = "adaptertype"; + public static final String ADDITONAL_CONFIG_ENABLED = "additionalconfigenabled"; public static final String ADDRESS = "address"; public static final String ALGORITHM = "algorithm"; public static final String ALIAS = "alias"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index c46e4cd6b445..557c76d63c36 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -151,7 +151,7 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, required = false, - description = "the cache mode to use for this disk offering. none, writeback or writethrough", + description = "the cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default. If the hypervisor default cache mode is used on other hypervisors than KVM, it will fall back to none cache mode", since = "4.14") private String cacheMode; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 3d20ed50a5db..ec109a2a4f37 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -190,7 +190,7 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, required = false, - description = "the cache mode to use for this disk offering. none, writeback or writethrough", + description = "the cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default. If the hypervisor default cache mode is used on other hypervisors than KVM, it will fall back to none cache mode", since = "4.14") private String cacheMode; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index e73bb97a21ba..ed1bd7b063b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -75,6 +75,7 @@ public void execute() { response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED)); response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH)); response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); + response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED)); response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index eb0daf75148a..d2c71b5f3525 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -149,6 +149,10 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0") private Boolean dynamicScalingEnabled; + @SerializedName(ApiConstants.ADDITONAL_CONFIG_ENABLED) + @Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2") + private Boolean additionalConfigEnabled; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -272,4 +276,8 @@ public void setExtensionsPath(String extensionsPath) { public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } + + public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) { + this.additionalConfigEnabled = additionalConfigEnabled; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index 4565a878b348..69f80b54010e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -197,7 +197,7 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { private Boolean isCustomized; @SerializedName("cacheMode") - @Param(description = "the cache mode to use for this disk offering. none, writeback or writethrough", since = "4.14") + @Param(description = "the cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default", since = "4.14") private String cacheMode; @SerializedName("vspherestoragepolicy") 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 abc674ff0f9a..7867c685bbab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -77,19 +77,24 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { @Param(description = "the name of the cluster for the storage pool") private String clusterName; + @SerializedName(ApiConstants.CAPACITY_BYTES) + @Param(description = "bytes CloudStack can provision from this storage pool", since = "4.22.0") + private Long capacityBytes; + + @Deprecated(since = "4.22.0") @SerializedName("disksizetotal") @Param(description = "the total disk size of the storage pool") private Long diskSizeTotal; @SerializedName("disksizeallocated") - @Param(description = "the host's currently allocated disk size") + @Param(description = "the pool's currently allocated disk size") private Long diskSizeAllocated; @SerializedName("disksizeused") - @Param(description = "the host's currently used disk size") + @Param(description = "the pool's currently used disk size") private Long diskSizeUsed; - @SerializedName("capacityiops") + @SerializedName(ApiConstants.CAPACITY_IOPS) @Param(description = "IOPS CloudStack can provision from this storage pool") private Long capacityIops; @@ -288,6 +293,14 @@ public void setClusterName(String clusterName) { this.clusterName = clusterName; } + public Long getCapacityBytes() { + return capacityBytes; + } + + public void setCapacityBytes(Long capacityBytes) { + this.capacityBytes = capacityBytes; + } + public Long getDiskSizeTotal() { return diskSizeTotal; } diff --git a/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java index b4bede248907..7e718413118e 100644 --- a/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java +++ b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java @@ -22,6 +22,8 @@ import com.cloud.utils.component.Manager; +import java.io.IOException; + public interface UserDataManager extends Manager, Configurable { String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length"; ConfigKey VM_USERDATA_MAX_LENGTH = new ConfigKey<>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768", @@ -29,4 +31,14 @@ public interface UserDataManager extends Manager, Configurable { String concatenateUserData(String userdata1, String userdata2, String userdataProvider); String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod); + + /** + * This method validates the user data uuid for system VMs and returns the user data + * after compression and base64 encoding for the system VM to consume. + * + * @param userDataUuid + * @return a String containing the user data after compression and base64 encoding + * @throws IOException + */ + String validateAndGetUserDataForSystemVM(String userDataUuid) throws IOException; } diff --git a/client/pom.xml b/client/pom.xml index 605487404a87..997d530725da 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -121,6 +121,11 @@ cloud-plugin-storage-volume-adaptive ${project.version} + + org.apache.cloudstack + cloud-plugin-storage-volume-ontap + ${project.version} + org.apache.cloudstack cloud-plugin-storage-volume-solidfire diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java index 92f9ee497a44..827403ac5ef8 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java +++ b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java @@ -116,8 +116,8 @@ public VolumeObjectTO(VolumeInfo volume) { iopsWriteRate = volume.getIopsWriteRate(); iopsWriteRateMax = volume.getIopsWriteRateMax(); iopsWriteRateMaxLength = volume.getIopsWriteRateMaxLength(); - cacheMode = volume.getCacheMode(); hypervisorType = volume.getHypervisorType(); + setCacheMode(volume.getCacheMode()); setDeviceId(volume.getDeviceId()); this.migrationOptions = volume.getMigrationOptions(); this.directDownload = volume.isDirectDownload(); @@ -343,6 +343,10 @@ public void setDeviceId(Long deviceId) { } public void setCacheMode(DiskCacheMode cacheMode) { + if (DiskCacheMode.HYPERVISOR_DEFAULT.equals(cacheMode) && !Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + this.cacheMode = DiskCacheMode.NONE; + return; + } this.cacheMode = cacheMode; } 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 cffba3d9a132..702404546894 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -106,6 +106,9 @@ public interface VirtualMachineManager extends Manager { ConfigKey VmSyncPowerStateTransitioning = new ConfigKey<>("Advanced", Boolean.class, "vm.sync.power.state.transitioning", "true", "Whether to sync power states of the transitioning and stalled VMs while processing VM power reports.", false); + ConfigKey SystemVmEnableUserData = new ConfigKey<>(Boolean.class, "systemvm.userdata.enabled", "Advanced", "false", + "Enable user data for system VMs. When enabled, the CPVM, SSVM, and Router system VMs will use the values from the global settings console.proxy.vm.userdata, secstorage.vm.userdata, and virtual.router.userdata, respectively, to provide cloud-init user data to the VM.", + true, ConfigKey.Scope.Zone, null); interface Topics { String VM_POWER_STATE = "vm.powerstate"; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java index 54f3c63f8d73..1acaccf09df4 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java @@ -24,8 +24,8 @@ import com.cloud.storage.StoragePool; public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { - public static final String CAPACITY_BYTES = "capacityBytes"; - public static final String CAPACITY_IOPS = "capacityIops"; + String CAPACITY_BYTES = "capacityBytes"; + String CAPACITY_IOPS = "capacityIops"; void updateStoragePool(StoragePool storagePool, Map details); void enableStoragePool(DataStore store); diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index a87d32354896..dbaffa8b3b5c 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -302,6 +302,8 @@ static Boolean getFullCloneConfiguration(Long storeId) { Answer sendToPool(StoragePool pool, long[] hostIdsToTryFirst, Command cmd) throws StorageUnavailableException; + void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer); + CapacityVO getSecondaryStorageUsedStats(Long hostId, Long zoneId); CapacityVO getStoragePoolUsedStats(Long poolId, Long clusterId, Long podId, Long zoneId); 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 7c5d43fea97c..a8a92d6b3dbf 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -5244,7 +5244,7 @@ public ConfigKey[] getConfigKeys() { VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName, - VmSyncPowerStateTransitioning + VmSyncPowerStateTransitioning, SystemVmEnableUserData }; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 2f19d36c37a2..6ac49967540a 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -116,17 +116,17 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected Attribute _updateTimeAttr; - private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) " + + private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) " + "FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id WHERE "; private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " AND host.type = 'Routing' AND host.removed is null GROUP BY host.cluster_id " + "ORDER BY 2 ASC "; - private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" + + private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" + "host_pod_ref` pod LEFT JOIN `cloud`.`vm_instance` vm ON pod.id = vm.pod_id WHERE pod.data_center_id = ? AND pod.removed is null " + " GROUP BY pod.id ORDER BY 2 ASC "; private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT = - "SELECT host.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " + + "SELECT host.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " + "WHERE host.data_center_id = ? AND host.type = 'Routing' AND host.removed is null "; private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC "; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java index 8b230d03154e..6da02d7716b1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java @@ -320,6 +320,9 @@ public StoragePoolVO persist(StoragePoolVO pool, Map details, Li pool = super.persist(pool); if (details != null) { for (Map.Entry detail : details.entrySet()) { + if (detail.getKey().toLowerCase().contains("password") || detail.getKey().toLowerCase().contains("token")) { + displayDetails = false; + } StoragePoolDetailVO vo = new StoragePoolDetailVO(pool.getId(), detail.getKey(), detail.getValue(), displayDetails); _detailsDao.persist(vo); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql index 62ae10b7cc9c..405f2af9564e 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql @@ -26,6 +26,9 @@ CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('router_health_check', 'check_result', ' -- Increase length of scripts_version column to 128 due to md5sum to sha512sum change CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.domain_router', 'scripts_version', 'scripts_version', 'VARCHAR(128)'); +-- Increase the cache_mode column size from cloud.disk_offering table +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.disk_offering', 'cache_mode', 'cache_mode', 'varchar(18) DEFAULT "none" COMMENT "The disk cache mode to use for disks created with this offering"'); + -- Add uuid column to ldap_configuration table CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.ldap_configuration', 'uuid', 'VARCHAR(40) NOT NULL'); @@ -34,3 +37,7 @@ UPDATE `cloud`.`ldap_configuration` SET uuid = UUID() WHERE uuid IS NULL OR uuid -- Add the column cross_zone_instance_creation to cloud.backup_repository. if enabled it means that new Instance can be created on all Zones from Backups on this Repository. CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_repository', 'cross_zone_instance_creation', 'TINYINT(1) DEFAULT NULL COMMENT ''Backup Repository can be used for disaster recovery on another zone'''); + +-- Updated display to false for password/token detail of the storage pool details +UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%password%'; +UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%token%'; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java index 5e9891ef9895..d17dae132a04 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java @@ -85,8 +85,7 @@ public class PrimaryDataStoreHelper { DataStoreProviderManager dataStoreProviderMgr; public DataStore createPrimaryDataStore(PrimaryDataStoreParameters params) { - if(params == null) - { + if (params == null) { throw new InvalidParameterValueException("createPrimaryDataStore: Input params is null, please check"); } StoragePoolVO dataStoreVO = dataStoreDao.findPoolByUUID(params.getUuid()); @@ -190,7 +189,9 @@ public DataStore attachHost(DataStore store, HostScope scope, StoragePoolInfo ex pool.setScope(scope.getScopeType()); pool.setUsedBytes(existingInfo.getCapacityBytes() - existingInfo.getAvailableBytes()); pool.setCapacityBytes(existingInfo.getCapacityBytes()); - pool.setStatus(StoragePoolStatus.Up); + if (pool.getStatus() != StoragePoolStatus.Disabled) { + pool.setStatus(StoragePoolStatus.Up); + } this.dataStoreDao.update(pool.getId(), pool); this.storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, pool.getUsedBytes()); return dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); diff --git a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java index 664d308e28d1..7c5692564c99 100644 --- a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java +++ b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java @@ -16,12 +16,18 @@ // under the License. package org.apache.cloudstack.userdata; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.cloud.domain.Domain; +import com.cloud.user.User; +import com.cloud.user.UserDataVO; +import com.cloud.user.dao.UserDataDao; +import com.cloud.utils.compression.CompressionUtil; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.commons.codec.binary.Base64; @@ -31,7 +37,12 @@ import com.cloud.utils.component.ManagerBase; import com.cloud.utils.exception.CloudRuntimeException; +import javax.inject.Inject; + public class UserDataManagerImpl extends ManagerBase implements UserDataManager { + @Inject + UserDataDao userDataDao; + private static final int MAX_USER_DATA_LENGTH_BYTES = 2048; private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES; // 4KB private static final int NUM_OF_2K_BLOCKS = 512; @@ -118,6 +129,25 @@ public String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod) { return Base64.encodeBase64String(decodedUserData); } + @Override + public String validateAndGetUserDataForSystemVM(String userDataUuid) throws IOException { + if (StringUtils.isBlank(userDataUuid)) { + return null; + } + UserDataVO userDataVo = userDataDao.findByUuid(userDataUuid); + if (userDataVo == null) { + return null; + } + if (userDataVo.getDomainId() == Domain.ROOT_DOMAIN && userDataVo.getAccountId() == User.UID_ADMIN) { + // Decode base64 user data, compress it, then re-encode to reduce command line length + String plainTextUserData = new String(java.util.Base64.getDecoder().decode(userDataVo.getUserData())); + CompressionUtil compressionUtil = new CompressionUtil(); + byte[] compressedUserData = compressionUtil.compressString(plainTextUserData); + return java.util.Base64.getEncoder().encodeToString(compressedUserData); + } + throw new CloudRuntimeException("User data can only be used by system VMs if it belongs to the ROOT domain and ADMIN account."); + } + private byte[] validateAndDecodeByHTTPMethod(String userData, int maxHTTPLength, BaseCmd.HTTPMethod httpMethod) { byte[] decodedUserData = Base64.decodeBase64(userData.getBytes()); if (decodedUserData == null || decodedUserData.length < 1) { diff --git a/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java b/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java index 67e7b38e37d0..70a56bc0c61f 100644 --- a/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java +++ b/engine/userdata/src/test/java/org/apache/cloudstack/userdata/UserDataManagerImplTest.java @@ -17,19 +17,37 @@ package org.apache.cloudstack.userdata; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.apache.cloudstack.api.BaseCmd; 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.Domain; +import com.cloud.user.User; +import com.cloud.user.UserDataVO; +import com.cloud.user.dao.UserDataDao; +import com.cloud.utils.exception.CloudRuntimeException; + @RunWith(MockitoJUnitRunner.class) public class UserDataManagerImplTest { + @Mock + private UserDataDao userDataDao; + @Spy @InjectMocks private UserDataManagerImpl userDataManager; @@ -56,4 +74,76 @@ public void testValidateUrlEncodedBase64() { assertEquals("validate return the value with padding", encodedUserdata, userDataManager.validateUserData(urlEncodedUserdata, BaseCmd.HTTPMethod.GET)); } + @Test + public void testValidateAndGetUserDataForSystemVMWithBlankUuid() throws IOException { + // Test with blank UUID should return null + assertNull("null UUID should return null", userDataManager.validateAndGetUserDataForSystemVM(null)); + assertNull("blank UUID should return null", userDataManager.validateAndGetUserDataForSystemVM("")); + assertNull("blank UUID should return null", userDataManager.validateAndGetUserDataForSystemVM(" ")); + } + + @Test + public void testValidateAndGetUserDataForSystemVMNotFound() throws IOException { + // Test when userDataVo is not found + String testUuid = "test-uuid-123"; + when(userDataDao.findByUuid(testUuid)).thenReturn(null); + + assertNull("userdata not found should return null", userDataManager.validateAndGetUserDataForSystemVM(testUuid)); + } + + @Test(expected = CloudRuntimeException.class) + public void testValidateAndGetUserDataForSystemVMInvalidDomain() throws IOException { + // Test with userDataVo that doesn't belong to ROOT domain + String testUuid = "test-uuid-123"; + UserDataVO userDataVo = Mockito.mock(UserDataVO.class); + when(userDataVo.getDomainId()).thenReturn(2L); // Not ROOT domain + + when(userDataDao.findByUuid(testUuid)).thenReturn(userDataVo); + userDataManager.validateAndGetUserDataForSystemVM(testUuid); + } + + @Test(expected = CloudRuntimeException.class) + public void testValidateAndGetUserDataForSystemVMInvalidAccount() throws IOException { + // Test with userDataVo that doesn't belong to ADMIN account + String testUuid = "test-uuid-123"; + UserDataVO userDataVo = Mockito.mock(UserDataVO.class); + when(userDataVo.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + when(userDataVo.getAccountId()).thenReturn(3L); + userDataVo.setUserData("dGVzdCBkYXRh"); // "test data" in base64 + + when(userDataDao.findByUuid(testUuid)).thenReturn(userDataVo); + userDataManager.validateAndGetUserDataForSystemVM(testUuid); + } + + @Test + public void testValidateAndGetUserDataForSystemVMValidSystemVMUserData() throws IOException { + // Test with valid system VM userdata (ROOT domain + ADMIN account) + String testUuid = "test-uuid-123"; + String originalText = "#!/bin/bash\necho 'Hello World'"; + String base64EncodedUserData = Base64.getEncoder().encodeToString(originalText.getBytes()); + + UserDataVO userDataVo = Mockito.mock(UserDataVO.class); + when(userDataVo.getDomainId()).thenReturn(Domain.ROOT_DOMAIN); + when(userDataVo.getAccountId()).thenReturn(User.UID_ADMIN); + when(userDataVo.getUserData()).thenReturn(base64EncodedUserData); + + when(userDataDao.findByUuid(testUuid)).thenReturn(userDataVo); + + String result = userDataManager.validateAndGetUserDataForSystemVM(testUuid); + + // Verify result is not null and is base64 encoded + assertNotNull("result should not be null", result); + assertFalse("result should be base64 encoded", result.isEmpty()); + + // Verify the result is valid base64 + try { + Base64.getDecoder().decode(result); + } catch (IllegalArgumentException e) { + throw new AssertionError("Result should be valid base64", e); + } + + // The result should be different from input since it's compressed + assertNotEquals("compressed result should be different from original", result, base64EncodedUserData); + } + } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java index 88eca1d28dee..0ea910fcb6d2 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java @@ -43,6 +43,64 @@ public class ConfigKey { public static final String CATEGORY_NETWORK = "Network"; public static final String CATEGORY_SYSTEM = "System"; + // Configuration Groups to be used to define group for a config key + // Group name, description, precedence + public static final Ternary GROUP_MISCELLANEOUS = new Ternary<>("Miscellaneous", "Miscellaneous configuration", 999L); + public static final Ternary GROUP_ACCESS = new Ternary<>("Access", "Identity and Access management configuration", 1L); + public static final Ternary GROUP_COMPUTE = new Ternary<>("Compute", "Compute configuration", 2L); + public static final Ternary GROUP_STORAGE = new Ternary<>("Storage", "Storage configuration", 3L); + public static final Ternary GROUP_NETWORK = new Ternary<>("Network", "Network configuration", 4L); + public static final Ternary GROUP_HYPERVISOR = new Ternary<>("Hypervisor", "Hypervisor specific configuration", 5L); + public static final Ternary GROUP_MANAGEMENT_SERVER = new Ternary<>("Management Server", "Management Server configuration", 6L); + public static final Ternary GROUP_SYSTEM_VMS = new Ternary<>("System VMs", "System VMs related configuration", 7L); + public static final Ternary GROUP_INFRASTRUCTURE = new Ternary<>("Infrastructure", "Infrastructure configuration", 8L); + public static final Ternary GROUP_USAGE_SERVER = new Ternary<>("Usage Server", "Usage Server related configuration", 9L); + + // Configuration Subgroups to be used to define subgroup for a config key + // Subgroup name, description, precedence + public static final Pair SUBGROUP_OTHERS = new Pair<>("Others", 999L); + public static final Pair SUBGROUP_ACCOUNT = new Pair<>("Account", 1L); + public static final Pair SUBGROUP_DOMAIN = new Pair<>("Domain", 2L); + public static final Pair SUBGROUP_PROJECT = new Pair<>("Project", 3L); + public static final Pair SUBGROUP_LDAP = new Pair<>("LDAP", 4L); + public static final Pair SUBGROUP_SAML = new Pair<>("SAML", 5L); + public static final Pair SUBGROUP_VIRTUAL_MACHINE = new Pair<>("Virtual Machine", 1L); + public static final Pair SUBGROUP_KUBERNETES = new Pair<>("Kubernetes", 2L); + public static final Pair SUBGROUP_HIGH_AVAILABILITY = new Pair<>("High Availability", 3L); + public static final Pair SUBGROUP_IMAGES = new Pair<>("Images", 1L); + public static final Pair SUBGROUP_VOLUME = new Pair<>("Volume", 2L); + public static final Pair SUBGROUP_SNAPSHOT = new Pair<>("Snapshot", 3L); + public static final Pair SUBGROUP_VM_SNAPSHOT = new Pair<>("VM Snapshot", 4L); + public static final Pair SUBGROUP_NETWORK = new Pair<>("Network", 1L); + public static final Pair SUBGROUP_DHCP = new Pair<>("DHCP", 2L); + public static final Pair SUBGROUP_VPC = new Pair<>("VPC", 3L); + public static final Pair SUBGROUP_LOADBALANCER = new Pair<>("LoadBalancer", 4L); + public static final Pair SUBGROUP_API = new Pair<>("API", 1L); + public static final Pair SUBGROUP_ALERTS = new Pair<>("Alerts", 2L); + public static final Pair SUBGROUP_EVENTS = new Pair<>("Events", 3L); + public static final Pair SUBGROUP_SECURITY = new Pair<>("Security", 4L); + public static final Pair SUBGROUP_USAGE = new Pair<>("Usage", 1L); + public static final Pair SUBGROUP_LIMITS = new Pair<>("Limits", 6L); + public static final Pair SUBGROUP_JOBS = new Pair<>("Jobs", 7L); + public static final Pair SUBGROUP_AGENT = new Pair<>("Agent", 8L); + public static final Pair SUBGROUP_HYPERVISOR = new Pair<>("Hypervisor", 1L); + public static final Pair SUBGROUP_KVM = new Pair<>("KVM", 2L); + public static final Pair SUBGROUP_VMWARE = new Pair<>("VMware", 3L); + public static final Pair SUBGROUP_XENSERVER = new Pair<>("XenServer", 4L); + public static final Pair SUBGROUP_OVM = new Pair<>("OVM", 5L); + public static final Pair SUBGROUP_BAREMETAL = new Pair<>("Baremetal", 6L); + public static final Pair SUBGROUP_CONSOLE_PROXY_VM = new Pair<>("ConsoleProxyVM", 1L); + public static final Pair SUBGROUP_SEC_STORAGE_VM = new Pair<>("SecStorageVM", 2L); + public static final Pair SUBGROUP_VIRTUAL_ROUTER = new Pair<>("VirtualRouter", 3L); + public static final Pair SUBGROUP_DIAGNOSTICS = new Pair<>("Diagnostics", 4L); + public static final Pair SUBGROUP_PRIMARY_STORAGE = new Pair<>("Primary Storage", 1L); + public static final Pair SUBGROUP_SECONDARY_STORAGE = new Pair<>("Secondary Storage", 2L); + public static final Pair SUBGROUP_BACKUP_AND_RECOVERY = new Pair<>("Backup & Recovery", 1L); + public static final Pair SUBGROUP_CERTIFICATE_AUTHORITY = new Pair<>("Certificate Authority", 2L); + public static final Pair SUBGROUP_QUOTA = new Pair<>("Quota", 3L); + public static final Pair SUBGROUP_CLOUDIAN = new Pair<>("Cloudian", 4L); + public static final Pair SUBGROUP_DRS = new Pair<>("DRS", 4L); + public enum Scope { Global(null, 1), Zone(Global, 1 << 1), diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java index cdae48fdb3a8..bef68c2d277b 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java @@ -56,7 +56,8 @@ public class DeleteExtensionCmd extends BaseCmd { private Long id; @Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, - entityType = ExtensionResponse.class, description = "Whether cleanup entry-point files for the extension") + entityType = ExtensionResponse.class, + description = "Whether to cleanup files for the extension. If true, the extension files will be deleted from all the management servers.") private Boolean cleanup; ///////////////////////////////////////////////////// diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java index 9af5cb69739f..f66c195399d1 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java @@ -152,7 +152,7 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana ConfigKey PathStateCheckInterval = new ConfigKey<>("Advanced", Integer.class, "extension.path.state.check.interval", "300", - "Interval (in seconds) for checking entry-point state of extensions", + "Interval (in seconds) for checking state of extensions path", false, ConfigKey.Scope.Global); @Inject @@ -264,11 +264,11 @@ protected Pair getResultFromAnswersString(String answersStr, Ex protected boolean prepareExtensionPathOnMSPeer(Extension extension, ManagementServerHostVO msHost) { final String msPeer = Long.toString(msHost.getMsid()); - logger.debug("Sending prepare extension entry-point for {} command to MS: {}", extension, msPeer); + logger.debug("Sending prepare extension path for {} command to MS: {}", extension, msPeer); final Command[] commands = new Command[1]; commands[0] = new PrepareExtensionPathCommand(ManagementServerNode.getManagementServerId(), extension); String answersStr = clusterManager.execute(msPeer, 0L, GsonHelper.getGson().toJson(commands), true); - return getResultFromAnswersString(answersStr, extension, msHost, "prepare entry-point").first(); + return getResultFromAnswersString(answersStr, extension, msHost, "prepare path").first(); } protected Pair prepareExtensionPathOnCurrentServer(String name, boolean userDefined, @@ -276,7 +276,7 @@ protected Pair prepareExtensionPathOnCurrentServer(String name, try { externalProvisioner.prepareExtensionPath(name, userDefined, relativePath); } catch (CloudRuntimeException e) { - logger.error("Failed to prepare entry-point for Extension [name: {}, userDefined: {}, relativePath: {}] on this server", + logger.error("Failed to prepare path for Extension [name: {}, userDefined: {}, relativePath: {}] on this server", name, userDefined, relativePath, e); return new Pair<>(false, e.getMessage()); } @@ -285,11 +285,11 @@ protected Pair prepareExtensionPathOnCurrentServer(String name, protected boolean cleanupExtensionFilesOnMSPeer(Extension extension, ManagementServerHostVO msHost) { final String msPeer = Long.toString(msHost.getMsid()); - logger.debug("Sending cleanup extension entry-point for {} command to MS: {}", extension, msPeer); + logger.debug("Sending cleanup extension files for {} command to MS: {}", extension, msPeer); final Command[] commands = new Command[1]; commands[0] = new CleanupExtensionFilesCommand(ManagementServerNode.getManagementServerId(), extension); String answersStr = clusterManager.execute(msPeer, 0L, GsonHelper.getGson().toJson(commands), true); - return getResultFromAnswersString(answersStr, extension, msHost, "cleanup entry-point").first(); + return getResultFromAnswersString(answersStr, extension, msHost, "cleanup files").first(); } protected Pair cleanupExtensionFilesOnCurrentServer(String name, String relativePath) { @@ -297,7 +297,7 @@ protected Pair cleanupExtensionFilesOnCurrentServer(String name externalProvisioner.cleanupExtensionPath(name, relativePath); externalProvisioner.cleanupExtensionData(name, 0, true); } catch (CloudRuntimeException e) { - logger.error("Failed to cleanup entry-point files for Extension [name: {}, relativePath: {}] on this server", + logger.error("Failed to cleanup files for Extension [name: {}, relativePath: {}] on this server", name, relativePath, e); return new Pair<>(false, e.getMessage()); } @@ -305,18 +305,18 @@ protected Pair cleanupExtensionFilesOnCurrentServer(String name } protected void cleanupExtensionFilesAcrossServers(Extension extension) { - boolean cleanup = true; + boolean cleanedUp = true; List msHosts = managementServerHostDao.listBy(ManagementServerHost.State.Up); for (ManagementServerHostVO msHost : msHosts) { if (msHost.getMsid() == ManagementServerNode.getManagementServerId()) { - cleanup = cleanup && cleanupExtensionFilesOnCurrentServer(extension.getName(), + cleanedUp = cleanedUp && cleanupExtensionFilesOnCurrentServer(extension.getName(), extension.getRelativePath()).first(); continue; } - cleanup = cleanup && cleanupExtensionFilesOnMSPeer(extension, msHost); + cleanedUp = cleanedUp && cleanupExtensionFilesOnMSPeer(extension, msHost); } - if (!cleanup) { - throw new CloudRuntimeException("Extension is deleted but its entry-point files are not cleaned up across servers"); + if (!cleanedUp) { + throw new CloudRuntimeException("Extension is deleted but its files are not cleaned up across servers"); } } @@ -327,7 +327,7 @@ protected Pair getChecksumForExtensionPathOnMSPeer(Extension ex cmds[0] = new GetExtensionPathChecksumCommand(ManagementServerNode.getManagementServerId(), extension); String answersStr = clusterManager.execute(msPeer, 0L, GsonHelper.getGson().toJson(cmds), true); - return getResultFromAnswersString(answersStr, extension, msHost, "prepare entry-point"); + return getResultFromAnswersString(answersStr, extension, msHost, "get path checksum"); } protected List getParametersListFromMap(String actionName, Map parametersMap) { @@ -549,7 +549,7 @@ protected void checkExtensionPathState(Extension extension, List msPeerChecksumResult = getChecksumForExtensionPathOnMSPeer(extension, msHost); if (!msPeerChecksumResult.first() || !checksum.equals(msPeerChecksumResult.second())) { - logger.error("Entry-point checksum for {} is different [msid: {}, checksum: {}] and [msid: {}, checksum: {}]", + logger.error("Path checksum for {} is different [msid: {}, checksum: {}] and [msid: {}, checksum: {}]", extension, ManagementServerNode.getManagementServerId(), checksum, msHost.getMsid(), (msPeerChecksumResult.first() ? msPeerChecksumResult.second() : "unknown")); updateExtensionPathReady(extension, false); @@ -630,7 +630,7 @@ public Extension createExtension(CreateExtensionCmd cmd) { !prepareExtensionPathAcrossServers(extensionVO)) { disableExtension(extensionVO.getId()); throw new CloudRuntimeException(String.format( - "Failed to enable extension: %s as it entry-point is not ready", + "Failed to enable extension: %s as its path is not ready", extensionVO.getName())); } return extensionVO; @@ -736,7 +736,7 @@ public Extension updateExtension(UpdateExtensionCmd cmd) { !prepareExtensionPathAcrossServers(result)) { disableExtension(result.getId()); throw new CloudRuntimeException(String.format( - "Failed to enable extension: %s as it entry-point is not ready", + "Failed to enable extension: %s as it path is not ready", extensionVO.getName())); } updateAllExtensionHosts(extensionVO, null, false); diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java index 046d0e2aa427..92205b13c6ff 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java @@ -631,18 +631,18 @@ public void cleanupExtensionPath(String extensionName, String extensionRelativeP } if (!Files.isDirectory(filePath) && !Files.isRegularFile(filePath)) { throw new CloudRuntimeException( - String.format("Failed to cleanup extension entry-point: %s for extension: %s as it either " + + String.format("Failed to cleanup path: %s for extension: %s as it either " + "does not exist or is not a regular file/directory", extensionName, extensionRelativePath)); } if (!FileUtil.deleteRecursively(filePath)) { throw new CloudRuntimeException( - String.format("Failed to delete extension entry-point: %s for extension: %s", + String.format("Failed to delete path: %s for extension: %s", extensionName, filePath)); } } catch (IOException e) { throw new CloudRuntimeException( - String.format("Failed to cleanup extension entry-point: %s for extension: %s due to: %s", + String.format("Failed to cleanup path: %s for extension: %s due to: %s", extensionName, normalizedPath, e.getMessage()), e); } } 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 46a5024bf154..bf002b37f355 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 @@ -707,7 +707,7 @@ public String toString() { } public enum DiskCacheMode { - NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"); + NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"), HYPERVISOR_DEFAULT("default"); String _diskCacheMode; DiskCacheMode(String cacheMode) { 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 289dae42e910..be5ffe1b96dc 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 @@ -5838,11 +5838,20 @@ protected Answer execute(GetVmIpAddressCommand cmd) { if (toolsStatus == VirtualMachineToolsStatus.TOOLS_NOT_INSTALLED) { details += "Vmware tools not installed."; } else { - ip = guestInfo.getIpAddress(); - if (ip != null) { - result = true; + var normalizedMac = cmd.getMacAddress().replaceAll("-", ":"); + for(var guestInfoNic : guestInfo.getNet()) { + var normalizedNicMac = guestInfoNic.getMacAddress().replaceAll("-", ":"); + if (!result && normalizedNicMac.equalsIgnoreCase(normalizedMac)) { + result = true; + details = null; + for (var ipAddr : guestInfoNic.getIpAddress()) { + if (NetUtils.isValidIp4(ipAddr) && (cmd.getVmNetworkCidr() == null || NetUtils.isIpWithInCidrRange(ipAddr, cmd.getVmNetworkCidr()))) { + details = ipAddr; + } + } + break; + } } - details = ip; } } else { details += "VM " + vmName + " no longer exists on vSphere host: " + hyperHost.getHyperHostName(); 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 1466fdf87826..c46b0c00fa12 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 @@ -430,6 +430,9 @@ private boolean isKubernetesServiceNetworkOfferingConfigured(DataCenter zone, Lo logger.warn("Unable to find the network with ID: {} passed for the Kubernetes cluster", networkId); return false; } + if (isDirectAccess(network)) { + return true; + } networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId()); if (networkOffering == null) { logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid()); @@ -1870,7 +1873,7 @@ protected Account createProjectKubernetesAccount(final Project project, final St try { Role role = getProjectKubernetesAccountRole(); UserAccount userAccount = accountService.createUserAccount(accountName, - UuidUtils.first(UUID.randomUUID().toString()), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME, + UUID.randomUUID().toString(), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME, PROJECT_KUBERNETES_ACCOUNT_LAST_NAME, null, null, accountName, Account.Type.NORMAL, role.getId(), project.getDomainId(), null, null, null, null, User.Source.NATIVE); projectManager.assignAccountToProject(project, userAccount.getAccountId(), ProjectAccount.Role.Regular, diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java index f895ba2944cc..4dadec21325b 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java +++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java @@ -35,6 +35,8 @@ import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.userdata.UserDataManager; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; @@ -101,6 +103,9 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import static com.cloud.network.router.VirtualNetworkApplianceManager.VirtualRouterUserData; +import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData; + @Component public class ElasticLoadBalancerManagerImpl extends ManagerBase implements ElasticLoadBalancerManager, VirtualMachineGuru { @@ -136,6 +141,8 @@ public class ElasticLoadBalancerManagerImpl extends ManagerBase implements Elast private ElasticLbVmMapDao _elbVmMapDao; @Inject private NicDao _nicDao; + @Inject + private UserDataManager userDataManager; String _instance; @@ -477,6 +484,19 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl } String msPublicKey = _configDao.getValue("ssh.publickey"); buf.append(" authorized_key=").append(VirtualMachineGuru.getEncodedMsPublicKey(msPublicKey)); + + if (SystemVmEnableUserData.valueIn(dc.getId())) { + String userDataUuid = VirtualRouterUserData.valueIn(dc.getId()); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the elastic lb vm, ignored", e); + } + } + if (logger.isDebugEnabled()) { logger.debug("Boot Args for " + profile + ": " + buf.toString()); } diff --git a/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java b/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java index d979a4b30335..2c78120559ee 100644 --- a/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java +++ b/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java @@ -21,6 +21,8 @@ import static com.cloud.hypervisor.Hypervisor.HypervisorType.LXC; import static com.cloud.hypervisor.Hypervisor.HypervisorType.VMware; import static com.cloud.hypervisor.Hypervisor.HypervisorType.XenServer; +import static com.cloud.network.router.VirtualNetworkApplianceManager.VirtualRouterUserData; +import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +41,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO; import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao; +import org.apache.cloudstack.userdata.UserDataManager; import org.apache.commons.collections.CollectionUtils; import com.cloud.agent.AgentManager; @@ -126,6 +129,7 @@ import com.cloud.vm.VirtualMachineProfile.Param; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import org.apache.commons.lang3.StringUtils; public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements InternalLoadBalancerVMManager, InternalLoadBalancerVMService, VirtualMachineGuru { static final private String InternalLbVmNamePrefix = "b"; @@ -175,6 +179,8 @@ public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements In ResourceManager _resourceMgr; @Inject UserDao _userDao; + @Inject + private UserDataManager userDataManager; @Override public boolean finalizeVirtualMachineProfile(final VirtualMachineProfile profile, final DeployDestination dest, final ReservationContext context) { @@ -243,6 +249,19 @@ public boolean finalizeVirtualMachineProfile(final VirtualMachineProfile profile final String type = "ilbvm"; buf.append(" type=" + type); + long dcId = profile.getVirtualMachine().getDataCenterId(); + if (SystemVmEnableUserData.valueIn(dcId)) { + String userDataUuid = VirtualRouterUserData.valueIn(dcId); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the internal lb vm, ignored", e); + } + } + if (logger.isDebugEnabled()) { logger.debug("Boot Args for " + profile + ": " + buf.toString()); } diff --git a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java index 6af10b51936c..23c914bc7b00 100644 --- a/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java +++ b/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java @@ -18,6 +18,7 @@ import java.io.IOException; +import org.apache.cloudstack.userdata.UserDataManager; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -166,6 +167,11 @@ public AccountDao accountDao() { return Mockito.mock(AccountDao.class); } + @Bean + public UserDataManager userDataManager() { + return Mockito.mock(UserDataManager.class); + } + @Override public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { mdr.getClassMetadata().getClassName(); diff --git a/plugins/pom.xml b/plugins/pom.xml index d665f90b623d..cc05cab121cd 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -129,6 +129,7 @@ storage/volume/default storage/volume/nexenta storage/volume/sample + storage/volume/ontap storage/volume/solidfire storage/volume/scaleio storage/volume/linstor diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java index 1db5efbb8ec6..293f5f3db2a6 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/adapter/ProviderAdapterDiskOffering.java @@ -34,7 +34,7 @@ public ProviderAdapterDiskOffering(DiskOffering hiddenDiskOffering) { this.type = ProvisioningType.getProvisioningType(hiddenDiskOffering.getProvisioningType().toString()); } if (hiddenDiskOffering.getCacheMode() != null) { - this.diskCacheMode = DiskCacheMode.getDiskCasehMode(hiddenDiskOffering.getCacheMode().toString()); + this.diskCacheMode = DiskCacheMode.getDiskCacheMode(hiddenDiskOffering.getCacheMode().toString()); } if (hiddenDiskOffering.getState() != null) { this.state = State.valueOf(hiddenDiskOffering.getState().toString()); @@ -166,7 +166,7 @@ enum State { } enum DiskCacheMode { - NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"); + NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"), HYPERVISOR_DEFAULT("hypervisor_default"); private final String _diskCacheMode; @@ -179,13 +179,15 @@ public String toString() { return _diskCacheMode; } - public static DiskCacheMode getDiskCasehMode(String cacheMode) { + public static DiskCacheMode getDiskCacheMode(String cacheMode) { if (cacheMode.equals(NONE._diskCacheMode)) { return NONE; } else if (cacheMode.equals(WRITEBACK._diskCacheMode)) { return WRITEBACK; } else if (cacheMode.equals(WRITETHROUGH._diskCacheMode)) { return WRITETHROUGH; + } else if (cacheMode.equals(HYPERVISOR_DEFAULT._diskCacheMode)) { + return HYPERVISOR_DEFAULT; } else { throw new NotImplementedException("Invalid cache mode specified: " + cacheMode); } diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java index b3cf825ea592..e4eb10f51fb2 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java @@ -18,6 +18,7 @@ */ package org.apache.cloudstack.storage.datastore.lifecycle; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -139,7 +140,6 @@ public DataStore initialize(Map dsInfos) { Long clusterId = (Long)dsInfos.get("clusterId"); Long podId = (Long)dsInfos.get("podId"); Long zoneId = (Long)dsInfos.get("zoneId"); - String url = (String)dsInfos.get("url"); String providerName = (String)dsInfos.get("providerName"); HypervisorType hypervisorType = (HypervisorType)dsInfos.get("hypervisorType"); if (clusterId != null && podId == null) { @@ -148,19 +148,43 @@ public DataStore initialize(Map dsInfos) { PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); - String tags = (String)dsInfos.get("tags"); - String storageAccessGroups = (String)dsInfos.get(ApiConstants.STORAGE_ACCESS_GROUPS); Map details = (Map)dsInfos.get("details"); + if (dsInfos.get("capacityBytes") != null) { + Long capacityBytes = (Long)dsInfos.get("capacityBytes"); + if (capacityBytes <= 0) { + throw new IllegalArgumentException("'capacityBytes' must be greater than 0."); + } + if (details == null) { + details = new HashMap<>(); + } + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(capacityBytes)); + parameters.setCapacityBytes(capacityBytes); + } + if (dsInfos.get("capacityIops") != null) { + Long capacityIops = (Long)dsInfos.get("capacityIops"); + if (capacityIops <= 0) { + throw new IllegalArgumentException("'capacityIops' must be greater than 0."); + } + if (details == null) { + details = new HashMap<>(); + } + details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, String.valueOf(capacityIops)); + parameters.setCapacityIops(capacityIops); + } + + parameters.setDetails(details); + + String tags = (String)dsInfos.get("tags"); parameters.setTags(tags); - parameters.setStorageAccessGroups(storageAccessGroups); parameters.setIsTagARule((Boolean)dsInfos.get("isTagARule")); - parameters.setDetails(details); + + String storageAccessGroups = (String)dsInfos.get(ApiConstants.STORAGE_ACCESS_GROUPS); + parameters.setStorageAccessGroups(storageAccessGroups); String scheme = dsInfos.get("scheme").toString(); String storageHost = dsInfos.get("host").toString(); String hostPath = dsInfos.get("hostPath").toString(); - String uri = String.format("%s://%s%s", scheme, storageHost, hostPath); Object localStorage = dsInfos.get("localStorage"); if (localStorage != null) { @@ -452,8 +476,8 @@ public boolean maintain(DataStore dataStore) { @Override public boolean cancelMaintain(DataStore store) { - storagePoolAutmation.cancelMaintain(store); dataStoreHelper.cancelMaintain(store); + storagePoolAutmation.cancelMaintain(store); return true; } @@ -504,7 +528,7 @@ private HypervisorType getHypervisorType(long hostId) { @Override public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo); - if(existingInfo.getCapacityBytes() == 0){ + if (existingInfo.getCapacityBytes() == 0) { try { storageMgr.connectHostToSharedPool(hostDao.findById(scope.getScopeId()), dataStore.getId()); } catch (StorageUnavailableException ex) { diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index c0991a9aa2b8..7da3516955df 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2025-10-03] + +### Changed + +- Revert qcow2 snapshot now use sparse/discard options to convert on thin devices. + ## [2025-08-05] ### Fixed diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index 40aca2fdd45c..7e1ea0542aba 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -42,6 +42,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.logging.log4j.Logger; @@ -431,4 +432,37 @@ public static ResourceDefinition findResourceDefinition(DevelopersApi api, Strin public static boolean isRscDiskless(ResourceWithVolumes rsc) { return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS); } + + /** + * Checks if all diskful resource are on a zeroed block device. + * @param pool Linstor pool to use + * @param resName Linstor resource name + * @return true if all resources are on a provider with zeroed blocks. + */ + public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, String resName) { + final DevelopersApi api = getLinstorAPI(pool.getSourceHost()); + 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) { + s_logger.error(apiExc.getMessage()); + } + return false; + } } diff --git a/plugins/storage/volume/ontap/pom.xml b/plugins/storage/volume/ontap/pom.xml new file mode 100644 index 000000000000..00be65a8e011 --- /dev/null +++ b/plugins/storage/volume/ontap/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + cloud-plugin-storage-volume-ontap + Apache CloudStack Plugin - Storage Volume ONTAP Provider + + org.apache.cloudstack + cloudstack-plugins + 4.22.0.0-SNAPSHOT + ../../../pom.xml + + + 2021.0.7 + 11.0 + 20230227 + 1.6.2 + 3.8.1 + 2.22.2 + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + org.apache.cloudstack + cloud-plugin-storage-volume-default + ${project.version} + + + org.json + json + ${json.version} + + + org.springframework.cloud + spring-cloud-commons + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.security + spring-security-crypto + + + + + io.github.openfeign + feign-httpclient + ${openfeign.version} + + + org.apache.cloudstack + cloud-engine-storage-volume + ${project.version} + + + io.swagger + swagger-annotations + ${swagger-annotations.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 11 + 11 + + + + maven-surefire-plugin + ${maven-surefire-plugin.version} + + true + + + + integration-test + + test + + + + + + + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java new file mode 100644 index 000000000000..3310064406fd --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.driver; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.host.Host; +import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.utils.Pair; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { + + private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreDriver.class); + @Override + public Map getCapabilities() { + s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called"); + Map mapCapabilities = new HashMap<>(); + + mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString()); + mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString()); + + return mapCapabilities; + } + + @Override + public DataTO getTO(DataObject data) { + return null; + } + + @Override + public DataStoreTO getStoreTO(DataStore store) { + return null; + } + + @Override + public void createAsync(DataStore store, DataObject data, AsyncCompletionCallback callback) { + + s_logger.trace("OntapPrimaryDatastoreDriver: createAsync: Store: "+store+", data: "+data); + } + + @Override + public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback callback) { + + } + + @Override + public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback) { + + } + + @Override + public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback callback) { + + } + + @Override + public boolean canCopy(DataObject srcData, DataObject destData) { + return false; + } + + @Override + public void resize(DataObject data, AsyncCompletionCallback callback) { + + } + + @Override + public ChapInfo getChapInfo(DataObject dataObject) { + return null; + } + + @Override + public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) { + return true; + } + + @Override + public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { + + } + + @Override + public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool storagePool) { + return 0; + } + + @Override + public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) { + return 0; + } + + @Override + public long getUsedBytes(StoragePool storagePool) { + return 0; + } + + @Override + public long getUsedIops(StoragePool storagePool) { + return 0; + } + + @Override + public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { + + } + + @Override + public void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { + + } + + @Override + public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) { + + } + + @Override + public boolean canProvideStorageStats() { + return true; + } + + @Override + public Pair getStorageStats(StoragePool storagePool) { + return null; + } + + @Override + public boolean canProvideVolumeStats() { + return true; + } + + @Override + public Pair getVolumeStats(StoragePool storagePool, String volumeId) { + return null; + } + + @Override + public boolean canHostAccessStoragePool(Host host, StoragePool pool) { + return true; + } + + @Override + public boolean isVmInfoNeeded() { + return true; + } + + @Override + public void provideVmInfo(long vmId, long volumeId) { + + } + + @Override + public boolean isVmTagsNeeded(String tagKey) { + return true; + } + + @Override + public void provideVmTags(long vmId, long volumeId, String tagValue) { + + } + + @Override + public boolean isStorageSupportHA(Storage.StoragePoolType type) { + return true; + } + + @Override + public void detachVolumeFromAllStorageNodes(Volume volume) { + + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java new file mode 100644 index 000000000000..576c2dd1c1b4 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign; + + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.Retryer; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.ssl.SSLContexts; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import feign.Client; +import feign.httpclient.ApacheHttpClient; +import javax.net.ssl.SSLContext; +import java.util.concurrent.TimeUnit; + +@Configuration +public class FeignConfiguration { + private static Logger logger = LogManager.getLogger(FeignConfiguration.class); + + private int retryMaxAttempt = 3; + + private int retryMaxInterval = 5; + + private String ontapFeignMaxConnection = "80"; + + private String ontapFeignMaxConnectionPerRoute = "20"; + + @Bean + public Client client(ApacheHttpClientFactory httpClientFactory) { + + int maxConn; + int maxConnPerRoute; + try { + maxConn = Integer.parseInt(this.ontapFeignMaxConnection); + } catch (Exception e) { + logger.error("ontapFeignClient: encounter exception while parse the max connection from env. setting default value"); + maxConn = 20; + } + try { + maxConnPerRoute = Integer.parseInt(this.ontapFeignMaxConnectionPerRoute); + } catch (Exception e) { + logger.error("ontapFeignClient: encounter exception while parse the max connection per route from env. setting default value"); + maxConnPerRoute = 2; + } + // Disable Keep Alive for Http Connection + logger.debug("ontapFeignClient: Setting the feign client config values as max connection: {}, max connections per route: {}", maxConn, maxConnPerRoute); + ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0; + CloseableHttpClient httpClient = httpClientFactory.createBuilder() + .setMaxConnTotal(maxConn) + .setMaxConnPerRoute(maxConnPerRoute) + .setKeepAliveStrategy(keepAliveStrategy) + .setSSLSocketFactory(getSSLSocketFactory()) + .setConnectionTimeToLive(60, TimeUnit.SECONDS) + .build(); + return new ApacheHttpClient(httpClient); + } + + private SSLConnectionSocketFactory getSSLSocketFactory() { + try { + // The TrustAllStrategy is a strategy used in SSL context configuration that accepts any certificate + SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy()).build(); + return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + + @Bean + public RequestInterceptor requestInterceptor() { + return new RequestInterceptor() { + @Override + public void apply(RequestTemplate template) { + logger.info("Feign Request URL: {}", template.url()); + logger.info("HTTP Method: {}", template.method()); + logger.info("Headers: {}", template.headers()); + logger.info("Body: {}", template.requestBody().asString()); + } + }; + } + + @Bean + public Retryer feignRetryer() { + return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java new file mode 100644 index 000000000000..ed57bf419405 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.client; + +import org.apache.cloudstack.storage.feign.model.Aggregate; +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.net.URI; + +@Lazy +@FeignClient(name="AggregateClient", url="https://{clusterIP}/api/storage/aggregates", configuration = FeignConfiguration.class) +public interface AggregateFeignClient { + + //this method to get all aggregates and also filtered aggregates based on query params as a part of URL + @RequestMapping(method=RequestMethod.GET) + OntapResponse getAggregateResponse(URI baseURL, @RequestHeader("Authorization") String header); + + @RequestMapping(method=RequestMethod.GET, value="/{uuid}") + Aggregate getAggregateByUUID(URI baseURL,@RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java new file mode 100644 index 000000000000..7758a846f361 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.client; + +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.Cluster; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.net.URI; + +@FeignClient(name="ClusterClient", url="https://{clusterIP}/api/cluster", configuration = FeignConfiguration.class) +public interface ClusterFeignClient { + + @RequestMapping(method= RequestMethod.GET) + Cluster getCluster(URI baseURL, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java new file mode 100644 index 000000000000..4becf7bb29c4 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.client; + +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.Job; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import java.net.URI; + +/** + * @author Administrator + * + */ +@Lazy +@FeignClient(name = "JobClient", url = "https://{clusterIP}/api/cluster/jobs" , configuration = FeignConfiguration.class) +public interface JobFeignClient { + + @RequestMapping(method = RequestMethod.GET, value="/{uuid}") + Job getJobByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java new file mode 100644 index 000000000000..6e7b37d9378f --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.client; + +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.ExportPolicy; +import org.apache.cloudstack.storage.feign.model.FileInfo; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMethod; +import java.net.URI; + +/** + * @author Administrator + * + */ +@Lazy +@FeignClient(name = "NASClient", url = "" , configuration = FeignConfiguration.class) +public interface NASFeignClient { + + //File Operations + + @RequestMapping(method = RequestMethod.GET, value="/{volume.uuid}/files/{path}") + OntapResponse getFileResponse(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, + @PathVariable(name = "path", required = true) String filePath); + @RequestMapping(method = RequestMethod.DELETE, value="/{volume.uuid}/files/{path}") + void deleteFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, + @PathVariable(name = "path", required = true) String filePath); + @RequestMapping(method = RequestMethod.PATCH, value="/{volume.uuid}/files/{path}") + void updateFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, + @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo fileInfo); + @RequestMapping(method = RequestMethod.POST, value="/{volume.uuid}/files/{path}") + void createFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, + @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo file); + + + + //Export Policy Operations + + @RequestMapping(method = RequestMethod.POST) + ExportPolicy createExportPolicy(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value, + @RequestBody ExportPolicy exportPolicy); + + //this method to get all export policies and also filtered export policy based on query params as a part of URL + @RequestMapping(method = RequestMethod.GET) + OntapResponse getExportPolicyResponse(URI baseURL, @RequestHeader("Authorization") String header); + + @RequestMapping(method = RequestMethod.GET, value="/{id}") + OntapResponse getExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + + @RequestMapping(method = RequestMethod.DELETE, value="/{id}") + void deleteExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + + @RequestMapping(method = RequestMethod.PATCH, value="/{id}") + OntapResponse updateExportPolicy(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id, + @RequestBody ExportPolicy request); +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java new file mode 100644 index 000000000000..57c1cfb6b3ed --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.client; + +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.Svm; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.net.URI; + +@FeignClient(name = "SvmClient", url = "https://{clusterIP}/api/svm/svms", configuration = FeignConfiguration.class) +public interface SvmFeignClient { + + //this method to get all svms and also filtered svms based on query params as a part of URL + @RequestMapping(method = RequestMethod.GET) + OntapResponse getSvms(URI baseURL, @RequestHeader("Authorization") String header); + + @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") + Svm getSvmByUUID(URI baseURL, @RequestHeader("Authorization") String header); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java new file mode 100644 index 000000000000..af92754da42e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.client; + + +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.Volume; +import org.apache.cloudstack.storage.feign.model.response.JobResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.net.URI; + + +@Lazy +@FeignClient(name = "VolumeClient", url = "https://{clusterIP}/api/storage/volumes", configuration = FeignConfiguration.class) +public interface VolumeFeignClient { + + @RequestMapping(method = RequestMethod.DELETE, value="/{uuid}") + void deleteVolume(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); + + @RequestMapping(method = RequestMethod.POST) + JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest); + + @RequestMapping(method = RequestMethod.GET, value="/{uuid}") + Volume getVolumeByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); + + @RequestMapping(method = RequestMethod.PATCH) + JobResponse updateVolumeRebalancing(URI baseURL, @RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java new file mode 100644 index 000000000000..85b72a0af27e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Aggregate { + + @JsonProperty("name") + private String name = null; + + @Override + public int hashCode() { + return Objects.hash(getName(), getUuid()); + } + + @JsonProperty("uuid") + private String uuid = null; + + public Aggregate name(String name) { + this.name = name; + return this; + } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Aggregate uuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Aggregate diskAggregates = (Aggregate) o; + return Objects.equals(this.name, diskAggregates.name) && + Objects.equals(this.uuid, diskAggregates.uuid); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + @Override + public String toString() { + return "DiskAggregates [name=" + name + ", uuid=" + uuid + "]"; + } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/AntiRansomware.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/AntiRansomware.java new file mode 100644 index 000000000000..21748dcd53ec --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/AntiRansomware.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AntiRansomware { + @JsonProperty("state") + private String state; + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java new file mode 100644 index 000000000000..9dcf8aa738c1 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +/** + * Complete cluster information + */ +@SuppressWarnings("checkstyle:RegexpSingleline") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Cluster { + + @JsonProperty("name") + private String name = null; + @JsonProperty("uuid") + private String uuid = null; + @JsonProperty("version") + private Version version = null; + @JsonProperty("health") + private String health = null; + + @JsonProperty("san_optimized") + private Boolean sanOptimized = null; + + @JsonProperty("disaggregated") + private Boolean disaggregated = null; + + + public String getHealth() { + return health; + } + + public void setHealth(String health) { + this.health = health; + } + + public Cluster name(String name) { + this.name = name; + return this; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String getUuid() { + return uuid; + } + + public Cluster version(Version version) { + this.version = version; + return this; + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public Boolean getSanOptimized() { + return sanOptimized; + } + + public void setSanOptimized(Boolean sanOptimized) { + this.sanOptimized = sanOptimized; + } + + public Boolean getDisaggregated() { + return disaggregated; + } + public void setDisaggregated(Boolean disaggregated) { + this.disaggregated = disaggregated; + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getUuid()); + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Cluster cluster = (Cluster) o; + return Objects.equals(this.name, cluster.name) && + Objects.equals(this.uuid, cluster.uuid); + } + @Override + public String toString() { + return "Cluster{" + + "name='" + name + '\'' + + ", uuid='" + uuid + '\'' + + ", version=" + version + + ", sanOptimized=" + sanOptimized + + ", disaggregated=" + disaggregated + + '}'; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java new file mode 100644 index 000000000000..be78639844b3 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ExportPolicy { + + @JsonProperty("id") + private BigInteger id = null; + @JsonProperty("name") + private String name = null; + @JsonProperty("rules") + private List rules = null; + @JsonProperty("svm") + private Svm svm = null; + + public BigInteger getId() { + return id; + } + + public ExportPolicy name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ExportPolicy rules(List rules) { + this.rules = rules; + return this; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public ExportPolicy svm(Svm svm) { + this.svm = svm; + return this; + } + + public Svm getSvm() { + return svm; + } + + public void setSvm(Svm svm) { + this.svm = svm; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExportPolicy exportPolicy = (ExportPolicy) o; + return Objects.equals(this.id, exportPolicy.id) && + Objects.equals(this.name, exportPolicy.name); + } + + @Override + public int hashCode() { + return Objects.hash( id, name, rules, svm); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ExportPolicy {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" rules: ").append(toIndentedString(rules)).append("\n"); + sb.append(" svm: ").append(toIndentedString(svm)).append("\n"); + sb.append("}"); + return sb.toString(); + } + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java new file mode 100644 index 000000000000..6d4798667075 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * ExportRule + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ExportRule { + @JsonProperty("anonymous_user") + private String anonymousUser ; + + @JsonProperty("clients") + private List clients = null; + + @JsonProperty("index") + private Integer index = null; + + public enum ProtocolsEnum { + any("any"), + + nfs("nfs"), + + nfs3("nfs3"), + + nfs4("nfs4"); + + private String value; + + ProtocolsEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static ProtocolsEnum fromValue(String text) { + for (ProtocolsEnum b : ProtocolsEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + } + + @JsonProperty("protocols") + private List protocols = null; + + public ExportRule anonymousUser(String anonymousUser) { + this.anonymousUser = anonymousUser; + return this; + } + + public String getAnonymousUser() { + return anonymousUser; + } + + public void setAnonymousUser(String anonymousUser) { + this.anonymousUser = anonymousUser; + } + + public ExportRule clients(List clients) { + this.clients = clients; + return this; + } + + public List getClients() { + return clients; + } + + public void setClients(List clients) { + this.clients = clients; + } + + public Integer getIndex() { + return index; + } + public void setIndex(Integer index) + { + this.index=index; + } + + public ExportRule protocols(List protocols) { + this.protocols = protocols; + return this; + } + + public List getProtocols() { + return protocols; + } + + public void setProtocols(List protocols) { + this.protocols = protocols; + } + + public static class ExportClient { + @JsonProperty("match") + private String match = null; + + public ExportClient match (String match) { + this.match = match; + return this; + } + public String getMatch () { + return match; + } + + public void setMatch (String match) { + this.match = match; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ExportRule {\n"); + + sb.append(" anonymousUser: ").append(toIndentedString(anonymousUser)).append("\n"); + sb.append(" clients: ").append(toIndentedString(clients)).append("\n"); + sb.append(" index: ").append(toIndentedString(index)).append("\n"); + sb.append(" protocols: ").append(toIndentedString(protocols)).append("\n"); + sb.append("}"); + return sb.toString(); + } + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java new file mode 100644 index 000000000000..b67704435a9e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Information about a single file. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class FileInfo { + @JsonProperty("bytes_used") + private Long bytesUsed = null; + @JsonProperty("creation_time") + private OffsetDateTime creationTime = null; + @JsonProperty("fill_enabled") + private Boolean fillEnabled = null; + @JsonProperty("is_empty") + private Boolean isEmpty = null; + @JsonProperty("is_snapshot") + private Boolean isSnapshot = null; + @JsonProperty("is_vm_aligned") + private Boolean isVmAligned = null; + @JsonProperty("modified_time") + private OffsetDateTime modifiedTime = null; + @JsonProperty("name") + private String name = null; + @JsonProperty("overwrite_enabled") + private Boolean overwriteEnabled = null; + @JsonProperty("path") + private String path = null; + @JsonProperty("size") + private Long size = null; + @JsonProperty("target") + private String target = null; + + /** + * Type of the file. + */ + public enum TypeEnum { + FILE("file"), + DIRECTORY("directory"); + + private String value; + + TypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static TypeEnum fromValue(String value) { + for (TypeEnum b : TypeEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + return null; + } + } + + @JsonProperty("type") + private TypeEnum type = null; + + @JsonProperty("unique_bytes") + private Long uniqueBytes = null; + + @JsonProperty("unix_permissions") + private Integer unixPermissions = null; + + /** + * The actual number of bytes used on disk by this file. If byte_offset and length parameters are specified, this will return the bytes used by the file within the given range. + * @return bytesUsed + **/ + public Long getBytesUsed() { + return bytesUsed; + } + + public OffsetDateTime getCreationTime() { + return creationTime; + } + + public FileInfo fillEnabled(Boolean fillEnabled) { + this.fillEnabled = fillEnabled; + return this; + } + + public Boolean isFillEnabled() { + return fillEnabled; + } + + public void setFillEnabled(Boolean fillEnabled) { + this.fillEnabled = fillEnabled; + } + + + public Boolean isIsEmpty() { + return isEmpty; + } + + public void setIsEmpty(Boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public Boolean isIsSnapshot() { + return isSnapshot; + } + + public void setIsSnapshot(Boolean isSnapshot) { + this.isSnapshot = isSnapshot; + } + + + public Boolean isIsVmAligned() { + return isVmAligned; + } + + + public OffsetDateTime getModifiedTime() { + return modifiedTime; + } + + public FileInfo name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public FileInfo overwriteEnabled(Boolean overwriteEnabled) { + this.overwriteEnabled = overwriteEnabled; + return this; + } + + public Boolean isOverwriteEnabled() { + return overwriteEnabled; + } + + public void setOverwriteEnabled(Boolean overwriteEnabled) { + this.overwriteEnabled = overwriteEnabled; + } + + public FileInfo path(String path) { + this.path = path; + return this; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public FileInfo target(String target) { + this.target = target; + return this; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public FileInfo type(TypeEnum type) { + this.type = type; + return this; + } + + public TypeEnum getType() { + return type; + } + + public void setType(TypeEnum type) { + this.type = type; + } + + public Long getUniqueBytes() { + return uniqueBytes; + } + + public FileInfo unixPermissions(Integer unixPermissions) { + this.unixPermissions = unixPermissions; + return this; + } + + public Integer getUnixPermissions() { + return unixPermissions; + } + + public void setUnixPermissions(Integer unixPermissions) { + this.unixPermissions = unixPermissions; + } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FileInfo fileInfo = (FileInfo) o; + return Objects.equals(this.name, fileInfo.name) && + Objects.equals(this.path, fileInfo.path); + } + + @Override + public int hashCode() { + return Objects.hash(bytesUsed, creationTime, fillEnabled, isEmpty, isSnapshot, isVmAligned, modifiedTime, name, overwriteEnabled, path, size, target, type, uniqueBytes, unixPermissions); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class FileInfo {\n"); + sb.append(" bytesUsed: ").append(toIndentedString(bytesUsed)).append("\n"); + sb.append(" creationTime: ").append(toIndentedString(creationTime)).append("\n"); + sb.append(" fillEnabled: ").append(toIndentedString(fillEnabled)).append("\n"); + sb.append(" isEmpty: ").append(toIndentedString(isEmpty)).append("\n"); + sb.append(" isSnapshot: ").append(toIndentedString(isSnapshot)).append("\n"); + sb.append(" isVmAligned: ").append(toIndentedString(isVmAligned)).append("\n"); + sb.append(" modifiedTime: ").append(toIndentedString(modifiedTime)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" overwriteEnabled: ").append(toIndentedString(overwriteEnabled)).append("\n"); + sb.append(" path: ").append(toIndentedString(path)).append("\n"); + sb.append(" size: ").append(toIndentedString(size)).append("\n"); + sb.append(" target: ").append(toIndentedString(target)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" uniqueBytes: ").append(toIndentedString(uniqueBytes)).append("\n"); + sb.append(" unixPermissions: ").append(toIndentedString(unixPermissions)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java new file mode 100644 index 000000000000..a1a0c2698f14 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Administrator + * + */ +@JsonInclude(Include.NON_NULL) +public class Job { + + @JsonProperty("uuid") + String uuid; + @JsonProperty("description") + String description; + @JsonProperty("state") + String state; + @JsonProperty("message") + String message; + @JsonProperty("code") + String code; + @JsonProperty("_links") + private Links links; + + @JsonProperty("error") + private JobError error; + public JobError getError () { return error; } + public void setError (JobError error) { this.error = error; } + public Links getLinks() { return links; } + public void setLinks(Links links) { this.links = links; } + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getState() { + return state; + } + public void setState(String state) { + this.state = state; + } + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + @Override + public String toString() { + return "JobDTO [uuid=" + uuid + ", description=" + description + ", state=" + state + ", message=" + + message + ", code=" + code + "]"; + } + + public static class Links { + @JsonProperty("message") + private Self self; + public Self getSelf() { return self; } + public void setSelf(Self self) { this.self = self; } + } + + public static class Self { + @JsonProperty("message") + private String href; + public String getHref() { return href; } + public void setHref(String href) { this.href = href; } + } + + public static class JobError { + @JsonProperty("message") + String errorMesssage; + @JsonProperty("code") + String code; + public String getErrorMesssage () { return errorMesssage; } + public void setErrorMesssage (String errorMesssage) { this.errorMesssage = errorMesssage; } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + @Override + public String toString() { + return "JobError [errorMesssage=" + errorMesssage + ", code=" + code + "]"; + } + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java new file mode 100644 index 000000000000..27590d3fde25 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Nas { + @JsonProperty("path") + private String path; + + @JsonProperty("export_policy") + private ExportPolicy exportPolicy; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public ExportPolicy getExportPolicy() { + return exportPolicy; + } + + public void setExportPolicy(ExportPolicy exportPolicy) { + this.exportPolicy = exportPolicy; + } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java new file mode 100644 index 000000000000..d02112422f60 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +public class OntapStorage { + public static String Username; + public static String Password; + public static String ManagementLIF; + public static String Svm; + public static String Protocol; + public static Boolean IsDisaggregated; + + public OntapStorage(String username, String password, String managementLIF, String svm, String protocol, Boolean isDisaggregated) { + Username = username; + Password = password; + ManagementLIF = managementLIF; + Svm = svm; + Protocol = protocol; + IsDisaggregated = isDisaggregated; + } + + public String getUsername() { + return Username; + } + + public void setUsername(String username) { + Username = username; + } + + public String getPassword() { + return Password; + } + + public void setPassword(String password) { + Password = password; + } + + public String getManagementLIF() { + return ManagementLIF; + } + + public void setManagementLIF(String managementLIF) { + ManagementLIF = managementLIF; + } + + public String getSVM() { + return Svm; + } + + public void setSVM(String svm) { + Svm = svm; + } + + public String getProtocol() { + return Protocol; + } + + public void setProtocol(String protocol) { + Protocol = protocol; + } + + public Boolean getIsDisaggregated() { + return IsDisaggregated; + } + + public void setIsDisaggregated(Boolean isDisaggregated) { + IsDisaggregated = isDisaggregated; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java new file mode 100644 index 000000000000..82ba149bd040 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Policy { + private int minThroughputIops; + private int minThroughputMbps; + private int maxThroughputIops; + private int maxThroughputMbps; + private String uuid; + private String name; + public int getMinThroughputIops() { return minThroughputIops; } + public void setMinThroughputIops(int minThroughputIops) { this.minThroughputIops = minThroughputIops; } + public int getMinThroughputMbps() { return minThroughputMbps; } + public void setMinThroughputMbps(int minThroughputMbps) { this.minThroughputMbps = minThroughputMbps; } + public int getMaxThroughputIops() { return maxThroughputIops; } + public void setMaxThroughputIops(int maxThroughputIops) { this.maxThroughputIops = maxThroughputIops; } + public int getMaxThroughputMbps() { return maxThroughputMbps; } + public void setMaxThroughputMbps(int maxThroughputMbps) { this.maxThroughputMbps = maxThroughputMbps; } + public String getUuid() { return uuid; } + public void setUuid(String uuid) { this.uuid = uuid; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Policy policy = (Policy) o; + return Objects.equals(getUuid(), policy.getUuid()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getUuid()); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java new file mode 100644 index 000000000000..ccc230c6ea53 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Qos { + @JsonProperty("policy") + private Policy policy; + + public Policy getPolicy() { + return policy; + } + + public void setPolicy(Policy policy) { + this.policy = policy; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java new file mode 100644 index 000000000000..e89c1f8426a6 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Svm { + @JsonProperty("uuid") + private String uuid = null; + + @JsonProperty("name") + private String name = null; + + @JsonProperty("iscsi.enabled") + private Boolean iscsiEnabled = null; + + @JsonProperty("fcp.enabled") + private Boolean fcpEnabled = null; + + @JsonProperty("nfs.enabled") + private Boolean nfsEnabled = null; + + @JsonProperty("aggregates") + private List aggregates = null; + + @JsonProperty("aggregates_delegated") + private Boolean aggregatesDelegated = null; + + @JsonProperty("state.value") + private String state = null; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getIscsiEnabled() { + return iscsiEnabled; + } + + public void setIscsiEnabled(Boolean iscsiEnabled) { + this.iscsiEnabled = iscsiEnabled; + } + + + public Boolean getFcpEnabled() { + return fcpEnabled; + } + + public void setFcpEnabled(Boolean fcpEnabled) { + this.fcpEnabled = fcpEnabled; + } + + + public Boolean getNfsEnabled() { + return nfsEnabled; + } + + public void setNfsEnabled(Boolean nfsEnabled) { + this.nfsEnabled = nfsEnabled; + } + + + public List getAggregates() { + return aggregates; + } + + public void setAggregates(List aggregates) { + this.aggregates = aggregates; + } + + + public Boolean getAggregatesDelegated() { + return aggregatesDelegated; + } + + public void setAggregatesDelegated(Boolean aggregatesDelegated) { + this.aggregatesDelegated = aggregatesDelegated; + } + + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Svm svm = (Svm) o; + return Objects.equals(getUuid(), svm.getUuid()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getUuid()); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java new file mode 100644 index 000000000000..056e20eb3400 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +/** + * This returns the cluster version information. When the cluster has more than one node, the cluster version is equivalent to the lowest of generation, major, and minor versions on all nodes. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Version { + @JsonProperty("full") + private String full = null; + + @JsonProperty("generation") + private Integer generation = null; + + @JsonProperty("major") + private Integer major = null; + + @JsonProperty("minor") + private Integer minor = null; + + public String getFull() { + return full; + } + + public Integer getGeneration() { + return generation; + } + + public Integer getMajor() { + return major; + } + + public Integer getMinor() { + return minor; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Version clusterVersion = (Version) o; + return Objects.equals(this.full, clusterVersion.full) && + Objects.equals(this.generation, clusterVersion.generation) && + Objects.equals(this.major, clusterVersion.major) && + Objects.equals(this.minor, clusterVersion.minor); + } + + @Override + public int hashCode() { + return Objects.hash(full, generation, major, minor); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ClusterVersion {\n"); + sb.append(" full: ").append(toIndentedString(full)).append("\n"); + sb.append(" generation: ").append(toIndentedString(generation)).append("\n"); + sb.append(" major: ").append(toIndentedString(major)).append("\n"); + sb.append(" minor: ").append(toIndentedString(minor)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java new file mode 100644 index 000000000000..3d384c56db2e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Volume { + @JsonProperty("uuid") + private String uuid; + + @JsonProperty("name") + private String name; + + @JsonProperty("state") + private String state; + + @JsonProperty("nas") + private Nas nas; + + @JsonProperty("svm") + private Svm svm; + + @JsonProperty("qos") + private Qos qos; + + @JsonProperty("space") + private VolumeSpace space; + + @JsonProperty("anti_ransomware") + private AntiRansomware antiRansomware; + + @JsonProperty("aggregates") + private List aggregates = null; + + @JsonProperty("size") + private Long size = null; + + // Getters and setters + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Nas getNas() { + return nas; + } + + public void setNas(Nas nas) { + this.nas = nas; + } + + public Svm getSvm() { + return svm; + } + + public void setSvm(Svm svm) { + this.svm = svm; + } + + public Qos getQos() { + return qos; + } + + public void setQos(Qos qos) { + this.qos = qos; + } + + public VolumeSpace getSpace() { + return space; + } + + public void setSpace(VolumeSpace space) { + this.space = space; + } + + public AntiRansomware getAntiRansomware() { + return antiRansomware; + } + + public void setAntiRansomware(AntiRansomware antiRansomware) { + this.antiRansomware = antiRansomware; + } + + public List getAggregates () { return aggregates; } + + public void setAggregates (List aggregates) { this.aggregates = aggregates; } + + public Long getSize () { return size; } + + public void setSize (Long size) { this.size = size; } + + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Volume volume = (Volume) o; + return Objects.equals(uuid, volume.uuid); + } + + @Override + public int hashCode() { + return Objects.hashCode(uuid); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java new file mode 100644 index 000000000000..ae4cfa519c14 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VolumeQosPolicy { + @JsonProperty("max_throughput_iops") + private Integer maxThroughputIops = null; + + @JsonProperty("max_throughput_mbps") + private Integer maxThroughputMbps = null; + + @JsonProperty("min_throughput_iops") + private Integer minThroughputIops = null; + + @JsonProperty("name") + private String name = null; + + @JsonProperty("uuid") + private String uuid = null; + + public Integer getMaxThroughputIops() { + return maxThroughputIops; + } + + public void setMaxThroughputIops(Integer maxThroughputIops) { + this.maxThroughputIops = maxThroughputIops; + } + + public Integer getMaxThroughputMbps() { + return maxThroughputMbps; + } + + public void setMaxThroughputMbps(Integer maxThroughputMbps) { + this.maxThroughputMbps = maxThroughputMbps; + } + + public Integer getMinThroughputIops() { + return minThroughputIops; + } + + public void setMinThroughputIops(Integer minThroughputIops) { + this.minThroughputIops = minThroughputIops; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java new file mode 100644 index 000000000000..067997fcd440 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VolumeSpace { + @JsonProperty("size") + private long size; + + @JsonProperty("available") + private long available; + + @JsonProperty("used") + private long used; + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public long getAvailable() { + return available; + } + + public void setAvailable(long available) { + this.available = available; + } + + public long getUsed() { + return used; + } + + public void setUsed(long used) { + this.used = used; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java new file mode 100644 index 000000000000..354a314a5017 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VolumeSpaceLogicalSpace { + + @JsonProperty("available") + private Long available = null; + + @JsonProperty("used") + private Double used = null; + + public Long getAvailable() { + return available; + } + + public void setAvailable(Long available) { + this.available = available; + } + + public Double getUsed() { + return used; + } + + public void setUsed(Double used) { + this.used = used; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/JobResponse.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/JobResponse.java new file mode 100644 index 000000000000..a794c191c493 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/JobResponse.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.feign.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.cloudstack.storage.feign.model.Job; + +public class JobResponse { + @JsonProperty("job") + private Job job; + public Job getJob() { return job; } + public void setJob(Job job) { this.job = job; } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java new file mode 100644 index 000000000000..b78f41e7df3b --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.feign.model.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * OntapResponse + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class OntapResponse { + @JsonProperty("num_records") + private Integer numRecords; + + @JsonProperty("records") + private List records; + + public OntapResponse() { + // Default constructor + } + + public OntapResponse(List records) { + this.records = records; + this.numRecords = (records != null) ? records.size() : 0; + } + + public Integer getNumRecords() { + return numRecords; + } + + public void setNumRecords(Integer numRecords) { + this.numRecords = numRecords; + } + + public List getRecords() { + return records; + } + + public void setRecords(List records) { + this.records = records; + this.numRecords = (records != null) ? records.size() : 0; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java new file mode 100644 index 000000000000..cc7eb5618f1a --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.lifecycle; + + +import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.base.Preconditions; +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.lifecycle.BasePrimaryDataStoreLifeCycleImpl; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.provider.StorageProviderFactory; +import org.apache.cloudstack.storage.service.StorageStrategy; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { + @Inject private ClusterDao _clusterDao; + @Inject private StorageManager _storageMgr; + @Inject private ResourceManager _resourceMgr; + @Inject private PrimaryDataStoreHelper _dataStoreHelper; + private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class); + + /** + * Creates primary storage on NetApp storage + * @param dsInfos + * @return + */ + @Override + public DataStore initialize(Map dsInfos) { + if (dsInfos == null) { + throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage"); + } + String url = dsInfos.get("url").toString(); // TODO: Decide on whether should the customer enter just the Management LIF IP or https://ManagementLIF + Long zoneId = (Long) dsInfos.get("zoneId"); + Long podId = (Long)dsInfos.get("podId"); + Long clusterId = (Long)dsInfos.get("clusterId"); + String storagePoolName = dsInfos.get("name").toString(); + String providerName = dsInfos.get("providerName").toString(); + String tags = dsInfos.get("tags").toString(); + Boolean isTagARule = (Boolean) dsInfos.get("isTagARule"); + String scheme = dsInfos.get("scheme").toString(); + + s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + + ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme); + + // Additional details requested for ONTAP primary storage pool creation + @SuppressWarnings("unchecked") + Map details = (Map)dsInfos.get("details"); + // Validations + if (podId != null && clusterId == null) { + s_logger.error("Cluster Id is null, cannot create primary storage"); + return null; + } else if (podId == null && clusterId != null) { + s_logger.error("Pod Id is null, cannot create primary storage"); + return null; + } + + if (podId == null && clusterId == null) { + if (zoneId != null) { + s_logger.info("Both Pod Id and Cluster Id are null, Primary storage pool will be associated with a Zone"); + } else { + throw new CloudRuntimeException("Pod Id, Cluster Id and Zone Id are all null, cannot create primary storage"); + } + } + + if (storagePoolName == null || storagePoolName.isEmpty()) { + throw new CloudRuntimeException("Storage pool name is null or empty, cannot create primary storage"); + } + + if (providerName == null || providerName.isEmpty()) { + throw new CloudRuntimeException("Provider name is null or empty, cannot create primary storage"); + } + + PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); + if (clusterId != null) { + ClusterVO clusterVO = _clusterDao.findById(clusterId); + Preconditions.checkNotNull(clusterVO, "Unable to locate the specified cluster"); + if (clusterVO.getHypervisorType() != Hypervisor.HypervisorType.KVM) { + throw new CloudRuntimeException("ONTAP primary storage is not supported for KVM hypervisor"); + } + parameters.setHypervisorType(clusterVO.getHypervisorType()); + } + + // TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set + // TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user + String protocol = details.get(Constants.PROTOCOL); + switch (protocol.toLowerCase()) { + case Constants.NFS: + parameters.setType(Storage.StoragePoolType.NetworkFilesystem); + break; + case Constants.ISCSI: + parameters.setType(Storage.StoragePoolType.Iscsi); + break; + default: + throw new CloudRuntimeException("Unsupported protocol: " + scheme + ", cannot create primary storage"); + } + + details.put(Constants.MANAGEMENTLIF, url); + + // Validate the ONTAP details + if(details.get(Constants.ISDISAGGREGATED) == null || details.get(Constants.ISDISAGGREGATED).isEmpty()) { + details.put(Constants.ISDISAGGREGATED, "false"); + } + + OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD), + details.get(Constants.MANAGEMENTLIF), details.get(Constants.SVMNAME), details.get(Constants.PROTOCOL), + Boolean.parseBoolean(details.get(Constants.ISDISAGGREGATED))); + StorageProviderFactory storageProviderManager = new StorageProviderFactory(ontapStorage); + StorageStrategy storageStrategy = storageProviderManager.getStrategy(); + boolean isValid = storageStrategy.connect(); + if (isValid) { +// String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention + storageStrategy.createVolume(storagePoolName, Long.parseLong((details.get("size")))); // TODO: size should be in bytes, so see if conversion is needed + } else { + throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage"); + } + + parameters.setTags(tags); + parameters.setIsTagARule(isTagARule); + parameters.setDetails(details); + parameters.setUuid(UUID.randomUUID().toString()); + parameters.setZoneId(zoneId); + parameters.setPodId(podId); + parameters.setClusterId(clusterId); + parameters.setName(storagePoolName); + parameters.setProviderName(providerName); + parameters.setManaged(true); + + return _dataStoreHelper.createPrimaryDataStore(parameters); + } + + @Override + public boolean attachCluster(DataStore dataStore, ClusterScope scope) { + logger.debug("In attachCluster for ONTAP primary storage"); + PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo)dataStore; + List hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primarystore); + + logger.debug(String.format("Attaching the pool to each of the hosts %s in the cluster: %s", hostsToConnect, primarystore.getClusterId())); + for (HostVO host : hostsToConnect) { + // TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster + try { + _storageMgr.connectHostToSharedPool(host, dataStore.getId()); + } catch (Exception e) { + logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + } + } + _dataStoreHelper.attachCluster(dataStore); + return true; + } + + @Override + public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { + return false; + } + + @Override + public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.HypervisorType hypervisorType) { + logger.debug("In attachZone for ONTAP primary storage"); + List hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), Hypervisor.HypervisorType.KVM); + + logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect)); + for (HostVO host : hostsToConnect) { + // TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster + try { + _storageMgr.connectHostToSharedPool(host, dataStore.getId()); + } catch (Exception e) { + logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + } + } + _dataStoreHelper.attachZone(dataStore); + return true; + } + + @Override + public boolean maintain(DataStore store) { + return true; + } + + @Override + public boolean cancelMaintain(DataStore store) { + return true; + } + + @Override + public boolean deleteDataStore(DataStore store) { + return true; + } + + @Override + public boolean migrateToObjectStore(DataStore store) { + return true; + } + + @Override + public void updateStoragePool(StoragePool storagePool, Map details) { + + } + + @Override + public void enableStoragePool(DataStore store) { + + } + + @Override + public void disableStoragePool(DataStore store) { + + } + + @Override + public void changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + + } + + @Override + public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java new file mode 100644 index 000000000000..0240201b1057 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.provider; + + +import com.cloud.utils.component.ComponentContext; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider; +import org.apache.cloudstack.storage.driver.OntapPrimaryDatastoreDriver; +import org.apache.cloudstack.storage.lifecycle.OntapPrimaryDatastoreLifecycle; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Component +public class OntapPrimaryDatastoreProvider implements PrimaryDataStoreProvider { + + private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreProvider.class); + private OntapPrimaryDatastoreDriver primaryDatastoreDriver; + private OntapPrimaryDatastoreLifecycle primaryDatastoreLifecycle; + + public OntapPrimaryDatastoreProvider() { + s_logger.info("OntapPrimaryDatastoreProvider initialized"); + } + @Override + public DataStoreLifeCycle getDataStoreLifeCycle() { + return primaryDatastoreLifecycle; + } + + @Override + public DataStoreDriver getDataStoreDriver() { + return primaryDatastoreDriver; + } + + @Override + public HypervisorHostListener getHostListener() { + return null; + } + + @Override + public String getName() { + s_logger.trace("OntapPrimaryDatastoreProvider: getName: Called"); + return "ONTAP Primary Datastore Provider"; + } + + @Override + public boolean configure(Map params) { + s_logger.trace("OntapPrimaryDatastoreProvider: configure: Called"); + primaryDatastoreDriver = ComponentContext.inject(OntapPrimaryDatastoreDriver.class); + primaryDatastoreLifecycle = ComponentContext.inject(OntapPrimaryDatastoreLifecycle.class); + return true; + } + + @Override + public Set getTypes() { + s_logger.trace("OntapPrimaryDatastoreProvider: getTypes: Called"); + Set typeSet = new HashSet(); + typeSet.add(DataStoreProviderType.PRIMARY); + return typeSet; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java new file mode 100644 index 000000000000..263d103e4c42 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.provider; + +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.service.StorageStrategy; +import org.apache.cloudstack.storage.service.UnifiedNASStrategy; +import org.apache.cloudstack.storage.service.UnifiedSANStrategy; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +@Component +public class StorageProviderFactory { + private final StorageStrategy storageStrategy; + private static final Logger s_logger = (Logger) LogManager.getLogger(StorageProviderFactory.class); + + public StorageProviderFactory(OntapStorage ontapStorage) { + String protocol = ontapStorage.getProtocol(); + s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); + switch (protocol.toLowerCase()) { + case Constants.NFS: + if(!ontapStorage.getIsDisaggregated()) { + this.storageStrategy = new UnifiedNASStrategy(ontapStorage); + } else { + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + } + break; + case Constants.ISCSI: + if (!ontapStorage.getIsDisaggregated()) { + this.storageStrategy = new UnifiedSANStrategy(ontapStorage); + } else { + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + } + break; + default: + this.storageStrategy = null; + throw new CloudRuntimeException("Unsupported protocol: " + protocol); + } + } + + public StorageStrategy getStrategy() { + return storageStrategy; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java new file mode 100644 index 000000000000..4e03daae4b4a --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public abstract class NASStrategy extends StorageStrategy { + public NASStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + public abstract String createExportPolicy(String svmName, String policyName); + public abstract String addExportRule(String policyName, String clientMatch, String[] protocols, String[] roRule, String[] rwRule); + public abstract String assignExportPolicyToVolume(String volumeUuid, String policyName); + public abstract String enableNFS(String svmUuid); +} + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java new file mode 100644 index 000000000000..4e6846ef7610 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public abstract class SANStrategy extends StorageStrategy { + public SANStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + public abstract String createLUN(String svmName, String volumeName, String lunName, long sizeBytes, String osType); + public abstract String createIgroup(String svmName, String igroupName, String[] initiators); + public abstract String mapLUNToIgroup(String lunName, String igroupName); + public abstract String enableISCSI(String svmUuid); +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java new file mode 100644 index 000000000000..cf590df5be1b --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.service; + +import com.cloud.utils.exception.CloudRuntimeException; +import feign.FeignException; +import org.apache.cloudstack.storage.feign.client.JobFeignClient; +import org.apache.cloudstack.storage.feign.client.SvmFeignClient; +import org.apache.cloudstack.storage.feign.client.VolumeFeignClient; +import org.apache.cloudstack.storage.feign.model.Aggregate; +import org.apache.cloudstack.storage.feign.model.Job; +import org.apache.cloudstack.storage.feign.model.Svm; +import org.apache.cloudstack.storage.feign.model.Volume; +import org.apache.cloudstack.storage.feign.model.response.JobResponse; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.cloudstack.storage.utils.Utility; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.net.URI; +import java.util.List; +import java.util.Objects; + +public abstract class StorageStrategy { + @Inject + private Utility utils; + + @Inject + private VolumeFeignClient volumeFeignClient; + + @Inject + private SvmFeignClient svmFeignClient; + + @Inject + private JobFeignClient jobFeignClient; + + private final OntapStorage storage; + + private List aggregates; + + private static final Logger s_logger = (Logger) LogManager.getLogger(StorageStrategy.class); + + public StorageStrategy(OntapStorage ontapStorage) { + storage = ontapStorage; + } + + // Connect method to validate ONTAP cluster, credentials, protocol, and SVM + public boolean connect() { + s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF()); + //Get AuthHeader + String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + try { + // Call the SVM API to check if the SVM exists + Svm svm = null; + URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GETSVMs); + OntapResponse svms = svmFeignClient.getSvms(url, authHeader); + for (Svm storageVM : svms.getRecords()) { + if (storageVM.getName().equals(storage.getSVM())) { + svm = storageVM; + s_logger.info("Found SVM: " + storage.getSVM()); + break; + } + } + + // Validations + if (svm == null) { + s_logger.error("SVM with name " + storage.getSVM() + " not found."); + throw new CloudRuntimeException("SVM with name " + storage.getSVM() + " not found."); + } else { + if (svm.getState() != Constants.RUNNING) { + s_logger.error("SVM " + storage.getSVM() + " is not in running state."); + throw new CloudRuntimeException("SVM " + storage.getSVM() + " is not in running state."); + } + if (Objects.equals(storage.getProtocol(), Constants.NFS) && !svm.getNfsEnabled()) { + s_logger.error("NFS protocol is not enabled on SVM " + storage.getSVM()); + throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + storage.getSVM()); + } else if (Objects.equals(storage.getProtocol(), Constants.ISCSI) && !svm.getIscsiEnabled()) { + s_logger.error("iSCSI protocol is not enabled on SVM " + storage.getSVM()); + throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + storage.getSVM()); + } + List aggrs = svm.getAggregates(); + if (aggrs == null || aggrs.isEmpty()) { + s_logger.error("No aggregates are assigned to SVM " + storage.getSVM()); + throw new CloudRuntimeException("No aggregates are assigned to SVM " + storage.getSVM()); + } + this.aggregates = aggrs; + } + s_logger.info("Successfully connected to ONTAP cluster and validated ONTAP details provided"); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e.getMessage()); + } + return true; + } + + // Common methods like create/delete etc., should be here + public void createVolume(String volumeName, Long size) { + s_logger.info("Creating volume: " + volumeName + " of size: " + size + " bytes"); + + if (aggregates == null || aggregates.isEmpty()) { + s_logger.error("No aggregates available to create volume on SVM " + storage.getSVM()); + throw new CloudRuntimeException("No aggregates available to create volume on SVM " + storage.getSVM()); + } + // Get the AuthHeader + String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + + // Generate the Create Volume Request + Volume volumeRequest = new Volume(); + Svm svm = new Svm(); + svm.setName(storage.getSVM()); + + volumeRequest.setName(volumeName); + volumeRequest.setSvm(svm); + volumeRequest.setAggregates(aggregates); + volumeRequest.setSize(size); + // Make the POST API call to create the volume + try { + // Create URI for POST CreateVolume API + URI url = utils.generateURI(Constants.CREATEVOLUME); + // Call the VolumeFeignClient to create the volume + JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(url, authHeader, volumeRequest); + String jobUUID = jobResponse.getJob().getUuid(); + + //Create URI for GET Job API + url = utils.generateURI(Constants.GETJOBBYUUID); + int jobRetryCount = 0, maxJobRetries = Constants.JOBMAXRETRIES; + Job createVolumeJob = null; + while(createVolumeJob == null || createVolumeJob.getState().equals(Constants.JOBRUNNING) || createVolumeJob.getState().equals(Constants.JOBQUEUE) || createVolumeJob.getState().equals(Constants.JOBPAUSED)) { + if(jobRetryCount >= maxJobRetries) { + s_logger.error("Job to create volume " + volumeName + " did not complete within expected time."); + throw new CloudRuntimeException("Job to create volume " + volumeName + " did not complete within expected time."); + } + + try { + createVolumeJob = jobFeignClient.getJobByUUID(url, authHeader, jobUUID); + if (createVolumeJob == null) { + s_logger.warn("Job with UUID " + jobUUID + " not found. Retrying..."); + } else if (createVolumeJob.getState().equals(Constants.JOBFAILURE)) { + throw new CloudRuntimeException("Job to create volume " + volumeName + " failed with error: " + createVolumeJob.getMessage()); + } + } catch (FeignException.FeignClientException e) { + throw new CloudRuntimeException("Failed to fetch job status: " + e.getMessage()); + } + + jobRetryCount++; + Thread.sleep(Constants.CREATEVOLUMECHECKSLEEPTIME); // Sleep for 2 seconds before polling again + } + } catch (Exception e) { + s_logger.error("Exception while creating volume: ", e); + throw new CloudRuntimeException("Failed to create volume: " + e.getMessage()); + } + s_logger.info("Volume created successfully: " + volumeName); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java new file mode 100644 index 000000000000..6c9a8735c4c1 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public class UnifiedNASStrategy extends NASStrategy{ + public UnifiedNASStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + @Override + public String createExportPolicy(String svmName, String policyName) { + return ""; + } + + @Override + public String addExportRule(String policyName, String clientMatch, String[] protocols, String[] roRule, String[] rwRule) { + return ""; + } + + @Override + public String assignExportPolicyToVolume(String volumeUuid, String policyName) { + return ""; + } + + @Override + public String enableNFS(String svmUuid) { + return ""; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java new file mode 100644 index 000000000000..e954ec312006 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public class UnifiedSANStrategy extends SANStrategy{ + public UnifiedSANStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + @Override + public String createLUN(String svmName, String volumeName, String lunName, long sizeBytes, String osType) { + return ""; + } + + @Override + public String createIgroup(String svmName, String igroupName, String[] initiators) { + return ""; + } + + @Override + public String mapLUNToIgroup(String lunName, String igroupName) { + return ""; + } + + @Override + public String enableISCSI(String svmUuid) { + return ""; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java new file mode 100644 index 000000000000..0dff67941e5e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.utils; + +public class Constants { + public static final String NFS = "nfs"; + public static final String ISCSI = "iscsi"; + public static final String PROTOCOL = "protocol"; + public static final String SVMNAME = "svmName"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String MANAGEMENTLIF = "managementLIF"; + public static final String ISDISAGGREGATED = "isDisaggregated"; + public static final String RUNNING = "running"; + + public static final String JOBRUNNING = "running"; + public static final String JOBQUEUE = "queued"; + public static final String JOBPAUSED = "paused"; + public static final String JOBFAILURE = "failure"; + public static final String JOBSUCCESS = "success"; + + public static final int JOBMAXRETRIES = 100; + public static final int CREATEVOLUMECHECKSLEEPTIME = 2000; + + public static final String HTTPS = "https://"; + public static final String GETSVMs = "/api/svm/svms"; + public static final String CREATEVOLUME = "/api/storage/volumes"; + public static final String GETJOBBYUUID = "/api/cluster/jobs"; +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java new file mode 100644 index 000000000000..6fcf155e27b5 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.storage.utils; + +import com.cloud.utils.StringUtils; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; + +import javax.inject.Inject; +import java.net.URI; + +@Component +public class Utility { + @Inject + OntapStorage ontapStorage; + + private static final String BASIC = "Basic"; + private static final String AUTH_HEADER_COLON = ":"; + /** + * Method generates authentication headers using storage backend credentials passed as normal string + * @param username -->> username of the storage backend + * @param password -->> normal decoded password of the storage backend + * @return + */ + public String generateAuthHeader(String username, String password) { + byte[] encodedBytes = Base64Utils.encode((username + AUTH_HEADER_COLON + password).getBytes()); + return BASIC + StringUtils.SPACE + new String(encodedBytes); + } + + public URI generateURI(String path) { + String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + path; + return URI.create(uriString); + } +} diff --git a/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/logback-spring.xml b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/logback-spring.xml new file mode 100644 index 000000000000..15872c82a64e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/logback-spring.xml @@ -0,0 +1,45 @@ + + + + + + + logs/feign-requests.log + + logs/feign-requests.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + diff --git a/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/module.properties b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/module.properties new file mode 100644 index 000000000000..67fd086eba10 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/module.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +name=storage-volume-ontap +parent=storage diff --git a/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/spring-storage-volume-ontap-context.xml b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/spring-storage-volume-ontap-context.xml new file mode 100644 index 000000000000..6ab9c46fcf9d --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/resources/META-INF/cloudstack/storage-volume-ontap/spring-storage-volume-ontap-context.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java index ce38727e42e5..8bfce47b1204 100644 --- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java @@ -40,6 +40,7 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.query.vo.StoragePoolJoinVO; import com.cloud.capacity.CapacityManager; +import com.cloud.server.ResourceTag; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; @@ -152,6 +153,7 @@ public StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO pool, boolea } } } + poolResponse.setCapacityBytes(pool.getCapacityBytes()); poolResponse.setDiskSizeTotal(pool.getCapacityBytes()); poolResponse.setDiskSizeAllocated(allocatedSize); poolResponse.setDiskSizeUsed(pool.getUsedBytes()); @@ -180,6 +182,8 @@ public StoragePoolResponse newStoragePoolResponse(StoragePoolJoinVO pool, boolea poolResponse.setIsTagARule(pool.getIsTagARule()); poolResponse.setOverProvisionFactor(Double.toString(CapacityManager.StorageOverprovisioningFactor.valueIn(pool.getId()))); poolResponse.setManaged(storagePool.isManaged()); + Map details = ApiDBUtils.getResourceDetails(pool.getId(), ResourceTag.ResourceObjectType.Storage); + poolResponse.setDetails(details); // set async job if (pool.getJobId() != null) { @@ -252,6 +256,7 @@ public StoragePoolResponse newStoragePoolForMigrationResponse(StoragePoolJoinVO } long allocatedSize = pool.getUsedCapacity(); + poolResponse.setCapacityBytes(pool.getCapacityBytes()); poolResponse.setDiskSizeTotal(pool.getCapacityBytes()); poolResponse.setDiskSizeAllocated(allocatedSize); poolResponse.setCapacityIops(pool.getCapacityIops()); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 2c5a931a8319..6f9db1dcdbd5 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -50,6 +50,9 @@ import javax.naming.ConfigurationException; import com.cloud.consoleproxy.ConsoleProxyManager; +import com.cloud.network.router.VirtualNetworkApplianceManager; +import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.vm.VirtualMachineManager; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; @@ -638,6 +641,11 @@ protected void overProvisioningFactorsForValidation() { protected void populateConfigKeysAllowedOnlyForDefaultAdmin() { configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key()); configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.allowOperationsOnUsersInSameAccount.key()); + + configKeysAllowedOnlyForDefaultAdmin.add(VirtualMachineManager.SystemVmEnableUserData.key()); + configKeysAllowedOnlyForDefaultAdmin.add(ConsoleProxyManager.ConsoleProxyVmUserData.key()); + configKeysAllowedOnlyForDefaultAdmin.add(SecondaryStorageVmManager.SecondaryStorageVmUserData.key()); + configKeysAllowedOnlyForDefaultAdmin.add(VirtualNetworkApplianceManager.VirtualRouterUserData.key()); } private void initMessageBusListener() { @@ -8432,7 +8440,7 @@ protected void validateCacheMode(String cacheMode){ !Enums.getIfPresent(DiskOffering.DiskCacheMode.class, cacheMode.toUpperCase()).isPresent()) { throw new InvalidParameterValueException(String.format("Invalid cache mode (%s). Please specify one of the following " + - "valid cache mode parameters: none, writeback or writethrough", cacheMode)); + "valid cache mode parameters: none, writeback, writethrough or hypervisor_default.", cacheMode)); } } diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java index 7b5fc123fb0e..f344b1b7c46a 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java @@ -97,6 +97,12 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService { ConfigKey ConsoleProxyManagementLastState = new ConfigKey(ConfigKey.CATEGORY_ADVANCED, String.class, "consoleproxy.management.state.last", com.cloud.consoleproxy.ConsoleProxyManagementState.Auto.toString(), "last console proxy service management state", false, ConfigKey.Kind.Select, consoleProxyManagementStates); + ConfigKey ConsoleProxyVmUserData = new ConfigKey<>(String.class, "console.proxy.vm.userdata", + ConfigKey.CATEGORY_ADVANCED, "", + "UUID for user data for console proxy VMs. This works only when systemvm.userdata.enabled is set to true", + true, ConfigKey.Scope.Zone, null, "User Data for CPVMs", + null, ConfigKey.GROUP_SYSTEM_VMS, ConfigKey.SUBGROUP_CONSOLE_PROXY_VM); + void setManagementState(ConsoleProxyManagementState state); ConsoleProxyManagementState getManagementState(); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index c273cf40e2fd..7089f1354ec1 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -48,6 +48,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.userdata.UserDataManager; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -152,6 +153,8 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; +import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData; + /** * Class to manage console proxys.

* Possible console proxy state transition cases:
@@ -227,6 +230,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy private CAManager caManager; @Inject private NetworkOrchestrationService networkMgr; + @Inject + private UserDataManager userDataManager; private ConsoleProxyListener consoleProxyListener; @@ -1265,6 +1270,19 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl buf.append(" vncport=").append(getVncPort(datacenterId)); } buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); + + if (SystemVmEnableUserData.valueIn(dc.getId())) { + String userDataUuid = ConsoleProxyVmUserData.valueIn(dc.getId()); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the cpvm, ignored", e); + } + } + String bootArgs = buf.toString(); if (logger.isDebugEnabled()) { logger.debug("Boot Args for " + profile + ": " + bootArgs); @@ -1570,9 +1588,10 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { ConsoleProxySslEnabled, NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, ConsoleProxyServiceOffering, - ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyCmdPort, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax, - ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState, NoVncConsoleShowDot }; + return new ConfigKey[] {ConsoleProxySslEnabled, NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, ConsoleProxyServiceOffering, + ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyCmdPort, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax, + ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState, NoVncConsoleShowDot, + ConsoleProxyVmUserData}; } protected ConsoleProxyStatus parseJsonToConsoleProxyStatus(String json) throws JsonParseException { diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java index f77081aa96cc..8ef77d3fb32f 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java @@ -64,6 +64,12 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA ConfigKey RouterTemplateOvm3 = new ConfigKey<>(String.class, RouterTemplateOvm3CK, "Advanced", "SystemVM Template (Ovm3)", "Name of the default router template on Ovm3.", true, ConfigKey.Scope.Zone, null); + ConfigKey VirtualRouterUserData = new ConfigKey<>(String.class, "virtual.router.userdata", + ConfigKey.CATEGORY_ADVANCED, "", + "UUID for user data of VR, VPC VR, internal LB, and elastic LB. This works only when systemvm.userdata.enabled is set to true", + true, ConfigKey.Scope.Zone, null, "User Data for VRs", + null, ConfigKey.GROUP_SYSTEM_VMS, ConfigKey.SUBGROUP_VIRTUAL_ROUTER); + ConfigKey SetServiceMonitor = new ConfigKey<>(Boolean.class, SetServiceMonitorCK, "Advanced", "true", "service monitoring in router enable/disable option, default true", true, ConfigKey.Scope.Zone, null); diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index eb5995b56f88..e881bd7ac6e8 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -18,6 +18,7 @@ package com.cloud.network.router; import static com.cloud.utils.NumbersUtil.toHumanReadableSize; +import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData; import java.lang.reflect.Type; import java.math.BigInteger; @@ -71,6 +72,7 @@ import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.network.topology.NetworkTopology; import org.apache.cloudstack.network.topology.NetworkTopologyContext; +import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.usage.UsageUtils; @@ -352,6 +354,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V @Inject BGPService bgpService; + @Inject + private UserDataManager userDataManager; private int _routerStatsInterval = 300; private int _routerCheckInterval = 30; private int _rvrStatusUpdatePoolSize = 10; @@ -2096,6 +2100,18 @@ public boolean finalizeVirtualMachineProfile(final VirtualMachineProfile profile " on the virtual router.", RouterLogrotateFrequency.key(), routerLogrotateFrequency, dc.getUuid())); buf.append(String.format(" logrotatefrequency=%s", routerLogrotateFrequency)); + if (SystemVmEnableUserData.valueIn(router.getDataCenterId())) { + String userDataUuid = VirtualRouterUserData.valueIn(dc.getId()); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the virtual router, ignored", e); + } + } + if (logger.isDebugEnabled()) { logger.debug("Boot Args for " + profile + ": " + buf); } @@ -3355,7 +3371,8 @@ public ConfigKey[] getConfigKeys() { RouterHealthChecksMaxMemoryUsageThreshold, ExposeDnsAndBootpServer, RouterLogrotateFrequency, - RemoveControlIpOnStop + RemoveControlIpOnStop, + VirtualRouterUserData }; } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index cbeec1a60b36..abaf0e3d0e50 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -3072,7 +3072,8 @@ private void handleRemoveChildStoragePoolFromDatastoreCluster(Set childD } - private void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer) { + @Override + public void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer) { StoragePoolHostVO poolHost = _storagePoolHostDao.findByPoolHost(pool.getId(), hostId); if (poolHost == null) { poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/")); @@ -3082,8 +3083,10 @@ private void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, Modi } StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId()); - poolVO.setUsedBytes(mspAnswer.getPoolInfo().getCapacityBytes() - mspAnswer.getPoolInfo().getAvailableBytes()); - poolVO.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes()); + if (!Storage.StoragePoolType.StorPool.equals(poolVO.getPoolType())) { + poolVO.setUsedBytes(mspAnswer.getPoolInfo().getCapacityBytes() - mspAnswer.getPoolInfo().getAvailableBytes()); + poolVO.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes()); + } _storagePoolDao.update(pool.getId(), poolVO); } diff --git a/server/src/main/java/com/cloud/storage/StoragePoolAutomationImpl.java b/server/src/main/java/com/cloud/storage/StoragePoolAutomationImpl.java index eea4da4879f4..7407de6c963d 100644 --- a/server/src/main/java/com/cloud/storage/StoragePoolAutomationImpl.java +++ b/server/src/main/java/com/cloud/storage/StoragePoolAutomationImpl.java @@ -363,6 +363,7 @@ public boolean cancelMaintain(DataStore store, Map details) { if (logger.isDebugEnabled()) { logger.debug("ModifyStoragePool add succeeded"); } + storageManager.updateStoragePoolHostVOAndBytes(pool, host.getId(), (ModifyStoragePoolAnswer) answer); if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) { logger.debug("Started synchronising datastore cluster storage pool {} with vCenter", pool); storageManager.syncDatastoreClusterStoragePool(pool.getId(), ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), host.getId()); diff --git a/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java b/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java index a34658a7f6d4..599e8c171c51 100644 --- a/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java +++ b/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java @@ -44,6 +44,13 @@ public interface SecondaryStorageVmManager extends Manager { "The time interval(in millisecond) to scan whether or not system needs more SSVM to ensure minimal standby capacity", false); + ConfigKey SecondaryStorageVmUserData = new ConfigKey<>(String.class, "secstorage.vm.userdata", + ConfigKey.CATEGORY_ADVANCED, "", + "UUID for user data for secondary storage VMs. This works only when systemvm.userdata.enabled is set to true", + true, ConfigKey.Scope.Zone, null, "User Data for SSVMs", + null, ConfigKey.GROUP_SYSTEM_VMS, ConfigKey.SUBGROUP_SEC_STORAGE_VM); + + public static final int DEFAULT_SS_VM_RAMSIZE = 512; // 512M public static final int DEFAULT_SS_VM_CPUMHZ = 500; // 500 MHz public static final int DEFAULT_SS_VM_MTUSIZE = 1500; diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index b5da605f3288..bbfc8fd36826 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2754,7 +2754,10 @@ protected UserVO createUser(long accountId, String userName, String password, St logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone); } - passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId()); + Account callingAccount = getCurrentCallingAccount(); + if (callingAccount.getId() != Account.ACCOUNT_ID_SYSTEM) { + passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId()); + } String encodedPassword = null; for (UserAuthenticator authenticator : _userPasswordEncoders) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index f2a8a672d42a..21ac6e3eb36e 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -83,6 +83,15 @@ public interface UserVmManager extends UserVmService { "If set to true, tags specified in `resource.limit.host.tags` are also included in vm.strict.host.tags.", true); + ConfigKey EnableAdditionalVmConfig = new ConfigKey<>( + "Advanced", + Boolean.class, + "enable.additional.vm.configuration", + "false", + "allow additional arbitrary configuration to vm", + true, + ConfigKey.Scope.Account); + static final int MAX_USER_DATA_LENGTH_BYTES = 2048; public static final String CKS_NODE = "cksnode"; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index a9877b97f848..f1bbfa072928 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -689,9 +689,6 @@ public void setKubernetesServiceHelpers(final List kube private static final ConfigKey AllowDeployVmIfGivenHostFails = new ConfigKey("Advanced", Boolean.class, "allow.deploy.vm.if.deploy.on.given.host.fails", "false", "allow vm to deploy on different host if vm fails to deploy on the given host ", true); - private static final ConfigKey EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class, - "enable.additional.vm.configuration", "false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account); - private static final ConfigKey KvmAdditionalConfigAllowList = new ConfigKey<>(String.class, "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null); @@ -775,7 +772,7 @@ private class VmIpAddrFetchThread extends ManagedContextRunnable { String networkCidr; String macAddress; - public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) { + public VmIpAddrFetchThread(long vmId, String vmUuid, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) { this.vmId = vmId; this.vmUuid = vmUuid; this.nicId = nicId; @@ -797,8 +794,13 @@ protected void runInContext() { Answer answer = _agentMgr.send(hostId, cmd); if (answer.getResult()) { String vmIp = answer.getDetails(); - - if (NetUtils.isValidIp4(vmIp)) { + if (vmIp == null) { + // we got a valid response and the NIC does not have an IP assigned, as such we will update the database with null + if (nic.getIPv4Address() != null) { + nic.setIPv4Address(null); + _nicDao.update(nicId, nic); + } + } else if (NetUtils.isValidIp4(vmIp)) { // set this vm ip addr in vm nic. if (nic != null) { nic.setIPv4Address(vmIp); @@ -813,12 +815,8 @@ protected void runInContext() { } } } else { - //previously vm has ip and nic table has ip address. After vm restart or stop/start - //if vm doesnot get the ip then set the ip in nic table to null - if (nic.getIPv4Address() != null) { - nic.setIPv4Address(null); - _nicDao.update(nicId, nic); - } + // since no changes are being done, we should not decrement IP usage + decrementCount = false; if (answer.getDetails() != null) { logger.debug("Failed to get vm ip for Vm [id: {}, uuid: {}, name: {}], details: {}", vmId, vmUuid, vmName, answer.getDetails()); @@ -2726,7 +2724,8 @@ protected void runInContext() { VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(userVm); VirtualMachine vm = vmProfile.getVirtualMachine(); boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); - _vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, nicId, vmInstance.getInstanceName(), + + _vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, vmInstance.getUuid(), nicId, vmInstance.getInstanceName(), isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress())); } @@ -6604,7 +6603,7 @@ private void checkIfVolumeTemplateIsTheSameAsTheProvided(VolumeInfo volume, Long protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) { boolean isValidConfig = isValidKeyValuePair(decodedUrl); if (isValidConfig) { - String[] extraConfigs = decodedUrl.split("\\r?\\n"); + String[] extraConfigs = decodedUrl.split("\\r?\\n+"); for (String cfg : extraConfigs) { // Validate cfg against unsupported operations set by admin here String[] allowedKeyList = VmwareAdditionalConfigAllowList.value().split(","); @@ -6632,7 +6631,7 @@ protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) { protected void persistExtraConfigXenServer(String decodedUrl, UserVm vm) { boolean isValidConfig = isValidKeyValuePair(decodedUrl); if (isValidConfig) { - String[] extraConfigs = decodedUrl.split("\\r?\\n"); + String[] extraConfigs = decodedUrl.split("\\r?\\n+"); int i = 1; String extraConfigKey = ApiConstants.EXTRA_CONFIG + "-"; for (String cfg : extraConfigs) { @@ -6712,8 +6711,8 @@ protected void persistExtraConfigKvm(String decodedUrl, UserVm vm) { // validate config against denied cfg commands validateKvmExtraConfig(decodedUrl, vm.getAccountId()); String[] extraConfigs = decodedUrl.split("\n\n"); + int i = 1; for (String cfg : extraConfigs) { - int i = 1; String[] cfgParts = cfg.split("\n"); String extraConfigKey = ApiConstants.EXTRA_CONFIG; String extraConfigValue; diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java index c7ec7f0c4881..377e57b31e93 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java @@ -55,6 +55,17 @@ public interface UserPasswordResetManager { "Use auth in the SMTP server for sending emails for resetting password for ACS users", false, ConfigKey.Scope.Global); + ConfigKey UserPasswordResetSMTPUseStartTLS = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + Boolean.class, "user.password.reset.smtp.useStartTLS", "false", + "If set to true and if we enable security via user.password.reset.smtp.useAuth, this will enable StartTLS to secure the connection.", + true, + ConfigKey.Scope.Global); + + ConfigKey UserPasswordResetSMTPEnabledSecurityProtocols = new ConfigKey(ConfigKey.CATEGORY_ADVANCED, + String.class, "user.password.reset.smtp.enabledSecurityProtocols", "", + "White-space separated security protocols; ex: \"TLSv1 TLSv1.1\". Supported protocols: SSLv2Hello, SSLv3, TLSv1, TLSv1.1 and TLSv1.2", + true, ConfigKey.Kind.WhitespaceSeparatedListWithOptions, "SSLv2Hello,SSLv3,TLSv1,TLSv1.1,TLSv1.2"); + ConfigKey UserPasswordResetSMTPUsername = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, String.class, "user.password.reset.smtp.username", null, "Username for SMTP server for sending emails for resetting password for ACS users", diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java index 8a278f6e45d7..618ad5c86572 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -92,6 +92,8 @@ public ConfigKey[] getConfigKeys() { UserPasswordResetSMTPHost, UserPasswordResetSMTPPort, UserPasswordResetSMTPUseAuth, + UserPasswordResetSMTPUseStartTLS, + UserPasswordResetSMTPEnabledSecurityProtocols, UserPasswordResetSMTPUsername, UserPasswordResetSMTPPassword, UserPasswordResetDomainURL, @@ -106,6 +108,8 @@ public boolean configure(String name, Map params) throws Configu Boolean useAuth = UserPasswordResetSMTPUseAuth.value(); String username = UserPasswordResetSMTPUsername.value(); String password = UserPasswordResetSMTPPassword.value(); + Boolean useStartTLS = UserPasswordResetSMTPUseStartTLS.value(); + String enabledSecurityProtocols = UserPasswordResetSMTPEnabledSecurityProtocols.value(); if (!StringUtils.isEmpty(smtpHost) && smtpPort != null && smtpPort > 0) { String namespace = "password.reset.smtp"; @@ -117,6 +121,8 @@ public boolean configure(String name, Map params) throws Configu configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_AUTH), useAuth.toString()); configs.put(getKey(namespace, SMTPMailSender.CONFIG_USERNAME), username); configs.put(getKey(namespace, SMTPMailSender.CONFIG_PASSWORD), password); + configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_STARTTLS), useStartTLS.toString()); + configs.put(getKey(namespace, SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), enabledSecurityProtocols); mailSender = new SMTPMailSender(configs, namespace); } diff --git a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index fae8e69a386c..2dbd34ad8ad6 100644 --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.secondarystorage; import static com.cloud.configuration.Config.SecStorageAllowedInternalDownloadSites; +import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData; import java.net.URI; import java.net.URISyntaxException; @@ -50,6 +51,7 @@ import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; @@ -255,6 +257,9 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar private IndirectAgentLB indirectAgentLB; @Inject private CAManager caManager; + @Inject + private UserDataManager userDataManager; + private int _secStorageVmMtuSize; private String _instance; @@ -1227,6 +1232,19 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl String nfsVersion = imageStoreDetailsUtil != null ? imageStoreDetailsUtil.getNfsVersion(secStores.get(0).getId()) : null; buf.append(" nfsVersion=").append(nfsVersion); buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); + + if (SystemVmEnableUserData.valueIn(dc.getId())) { + String userDataUuid = SecondaryStorageVmUserData.valueIn(dc.getId()); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the ssvm, ignored", e); + } + } + String bootArgs = buf.toString(); if (logger.isDebugEnabled()) { logger.debug(String.format("Boot args for machine profile [%s]: [%s].", profile.toString(), bootArgs)); @@ -1529,7 +1547,8 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {NTPServerConfig, MaxNumberOfSsvmsForMigration, SecondaryStorageCapacityScanInterval}; + return new ConfigKey[] {NTPServerConfig, MaxNumberOfSsvmsForMigration, SecondaryStorageCapacityScanInterval, + SecondaryStorageVmUserData}; } } diff --git a/systemvm/debian/opt/cloud/bin/setup/init.sh b/systemvm/debian/opt/cloud/bin/setup/init.sh index b6da70593669..66b08b992059 100755 --- a/systemvm/debian/opt/cloud/bin/setup/init.sh +++ b/systemvm/debian/opt/cloud/bin/setup/init.sh @@ -20,6 +20,8 @@ set -x PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" CMDLINE=/var/cache/cloud/cmdline +. /lib/lsb/init-functions + log_it() { echo "$(date) $@" >> /var/log/cloud.log log_action_msg "$@" diff --git a/systemvm/debian/opt/cloud/bin/setup/postinit.sh b/systemvm/debian/opt/cloud/bin/setup/postinit.sh index 801770fcc834..1ad4de4c1a56 100755 --- a/systemvm/debian/opt/cloud/bin/setup/postinit.sh +++ b/systemvm/debian/opt/cloud/bin/setup/postinit.sh @@ -18,6 +18,8 @@ # # This scripts before ssh.service but after cloud-early-config +. /lib/lsb/init-functions + log_it() { echo "$(date) $@" >> /var/log/cloud.log log_action_msg "$@" @@ -47,6 +49,97 @@ fi CMDLINE=/var/cache/cloud/cmdline TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) + +# Execute cloud-init if user data is present +run_cloud_init() { + if [ ! -f "$CMDLINE" ]; then + log_it "No cmdline file found, skipping cloud-init execution" + return 0 + fi + + local encoded_userdata=$(grep -Po 'userdata=\K[^[:space:]]*' "$CMDLINE" || true) + if [ -z "$encoded_userdata" ]; then + log_it "No user data found in cmdline, skipping cloud-init execution" + return 0 + fi + + log_it "User data detected, setting up and running cloud-init" + + # Update cloud-init config to use NoCloud datasource + cat < /etc/cloud/cloud.cfg.d/cloudstack.cfg +#cloud-config +datasource_list: ['NoCloud'] +network: + config: disabled +manage_etc_hosts: false +manage_resolv_conf: false +users: [] +disable_root: false +ssh_pwauth: false +cloud_init_modules: + - migrator + - seed_random + - bootcmd + - write-files + - growpart + - resizefs + - disk_setup + - mounts + - rsyslog +cloud_config_modules: + - locale + - timezone + - runcmd +cloud_final_modules: + - scripts-per-once + - scripts-per-boot + - scripts-per-instance + - scripts-user + - final-message + - power-state-change +EOF + + # Set up user data files (reuse the function from init.sh) + mkdir -p /var/lib/cloud/seed/nocloud + + # Decode and decompress user data + local decoded_userdata + decoded_userdata=$(echo "$encoded_userdata" | base64 -d 2>/dev/null | gunzip 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$decoded_userdata" ]; then + log_it "ERROR: Failed to decode or decompress user data" + return 1 + fi + + # Write user data + echo "$decoded_userdata" > /var/lib/cloud/seed/nocloud/user-data + chmod 600 /var/lib/cloud/seed/nocloud/user-data + + # Create meta-data + local instance_name=$(grep -Po 'name=\K[^[:space:]]*' "$CMDLINE" || hostname) + cat > /var/lib/cloud/seed/nocloud/meta-data << EOF +instance-id: $instance_name +local-hostname: $instance_name +EOF + chmod 644 /var/lib/cloud/seed/nocloud/meta-data + + log_it "User data files created, executing cloud-init..." + + # Run cloud-init stages manually + cloud-init init --local && \ + cloud-init init && \ + cloud-init modules --mode=config && \ + cloud-init modules --mode=final + + local cloud_init_result=$? + if [ $cloud_init_result -eq 0 ]; then + log_it "Cloud-init executed successfully" + else + log_it "ERROR: Cloud-init execution failed with exit code: $cloud_init_result" + fi + + return $cloud_init_result +} + if [ "$TYPE" == "router" ] || [ "$TYPE" == "vpcrouter" ] || [ "$TYPE" == "dhcpsrvr" ] then if [ -x /opt/cloud/bin/update_config.py ] @@ -71,4 +164,6 @@ do systemctl disable --now --no-block $svc done +run_cloud_init + date > /var/cache/cloud/boot_up_done diff --git a/test/integration/smoke/test_ssvm.py b/test/integration/smoke/test_ssvm.py index 0784bc3820c6..798401f68e1f 100644 --- a/test/integration/smoke/test_ssvm.py +++ b/test/integration/smoke/test_ssvm.py @@ -18,15 +18,14 @@ """ # Import Local Modules from marvin.cloudstackTestCase import cloudstackTestCase -from marvin.cloudstackAPI import (stopSystemVm, +from marvin.cloudstackAPI import (getDiagnosticsData, stopSystemVm, rebootSystemVm, destroySystemVm, updateConfiguration) from marvin.lib.utils import (cleanup_resources, get_process_status, get_host_credentials, wait_until) -from marvin.lib.base import (PhysicalNetwork, - NetScaler, ImageStore) +from marvin.lib.base import (PhysicalNetwork, NetScaler, ImageStore, UserData) from marvin.lib.common import (get_zone, list_hosts, list_ssvms, @@ -35,6 +34,10 @@ from nose.plugins.attrib import attr import telnetlib import logging +import base64 +import os +import urllib +import zipfile # Import System modules import time diff --git a/test/integration/smoke/test_systemvm_userdata.py b/test/integration/smoke/test_systemvm_userdata.py new file mode 100644 index 000000000000..e34ae53fcc75 --- /dev/null +++ b/test/integration/smoke/test_systemvm_userdata.py @@ -0,0 +1,410 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import ( + getDiagnosticsData, + stopSystemVm, + rebootSystemVm, + destroySystemVm, + updateConfiguration, +) +from marvin.lib.utils import ( + cleanup_resources, + get_process_status, + get_host_credentials, + wait_until, +) +from marvin.lib.base import UserData, Network +from marvin.lib.common import ( + get_zone, + list_hosts, + list_routers, + list_ssvms, + list_zones, + list_vlan_ipranges, + createEnabledNetworkOffering, +) +from marvin.codes import PASS +from nose.plugins.attrib import attr +import telnetlib +import logging +import base64 +import os +import urllib +import zipfile +import uuid +import shutil + +# Import System modules +import time + +_multiprocess_shared_ = True + + +class TestSystemVMUserData(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.testClient = super(TestSystemVMUserData, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + # Fill services from the external config file + cls.testData = cls.testClient.getParsedTestDataConfig() + + # Enable user data and set the script to be run on SSVM + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = "systemvm.userdata.enabled" + cmd.value = "true" + cls.api_client.updateConfiguration(cmd) + + @classmethod + def tearDownClass(cls): + # Disable user data + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = "systemvm.userdata.enabled" + cmd.value = "false" + cls.api_client.updateConfiguration(cmd) + + def setUp(self): + test_case = super(TestSystemVMUserData, self) + self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() + self.cleanup = [] + self.config = test_case.getClsConfig() + self.services = self.testClient.getParsedTestDataConfig() + self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) + + self.logger = logging.getLogger("TestSystemVMUserData") + self.stream_handler = logging.StreamHandler() + self.logger.setLevel(logging.DEBUG) + self.logger.addHandler(self.stream_handler) + + def tearDown(self): + if self.userdata_id: + UserData.delete(self.apiclient, self.userdata_id) + self.userdata_id = None + + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def waitForSystemVMAgent(self, vmname): + def checkRunningAgent(): + list_host_response = list_hosts(self.apiclient, name=vmname) + if isinstance(list_host_response, list): + return list_host_response[0].state == "Up", None + return False, None + + res, _ = wait_until(3, 300, checkRunningAgent) + if not res: + raise Exception("Failed to wait for SSVM agent to be Up") + + def checkForRunningSystemVM(self, ssvm, ssvm_type=None): + if not ssvm: + return None + + def checkRunningState(): + if not ssvm_type: + response = list_ssvms(self.apiclient, id=ssvm.id) + else: + response = list_ssvms( + self.apiclient, zoneid=self.zone.id, systemvmtype=ssvm_type + ) + + if isinstance(response, list): + ssvm_response = response[0] + return ssvm_response.state == "Running", ssvm_response + return False, None + + res, ssvm_response = wait_until(3, 300, checkRunningState) + if not res: + self.fail("Failed to reach systemvm state to Running") + return ssvm_response + + def register_userdata( + self, userdata_name, global_setting_name, vm_type_display_name + ): + """Helper method to register userdata and configure the global setting + + Args: + userdata_name: Name for the userdata entry + global_setting_name: Global setting name to update (e.g., 'secstorage.vm.userdata', 'console.proxy.vm.userdata', 'virtual.router.userdata') + vm_type_display_name: Display name for the VM type (e.g., 'SSVM', 'CPVM', 'VR') + + Returns: + UserData object + """ + userdata_script = f"""#!/bin/bash +echo "User data script ran successfully on {vm_type_display_name}" > /tmp/userdata.txt +""" + b64_encoded_userdata = base64.b64encode(userdata_script.encode("utf-8")).decode( + "utf-8" + ) + + # Create a userdata entry + try: + userdata = UserData.register( + self.apiclient, name=userdata_name, userdata=b64_encoded_userdata + ) + userdata_id = userdata["userdata"]["id"] + except Exception as e: + if "already exists" in str(e): + self.debug("Userdata already exists, getting it") + userdata = UserData.list( + self.apiclient, name=userdata_name, listall=True + )[0] + userdata_id = userdata.id + else: + self.fail("Failed to register userdata: %s" % e) + + # Update global configuration to use this userdata + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = global_setting_name + cmd.value = userdata_id + self.apiclient.updateConfiguration(cmd) + self.debug( + "Updated global setting %s with userdata ID: %s" + % (global_setting_name, userdata.id) + ) + + return userdata_id + + def download_and_verify_diagnostics_data( + self, target_id, vm_type_display_name, expected_content, retries=4 + ): + """Helper method to download and verify diagnostics data + + Args: + target_id: ID of the target VM/router + vm_type_display_name: Display name for log messages (e.g., 'SSVM', 'CPVM', 'VR') + expected_content: Expected content to verify in the userdata file + retries: Number of retries for getDiagnosticsData (default: 4) + """ + # Create a random temporary directory for this test + random_suffix = uuid.uuid4().hex[:8] + vm_type_prefix = vm_type_display_name.lower() + temp_dir = f"/tmp/{vm_type_prefix}-{random_suffix}" + os.makedirs(temp_dir, exist_ok=True) + + # Download the file created by userdata script using the getDiagnosticsData command + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = target_id + cmd.files = "/tmp/userdata.txt" + + # getDiagnosticsData command takes some time to work after a VM is started + response = None + while retries > -1: + try: + response = self.apiclient.getDiagnosticsData(cmd) + break # Success, exit retry loop + except Exception as e: + if retries >= 0: + retries = retries - 1 + self.debug( + "getDiagnosticsData failed (retries left: %d): %s" + % (retries + 1, e) + ) + if retries > -1: + time.sleep(30) + continue + # If all retries exhausted, re-raise the exception + self.fail("Failed to get diagnostics data after retries: %s" % e) + + # Download response.url file to temporary directory and extract it + zip_file_path = os.path.join(temp_dir, "userdata.zip") + extracted_file_path = os.path.join(temp_dir, "userdata.txt") + + self.debug( + "Downloading userdata file from %s to %s" + % (vm_type_display_name, zip_file_path) + ) + try: + urllib.request.urlretrieve(response.url, zip_file_path) + except Exception as e: + self.fail( + "Failed to download userdata file from %s: %s" + % (vm_type_display_name, e) + ) + self.debug( + "Downloaded userdata file from %s: %s" + % (vm_type_display_name, zip_file_path) + ) + + try: + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(temp_dir) + except zipfile.BadZipFile as e: + self.fail("Downloaded userdata file is not a zip file: %s" % e) + self.debug("Extracted userdata file from zip: %s" % extracted_file_path) + + # Verify the file contains the expected content + try: + with open(extracted_file_path, "r") as f: + content = f.read().strip() + self.debug("Userdata file content: %s" % content) + self.assertEqual( + expected_content in content, + True, + f"Check that userdata file contains expected content: '{expected_content}'", + ) + except FileNotFoundError: + self.fail( + "Userdata file not found in extracted zip at %s" % extracted_file_path + ) + except Exception as e: + self.fail("Failed to read userdata file: %s" % e) + finally: + # Clean up temporary directory + try: + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + self.debug("Cleaned up temporary directory: %s" % temp_dir) + except Exception as e: + self.debug( + "Failed to clean up temporary directory %s: %s" % (temp_dir, e) + ) + + def test_userdata_on_systemvm( + self, systemvm_type, userdata_name, vm_type_display_name, global_setting_name + ): + """Helper method to test user data functionality on system VMs + + Args: + systemvm_type: Type of system VM ('secondarystoragevm' or 'consoleproxy') + userdata_name: Name for the userdata entry + vm_type_display_name: Display name for log messages (e.g., 'SSVM' or 'CPVM') + global_setting_name: Global setting name for userdata (e.g., 'secstorage.vm.userdata' or 'console.proxy.vm.userdata') + """ + # 1) Register userdata and configure global setting + self.userdata_id = self.register_userdata( + userdata_name, global_setting_name, vm_type_display_name + ) + + # 2) Get and destroy the system VM to trigger recreation with userdata + list_ssvm_response = list_ssvms( + self.apiclient, + systemvmtype=systemvm_type, + state="Running", + zoneid=self.zone.id, + ) + self.assertEqual( + isinstance(list_ssvm_response, list), + True, + "Check list response returns a valid list", + ) + + ssvm = list_ssvm_response[0] + self.debug("Destroying %s: %s" % (vm_type_display_name, ssvm.id)) + cmd = destroySystemVm.destroySystemVmCmd() + cmd.id = ssvm.id + self.apiclient.destroySystemVm(cmd) + + # 3) Wait for the system VM to be running again + ssvm_response = self.checkForRunningSystemVM(ssvm, systemvm_type) + self.debug( + "%s state after restart: %s" % (vm_type_display_name, ssvm_response.state) + ) + self.assertEqual( + ssvm_response.state, + "Running", + "Check whether %s is running or not" % vm_type_display_name, + ) + # Wait for the agent to be up + self.waitForSystemVMAgent(ssvm_response.name) + + # 4) Download and verify the diagnostics data + expected_content = ( + f"User data script ran successfully on {vm_type_display_name}" + ) + self.download_and_verify_diagnostics_data( + ssvm_response.id, vm_type_display_name, expected_content + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_1_userdata_on_ssvm(self): + """Test user data functionality on SSVM""" + self.test_userdata_on_systemvm( + systemvm_type="secondarystoragevm", + userdata_name="ssvm_userdata", + vm_type_display_name="SSVM", + global_setting_name="secstorage.vm.userdata", + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_2_userdata_on_cpvm(self): + """Test user data functionality on CPVM""" + self.test_userdata_on_systemvm( + systemvm_type="consoleproxy", + userdata_name="cpvm_userdata", + vm_type_display_name="CPVM", + global_setting_name="console.proxy.vm.userdata", + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_3_userdata_on_vr(self): + """Test user data functionality on VR""" + # 1) Register userdata and configure global setting + self.userdata_id = self.register_userdata("vr_userdata", "virtual.router.userdata", "VR") + + # 2) Create an isolated network which will trigger VR creation with userdata + result = createEnabledNetworkOffering( + self.apiclient, self.testData["nw_off_isolated_persistent"] + ) + assert result[0] == PASS, ( + "Network offering creation/enabling failed due to %s" % result[2] + ) + isolated_persistent_network_offering = result[1] + + # Create an isolated network + self.network = Network.create( + self.apiclient, + self.testData["isolated_network"], + networkofferingid=isolated_persistent_network_offering.id, + zoneid=self.zone.id, + ) + self.assertIsNotNone(self.network, "Network creation failed") + self.cleanup.append(self.network) + self.cleanup.append(isolated_persistent_network_offering) + + # 3) Get the VR and verify it's running + routers = list_routers( + self.apiclient, networkid=self.network.id, state="Running" + ) + self.assertEqual( + isinstance(routers, list), + True, + "Check list router response returns a valid list", + ) + self.assertNotEqual(len(routers), 0, "Check list router response") + router = routers[0] + self.debug("Found VR: %s" % router.id) + + # 4) Download and verify the diagnostics data + # VR doesn't need retries as it's freshly created + expected_content = "User data script ran successfully on VR" + self.download_and_verify_diagnostics_data(router.id, "VR", expected_content) diff --git a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh index 02a5c39dc712..219586a97291 100755 --- a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh +++ b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh @@ -133,7 +133,7 @@ function configure_services() { systemctl disable containerd # Disable cloud init by default -cat < /etc/cloud/cloud.cfg.d/cloudstack.cfg + cat < /etc/cloud/cloud.cfg.d/cloudstack.cfg datasource_list: ['CloudStack'] datasource: CloudStack: diff --git a/tools/appliance/systemvmtemplate/template-base_aarch64-target_aarch64.json b/tools/appliance/systemvmtemplate/template-base_aarch64-target_aarch64.json index e87c37be61eb..f15552ca5cc4 100644 --- a/tools/appliance/systemvmtemplate/template-base_aarch64-target_aarch64.json +++ b/tools/appliance/systemvmtemplate/template-base_aarch64-target_aarch64.json @@ -32,8 +32,8 @@ "format": "qcow2", "headless": true, "http_directory": "http", - "iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3", - "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso", + "iso_checksum": "sha512:55ab206cd8b0da2898767c3eb6ab5ebef101e3925ec91b3b5f0a286136195b7072588f6ac2d059c545c6938978704ae78cd18d7d9d2a86a7380e46ce27ee4e7b", + "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.12.0/arm64/iso-cd/debian-12.12.0-arm64-netinst.iso", "net_device": "virtio-net", "output_directory": "../dist", "qemu_binary": "qemu-system-aarch64", diff --git a/tools/appliance/systemvmtemplate/template-base_x86_64-target_aarch64.json b/tools/appliance/systemvmtemplate/template-base_x86_64-target_aarch64.json index 4d803b258906..c16bf1fb2a29 100644 --- a/tools/appliance/systemvmtemplate/template-base_x86_64-target_aarch64.json +++ b/tools/appliance/systemvmtemplate/template-base_x86_64-target_aarch64.json @@ -31,8 +31,8 @@ "format": "qcow2", "headless": true, "http_directory": "http", - "iso_checksum": "sha512:892cf1185a214d16ff62a18c6b89cdcd58719647c99916f6214bfca6f9915275d727b666c0b8fbf022c425ef18647e9759974abf7fc440431c39b50c296a98d3", - "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/arm64/iso-cd/debian-12.11.0-arm64-netinst.iso", + "iso_checksum": "sha512:55ab206cd8b0da2898767c3eb6ab5ebef101e3925ec91b3b5f0a286136195b7072588f6ac2d059c545c6938978704ae78cd18d7d9d2a86a7380e46ce27ee4e7b", + "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.12.0/arm64/iso-cd/debian-12.12.0-arm64-netinst.iso", "net_device": "virtio-net", "output_directory": "../dist", "qemu_binary": "qemu-system-aarch64", diff --git a/tools/appliance/systemvmtemplate/template-base_x86_64-target_x86_64.json b/tools/appliance/systemvmtemplate/template-base_x86_64-target_x86_64.json index e825e98e5f10..941eea9dcd7b 100644 --- a/tools/appliance/systemvmtemplate/template-base_x86_64-target_x86_64.json +++ b/tools/appliance/systemvmtemplate/template-base_x86_64-target_x86_64.json @@ -27,8 +27,8 @@ "format": "qcow2", "headless": true, "http_directory": "http", - "iso_checksum": "sha512:0921d8b297c63ac458d8a06f87cd4c353f751eb5fe30fd0d839ca09c0833d1d9934b02ee14bbd0c0ec4f8917dde793957801ae1af3c8122cdf28dde8f3c3e0da", - "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.11.0/amd64/iso-cd/debian-12.11.0-amd64-netinst.iso", + "iso_checksum": "sha512:c93055182057dd19a334260671c7e10880541b7721ad9c8df87be47e0a11d5bbf85018350ff224ff6a5f6a68320b07e95d539cef9dc020c93966bfaa86d4b2ce", + "iso_url": "https://cdimage.debian.org/mirror/cdimage/archive/12.12.0/amd64/iso-cd/debian-12.12.0-amd64-netinst.iso", "net_device": "virtio-net", "output_directory": "../dist", "qemuargs": [ diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index d3e75d895add..c372076e0f3c 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1051,6 +1051,8 @@ "label.externalid": "External Id", "label.externalloadbalanceripaddress": "External load balancer IP address.", "label.extra": "Extra arguments", +"label.extraconfig": "Additional Configuration", +"label.extraconfig.tooltip": "Additional configuration parameters (extraconfig) to pass to the instance in plain text", "label.f5": "F5", "label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.", "label.failed": "Failed", @@ -1209,6 +1211,7 @@ "label.hourly": "Hourly", "label.hypervisor": "Hypervisor", "label.hypervisor.capabilities": "Hypervisor Capabilities", +"label.hypervisor.default": "Hypervisor default", "label.hypervisor.type": "Hypervisor type", "label.hypervisors": "Hypervisors", "label.hypervisorsnapshotreserve": "Hypervisor Snapshot reserve", @@ -2811,7 +2814,7 @@ "label.windows": "Windows", "label.with.snapshotid": "with Snapshot ID", "label.write": "Write", -"label.writeback": "Write-back disk caching", +"label.writeback": "Write-back", "label.writecachetype": "Write-cache Type", "label.writeio": "Write (IO)", "label.writethrough": "Write-through", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 711dd36deff1..c4b5add82fc3 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -773,6 +773,7 @@ "label.hourly": "A cada hora", "label.hypervisor": "Virtualizador", "label.hypervisor.capabilities": "Recursos do virtualizador", +"label.hypervisor.default": "Padr\u00e3o do virtualizador", "label.hypervisor.type": "Tipo do virtualizador", "label.hypervisors": "Virtualizadores", "label.hypervisorsnapshotreserve": "Reserva de snapshot do virtualizador", @@ -1813,7 +1814,7 @@ "label.windows": "Windows", "label.with.snapshotid": "com o ID da snapshot", "label.write": "Escreva", -"label.writeback": "Cache de disco write-back", +"label.writeback": "Write-back", "label.writecachetype": "Tipo do cache de escrita", "label.writeio": "Escrita (IO)", "label.writethrough": "Write-through", diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js index 51037164aed4..2c0edf656d73 100644 --- a/ui/src/store/modules/user.js +++ b/ui/src/store/modules/user.js @@ -338,7 +338,6 @@ const user = { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) - store.dispatch('SetCsLatestVersion', result.rolename) resolve(cachedApis) }).catch(error => { reject(error) @@ -588,6 +587,9 @@ const user = { commit('SET_DOMAIN_STORE', domainStore) }, SetCsLatestVersion ({ commit }, rolename) { + if (!vueProps.$config.notifyLatestCSVersion) { + return + } const lastFetchTs = store.getters.latestVersion?.fetchedTs ? store.getters.latestVersion.fetchedTs : 0 if (rolename === 'Root Admin' && (+new Date() - lastFetchTs) > 24 * 60 * 60 * 1000) { axios.get( diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index 854d082075f1..423338c92641 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -762,6 +762,12 @@ + + + + 0) { deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata) } + if (values.extraconfig && values.extraconfig.length > 0) { + deployVmData.extraconfig = encodeURIComponent(values.extraconfig) + } // step 2: select template/iso if (this.imageType === 'templateid') { deployVmData.templateid = values.templateid diff --git a/ui/src/views/extension/CreateExtension.vue b/ui/src/views/extension/CreateExtension.vue index 0dfde2382796..9a7e0c8ae769 100644 --- a/ui/src/views/extension/CreateExtension.vue +++ b/ui/src/views/extension/CreateExtension.vue @@ -46,15 +46,15 @@ -
- +
+ {{ extenstionBasePath }}
@@ -236,20 +236,20 @@ export default { } } -.entry-point-input-container { +.path-input-container { display: flex; align-items: center; gap: 8px; } -.entry-point-input-base-path { +.path-input-base { max-width: 70%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } -.entry-point-input-relative-path { +.path-input-relative { flex: 1 1 0%; min-width: 0; } diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue b/ui/src/views/infra/zone/ZoneWizardAddResources.vue index dc7aeabf24ac..b2a273f4c882 100644 --- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue +++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue @@ -396,7 +396,7 @@ export default { placeHolder: 'message.error.server', required: true, display: { - primaryStorageProtocol: ['nfs', 'iscsi', 'gluster', 'SMB', 'Linstor'] + primaryStorageProtocol: ['nfs', 'iscsi', 'gluster', 'SMB', 'Linstor', 'datastorecluster', 'vmfs'] } }, { diff --git a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue index 0334b38515ff..006228213291 100644 --- a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue +++ b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue @@ -1569,10 +1569,10 @@ export default { } path += '/' + this.prefillContent.primaryStorageVmfsDatastore if (protocol === 'vmfs') { - url = this.vmfsURL('dummy', path) + url = this.vmfsURL(server, path) } if (protocol === 'datastorecluster') { - url = this.datastoreclusterURL('dummy', path) + url = this.datastoreclusterURL(server, path) } } else if (protocol === 'iscsi') { let iqn = this.prefillContent?.primaryStorageTargetIQN || '' diff --git a/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue b/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue index 9d221bdccc3c..88f681de3796 100644 --- a/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue +++ b/ui/src/views/infra/zone/ZoneWizardPhysicalNetworkSetupStep.vue @@ -414,7 +414,7 @@ export default { for (const index in net.traffics) { if (this.hypervisor === 'VMware') { delete this.physicalNetworks[idx].traffics[index].label - } else { + } else if (!net.traffics[index].label) { this.physicalNetworks[idx].traffics[index].label = '' } const traffic = net.traffics[index] diff --git a/ui/src/views/offering/AddComputeOffering.vue b/ui/src/views/offering/AddComputeOffering.vue index 0455551723c1..dc4d5b188181 100644 --- a/ui/src/views/offering/AddComputeOffering.vue +++ b/ui/src/views/offering/AddComputeOffering.vue @@ -467,6 +467,9 @@ {{ $t('label.writethrough') }} + + {{ $t('label.hypervisor.default') }} + diff --git a/ui/src/views/offering/AddDiskOffering.vue b/ui/src/views/offering/AddDiskOffering.vue index 886465cfcf71..bfdd778d4493 100644 --- a/ui/src/views/offering/AddDiskOffering.vue +++ b/ui/src/views/offering/AddDiskOffering.vue @@ -211,6 +211,9 @@ {{ $t('label.writethrough') }} + + {{ $t('label.hypervisor.default') }} + @@ -604,7 +607,7 @@ export default { width: 80vw; @media (min-width: 800px) { - width: 430px; + width: 480px; } } diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java index 0db5c4b326f0..28ae32643553 100644 --- a/utils/src/main/java/com/cloud/utils/script/Script.java +++ b/utils/src/main/java/com/cloud/utils/script/Script.java @@ -28,6 +28,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.concurrent.Callable; @@ -157,13 +158,7 @@ protected String buildCommandLine(String[] command) { boolean obscureParam = false; for (int i = 0; i < command.length; i++) { String cmd = command[i]; - if (StringUtils.isNotEmpty(cmd) && cmd.startsWith("vi://")) { - String[] tokens = cmd.split("@"); - if (tokens.length >= 2) { - builder.append("vi://").append("******@").append(tokens[1]).append(" "); - } else { - builder.append("vi://").append("******").append(" "); - } + if (sanitizeViCmdParameter(cmd, builder) || sanitizeRbdFileFormatCmdParameter(cmd, builder)) { continue; } if (obscureParam) { @@ -181,6 +176,41 @@ protected String buildCommandLine(String[] command) { return builder.toString(); } + private boolean sanitizeViCmdParameter(String cmd, StringBuilder builder) { + if (StringUtils.isEmpty(cmd) || !cmd.startsWith("vi://")) { + return false; + } + + String[] tokens = cmd.split("@"); + if (tokens.length >= 2) { + builder.append("vi://").append("******@").append(tokens[1]).append(" "); + } else { + builder.append("vi://").append("******").append(" "); + } + return true; + } + + private boolean sanitizeRbdFileFormatCmdParameter(String cmd, StringBuilder builder) { + if (StringUtils.isEmpty(cmd) || !cmd.startsWith("rbd:") || !cmd.contains("key=")) { + return false; + } + + String[] tokens = cmd.split("key="); + if (tokens.length != 2) { + return false; + } + + String tokenWithKey = tokens[1]; + String[] options = tokenWithKey.split(":"); + if (options.length > 1) { + String optionsAfterKey = String.join(":", Arrays.copyOfRange(options, 1, options.length)); + builder.append(tokens[0]).append("key=").append("******").append(":").append(optionsAfterKey).append(" "); + } else { + builder.append(tokens[0]).append("key=").append("******").append(" "); + } + return true; + } + public long getTimeout() { return _timeout; }