This is an automated email from the ASF dual-hosted git repository. dahn pushed a commit to branch 4.20-main-merge-try in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit bcafbd16495ef9d0f50b9d23def24462d090ea6c Merge: 0d4147f3f63 609efcc231a Author: Daan Hoogland <d...@apache.org> AuthorDate: Fri Jul 25 23:38:12 2025 +0200 Merge branch '4.20' .../main/java/com/cloud/user/AccountService.java | 2 + .../api/command/admin/cluster/ListClustersCmd.java | 6 +- .../api/command/admin/pod/ListPodsByCmd.java | 4 +- .../command/user/config/ListCapabilitiesCmd.java | 1 + .../command/user/userdata/RegisterUserDataCmd.java | 52 +- .../api/command/user/zone/ListZonesCmd.java | 7 - .../api/response/CapabilitiesResponse.java | 8 + .../cloudstack/api/response/UserDataResponse.java | 22 +- .../org/apache/cloudstack/query/QueryService.java | 12 +- .../com/cloud/agent/api/CheckVolumeAnswer.java | 15 +- .../cloud/agent/api/CopyRemoteVolumeAnswer.java | 15 +- .../main/java/com/cloud/vm/VirtualMachineGuru.java | 4 + .../cloudstack/backup/NASBackupProvider.java | 13 +- .../kvm/resource/LibvirtComputingResource.java | 11 +- .../LibvirtCheckConvertInstanceCommandWrapper.java | 2 +- .../wrapper/LibvirtCheckVolumeCommandWrapper.java | 80 ++- .../LibvirtConvertInstanceCommandWrapper.java | 2 +- .../LibvirtCopyRemoteVolumeCommandWrapper.java | 76 +- .../LibvirtGetVolumesOnStorageCommandWrapper.java | 66 +- .../wrapper/LibvirtReadyCommandWrapper.java | 6 +- .../wrapper/LibvirtResizeVolumeCommandWrapper.java | 19 +- .../LibvirtRestoreBackupCommandWrapper.java | 60 +- .../hypervisor/kvm/storage/KVMPhysicalDisk.java | 32 + .../contrail/management/MockAccountManager.java | 6 + .../lifecycle/StorageVmSharedFSLifeCycle.java | 25 +- .../lifecycle/StorageVmSharedFSLifeCycleTest.java | 11 +- plugins/storage/volume/linstor/CHANGELOG.md | 6 + .../kvm/storage/LinstorStorageAdaptor.java | 2 +- .../driver/LinstorPrimaryDataStoreDriverImpl.java | 12 +- .../driver/ScaleIOPrimaryDataStoreDriver.java | 16 +- .../driver/ScaleIOPrimaryDataStoreDriverTest.java | 12 + .../kvm/storage/StorPoolStorageAdaptor.java | 3 + .../cloudstack/api/command/LdapListUsersCmd.java | 5 +- .../apache/cloudstack/ldap/LdapAuthenticator.java | 58 +- .../apache/cloudstack/ldap/LdapManagerImpl.java | 22 +- .../java/com/cloud/api/query/QueryManagerImpl.java | 482 +++++++------ .../cloud/api/query/dao/TemplateJoinDaoImpl.java | 7 +- .../consoleproxy/ConsoleProxyManagerImpl.java | 6 +- .../resourcelimit/ResourceLimitManagerImpl.java | 113 ++- .../com/cloud/server/ManagementServerImpl.java | 8 +- .../com/cloud/storage/VolumeApiServiceImpl.java | 16 +- .../java/com/cloud/user/AccountManagerImpl.java | 1 + .../cloudstack/backup/BackupManagerImpl.java | 2 + .../cloudstack/vm/UnmanagedVMsManagerImpl.java | 45 +- .../cloud/storage/VolumeApiServiceImplTest.java | 7 +- .../com/cloud/user/MockAccountManagerImpl.java | 5 + .../java/com/cloud/consoleproxy/ConsoleProxy.java | 7 + .../consoleproxy/ConsoleProxyNoVncClient.java | 84 ++- .../com/cloud/consoleproxy/vnc/NoVncClient.java | 8 +- .../cloud/consoleproxy/vnc/network/NioSocket.java | 3 +- .../consoleproxy/vnc/network/NioSocketHandler.java | 3 +- .../vnc/network/NioSocketHandlerImpl.java | 16 +- .../vnc/network/NioSocketInputStream.java | 46 +- .../vnc/network/NioSocketSSLEngineManager.java | 40 +- .../vnc/network/NioSocketTLSInputStream.java | 3 +- .../vnc/network/NioSocketTLSOutputStream.java | 5 +- .../SecondaryStorageManagerImpl.java | 5 +- .../storage/resource/HttpUploadServerHandler.java | 12 +- systemvm/agent/conf/consoleproxy.properties | 1 + systemvm/debian/opt/cloud/bin/setup/common.sh | 6 +- .../debian/opt/cloud/bin/setup/consoleproxy.sh | 4 + systemvm/debian/opt/cloud/bin/setup/secstorage.sh | 1 + .../plugins/linstor/test_linstor_volumes.py | 73 +- ui/public/locales/ar.json | 24 +- ui/public/locales/ca.json | 22 +- ui/public/locales/de_DE.json | 26 +- ui/public/locales/el_GR.json | 51 +- ui/public/locales/en.json | 788 ++++++++++----------- ui/public/locales/es.json | 24 +- ui/public/locales/fr_FR.json | 24 +- ui/public/locales/hi.json | 4 +- ui/public/locales/hu.json | 26 +- ui/public/locales/it_IT.json | 24 +- ui/public/locales/ja_JP.json | 24 +- ui/public/locales/ko_KR.json | 24 +- ui/public/locales/nb_NO.json | 24 +- ui/public/locales/nl_NL.json | 24 +- ui/public/locales/pl.json | 24 +- ui/public/locales/pt_BR.json | 24 +- ui/public/locales/ru_RU.json | 24 +- ui/public/locales/zh_CN.json | 24 +- ui/src/components/view/InfoCard.vue | 22 +- ui/src/components/view/ListView.vue | 7 +- ui/src/config/section/account.js | 2 +- ui/src/config/section/compute.js | 6 +- ui/src/config/section/domain.js | 5 + ui/src/config/section/network.js | 21 +- ui/src/views/compute/AutoScaleVmProfile.vue | 14 +- ui/src/views/compute/CreateAutoScaleVmGroup.vue | 14 +- ui/src/views/compute/DeployVM.vue | 56 +- ui/src/views/compute/DeployVnfAppliance.vue | 73 +- ui/src/views/compute/EditVM.vue | 7 +- ui/src/views/compute/RegisterUserData.vue | 39 +- ui/src/views/compute/ResetUserData.vue | 14 +- ui/src/views/compute/wizard/UserDataSelection.vue | 4 +- ui/src/views/image/RegisterOrUploadIso.vue | 4 +- ui/src/views/image/RegisterOrUploadTemplate.vue | 4 +- ui/src/views/image/UpdateISO.vue | 4 +- ui/src/views/image/UpdateTemplate.vue | 4 +- ui/src/views/infra/network/ServiceProvidersTab.vue | 2 +- .../{AclListRulesTab.vue => AclRulesTab.vue} | 2 +- ui/src/views/network/VpcTab.vue | 8 +- ui/src/views/offering/AddNetworkOffering.vue | 2 +- .../com/cloud/hypervisor/vmware/mo/HostMO.java | 152 +--- 104 files changed, 1882 insertions(+), 1491 deletions(-) diff --cc api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index 77a7a7fd8ea,bd3f39a09aa..318bed11ad9 --- 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 @@@ -72,7 -72,7 +72,8 @@@ public class ListCapabilitiesCmd extend response.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME)); response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT)); response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE)); + response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED)); + response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --cc api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index 74dbfa15a43,ff2e33b1389..910dc5fa38d --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@@ -136,10 -136,10 +136,14 @@@ public class CapabilitiesResponse exten @Param(description = "the min Ram size for the service offering used by the shared filesystem instance", since = "4.20.0") private Integer sharedFsVmMinRamSize; + @SerializedName(ApiConstants.INSTANCE_LEASE_ENABLED) + @Param(description = "true if instance lease feature is enabled", since = "4.21.0") + private Boolean instanceLeaseEnabled; + + @SerializedName(ApiConstants.DYNAMIC_SCALING_ENABLED) + @Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0") + private Boolean dynamicScalingEnabled; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@@ -252,7 -252,7 +256,11 @@@ this.sharedFsVmMinRamSize = sharedFsVmMinRamSize; } + public void setInstanceLeaseEnabled(Boolean instanceLeaseEnabled) { + this.instanceLeaseEnabled = instanceLeaseEnabled; + } ++ + public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { + this.dynamicScalingEnabled = dynamicScalingEnabled; + } } diff --cc plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java index 813d8978697,2cc909ce0d7..21753257f75 --- a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java +++ b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java @@@ -257,11 -258,16 +257,16 @@@ public class StorageVmSharedFSLifeCycle anyString(), anyLong(), anyLong(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(), isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(), anyMap(), isNull(), isNull(), isNull(), isNull(), - anyBoolean(), anyString(), isNull())).thenReturn(vm); + anyBoolean(), anyString(), isNull(), isNull(), isNull())).thenReturn(vm); - VolumeVO volume = mock(VolumeVO.class); - when(volume.getId()).thenReturn(s_volumeId); - when(volumeDao.findByInstanceAndType(s_vmId, Volume.Type.DATADISK)).thenReturn(List.of(volume)); + VolumeVO rootVol = mock(VolumeVO.class); + when(rootVol.getVolumeType()).thenReturn(Volume.Type.ROOT); + when(rootVol.getName()).thenReturn("ROOT-1"); + VolumeVO dataVol = mock(VolumeVO.class); + when(dataVol.getId()).thenReturn(s_volumeId); + when(dataVol.getName()).thenReturn("DATA-1"); + when(dataVol.getVolumeType()).thenReturn(Volume.Type.DATADISK); + when(volumeDao.findByInstance(s_vmId)).thenReturn(List.of(rootVol, dataVol)); Pair<Long, Long> result = lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops); Assert.assertEquals(Optional.ofNullable(result.first()), Optional.ofNullable(s_volumeId)); diff --cc server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index cdedd830c0a,6fb9ab515cb..0f8adf1a666 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@@ -167,13 -161,11 +168,13 @@@ import org.apache.cloudstack.storage.da import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; - import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.vm.lease.VMLeaseManager; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; + import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import com.cloud.api.query.dao.AccountJoinDao; @@@ -728,10 -643,10 +716,10 @@@ public class QueryManagerImpl extends M String keyword = null; Pair<List<UserAccountJoinVO>, Integer> result = getUserListInternal(caller, permittedAccounts, listAll, id, - username, type, accountName, state, keyword, null, domainId, recursive, null); + username, type, accountName, state, keyword, null, domainId, recursive, null, null); - ListResponse<UserResponse> response = new ListResponse<UserResponse>(); + ListResponse<UserResponse> response = new ListResponse<>(); List<UserResponse> userResponses = ViewResponseHelper.createUserResponse(ResponseView.Restricted, CallContext.current().getCallingAccount().getDomainId(), - result.first().toArray(new UserAccountJoinVO[result.first().size()])); + result.first().toArray(new UserAccountJoinVO[0])); response.setResponses(userResponses, result.second()); return response; } @@@ -769,8 -683,8 +757,8 @@@ } private Pair<List<UserAccountJoinVO>, Integer> getUserListInternal(Account caller, List<Long> permittedAccounts, boolean listAll, Long id, Object username, Object type, - String accountName, Object state, String keyword, String apiKeyAccess, Long domainId, boolean recursive, Filter searchFilter) { + String accountName, Object state, String keyword, String apiKeyAccess, Long domainId, boolean recursive, Filter searchFilter, User.Source userSource) { - Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, recursive, null); + Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(domainId, recursive, null); accountMgr.buildACLSearchParameters(caller, id, accountName, null, permittedAccounts, domainIdRecursiveListProject, listAll, false); domainId = domainIdRecursiveListProject.first(); Boolean isRecursive = domainIdRecursiveListProject.second(); @@@ -3235,17 -3096,25 +3222,17 @@@ private ListResponse<StoragePoolResponse> createStoragesPoolResponse(Pair<List<StoragePoolJoinVO>, Integer> storagePools, boolean getCustomStats) { ListResponse<StoragePoolResponse> response = new ListResponse<>(); - List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(getCustomStats, storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); + List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(getCustomStats, storagePools.first().toArray(new StoragePoolJoinVO[0])); Map<String, Long> poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId, (a, b) -> a)); for (StoragePoolResponse poolResponse : poolResponses) { + Long poolId = poolUuidToIdMap.get(poolResponse.getId()); DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId()); + if (store != null) { - DataStoreDriver driver = store.getDriver(); - if (driver != null && driver.getCapabilities() != null) { - Map<String, String> caps = driver.getCapabilities(); - if (Storage.StoragePoolType.NetworkFilesystem.toString().equals(poolResponse.getType()) && - HypervisorType.VMware.toString().equals(poolResponse.getHypervisor())) { - StoragePoolDetailVO detail = _storagePoolDetailsDao.findDetail(poolUuidToIdMap.get(poolResponse.getId()), Storage.Capability.HARDWARE_ACCELERATION.toString()); - if (detail != null) { - caps.put(Storage.Capability.HARDWARE_ACCELERATION.toString(), detail.getValue()); - } - } - poolResponse.setCaps(caps); - } + addPoolDetailsAndCapabilities(poolResponse, store, poolId); } - setPoolResponseNFSMountOptions(poolResponse, poolUuidToIdMap.get(poolResponse.getId())); + + setPoolResponseNFSMountOptions(poolResponse, poolId); } response.setResponses(poolResponses, storagePools.second()); @@@ -3319,100 -3158,7 +3306,100 @@@ return response; } + @Override + public ListResponse<StorageAccessGroupResponse> searchForStorageAccessGroups(ListStorageAccessGroupsCmd cmd) { + String name = cmd.getName(); + String keyword = cmd.getKeyword(); + Set<String> storageAccessGroups = new HashSet<>(); + + addStorageAccessGroups(storageAccessGroups, storagePoolAndAccessGroupMapDao.listDistinctStorageAccessGroups(name, keyword)); + addStorageAccessGroups(storageAccessGroups, hostDao.listDistinctStorageAccessGroups(name, keyword)); + addStorageAccessGroups(storageAccessGroups, clusterDao.listDistinctStorageAccessGroups(name, keyword)); + addStorageAccessGroups(storageAccessGroups, podDao.listDistinctStorageAccessGroups(name, keyword)); + addStorageAccessGroups(storageAccessGroups, dataCenterDao.listDistinctStorageAccessGroups(name, keyword)); + + if (StringUtils.isNotEmpty(name) && storageAccessGroups.contains(name)) { + storageAccessGroups = Collections.singleton(name); + } + + if (StringUtils.isNotEmpty(keyword)) { + storageAccessGroups = storageAccessGroups.stream() + .filter(group -> group.contains(keyword)) + .collect(Collectors.toSet()); + } + + List<StorageAccessGroupResponse> responseList = buildStorageAccessGroupResponses(storageAccessGroups, name); + + ListResponse<StorageAccessGroupResponse> response = new ListResponse<>(); + response.setResponses(responseList, storageAccessGroups.size()); + return response; + } + + private void addStorageAccessGroups(Set<String> storageAccessGroups, List<String> groups) { + for (String group : groups) { + if (group != null && !group.isEmpty()) { + storageAccessGroups.addAll(Arrays.asList(group.split(","))); + } + } + } + + private List<StorageAccessGroupResponse> buildStorageAccessGroupResponses( + Set<String> storageAccessGroups, String name) { + List<StorageAccessGroupResponse> responseList = new ArrayList<>(); + + for (String sag : storageAccessGroups) { + StorageAccessGroupResponse sagResponse = new StorageAccessGroupResponse(); + sagResponse.setName(sag); + sagResponse.setObjectName(ApiConstants.STORAGE_ACCESS_GROUP); + + if (StringUtils.isNotBlank(name)) { + fetchStorageAccessGroupResponse(sagResponse, name); + } + + responseList.add(sagResponse); + } + return responseList; + } + + private void fetchStorageAccessGroupResponse(StorageAccessGroupResponse sagResponse, String name) { + sagResponse.setHostResponseList(searchForServersWithMinimalResponse(new ListHostsCmd(name))); + sagResponse.setZoneResponseList(listDataCentersWithMinimalResponse(new ListZonesCmd(name))); + sagResponse.setPodResponseList(fetchPodsByStorageAccessGroup(name)); + sagResponse.setClusterResponseList(fetchClustersByStorageAccessGroup(name)); + sagResponse.setStoragePoolResponseList(searchForStoragePoolsWithMinimalResponse(new ListStoragePoolsCmd(name))); + } + + private ListResponse<PodResponse> fetchPodsByStorageAccessGroup(String name) { + ListPodsByCmd listPodsByCmd = new ListPodsByCmd(name); + Pair<List<? extends Pod>, Integer> podResponsePair = managementService.searchForPods(listPodsByCmd); + List<PodResponse> podResponses = podResponsePair.first().stream() + .map(pod -> { + PodResponse podResponse = responseGenerator.createMinimalPodResponse(pod); + podResponse.setObjectName("pod"); + return podResponse; + }).collect(Collectors.toList()); + + ListResponse<PodResponse> podResponse = new ListResponse<>(); + podResponse.setResponses(podResponses, podResponsePair.second()); + return podResponse; + } + + private ListResponse<ClusterResponse> fetchClustersByStorageAccessGroup(String name) { + ListClustersCmd listClustersCmd = new ListClustersCmd(name); + Pair<List<? extends Cluster>, Integer> clusterResponsePair = managementService.searchForClusters(listClustersCmd); + List<ClusterResponse> clusterResponses = clusterResponsePair.first().stream() + .map(cluster -> { + ClusterResponse clusterResponse = responseGenerator.createMinimalClusterResponse(cluster); + clusterResponse.setObjectName("cluster"); + return clusterResponse; + }).collect(Collectors.toList()); + + ListResponse<ClusterResponse> clusterResponse = new ListResponse<>(); + clusterResponse.setResponses(clusterResponses, clusterResponsePair.second()); + return clusterResponse; + } + - private Pair<List<StoragePoolTagVO>, Integer> searchForStorageTagsInternal(ListStorageTagsCmd cmd) { + private Pair<List<StoragePoolTagVO>, Integer> searchForStorageTagsInternal() { Filter searchFilter = new Filter(StoragePoolTagVO.class, "id", Boolean.TRUE, null, null); SearchBuilder<StoragePoolTagVO> sb = _storageTagDao.createSearchBuilder(); @@@ -4497,7 -4228,11 +4483,11 @@@ private Pair<List<DataCenterJoinVO>, Integer> listDataCentersInternal(ListZonesCmd cmd) { Account account = CallContext.current().getCallingAccount(); Long domainId = cmd.getDomainId(); - Long id = cmd.getId(); + Long zoneId = cmd.getId(); - if( ! AllowUserViewAllDataCenters.valueInDomain(account.getDomainId())) { ++ if( ! AllowUserViewAllDataCenters.valueInScope(ConfigKey.Scope.Domain, account.getDomainId())) { + zoneId = accountMgr.checkAccessAndSpecifyAuthority(CallContext.current().getCallingAccount(), zoneId); + logger.debug("not allowing users to view all zones ; selected zone is = {}", zoneId); + } List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); String keyword = cmd.getKeyword(); String name = cmd.getName(); @@@ -4675,6 -4300,12 +4563,19 @@@ } } + buildSearchCriteriaForTags(resourceTags, sc); + ++ if (storageAccessGroup != null) { ++ sc.setParameters("storageAccessGroupExact", storageAccessGroup); ++ sc.setParameters("storageAccessGroupPrefix", storageAccessGroup + ",%"); ++ sc.setParameters("storageAccessGroupSuffix", "%," + storageAccessGroup); ++ sc.setParameters("storageAccessGroupMiddle", "%," + storageAccessGroup + ",%"); ++ } ++ + return _dcJoinDao.searchAndCount(sc, searchFilter); + } + + private static void buildSearchCriteriaForTags(Map<String, String> resourceTags, SearchCriteria<DataCenterJoinVO> sc) { if (resourceTags != null && !resourceTags.isEmpty()) { int count = 0; sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.Zone.toString()); @@@ -4796,7 -4539,7 +4810,7 @@@ null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), - templateType, isVnf, cmd.getArch(), cmd.getOsCategoryId(), forCks); - templateType, isVnf, domainId, isRecursive, cmd.getArch()); ++ templateType, isVnf, domainId, isRecursive, cmd.getArch(), cmd.getOsCategoryId(), forCks); } private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, @@@ -4805,7 -4548,7 +4819,7 @@@ boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags, boolean showRemovedTmpl, List<Long> ids, Long parentTemplateId, Boolean showUnique, String templateType, - Boolean isVnf, CPU.CPUArch arch, Long osCategoryId, Boolean forCks) { - Boolean isVnf, Long domainId, boolean isRecursive, CPU.CPUArch arch) { ++ Boolean isVnf, Long domainId, boolean isRecursive, CPU.CPUArch arch, Long osCategoryId, Boolean forCks) { // check if zone is configured, if not, just return empty list List<HypervisorType> hypers = null; @@@ -5253,7 -4986,7 +5273,7 @@@ cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, - cmd.getArch(), cmd.getOsCategoryId(), null); - domainId, isRecursive, cmd.getArch()); ++ domainId, isRecursive, cmd.getArch(), cmd.getOsCategoryId(), null); } @Override diff --cc server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 37d74445776,85cca63546c..47f650da2cc --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@@ -615,9 -587,9 +610,9 @@@ public class ResourceLimitManagerImpl e @Override public long findDefaultResourceLimitForDomain(ResourceType resourceType) { - Long resourceLimit = null; + Long resourceLimit; resourceLimit = domainResourceLimitMap.get(resourceType.getName()); - if (resourceLimit != null && (resourceType == ResourceType.primary_storage || resourceType == ResourceType.secondary_storage)) { + if (resourceLimit != null && ResourceType.isStorageType(resourceType)) { if (! Long.valueOf(Resource.RESOURCE_UNLIMITED).equals(resourceLimit)) { resourceLimit = resourceLimit * ResourceType.bytesToGiB; } @@@ -1327,11 -1274,12 +1325,12 @@@ _resourceCountDao.persist(new ResourceCountVO(type, newCount, accountId, ResourceOwnerType.Account, tag)); } - // No need to log message for primary and secondary storage because both are recalculating the + // No need to log message for storage type resources because both are recalculating the // resource count which will not lead to any discrepancy. - if (newCount != null && !newCount.equals(oldCount) && !ResourceType.isStorageType(type)) { - logger.warn("Discrepancy in the resource count " + "(original count=" + oldCount + " correct count = " + newCount + ") for type " + type + - " for account ID " + accountId + " is fixed during resource count recalculation."); + if (newCount != null && !newCount.equals(oldCount) && + type != Resource.ResourceType.primary_storage && type != Resource.ResourceType.secondary_storage) { + logger.warn("Discrepancy in the resource count (original count={} correct count = {}) for type {} for account ID {} is fixed during resource count recalculation.", + oldCount, newCount, type, accountId); } return (newCount == null) ? 0 : newCount; diff --cc server/src/main/java/com/cloud/server/ManagementServerImpl.java index 650028e1247,271372bf656..2bbd7e1bf86 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@@ -777,7 -751,7 +777,6 @@@ import com.cloud.org.Grouping.Allocatio import com.cloud.projects.Project; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.projects.ProjectManager; --import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; @@@ -879,9 -854,6 +878,9 @@@ public class ManagementServerImpl exten static final ConfigKey<Integer> sshKeyLength = new ConfigKey<>("Advanced", Integer.class, "ssh.key.length", "2048", "Specifies custom SSH key length (bit)", true, ConfigKey.Scope.Global); static final ConfigKey<Boolean> humanReadableSizes = new ConfigKey<>("Advanced", Boolean.class, "display.human.readable.sizes", "true", "Enables outputting human readable byte sizes to logs and usage records.", false, ConfigKey.Scope.Global); public static final ConfigKey<String> customCsIdentifier = new ConfigKey<>("Advanced", String.class, "custom.cs.identifier", UUID.randomUUID().toString().split("-")[0].substring(4), "Custom identifier for the cloudstack installation", true, ConfigKey.Scope.Global); - public static final ConfigKey<Boolean> exposeCloudStackVersionInApiXmlResponse = new ConfigKey<Boolean>("Advanced", Boolean.class, "expose.cloudstack.version.api.xml.response", "true", "Indicates whether ACS version should appear in the root element of an API XML response.", true, ConfigKey.Scope.Global); - public static final ConfigKey<Boolean> exposeCloudStackVersionInApiListCapabilities = new ConfigKey<Boolean>("Advanced", Boolean.class, "expose.cloudstack.version.api.list.capabilities", "true", "Indicates whether ACS version should show in the listCapabilities API.", true, ConfigKey.Scope.Global); ++ public static final ConfigKey<Boolean> exposeCloudStackVersionInApiXmlResponse = new ConfigKey<>("Advanced", Boolean.class, "expose.cloudstack.version.api.xml.response", "true", "Indicates whether ACS version should appear in the root element of an API XML response.", true, ConfigKey.Scope.Global); ++ public static final ConfigKey<Boolean> exposeCloudStackVersionInApiListCapabilities = new ConfigKey<>("Advanced", Boolean.class, "expose.cloudstack.version.api.list.capabilities", "true", "Indicates whether ACS version should show in the listCapabilities API.", true, ConfigKey.Scope.Global); + private static final VirtualMachine.Type []systemVmTypes = { VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.ConsoleProxy}; private static final List<HypervisorType> LIVE_MIGRATION_SUPPORTING_HYPERVISORS = List.of(HypervisorType.Hyperv, HypervisorType.KVM, HypervisorType.LXC, HypervisorType.Ovm, HypervisorType.Ovm3, HypervisorType.Simulator, HypervisorType.VMware, HypervisorType.XenServer); @@@ -985,8 -955,8 +984,6 @@@ @Inject private ProjectManager _projectMgr; @Inject -- private ResourceManager _resourceMgr; -- @Inject private HighAvailabilityManager _haMgr; @Inject private HostTagsDao _hostTagsDao; @@@ -4728,7 -4528,7 +4725,8 @@@ capabilities.put(ApiConstants.INSTANCES_STATS_USER_ONLY, StatsCollector.vmStatsCollectUserVMOnly.value()); capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED, StatsCollector.vmDiskStatsRetentionEnabled.value()); capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME, StatsCollector.vmDiskStatsMaxRetentionTime.value()); + capabilities.put(ApiConstants.INSTANCE_LEASE_ENABLED, VMLeaseManager.InstanceLeaseEnabled.value()); + capabilities.put(ApiConstants.DYNAMIC_SCALING_ENABLED, UserVmManager.EnableDynamicallyScaleVm.value()); if (apiLimitEnabled) { capabilities.put("apiLimitInterval", apiLimitInterval); capabilities.put("apiLimitMax", apiLimitMax); diff --cc ui/public/locales/en.json index e26cd4d494e,c36b96cc961..6e675d1c061 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@@ -53,9 -53,7 +53,8 @@@ "label.acquiring.ip": "Acquiring IP", "label.associated.resource": "Associated resource", "label.action": "Action", -"label.action.attach.disk": "Attach Disk", +"label.action.add.nodes.to.kubernetes.cluster": "Add nodes to Kubernetes cluster", +"label.action.remove.nodes.from.kubernetes.cluster": "Remove nodes from Kubernetes cluster", - "label.action.attach.disk": "Attach disk", "label.action.attach.iso": "Attach ISO", "label.action.attach.to.instance": "Attach to Instance", "label.action.bulk.delete.egress.firewall.rules": "Bulk delete egress firewall rules", @@@ -72,9 -70,8 +71,9 @@@ "label.action.change.password": "Change password", "label.action.clear.webhook.deliveries": "Clear deliveries", "label.action.delete.webhook.deliveries": "Delete deliveries", - "label.action.change.primary.storage.scope": "Change primary storage scope", + "label.action.change.primary.storage.scope": "Change Primary Storage scope", "label.action.configure.stickiness": "Stickiness", +"label.action.configure.storage.access.group": "Update storage access group", "label.action.copy.iso": "Copy ISO", "label.action.copy.snapshot": "Copy Snapshot", "label.action.copy.template": "Copy Template", @@@ -84,13 -81,12 +83,13 @@@ "label.action.create.volume.add": "Create and Add Volume", "label.action.delete.account": "Delete Account", "label.action.delete.backup.offering": "Delete backup offering", - "label.action.delete.cluster": "Delete cluster", - "label.action.delete.domain": "Delete domain", - "label.action.delete.egress.firewall": "Delete egress firewall rule", - "label.action.delete.firewall": "Delete firewall rule", + "label.action.delete.cluster": "Delete Cluster", + "label.action.delete.domain": "Delete Domain", + "label.action.delete.egress.firewall": "Delete Egress Firewall Rule", + "label.action.delete.firewall": "Delete Firewall Rule", "label.action.delete.interface.static.route": "Remove Tungsten Fabric interface static route", "label.action.delete.guest.os": "Delete guest OS", +"label.action.delete.guest.os.category": "Delete guest OS category", "label.action.delete.guest.os.hypervisor.mapping": "Delete guest OS hypervisor mapping", "label.action.delete.ip.range": "Delete IP range", "label.action.delete.iso": "Delete ISO", @@@ -224,13 -220,9 +223,13 @@@ "label.action.unmanage.instance": "Unmanage Instance", "label.action.unmanage.instances": "Unmanage Instances", "label.action.unmanage.virtualmachine": "Unmanage Instance", +"label.action.update.cluster": "Update cluster", +"label.action.update.pod": "Update pod", +"label.action.update.zone": "Update zone", +"label.action.update.storage.pool": "Update storage pool", "label.action.unmanage.volume": "Unmanage Volume", "label.action.unmanage.volumes": "Unmanage Volumes", - "label.action.update.host": "Update host", + "label.action.update.host": "Update Host", "label.action.update.security.groups": "Update security groups", "label.action.update.offering.access": "Update offering access", "label.action.update.resource.count": "Update resource count", @@@ -256,40 -248,37 +255,39 @@@ "label.add.by": "Add by", "label.add.certificate": "Add certificate", "label.add.ciscoasa1000v": "Add CiscoASA1000v resource", - "label.add.cluster": "Add cluster", - "label.add.compute.offering": "Add compute offering", + "label.add.cluster": "Add Cluster", + "label.add.compute.offering": "Add Compute Offering", "label.add.condition": "Add condition", - "label.add.disk.offering": "Add disk offering", - "label.add.domain": "Add domain", - "label.add.egress.rule": "Add egress rule", + "label.add.disk.offering": "Add Disk Offering", + "label.add.domain": "Add Domain", + "label.add.egress.rule": "Add Egress Rule", "label.add.f5.device": "Add F5 device", - "label.add.firewall": "Add firewall rule", + "label.add.firewall": "Add Firewall Rule", "label.add.firewallrule": "Add Firewall Rule", - "label.add.guest.network": "Add guest Network", - "label.add.guest.os": "Add guest OS", + "label.add.guest.network": "Add Guest Network", + "label.add.guest.os": "Add Guest OS", +"label.add.guest.os.category": "Add guest OS category", - "label.add.guest.os.hypervisor.mapping": "Add guest OS hypervisor mapping", - "label.add.host": "Add host", - "label.add.ingress.rule": "Add ingress rule", + "label.add.guest.os.hypervisor.mapping": "Add guest os hypervisor mapping", + "label.add.host": "Add Host", + "label.add.ingress.rule": "Add Ingress Rule", "label.add.intermediate.certificate": "Add intermediate certificate", "label.add.internal.lb": "Add internal LB", - "label.add.ip.range": "Add IP range", - "label.add.ipv4.subnet": "Add IPv4 subnet for Routed networks", + "label.add.ip.range": "Add IP Range", + "label.add.ipv4.subnet": "Add IPv4 Subnet for Routed Networks", "label.add.ip.v6.prefix": "Add IPv6 prefix", - "label.add.isolated.network": "Add isolated Network", - "label.add.kubernetes.cluster": "Add Kubernetes cluster", + "label.add.isolated.network": "Add Isolated Network", + "label.add.kubernetes.cluster": "Add Kubernetes Cluster", + "label.add.acl.name": "ACL name", "label.add.ldap.account": "Add LDAP Account", - "label.add.list.name": "ACL List name", "label.add.logical.router": "Add Logical Router to this Network", "label.add.more": "Add more", +"label.add.nodes": "Add Nodes to Kubernetes Cluster", - "label.add.netscaler.device": "Add Netscaler device", + "label.add.netscaler.device": "Add Netscaler Device", "label.add.network": "Add Network", "label.add.network.acl": "Add Network ACL", - "label.add.network.acl.list": "Add Network ACL list", - "label.add.network.offering": "Add Network offering", + "label.add.network.offering": "Add Network Offering", "label.add.network.permission": "Add Network permission", - "label.add.new.gateway": "Add new gateway", + "label.add.new.gateway": "Add new Gateway", "label.add.new.tier": "Add new Network Tier", "label.add.niciranvp.device": "Add Nvp controller", "label.add.note": "Add comment", @@@ -439,16 -427,13 +437,16 @@@ "label.backup.configure.schedule": "Configure Backup Schedule", "label.backup.offering.assign": "Assign Instance to backup offering", "label.backup.offering.remove": "Remove Instance from backup offering", - "label.backup.offerings": "Backup offerings", + "label.backup.offerings": "Backup Offerings", "label.backup.repository": "Backup Repository", "label.backup.restore": "Restore Instance backup", - "label.backupofferingid": "Backup offering", - "label.backupofferingname": "Backup offering", - "label.backup.repository.add": "Add backup repository", - "label.backup.repository.remove": "Remove backup repository", + "label.backupofferingid": "Backup Offering", + "label.backupofferingname": "Backup Offering", + "label.backup.repository.add": "Add Backup Repository", + "label.backup.repository.remove": "Remove Backup Repository", +"label.backuplimit": "Backup Limits", +"label.backup.storage": "Backup Storage", +"label.backupstoragelimit": "Backup Storage Limits (GiB)", "label.balance": "Balance", "label.bandwidth": "Bandwidth", "label.baremetal.dhcp.devices": "Bare metal DHCP devices", @@@ -477,14 -462,13 +475,14 @@@ "label.brocade.vcs.address": "Vcs switch address", "label.browser": "Browser", "label.bucket": "Bucket", +"label.bucketlimit": "Bucket Limits", "label.by.account": "By Account", - "label.by.domain": "By domain", + "label.by.domain": "By Domain", "label.by.level": "By level", - "label.by.pod": "By pod", + "label.by.pod": "By Pod", "label.by.state": "By state", "label.by.type": "By type", - "label.by.zone": "By zone", + "label.by.zone": "By Zone", "label.bypassvlanoverlapcheck": "Bypass VLAN id/range overlap", "label.cachemode": "Write-cache type", "label.cancel": "Cancel", @@@ -683,10 -653,8 +681,10 @@@ "label.daily": "Daily", "label.dark.mode": "Dark mode", "label.dashboard": "Dashboard", - "label.data.disk": "Data disk", + "label.data.disk": "Data Disk", +"label.data.pool": "Data pool", +"label.data.pool.description": "Data pool is required when using a Ceph pool with erasure code", - "label.data.disk.offering": "Data disk offering", + "label.data.disk.offering": "Data Disk Offering", "label.date": "Date", "label.datetime.filter.period": "From <b>{startDate}</b> to <b>{endDate}</b>", "label.datetime.filter.starting": "Starting <b>{startDate}</b>.", @@@ -1033,9 -1000,8 +1031,9 @@@ "label.firstname": "First name", "label.firstname.lower": "firstname", "label.fix.errors": "Fix errors", - "label.fixed": "Fixed offering", + "label.fixed": "Fixed Offering", "label.for": "for", +"label.forcks": "For CKS", "label.forbidden": "Forbidden", "label.forced": "Force", "label.force.ms.to.import.vm.files": "Enable to force OVF Download via Management Server. Disable to use KVM Host ovftool (if installed)", @@@ -1085,9 -1050,7 +1083,9 @@@ "label.guest.netmask": "Guest netmask", "label.guest.networks": "Guest Networks", "label.guest.os": "Guest OS", +"label.guest.os.category": "Guest OS Category", +"label.guest.os.categories": "Guest OS Categories", - "label.guest.os.hypervisor.mappings": "Guest OS mappings", + "label.guest.os.hypervisor.mappings": "Guest OS Mappings", "label.guest.start.ip": "Guest start IP", "label.guest.traffic": "Guest traffic", "label.guestcidraddress": "Guest CIDR", @@@ -1163,14 -1126,12 +1161,14 @@@ "label.ikelifetime": "IKE lifetime (second)", "label.ikepolicy": "IKE policy", "label.ikeversion": "IKE version", +"label.image": "Image", +"label.image.type": "Image type", "label.images": "Images", "label.imagestoreid": "Secondary Storage", - "label.import.backup.offering": "Import backup offering", + "label.import.backup.offering": "Import Backup Offering", "label.import.instance": "Import Instance", - "label.import.offering": "Import offering", - "label.import.role": "Import role", + "label.import.offering": "Import Offering", + "label.import.role": "Import Role", "label.import.volume": "Import Volume", "label.inactive": "Inactive", "label.in.progress": "in progress", @@@ -1312,18 -1272,16 +1310,18 @@@ "label.keyboardtype": "Keyboard type", "label.keypair": "SSH key pair", "label.keypairs": "SSH key pair(s)", - "label.kubeconfig.cluster": "Kubernetes cluster config", + "label.kubeconfig.cluster": "Kubernetes Cluster config", "label.kubernetes": "Kubernetes", - "label.kubernetes.access.details": "The kubernetes nodes can be accessed via ssh using: <br> <code><b> ssh -i [ssh_key] -p [port_number] cloud@[public_ip_address] </b></code> <br><br> where, <br> <code><b>ssh_key:</b></code> points to the ssh private key file corresponding to the key that was associated while creating the Kubernetes cluster. If no ssh key was provided during Kubernetes cluster creation, use the ssh private key of the management server. <br> <code><b>port_number:</b></co [...] - "label.kubernetes.cluster": "Kubernetes cluster", + "label.kubernetes.access.details": "The kubernetes nodes can be accessed via ssh using: <br> <code><b> ssh -i [ssh_key] -p [port_number] cloud@[public_ip_address] </b></code> <br><br> where, <br> <code><b>ssh_key:</b></code> points to the ssh private key file corresponding to the key that was associated while creating the Kubernetes Cluster. If no ssh key was provided during Kubernetes cluster creation, use the ssh private key of the management server. <br> <code><b>port_number:</b></co [...] + "label.kubernetes.cluster": "Kubernetes Cluster", +"label.kubernetes.cluster.add.nodes.to.cluster": "Add nodes to Kubernetes cluster", - "label.kubernetes.cluster.create": "Create Kubernetes cluster", - "label.kubernetes.cluster.delete": "Delete Kubernetes cluster", - "label.kubernetes.cluster.scale": "Scale Kubernetes cluster", - "label.kubernetes.cluster.start": "Start Kubernetes cluster", - "label.kubernetes.cluster.stop": "Stop Kubernetes cluster", + "label.kubernetes.cluster.create": "Create Kubernetes Cluster", + "label.kubernetes.cluster.delete": "Delete Kubernetes Cluster", +"label.kubernetes.cluster.remove.nodes.from.cluster": "Remove nodes from Kubernetes cluster", - "label.kubernetes.cluster.upgrade": "Upgrade Kubernetes cluster", + "label.kubernetes.cluster.scale": "Scale Kubernetes Cluster", + "label.kubernetes.cluster.start": "Start Kubernetes Cluster", + "label.kubernetes.cluster.stop": "Stop Kubernetes Cluster", + "label.kubernetes.cluster.upgrade": "Upgrade Kubernetes Cluster", "label.kubernetes.dashboard": "Kubernetes dashboard UI", "label.kubernetes.dashboard.create.token": "Create token for Kubernetes dashboard", "label.kubernetes.dashboard.create.token.desc": "Since Kubernetes v1.24.0, there is no auto-generation of secret-based service Account token due to security reason. You need to create a service Account and an optional long-lived Bearer Token for the service Account.", @@@ -1368,9 -1326,8 +1366,9 @@@ "label.lbprovider": "Load balancer provider", "label.lbruleid": "Load balancer ID", "label.lbtype": "Load balancer type", +"label.ldap": "LDAP", - "label.ldap.configuration": "LDAP configuration", - "label.ldap.group.name": "LDAP group", + "label.ldap.configuration": "LDAP Configuration", + "label.ldap.group.name": "LDAP Group", "label.level": "Level", "label.license.agreements": "License agreements", "label.limit": "Limit", @@@ -1427,13 -1384,12 +1425,12 @@@ "label.managed.volumes": "Managed Volumes", "label.managedstate": "Managed state", "label.management": "Management", --"label.managementserverid": "Management server", --"label.managementservername": "Management server", "label.management.ips": "Management IP addresses", - "label.management.server": "Management server", - "label.management.servers": "Management servers", + "label.management.server": "Management Server", + "label.management.servers": "Management Servers", "label.management.server.peers": "Peers", ++"label.managementserverid": "Management server", +"label.managementservername": "Management Server", "label.managementservers": "Number of management servers", "label.matchall": "Match all", "label.max": "Max.", @@@ -1456,9 -1408,8 +1453,9 @@@ "label.maxmembers": "Max members", "label.maxmemory": "Max. memory (MiB)", "label.maxnetwork": "Max. Networks", +"label.maxobjectstorage": "Max. Object Storage (GiB)", "label.maxprimarystorage": "Max. primary storage (GiB)", - "label.maxproject": "Max. projects", + "label.maxproject": "Max. Projects", "label.maxpublicip": "Max. public IPs", "label.maxsecondarystorage": "Max. secondary storage (GiB)", "label.maxsize": "Maximum size", @@@ -1753,9 -1688,8 +1750,9 @@@ "label.peerstate": "Peer State", "label.peerstate.lastupdated": "Peer State Updated Time", "label.pending.jobs": "Pending Jobs", +"label.pendingjobscount": "Number Of pending jobs", "label.per.account": "Per Account", - "label.per.zone": "Per zone", + "label.per.zone": "Per Zone", "label.percentage": "Percentage", "label.perfectforwardsecrecy": "Perfect forward secrecy", "label.perform.fresh.checks": "Perform fresh checks", @@@ -1934,7 -1863,7 +1931,8 @@@ "label.register.oauth": "Register OAuth", "label.register.template": "Register Template", "label.register.user.data": "Register User Data", +"label.register.cni.config": "Register CNI Configuration", + "label.register.user.data.details": "Enter the User Data in plain text or in Base64 encoding. Up to 32KB of Base64 encoded User Data can be sent by default. The setting vm.userdata.max.length can be used to increase the limit to upto 1MB.", "label.reinstall.vm": "Reinstall Instance", "label.reject": "Reject", "label.related": "Related", @@@ -1960,9 -1888,8 +1958,9 @@@ "label.remove.ldap": "Remove LDAP", "label.remove.logical.network": "Remove Network from logical router", "label.remove.logical.router": "Remove logical router", - "label.remove.network.offering": "Remove Network offering", + "label.remove.network.offering": "Remove Network Offering", "label.remove.network.route.table": "Remove Tungsten Fabric Network routing table", +"label.remove.nodes": "Remove nodes from Kubernetes cluster", "label.remove.pf": "Remove port forwarding rule", "label.remove.policy": "Remove policy", "label.remove.project.account": "Remove Account from project", @@@ -2130,20 -2054,16 +2127,19 @@@ "label.sequence": "Sequence", "label.server": "Server", "label.server.certificate": "Server certificate", - "label.serviceip": "Service IP", "label.service.connectivity.distributedroutercapabilitycheckbox": "Distributed router", "label.service.connectivity.regionlevelvpccapabilitycheckbox": "Region level VPC", - "label.service.group": "Service group", - "label.serviceip": "Management IP", + "label.service.group": "Service Group", -"label.serviceip": "Management IP", ++"label.serviceip": "Management Server IP", "label.service.lb.elasticlbcheckbox": "Elastic LB", "label.service.lb.inlinemodedropdown": "Mode", "label.service.lb.lbisolationdropdown": "LB isolation", "label.service.lb.netscaler.servicepackages": "Netscaler service packages", "label.service.lb.netscaler.servicepackages.description": "Service package description", - "label.service.offering": "Service offering", + "label.service.offering": "Service Offering", +"label.service.offering.controlnodes": "Compute offering for Control Nodes", +"label.service.offering.etcdnodes": "Compute offering for etcd Nodes", +"label.service.offering.workernodes": "Compute offering for Worker Nodes", "label.service.staticnat.associatepublicip": "Associate public IP", "label.service.staticnat.elasticipcheckbox": "Elastic IP", "label.servicegroupuuid": "Service Group", @@@ -2219,24 -2138,21 +2215,25 @@@ "label.srctaguuid": "Source Tag", "label.srx": "SRX", "label.srx.firewall": "Juniper SRX firewall", - "label.ssh.key.pairs": "SSH key pairs", + "label.ssh.key.pairs": "SSH Key Pairs", +"label.storageaccessgroups": "Storage Access Groups", +"label.clusterstorageaccessgroups": "Cluster Storage Access Groups", +"label.podstorageaccessgroups": "Pod Storage Access Groups", +"label.zonestorageaccessgroups": "Zone Storage Access Groups", "label.uefi.supported": "UEFI supported", "label.usediops": "IOPS used", - "label.userdataid": "Userdata ID", - "label.userdataname": "Userdata name", - "label.userdatadetails": "Userdata details", - "label.userdataparams": "Userdata parameters", - "label.userdatapolicy": "Userdata link policy", - "label.userdata.text": "Manual Userdata entry", - "label.userdata.registered": "Stored Userdata", - "label.userdata.do.override": "Userdata override", - "label.userdata.do.append": "Userdata append", - "label.userdatapolicy.tooltip": "Userdata linked to the Template can be overridden by Userdata provided during Instance deploy. Select the override policy as required.", + "label.user.data.id": "User Data ID", + "label.user.data.name": "User Data name", + "label.user.data.details": "User Data details", + "label.user.data.params": "User Data parameters", + "label.user.data.policy": "User Data link policy", + "label.user.data.text": "Manual User Data entry", + "label.user.data.registered": "Stored User Data", + "label.user.data.do.override": "User Data override", + "label.user.data.do.append": "User Data append", + "label.user.data.policy.tooltip": "User Data linked to the Template can be overridden by User Data provided during Instance deploy. Select the override policy as required.", "label.user.data": "User Data", + "label.user.data.library": "User Data Library", "label.ssh.port": "SSH port", "label.sshkeypair": "New SSH key pair", "label.sshkeypairs": "SSH key pairs", @@@ -2518,14 -2433,12 +2517,12 @@@ "label.usagetypedescription": "Usage description", "label.use.kubectl.access.cluster": "<code><b>kubectl</b></code> and <code><b>kubeconfig</b></code> file to access cluster", "label.use.local.timezone": "Use local timezone", +"label.use.router.ip.resolver": "Use Virtual Router IP as resolver", "label.used": "Used", "label.usehttps": "Use HTTPS", - "label.usenewdiskoffering": "Replace disk offering?", + "label.usenewdiskoffering": "Replace Disk Offering?", "label.user": "User", "label.user.conflict": "Conflict", - "label.userdata": "Userdata", - "label.userdatal2": "User data", -"label.user.data": "User Data", "label.username": "Username", "label.username.tooltip": "The Username for the Host", "label.users": "Users", @@@ -2629,10 -2542,8 +2626,10 @@@ "label.vnf.templates": "VNF templates", "label.vnf.template.register": "Register VNF template", "label.vnmc": "VNMC", - "label.volgroup": "Volume group", + "label.volgroup": "Volume Group", "label.volume": "Volume", +"label.vms.empty": "No VMs available to be added to the Kubernetes cluster", +"label.vms.remove.empty": "No external VMs present in the Kubernetes cluster to be removed", "label.volume.empty": "No data volumes attached to this Instance", "label.volume.encryption.support": "Volume Encryption Supported", "label.volume.metrics": "Volume Metrics", @@@ -2650,10 -2561,9 +2647,10 @@@ "label.volumetype": "Volume Type", "label.vpc": "VPC", "label.vpcs": "VPCs", +"label.vpc.gateway.ip": "VPC Gateway IP", "label.vpc.id": "VPC ID", - "label.vpc.offerings": "VPC offerings", - "label.vpc.virtual.router": "VPC virtual router", + "label.vpc.offerings": "VPC Offerings", + "label.vpc.virtual.router": "VPC Virtual Router", "label.vpc.restart.required": "VPC restart required", "label.vpcid": "VPC", "label.vpclimit": "VPC limits", @@@ -2721,9 -2631,9 +2718,10 @@@ "label.oobm.username": "Out-of-band management username", "label.bucket.update": "Update Bucket", "label.bucket.delete": "Delete Bucket", -"label.quotagb": "Quota in GB", +"label.quotagib": "Quota in GiB", + "label.edgecluster": "Edge Cluster", "label.encryption": "Encryption", +"label.etcdnodes": "Number of etcd nodes", "label.versioning": "Versioning", "label.objectlocking": "Object Lock", "label.bucket.policy": "Bucket Policy", @@@ -2875,11 -2772,10 +2873,11 @@@ "message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule", "message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...", "message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule", +"message.add.netris.controller": "Add Netris Provider", "message.add.nsx.controller": "Add NSX Provider", - "message.add.network": "Add a new network for zone: <b><span id=\"zone_name\"></span></b>", - "message.add.network.acl.failed": "Adding network ACL list failed.", - "message.add.network.acl.processing": "Adding network ACL list...", + "message.add.network": "Add a new network for Zone: <b><span id=\"zone_name\"></span></b>", + "message.add.network.acl.failed": "Adding network ACL failed.", + "message.add.network.acl.processing": "Adding network ACL...", "message.add.network.failed": "Adding network failed.", "message.add.network.processing": "Adding network...", "message.add.new.gateway.to.vpc": "Please specify the information to add a new gateway to this VPC.", @@@ -3119,17 -3009,15 +3117,17 @@@ "message.desc.import.shared.kvm.wizard": "Import QCOW2 image from selected Primary Storage Pool", "message.desc.import.unmanage.volume": "Please choose a storage pool that you want to import or unmanage volumes. The storage pool should be in Up status. <br>This feature only supports KVM.", "message.desc.importexportinstancewizard": "By choosing to manage an Instance, CloudStack takes over the orchestration of that Instance. Unmanaging an Instance removes CloudStack ability to manage it. In both cases, the Instance is left running and no changes are done to the VM on the hypervisor.<br><br>For KVM, managing a VM is an experimental feature.", - "message.desc.importmigratefromvmwarewizard": "By selecting an existing or external VMware Datacenter and an instance to import, CloudStack migrates the selected instance from VMware to KVM on a conversion host using virt-v2v and imports it into a KVM cluster", - "message.desc.primary.storage": "Each cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", + "message.desc.importmigratefromvmwarewizard": "By selecting an existing or external VMware Datacenter and an instance to import, CloudStack migrates the selected instance from VMware to KVM on a conversion host using virt-v2v and imports it into a KVM Cluster", + "message.desc.primary.storage": "Each Cluster must contain one or more primary storage servers. We will add the first one now. Primary storage contains the disk volumes for all the Instances running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", +"message.desc.register.template": "Hosted on download.cloudstack.org, these templates can be easily registered directly within CloudStack. Simply click <strong>Register Template</strong> for the templates you wish to use.", "message.desc.reset.ssh.key.pair": "Please specify a ssh key pair that you would like to add to this Instance.", - "message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.", - "message.desc.register.user.data": "Please fill in the following data to register a User data.", + "message.desc.secondary.storage": "Each Zone must have at least one NFS or secondary storage server. We will add the first one now. Secondary storage stores Instance Templates, ISO images, and Instance disk volume Snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.", + "message.desc.register.user.data": "Please fill in the following to register new User Data.", +"message.desc.register.cni.config": "Please fill in the following data to register CNI Configuration as user data.", "message.desc.registered.user.data": "Registered a User Data.", - "message.desc.zone": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", - "message.desc.zone.edge": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 Networks can be deployed in such zones and functionalities that require secondary storages are not supported.", - "message.drs.plan.description": "The maximum number of live migrations allowed for DRS. Configure DRS under the settings tab before generating a plan or to enable automatic DRS for the cluster.", + "message.desc.zone": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more Pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", + "message.desc.zone.edge": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 Networks can be deployed in such zones and functionalities that require secondary storages are not supported.", + "message.drs.plan.description": "The maximum number of live migrations allowed for DRS. Configure DRS under the settings tab before generating a plan or to enable automatic DRS for the Cluster.", "message.drs.plan.executed": "DRS plan executed successfully.", "message.zone.edge.local.storage": "Local storage will be used by default for User Instances and virtual routers", "message.detach.disk": "Are you sure you want to detach this disk?", @@@ -3344,9 -3232,8 +3342,9 @@@ "message.import.volume": "Please specify the domain, account or project name. <br>If not set, the volume will be imported for the caller.", "message.info.cloudian.console": "Cloudian Management Console should open in another window.", "message.installwizard.cloudstack.helptext.website": " * Project website:\t ", +"message.infra.setup.netris.description": "This zone must contain a Netris provider because the isolation method is Netris", - "message.infra.setup.nsx.description": "This zone must contain an NSX provider because the isolation method is NSX", - "message.infra.setup.tungsten.description": "This zone must contain a Tungsten-Fabric provider because the isolation method is TF", + "message.infra.setup.nsx.description": "This Zone must contain an NSX provider because the isolation method is NSX", + "message.infra.setup.tungsten.description": "This Zone must contain a Tungsten-Fabric provider because the isolation method is TF", "message.installwizard.cloudstack.helptext.document": " * Documentation:\t ", "message.installwizard.cloudstack.helptext.header": "\nYou can find more information about Apache CloudStack™ on the pages listed below.\n", "message.installwizard.cloudstack.helptext.issues": " * Report issues:\t ", @@@ -3354,21 -3241,14 +3352,21 @@@ "message.installwizard.cloudstack.helptext.releasenotes": " * Release notes:\t ", "message.installwizard.cloudstack.helptext.survey": " * Take the survey:\t ", "message.installwizard.copy.whatiscloudstack": "CloudStack™ is a software platform that pools computing resources to build public, private, and hybrid Infrastructure as a Service (IaaS) clouds. CloudStack™ manages the Network, storage, and compute nodes that make up a cloud infrastructure. Use CloudStack™ to deploy, manage, and configure cloud computing environments.\n\nExtending beyond individual Instance images running on commodity hardware, CloudStack™ provides a turnkey cloud infras [...] - "message.installwizard.tooltip.addpod.name": "A name for the pod.", + "message.installwizard.tooltip.addpod.name": "A name for the Pod.", "message.installwizard.tooltip.addpod.reservedsystemendip": "This is the IP range in the private Network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.", - "message.installwizard.tooltip.addpod.reservedsystemgateway": "The gateway for the hosts in that pod.", + "message.installwizard.tooltip.addpod.reservedsystemgateway": "The gateway for the hosts in that Pod.", "message.installwizard.tooltip.addpod.reservedsystemstartip": "This is the IP range in the private Network that the CloudStack uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers.", - "message.installwizard.tooltip.configureguesttraffic.guestendip": "The range of IP addresses that will be available for allocation to guests in this zone. If one NIC is used, these IPs should be in the same CIDR as the pod CIDR.", + "message.installwizard.tooltip.configureguesttraffic.guestendip": "The range of IP addresses that will be available for allocation to guests in this Zone. If one NIC is used, these IPs should be in the same CIDR as the Pod CIDR.", "message.installwizard.tooltip.configureguesttraffic.guestgateway": "The gateway that the guests should use.", "message.installwizard.tooltip.configureguesttraffic.guestnetmask": "The netmask in use on the subnet that the guests should use.", - "message.installwizard.tooltip.configureguesttraffic.gueststartip": "The range of IP addresses that will be available for allocation to guests in this zone. If one NIC is used, these IPs should be in the same CIDR as the pod CIDR.", + "message.installwizard.tooltip.configureguesttraffic.gueststartip": "The range of IP addresses that will be available for allocation to guests in this Zone. If one NIC is used, these IPs should be in the same CIDR as the Pod CIDR.", +"message.installwizard.tooltip.netris.provider.name": "Netris Provider name is required", +"message.installwizard.tooltip.netris.provider.url": "Netris Provider URL not provided", +"message.installwizard.tooltip.netris.provider.username": "Netris Provider username not provided", +"message.installwizard.tooltip.netris.provider.password": "Netris Provider password not provided", +"message.installwizard.tooltip.netris.provider.site": "Netris Provider Site name not provided", +"message.installwizard.tooltip.netris.provider.tag": "Netris Tag to be assigned to vNets", +"message.installwizard.tooltip.netris.provider.tenant.name": "Netris Provider Admin Tenant name not provided", "message.installwizard.tooltip.nsx.provider.hostname": "NSX Provider hostname / IP address not provided", "message.installwizard.tooltip.nsx.provider.username": "NSX Provider username not provided", "message.installwizard.tooltip.nsx.provider.password": "NSX Provider password not provided", @@@ -3391,16 -3270,14 +3389,16 @@@ "message.ip.v6.prefix.delete": "IPv6 prefix deleted", "message.iso.arch": "Please select an ISO architecture", "message.iso.desc": "Disc image containing data or bootable media for OS.", - "message.kubeconfig.cluster.not.available": "Kubernetes cluster kubeconfig not available currently.", + "message.kubeconfig.cluster.not.available": "Kubernetes Cluster kubeconfig not available currently.", +"message.kubernetes.cluster.add.nodes": "Please confirm that you want to add the following nodes to the cluster", - "message.kubernetes.cluster.delete": "Please confirm that you want to destroy the cluster.", - "message.kubernetes.cluster.scale": "Please select desired cluster configuration.", - "message.kubernetes.cluster.start": "Please confirm that you want to start the cluster.", - "message.kubernetes.cluster.stop": "Please confirm that you want to stop the cluster.", + "message.kubernetes.cluster.delete": "Please confirm that you want to destroy the Cluster.", + "message.kubernetes.cluster.scale": "Please select desired Cluster configuration.", + "message.kubernetes.cluster.start": "Please confirm that you want to start the Cluster.", + "message.kubernetes.cluster.stop": "Please confirm that you want to stop the Cluster.", +"message.kubernetes.cluster.remove.nodes": "Please confirm that you want to remove the following nodes from the cluster", "message.kubernetes.cluster.upgrade": "Please select new Kubernetes version.", "message.kubernetes.version.delete": "Please confirm that you want to delete this Kubernetes version.", - "message.l2.network.unsupported.for.nsx": "L2 networks aren't supported for NSX enabled zones", + "message.l2.network.unsupported.for.nsx": "L2 networks aren't supported for NSX enabled Zones", "message.launch.zone": "Zone is ready to launch; please proceed to the next step.", "message.launch.zone.description": "Zone is ready to launch; please proceed to the next step.", "message.launch.zone.hint": "Configure Network components and traffic including IP addresses.", @@@ -3475,17 -3352,15 +3473,17 @@@ "message.password.reset.success": "Password has been reset successfully. Please login using your new credentials.", "message.path": "Path : ", "message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.", +"message.please.confirm.remove.cni.configuration": "Please confirm that you want to remove this CNI Configuration", "message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.", - "message.please.confirm.remove.user.data": "Please confirm that you want to remove this Userdata", + "message.please.confirm.remove.user.data": "Please confirm that you want to remove this User Data", "message.please.enter.valid.value": "Please enter a valid value.", "message.please.enter.value": "Please enter values.", "message.please.wait.while.autoscale.vmgroup.is.being.created": "Please wait while your AutoScaling Group is being created; this may take a while...", - "message.please.wait.while.zone.is.being.created": "Please wait while your zone is being created; this may take a while...", + "message.please.wait.while.zone.is.being.created": "Please wait while your Zone is being created; this may take a while...", "message.pod.dedicated": "Pod dedicated.", "message.pod.dedication.released": "Pod dedication released.", -"message.prepare.for.shutdown": "Please confirm that you would like to prep this Management server for shutdown. It will not accept any new Async Jobs but will NOT terminate after there are no pending jobs.", +"message.prepare.for.shutdown": "Please confirm that you would like to prepare this Management Server for shutdown. It will not accept any new Async Jobs but will NOT terminate after there are no pending jobs.", +"message.prepare.for.maintenance": "Please confirm that you would like to prepare this Management Server for maintenance. It will not accept any new Async Jobs.", "message.primary.storage.invalid.state": "Primary storage is not in Up state", "message.processing.complete": "Processing complete!", "message.protocol.description": "For XenServer, choose NFS, iSCSI, or PreSetup. For KVM, choose NFS, SharedMountPoint, RDB, CLVM or Gluster. For vSphere, choose NFS, PreSetup (VMFS or iSCSI or FiberChannel or vSAN or vVols) or DatastoreCluster. For Hyper-V, choose SMB/CIFS. For LXC, choose NFS or SharedMountPoint. For OVM, choose NFS or OCFS2.", @@@ -3571,13 -3446,12 +3569,13 @@@ "message.set.default.nic.manual": "Please manually update the default NIC on the Instance now.", "message.setting.updated": "Setting Updated:", "message.setting.update.delay": "The new value will take effect within 30 seconds.", - "message.setup.physical.network.during.zone.creation": "When adding a zone, you need to set up one or more physical networks. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined. Add or remove one or more traffic types onto each physical network.", - "message.setup.physical.network.during.zone.creation.basic": "When adding a basic zone, you can set up one physical Network, which corresponds to a NIC on the hypervisor. The Network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical Network.", + "message.setup.physical.network.during.zone.creation": "When adding a Zone, you need to set up one or more physical networks. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined. Add or remove one or more traffic types onto each physical network.", + "message.setup.physical.network.during.zone.creation.basic": "When adding a basic Zone, you can set up one physical Network, which corresponds to a NIC on the hypervisor. The Network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical Network.", "message.shared.network.offering.warning": "Domain admins and regular Users can only create shared Networks from Network offering with the setting specifyvlan=false. Please contact an administrator to create a Network offering if this list is empty.", - "message.shared.network.unsupported.for.nsx": "Shared networks aren't supported for NSX enabled zones", - "message.shutdown.triggered": "Shutdown has been triggered. This Management Server will not accept new jobs", + "message.shared.network.unsupported.for.nsx": "Shared networks aren't supported for NSX enabled Zones", + "message.shutdown.triggered": "A shutdown has been triggered. CloudStack will not accept new jobs", +"message.maintenance.initiated": "Maintenance has been initiated. This Management Server will not accept new jobs", - "message.snapshot.additional.zones": "Snapshots will always be created in its native zone - %x, here you can select additional zone(s) where it will be copied to at creation time", + "message.snapshot.additional.zones": "Snapshots will always be created in its native Zone - %x, here you can select additional zone(s) where it will be copied to at creation time", "message.sourcenatip.change.warning": "WARNING: Changing the sourcenat IP address of the network will cause connectivity downtime for the Instances with NICs in the Network.", "message.sourcenatip.change.inhibited": "Changing the sourcenat to this IP of the Network to this address is inhibited as firewall rules are defined for it. This can include port forwarding or load balancing rules.\n - If this is an Isolated Network, please use updateNetwork/click the edit button.\n - If this is a VPC, first clear all other rules for this address.", "message.specify.tag.key": "Please specify a tag key.", @@@ -3599,11 -3473,9 +3597,11 @@@ "message.success.add.kuberversion": "Successfully added Kubernetes version", "message.success.add.logical.router": "Successfully added Logical Router", "message.success.add.network": "Successfully added Network", - "message.success.add.network.acl": "Successfully added Network ACL list", + "message.success.add.network.acl": "Successfully added Network ACL", "message.success.add.network.static.route": "Successfully added Network Static Route", "message.success.add.network.permissions": "Successfully added Network permissions", +"message.success.add.nodes.to.cluster": "Successfully added nodes to Kubernetes cluster", +"message.success.remove.nodes.from.cluster": "Successfully removed nodes from Kubernetes cluster", "message.success.add.physical.network": "Successfully added Physical Network", "message.success.add.object.storage": "Successfully added Object Storage", "message.success.add.policy.rule": "Successfully added Policy rule", @@@ -3842,12 -3713,11 +3840,12 @@@ "message.warn.change.primary.storage.scope": "This feature is tested and supported for the following configurations:<br>KVM - NFS/Ceph - DefaultPrimary<br>VMware - NFS - DefaultPrimary<br>*There might be extra steps involved to make it work for other configurations.", "message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats.", "message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported.", +"message.warn.select.template": "Please select a Template for Registration.", - "message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network’s MTU settings", + "message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network's MTU settings", "message.webhook.deliveries.time.filter": "Webhook deliveries list can be filtered based on date-time. Select 'Custom' for specifying start and end date range.", "message.zone.creation.complete": "Zone creation complete.", - "message.zone.detail.description": "Populate zone details.", - "message.zone.detail.hint": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", + "message.zone.detail.description": "Populate Zone details.", + "message.zone.detail.hint": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more Pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", "message.validate.min": "Please enter a value greater than or equal to {0}.", "message.action.delete.object.storage": "Please confirm that you want to delete this Object Store", "message.bgp.peers.null": "Please note, if no BGP peers are selected, the VR will connect to <br> (1) dedicated BGP peers the owner can access, if the owner has dedicated BGP peers and account setting use.system.bgp.peers is set to false; <br> (2) all BGP peers the owner can access, otherwise.<br>", diff --cc ui/src/components/view/InfoCard.vue index c5e771de5a6,f1efcaef281..7ec91621a70 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@@ -31,11 -31,14 +31,14 @@@ <edit-outlined class="upload-icon"/> </div> <slot name="avatar"> - <span v-if="(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon) && !['router', 'systemvm', 'volume'].includes($route.path.split('/')[1])"> - <resource-icon :image="getImage(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon)" size="4x" style="margin-right: 5px"/> + <span v-if="resourceIcon && !['router', 'systemvm', 'volume'].includes($route.path.split('/')[1])"> + <resource-icon :image="resourceIcon" size="4x" style="margin-right: 5px"/> </span> + <span v-else-if="resource.vmtype === 'sharedfsvm'"> + <file-text-outlined style="font-size: 36px;" /> + </span> <span v-else> - <os-logo v-if="resource.ostypeid || resource.ostypename || ['guestoscategory'].includes($route.path.split('/')[1])" :osId="resource.ostypeid" :osName="resource.ostypename || resource.name" size="3x" @update-osname="setResourceOsType"/> + <os-logo v-if="resource.ostypeid || resource.ostypename || ['guestoscategory'].includes($route.path.split('/')[1])" :osId="resource.ostypeid" :osName="resource.ostypename || resource.osdisplayname || resource.name" size="3x" /> <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" /> <font-awesome-icon v-else-if="$route.meta.icon && Array.isArray($route.meta.icon)" @@@ -912,7 -891,7 +927,8 @@@ import UploadResourceIcon from '@/compo import eventBus from '@/config/eventBus' import ResourceIcon from '@/components/view/ResourceIcon' import ResourceLabel from '@/components/widgets/ResourceLabel' +import ImageDeployInstanceButton from '@/components/view/ImageDeployInstanceButton' + import { FileTextOutlined } from '@ant-design/icons-vue' export default { name: 'InfoCard', @@@ -925,7 -904,7 +941,8 @@@ UploadResourceIcon, ResourceIcon, ResourceLabel, + ImageDeployInstanceButton + FileTextOutlined }, props: { resource: { diff --cc ui/src/views/compute/DeployVM.vue index ac98f35d806,a604fe68fe4..7cdb5b52b9e --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@@ -583,44 -603,16 +583,44 @@@ @change="val => { dynamicscalingenabled = val }"/> </a-form-item> </a-form-item> + <a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseFeatureEnabled"> + <template #label> + <tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')"/> + </template> + <a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/> + </a-form-item> + <a-row :gutter="12" v-if="isLeaseFeatureEnabled && showLeaseOptions"> + <a-col :md="12" :lg="12"> + <a-form-item name="leaseduration" ref="leaseduration"> + <template #label> + <tooltip-label :title="$t('label.leaseduration')" /> + </template> + <a-input + v-model:value="form.leaseduration" + :placeholder="$t('label.instance.lease.placeholder')"/> + </a-form-item> + </a-col> + <a-col :md="12" :lg="12"> + <a-form-item name="leaseexpiryaction" ref="leaseexpiryaction"> + <template #label> + <tooltip-label :title="$t('label.leaseexpiryaction')" /> + </template> + <a-select v-model:value="form.leaseexpiryaction" :defaultValue="leaseexpiryaction"> + <a-select-option v-for="action in expiryActions" :key="action" :label="action" /> + </a-select> + </a-form-item> + </a-col> + </a-row> - <a-form-item :label="$t('label.userdata')"> + <a-form-item :label="$t('label.user.data')"> <a-card> <div v-if="this.template && this.template.userdataid"> - <a-text type="primary"> - Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" - </a-text><br/><br/> + <a-typography-text> + Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" + </a-typography-text><br/><br/> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> - <a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> - Enter the values for the variables in userdata - </a-text> + <a-typography-text v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> + Enter the values for the variables in userdata + </a-typography-text> <a-input-group> <a-table size="small" @@@ -2496,68 -2349,66 +2495,77 @@@ export default }) }, fetchOptions (param, name, exclude) { - if (exclude && exclude.length > 0) { - if (exclude.includes(name)) { - return + return new Promise((resolve, reject) => { + if (exclude && exclude.length > 0 && exclude.includes(name)) { + return resolve(null) } - this.loading[name] = true - param.loading = true - param.opts = [] - const options = param.options || {} - if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'dynamicScalingVmConfig', 'hypervisors'].includes(name)) { - options.listall = true - } - postAPI(param.list, options).then((response) => { - param.loading = false - _.map(response, (responseItem, responseKey) => { - if (Object.keys(responseItem).length === 0) { - this.rowCount[name] = 0 - this.options[name] = [] - return resolve(null) + } + this.loading[name] = true + param.loading = true + param.opts = [] + const options = param.options || {} + if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'hypervisors'].includes(name)) { + options.listall = true + } - api(param.list, options).then((response) => { ++ postApi(param.list, options).then((response) => { + param.loading = false + _.map(response, (responseItem, responseKey) => { + if (Object.keys(responseItem).length === 0) { + this.rowCount[name] = 0 + this.options[name] = [] + return + } + if (!responseKey.includes('response')) { + return + } + _.map(responseItem, (response, key) => { + if (key === 'count') { + this.rowCount[name] = response + return } - param.opts = response - this.options[name] = response - - if (name === 'hypervisors') { - const hypervisorFromResponse = response[0] && response[0].name ? response[0].name : null - this.dataPreFill.hypervisor = hypervisorFromResponse - this.form.hypervisor = hypervisorFromResponse + if (!responseKey.includes('response')) { + return resolve(null) } + _.map(responseItem, (response, key) => { + if (key === 'count') { + this.rowCount[name] = response + return + } + param.opts = response + this.options[name] = response - if (param.field) { - this.fillValue(param.field) - } - }) + if (name === 'hypervisors') { + const hypervisorFromResponse = response[0] && response[0].name ? response[0].name : null + this.dataPreFill.hypervisor = hypervisorFromResponse + this.form.hypervisor = hypervisorFromResponse + } - if (name === 'zones') { - let zoneid = '' - if (this.$route.query.zoneid) { - zoneid = this.$route.query.zoneid - } else if (this.options.zones.length === 1) { - zoneid = this.options.zones[0].id - } - if (zoneid) { - this.form.zoneid = zoneid - this.onSelectZoneId(zoneid) + if (param.field) { + this.fillValue(param.field) + } + }) + + if (name === 'zones') { + let zoneid = '' + if (this.$route.query.zoneid) { + zoneid = this.$route.query.zoneid + } else if (this.options.zones.length === 1) { + zoneid = this.options.zones[0].id + } + if (zoneid) { + this.form.zoneid = zoneid + this.onSelectZoneId(zoneid) + } } - } + }) + resolve(response) + }).catch(function (error) { + console.log(error.stack) + param.loading = false + reject(error) + }).finally(() => { + this.loading[name] = false }) - }).catch(function (error) { - console.log(error.stack) - param.loading = false - }).finally(() => { - this.loading[name] = false }) }, fetchTemplates (templateFilter, params) { diff --cc ui/src/views/compute/DeployVnfAppliance.vue index a5618f5d9d4,1117413d710..19a87ba8664 --- a/ui/src/views/compute/DeployVnfAppliance.vue +++ b/ui/src/views/compute/DeployVnfAppliance.vue @@@ -549,16 -564,16 +549,16 @@@ @change="val => { dynamicscalingenabled = val }"/> </a-form-item> </a-form-item> - <a-form-item :label="$t('label.userdata')"> + <a-form-item :label="$t('label.user.data')"> <a-card> <div v-if="this.template && this.template.userdataid"> - <a-text type="primary"> + <a-typography-text> Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" - </a-text><br/><br/> + </a-typography-text><br/><br/> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> - <a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> + <a-typography-text v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> Enter the values for the variables in userdata - </a-text> + </a-typography-text> <a-input-group> <a-table size="small" @@@ -1288,18 -1277,20 +1287,19 @@@ export default key: 'templateid', tab: this.$t('label.templates') }] - return tabList }, userdataTabList () { - return [ - { - key: 'userdataregistered', - tab: this.$t('label.userdata.registered') - }, - { - key: 'userdatatext', - tab: this.$t('label.userdata.text') - } - ] + let tabList = [] + tabList = [{ + key: 'userdataregistered', + tab: this.$t('label.user.data.registered') + }, + { + key: 'userdatatext', + tab: this.$t('label.user.data.text') + }] + + return tabList }, showVnfNicsSection () { return this.networks && this.networks.length > 0 && this.vm.templateid && this.templateVnfNics && this.templateVnfNics.length > 0 @@@ -2467,68 -2386,66 +2467,77 @@@ }) }, fetchOptions (param, name, exclude) { - if (exclude && exclude.length > 0) { - if (exclude.includes(name)) { - return + return new Promise((resolve, reject) => { + if (exclude && exclude.length > 0 && exclude.includes(name)) { + return resolve(null) } - this.loading[name] = true - param.loading = true - param.opts = [] - const options = param.options || {} - if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'dynamicScalingVmConfig', 'hypervisors'].includes(name)) { - options.listall = true - } - postAPI(param.list, options).then((response) => { - param.loading = false - _.map(response, (responseItem, responseKey) => { - if (Object.keys(responseItem).length === 0) { - this.rowCount[name] = 0 - this.options[name] = [] - return resolve(null) + } + this.loading[name] = true + param.loading = true + param.opts = [] + const options = param.options || {} + if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'hypervisors'].includes(name)) { + options.listall = true + } - api(param.list, options).then((response) => { ++ postApi(param.list, options).then((response) => { + param.loading = false + _.map(response, (responseItem, responseKey) => { + if (Object.keys(responseItem).length === 0) { + this.rowCount[name] = 0 + this.options[name] = [] + return + } + if (!responseKey.includes('response')) { + return + } + _.map(responseItem, (response, key) => { + if (key === 'count') { + this.rowCount[name] = response + return } - param.opts = response - this.options[name] = response - - if (name === 'hypervisors') { - const hypervisorFromResponse = response[0] && response[0].name ? response[0].name : null - this.dataPreFill.hypervisor = hypervisorFromResponse - this.form.hypervisor = hypervisorFromResponse + if (!responseKey.includes('response')) { + return resolve(null) } + _.map(responseItem, (response, key) => { + if (key === 'count') { + this.rowCount[name] = response + return + } + param.opts = response + this.options[name] = response - if (param.field) { - this.fillValue(param.field) - } - }) + if (name === 'hypervisors') { + const hypervisorFromResponse = response[0] && response[0].name ? response[0].name : null + this.dataPreFill.hypervisor = hypervisorFromResponse + this.form.hypervisor = hypervisorFromResponse + } - if (name === 'zones') { - let zoneid = '' - if (this.$route.query.zoneid) { - zoneid = this.$route.query.zoneid - } else if (this.options.zones.length === 1) { - zoneid = this.options.zones[0].id - } - if (zoneid) { - this.form.zoneid = zoneid - this.onSelectZoneId(zoneid) + if (param.field) { + this.fillValue(param.field) + } + }) + + if (name === 'zones') { + let zoneid = '' + if (this.$route.query.zoneid) { + zoneid = this.$route.query.zoneid + } else if (this.options.zones.length === 1) { + zoneid = this.options.zones[0].id + } + if (zoneid) { + this.form.zoneid = zoneid + this.onSelectZoneId(zoneid) + } } - } + }) + resolve(response) + }).catch(function (error) { + console.log(error.stack) + param.loading = false + reject(error) + }).finally(() => { + this.loading[name] = false }) - }).catch(function (error) { - console.log(error.stack) - param.loading = false - }).finally(() => { - this.loading[name] = false }) }, fetchTemplates (templateFilter, params) { diff --cc ui/src/views/compute/EditVM.vue index f7b7649b96b,d5e75fcc658..e35ca6dd49d --- a/ui/src/views/compute/EditVM.vue +++ b/ui/src/views/compute/EditVM.vue @@@ -291,19 -247,9 +289,22 @@@ export default this.template = templateResponses[0] }) }, + fetchDynamicScalingVmConfig () { + const params = {} + params.name = 'enable.dynamic.scale.vm' + params.zoneid = this.resource.zoneid + var apiName = 'listConfigurations' + getAPI(apiName, params).then(json => { + const configResponse = json.listconfigurationsresponse.configuration + this.dynamicScalingVmConfig = configResponse[0]?.value === 'true' + }) + }, + canDynamicScalingEnabled () { + return this.template.isdynamicallyscalable && this.serviceOffering.dynamicscalingenabled && this.dynamicScalingVmConfig + }, + isDynamicScalingEnabled () { + return this.template.isdynamicallyscalable && this.serviceOffering.dynamicscalingenabled && this.$store.getters.features.dynamicscalingenabled + }, fetchOsTypes () { this.osTypes.loading = true this.osTypes.opts = [] diff --cc ui/src/views/compute/RegisterUserData.vue index 4631ad70e7d,3fb54962f2b..8f7b438576d --- a/ui/src/views/compute/RegisterUserData.vue +++ b/ui/src/views/compute/RegisterUserData.vue @@@ -35,34 -35,20 +35,31 @@@ :placeholder="apiParams.name.description" v-focus="true" /> </a-form-item> - <div v-if="$route.name === 'userdata'"> - <a-form-item name="userdata" ref="userdata"> - <template #label> - <tooltip-label :title="$t('label.userdata')" :tooltip="apiParams.userdata.description"/> - </template> - <a-textarea - v-model:value="form.userdata" - :placeholder="apiParams.userdata.description"/> - </a-form-item> - </div> - <div v-else> - <a-form-item name="cniconfig" ref="cniconfig"> - <template #label> - <tooltip-label :title="$t('label.cniconfiguration')" :tooltip="apiParams.cniconfig.description"/> - </template> - <a-textarea - v-model:value="form.cniconfig" - :placeholder="apiParams.cniconfig.description"/> - </a-form-item> - </div> + <a-form-item name="userdata" ref="userdata"> + <template #label> + <tooltip-label :title="$t('label.user.data')" :tooltip="$t('label.register.user.data.details')"/> + </template> + <a-textarea + v-model:value="form.userdata" + :placeholder="$t('label.register.user.data.details')"/> + </a-form-item> ++ <a-form-item name="cniconfig" ref="cniconfig"> ++ <template #label> ++ <tooltip-label :title="$t('label.cniconfiguration')" :tooltip="apiParams.cniconfig.description"/> ++ </template> ++ <a-textarea ++ v-model:value="form.cniconfig" ++ :placeholder="apiParams.cniconfig.description"/> ++ </a-form-item> <a-form-item name="isbase64" ref="isbase64" :label="$t('label.is.base64.encoded')"> <a-checkbox v-model:checked="form.isbase64"></a-checkbox> </a-form-item> <a-form-item name="params" ref="params"> <template #label> + <tooltip-label + :title="$route.name === 'userdata' ? $t('label.userdataparams') : $t('label.cniconfigparams')" + :tooltip="apiParams.params.description"/> + <tooltip-label :title="$t('label.user.data.params')" :tooltip="apiParams.params.description"/> </template> <a-select mode="tags" diff --cc ui/src/views/compute/ResetUserData.vue index 462a0901b88,8849db5dcca..c05c9452b2d --- a/ui/src/views/compute/ResetUserData.vue +++ b/ui/src/views/compute/ResetUserData.vue @@@ -362,10 -362,13 +362,10 @@@ export default params.id = this.resource.resetUserDataResourceId ? this.resource.resetUserDataResourceId : this.resource.id const resetUserDataApiName = this.resource.resetUserDataApiName ? this.resource.resetUserDataApiName : 'resetUserDataForVirtualMachine' - const httpMethod = params.userdata ? 'POST' : 'GET' - const args = httpMethod === 'POST' ? {} : params - const data = httpMethod === 'POST' ? params : {} - api(resetUserDataApiName, args, httpMethod, data).then(json => { + postAPI(resetUserDataApiName, params).then(json => { this.$message.success({ - content: `${this.$t('label.action.userdata.reset')} - ${this.$t('label.success')}`, + content: `${this.$t('label.action.user.data.reset')} - ${this.$t('label.success')}`, duration: 2 }) this.$emit('refresh-data') diff --cc ui/src/views/compute/wizard/UserDataSelection.vue index 06eaefcc060,0b8fdc06952..b8739e81e53 --- a/ui/src/views/compute/wizard/UserDataSelection.vue +++ b/ui/src/views/compute/wizard/UserDataSelection.vue @@@ -34,8 -33,7 +34,8 @@@ :scroll="{ y: 225 }" > <template #headerCell="{ column }"> - <template v-if="column.key === 'name'"><solution-outlined /> {{ $t('label.userdata') }}</template> + <template v-if="column.key === 'name'"><solution-outlined /> {{ $t('label.user.data') }}</template> + <template v-if="column.key === 'AS_NUMBER'"><user-outlined /> {{ $t('label.account') }}</template> <template v-if="column.key === 'account'"><user-outlined /> {{ $t('label.account') }}</template> <template v-if="column.key === 'domain'"><block-outlined /> {{ $t('label.domain') }}</template> </template>