From f7ea076e6c524b3ec79c82b1736919daa0086ec0 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Tue, 2 Apr 2024 02:14:55 +0530 Subject: [PATCH 01/36] New feature: Change storage pool scope --- .../main/java/com/cloud/event/EventTypes.java | 1 + .../com/cloud/storage/StorageService.java | 3 + .../storage/ChangeStoragePoolScopeCmd.java | 90 +++++++ .../storage/PrimaryDataStoreLifeCycle.java | 3 + .../com/cloud/resource/ResourceManager.java | 2 + .../cloud/storage/dao/StoragePoolHostDao.java | 2 + .../storage/dao/StoragePoolHostDaoImpl.java | 28 +++ .../java/com/cloud/storage/dao/VolumeDao.java | 3 + .../com/cloud/storage/dao/VolumeDaoImpl.java | 34 +++ .../datastore/PrimaryDataStoreHelper.java | 33 +++ .../AdaptiveDataStoreLifeCycleImpl.java | 10 + .../ElastistorPrimaryDataStoreLifeCycle.java | 10 + .../DateraPrimaryDataStoreLifeCycle.java | 10 + ...oudStackPrimaryDataStoreLifeCycleImpl.java | 42 ++++ .../LinstorPrimaryDataStoreLifeCycleImpl.java | 10 + .../NexentaPrimaryDataStoreLifeCycle.java | 11 + .../SamplePrimaryDataStoreLifeCycleImpl.java | 11 + .../ScaleIOPrimaryDataStoreLifeCycle.java | 10 + .../SolidFirePrimaryDataStoreLifeCycle.java | 10 + ...idFireSharedPrimaryDataStoreLifeCycle.java | 10 + .../StorPoolPrimaryDataStoreLifeCycle.java | 10 + .../cloud/resource/ResourceManagerImpl.java | 11 + .../cloud/server/ManagementServerImpl.java | 2 + .../com/cloud/storage/StorageManagerImpl.java | 82 +++++++ .../resource/MockResourceManagerImpl.java | 6 + ui/public/locales/en.json | 6 + .../config/section/infra/primaryStorages.js | 17 +- ui/src/core/lazy_lib/icons_use.js | 2 + ui/src/views/infra/changeStoragePoolScope.vue | 221 ++++++++++++++++++ 29 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java create mode 100644 ui/src/views/infra/changeStoragePoolScope.vue diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index d385fa9ed07f..0ff05b16c3bf 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -449,6 +449,7 @@ public class EventTypes { public static final String EVENT_ENABLE_PRIMARY_STORAGE = "ENABLE.PS"; public static final String EVENT_DISABLE_PRIMARY_STORAGE = "DISABLE.PS"; public static final String EVENT_SYNC_STORAGE_POOL = "SYNC.STORAGE.POOL"; + public static final String EVENT_CHANGE_STORAGE_POOL_SCOPE = "CHANGE.STORAGE.POOL.SCOPE"; // VPN public static final String EVENT_REMOTE_ACCESS_VPN_CREATE = "VPN.REMOTE.ACCESS.CREATE"; diff --git a/api/src/main/java/com/cloud/storage/StorageService.java b/api/src/main/java/com/cloud/storage/StorageService.java index c3609cfd8eea..dc550963db68 100644 --- a/api/src/main/java/com/cloud/storage/StorageService.java +++ b/api/src/main/java/com/cloud/storage/StorageService.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd; @@ -127,4 +128,6 @@ public interface StorageService { boolean deleteObjectStore(DeleteObjectStoragePoolCmd cmd); ObjectStore updateObjectStore(Long id, UpdateObjectStoragePoolCmd cmd); + + StoragePool changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java new file mode 100644 index 000000000000..074a573125d6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.storage; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StorageService; + +@APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool.", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ChangeStoragePoolScopeCmd extends BaseAsyncCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true, description = "the Id of the storage pool") + private Long id; + + @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, required = true, description = "the scope of the storage: cluster or zone") + private String scope; + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "the CLuster ID to use for the storage pool's scope") + private Long clusterId; + + @Inject + public StorageService _storageService; + + @Override + public String getEventType() { + return EventTypes.EVENT_CHANGE_STORAGE_POOL_SCOPE; + } + + @Override + public String getEventDescription() { + return "Change Storage Pool Scope"; + } + + @Override + public void execute() { + StoragePool result = _storageService.changeStoragePoolScope(this); + if (result != null) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update storage pool"); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + + public Long getId() { + return id; + } + + public String getScope() { + return scope; + } + + public Long getClusterId() { + return clusterId; + } +} 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 fcbc19c28b7b..52d8482ea5cd 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 @@ -20,6 +20,7 @@ import java.util.Map; +import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.StoragePool; public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { @@ -29,4 +30,6 @@ public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { void updateStoragePool(StoragePool storagePool, Map details); void enableStoragePool(DataStore store); void disableStoragePool(DataStore store); + boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType); + boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType); } diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 9308be5fb320..38f1d67d98ef 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -132,6 +132,8 @@ public interface ResourceManager extends ResourceService, Configurable { public List listAllHostsInAllZonesByType(Type type); + public List listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType type, long dcId, long clusterId); + public List listAvailHypervisorInZone(Long hostId, Long zoneId); public HostVO findHostByGuid(String guid); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java index b099a6d6bdbb..62ef5b7570d1 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java @@ -41,4 +41,6 @@ public interface StoragePoolHostDao extends GenericDao public void deleteStoragePoolHostDetails(long hostId, long poolId); List listByHostId(long hostId); + + Pair, Integer> listByPoolIdNotInCluster(long clusterId, long poolId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java index c27aeb0f652e..d103e9f66084 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java @@ -23,13 +23,19 @@ import java.util.List; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.inject.Inject; + import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.host.dao.HostDao; import com.cloud.storage.StoragePoolHostVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; @@ -42,6 +48,11 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase HostSearch; protected final SearchBuilder PoolHostSearch; + protected SearchBuilder poolNotInClusterSearch; + + @Inject + HostDao _hostDao; + protected static final String HOST_FOR_POOL_SEARCH = "SELECT * FROM storage_pool_host_ref ph, host h where ph.host_id = h.id and ph.pool_id=? and h.status=? "; protected static final String HOSTS_FOR_POOLS_SEARCH = "SELECT DISTINCT(ph.host_id) FROM storage_pool_host_ref ph, host h WHERE ph.host_id = h.id AND h.status = 'Up' AND resource_state = 'Enabled' AND ph.pool_id IN (?)"; @@ -70,6 +81,15 @@ public StoragePoolHostDaoImpl() { } + @PostConstruct + public void init(){ + poolNotInClusterSearch = createSearchBuilder(); + poolNotInClusterSearch.and("poolId", poolNotInClusterSearch.entity().getPoolId(), SearchCriteria.Op.EQ); + SearchBuilder hostSearch = _hostDao.createSearchBuilder(); + poolNotInClusterSearch.join("hostSearch", hostSearch, hostSearch.entity().getId(), poolNotInClusterSearch.entity().getHostId(), JoinBuilder.JoinType.INNER); + hostSearch.and("clusterId", hostSearch.entity().getClusterId(), SearchCriteria.Op.NEQ); + } + @Override public List listByPoolId(long id) { SearchCriteria sc = PoolSearch.create(); @@ -196,4 +216,12 @@ public void deleteStoragePoolHostDetails(long hostId, long poolId) { remove(sc); txn.commit(); } + + @Override + public Pair, Integer> listByPoolIdNotInCluster(long clusterId, long poolId) { + SearchCriteria sc = poolNotInClusterSearch.create(); + sc.setParameters("poolId", poolId); + sc.setJoinParameters("hostSearch", "clusterId", clusterId); + return searchAndCount(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index be6588e3189f..ade85e07e621 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -27,6 +27,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; import com.cloud.utils.fsm.StateDao; +import com.cloud.vm.VirtualMachine; public interface VolumeDao extends GenericDao, StateDao { @@ -155,4 +156,6 @@ public interface VolumeDao extends GenericDao, StateDao listByIds(List ids); + + public List listByPoolIdVMStatesNotInCluster(long clusterId, List states, long poolId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index a773a9502ce2..c0d129baed02 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.List; +import javax.annotation.PostConstruct; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; @@ -31,6 +32,8 @@ import org.springframework.stereotype.Component; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.storage.ScopeType; @@ -45,6 +48,7 @@ import com.cloud.utils.db.DB; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder.JoinType; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; @@ -52,6 +56,9 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.UpdateBuilder; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; @Component public class VolumeDaoImpl extends GenericDaoBase implements VolumeDao { @@ -72,8 +79,14 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol protected GenericSearchBuilder primaryStorageSearch2; protected GenericSearchBuilder secondaryStorageSearch; private final SearchBuilder poolAndPathSearch; + protected SearchBuilder volumePoolNotInClusterSearch; + @Inject ResourceTagDao _tagsDao; + @Inject + HostDao _hostDao; + @Inject + VMInstanceDao _vmDao; protected static final String SELECT_VM_SQL = "SELECT DISTINCT instance_id from volumes v where v.host_id = ? and v.mirror_state = ?"; // need to account for zone-wide primary storage where storage_pool has @@ -493,7 +506,19 @@ public VolumeDaoImpl() { poolAndPathSearch.and("poolId", poolAndPathSearch.entity().getPoolId(), Op.EQ); poolAndPathSearch.and("path", poolAndPathSearch.entity().getPath(), Op.EQ); poolAndPathSearch.done(); + } + @PostConstruct + public void init() { + volumePoolNotInClusterSearch = createSearchBuilder(); + volumePoolNotInClusterSearch.and("poolId", volumePoolNotInClusterSearch.entity().getPoolId(), Op.EQ); + SearchBuilder vmSearch = _vmDao.createSearchBuilder(); + vmSearch.and("vmStateSearch", vmSearch.entity().getState(), Op.IN); + SearchBuilder hostSearch = _hostDao.createSearchBuilder(); + hostSearch.and("clusterId", hostSearch.entity().getClusterId(), SearchCriteria.Op.NEQ); + vmSearch.join("hostSearch", hostSearch, hostSearch.entity().getId(), vmSearch.entity().getHostId(), JoinType.INNER); + volumePoolNotInClusterSearch.join("vmSearch", vmSearch, vmSearch.entity().getId(), volumePoolNotInClusterSearch.entity().getInstanceId(), JoinType.INNER); + volumePoolNotInClusterSearch.done(); } @Override @@ -841,4 +866,13 @@ public List listByIds(List ids) { sc.setParameters("idIN", ids.toArray()); return listBy(sc, null); } + + @Override + public List listByPoolIdVMStatesNotInCluster(long clusterId, List states, long poolId) { + SearchCriteria sc = volumePoolNotInClusterSearch.create(); + sc.setParameters("poolId", poolId); + sc.setParameters("states", states); + sc.setJoinParameters("hostSearch", "clusterId", clusterId); + return listBy(sc, null); + } } 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 fbb4a6e16188..6fe913c9a6d5 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 @@ -31,6 +31,7 @@ import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +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.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; @@ -52,7 +53,10 @@ import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; @Component @@ -265,4 +269,33 @@ public boolean deletePrimaryDataStore(DataStore store) { return true; } + public DataStore switchToZone(DataStore store) { + StoragePoolVO pool = dataStoreDao.findById(store.getId()); + pool.setScope(ScopeType.ZONE); + pool.setPodId(null); + pool.setClusterId(null); + dataStoreDao.update(pool.getId(), pool); + return dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary); + } + + public DataStore switchToCluster(DataStore store, ClusterScope clusterScope) { + List hostPoolRecords = storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()).first(); + StoragePoolVO pool = dataStoreDao.findById(store.getId()); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (StoragePoolHostVO host : hostPoolRecords) { + storagePoolHostDao.deleteStoragePoolHostDetails(host.getHostId(), host.getPoolId()); + } + pool.setScope(ScopeType.CLUSTER); + pool.setPodId(clusterScope.getPodId()); + pool.setClusterId(clusterScope.getScopeId()); + dataStoreDao.update(pool.getId(), pool); + } + }); + + s_logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to cluster id=" + clusterScope.getScopeId()); + return dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary); + } } diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java index 56d9a25f34f8..db95b0c96ab3 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java @@ -404,4 +404,14 @@ public void disableStoragePool(DataStore store) { s_logger.info("Disabling storage pool [" + store.getName() + "]"); _dataStoreHelper.disable(store); } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java index 0798f9f2cd2a..28b57f656281 100644 --- a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java @@ -476,6 +476,16 @@ public void disableStoragePool(DataStore dataStore) { _dataStoreHelper.disable(dataStore); } + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + @SuppressWarnings("finally") @Override public boolean deleteDataStore(DataStore store) { diff --git a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java index 6fd420091257..1ec8eddc8239 100644 --- a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java @@ -395,6 +395,16 @@ public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + private HypervisorType getHypervisorTypeForCluster(long clusterId) { ClusterVO cluster = _clusterDao.findById(clusterId); 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 685565d73b03..5f90476ea354 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 @@ -45,6 +45,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachineManager; @@ -543,4 +544,45 @@ public void enableStoragePool(DataStore dataStore) { public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } + + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + List hosts = _resourceMgr.listAllHostsInOneZoneNotInClusterByHypervisor(hypervisorType, clusterScope.getZoneId(), clusterScope.getScopeId()); + s_logger.debug("In createPool. Attaching the pool to each of the hosts."); + List poolHosts = new ArrayList(); + for (HostVO host : hosts) { + try { + storageMgr.connectHostToSharedPool(host.getId(), store.getId()); + } catch (Exception e) { + s_logger.warn("Unable to establish a connection between " + host + " and " + store, e); + } + } + dataStoreHelper.switchToZone(store); + return true; + } + + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + Pair, Integer> hostPoolRecords = _storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()); + HypervisorType hType = null; + if (hostPoolRecords.second() > 0) { + hType = getHypervisorType(hostPoolRecords.first().get(0).getHostId()); + } + + StoragePool pool = (StoragePool) store; + for (StoragePoolHostVO host : hostPoolRecords.first()) { + DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool); + final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd); + + if (answer != null && answer.getResult()) { + if (HypervisorType.KVM != hType) { + break; + } + } else { + if (answer != null) { + s_logger.debug("Failed to delete storage pool: " + answer.getResult()); + } + } + } + dataStoreHelper.switchToCluster(store, clusterScope); + return true; + } } diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java index efc69438e75f..d5425a40405d 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java @@ -338,4 +338,14 @@ public void enableStoragePool(DataStore store) { public void disableStoragePool(DataStore store) { dataStoreHelper.disable(store); } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java index 507189edc14c..e2b40ffc2487 100644 --- a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java @@ -43,6 +43,7 @@ import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolAutomation; +import com.cloud.utils.exception.CloudRuntimeException; public class NexentaPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { @@ -177,6 +178,16 @@ public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + @Override public boolean deleteDataStore(DataStore store) { return dataStoreHelper.deletePrimaryDataStore(store); diff --git a/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SamplePrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SamplePrimaryDataStoreLifeCycleImpl.java index 3a0ce83f9513..dbfc1976c5fa 100644 --- a/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SamplePrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SamplePrimaryDataStoreLifeCycleImpl.java @@ -43,6 +43,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolStatus; +import com.cloud.utils.exception.CloudRuntimeException; public class SamplePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { @Inject @@ -146,4 +147,14 @@ public void enableStoragePool(DataStore store) { @Override public void disableStoragePool(DataStore store) { } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java index 17150699923d..5878eb664fe8 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java @@ -367,6 +367,16 @@ public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + @Override public boolean deleteDataStore(DataStore dataStore) { StoragePool storagePool = (StoragePool)dataStore; diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java index 7a2767c32e63..5516c7b56e3c 100644 --- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java @@ -387,4 +387,14 @@ public void enableStoragePool(DataStore dataStore) { public void disableStoragePool(DataStore dataStore) { _dataStoreHelper.disable(dataStore); } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java index 557cc3f60f62..429160b9de90 100644 --- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java @@ -770,4 +770,14 @@ public void enableStoragePool(DataStore dataStore) { public void disableStoragePool(DataStore dataStore) { primaryDataStoreHelper.disable(dataStore); } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java index 4dbc7e4a22c9..eb9a48942ae5 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java @@ -318,4 +318,14 @@ public void disableStoragePool(DataStore dataStore) { log.debug("disableStoragePool"); dataStoreHelper.disable(dataStore); } + + @Override + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } + + @Override + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + } } diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 922df25a7268..883f8cf08caf 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -3417,6 +3417,17 @@ public List listAvailableGPUDevice(final long hostId, final Str return _hostGpuGroupsDao.customSearch(sc, searchFilter); } + @Override + public List listAllHostsInOneZoneNotInClusterByHypervisor(final HypervisorType type, final long dcId, final long clusterId) { + final QueryBuilder sc = QueryBuilder.create(HostVO.class); + sc.and(sc.entity().getHypervisorType(), Op.EQ, type); + sc.and(sc.entity().getDataCenterId(), Op.EQ, dcId); + sc.and(sc.entity().getClusterId(), Op.NEQ, clusterId); + sc.and(sc.entity().getStatus(), Op.EQ, Status.Up); + sc.and(sc.entity().getResourceState(), Op.EQ, ResourceState.Enabled); + return sc.list(); + } + @Override public boolean isGPUDeviceAvailable(final long hostId, final String groupName, final String vgpuType) { if(!listAvailableGPUDevice(hostId, groupName, vgpuType).isEmpty()) { diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 5cd5b92054bb..49e14e29debb 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -210,6 +210,7 @@ import org.apache.cloudstack.api.command.admin.storage.AddImageStoreS3CMD; import org.apache.cloudstack.api.command.admin.storage.AddObjectStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd; @@ -3482,6 +3483,7 @@ public List> getCommands() { cmdList.add(UpgradeRouterCmd.class); cmdList.add(AddSwiftCmd.class); cmdList.add(CancelPrimaryStorageMaintenanceCmd.class); + cmdList.add(ChangeStoragePoolScopeCmd.class); cmdList.add(CreateStoragePoolCmd.class); cmdList.add(DeletePoolCmd.class); cmdList.add(ListSwiftsCmd.class); diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 615c7b6760ec..d798addc4a26 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -54,6 +54,7 @@ import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd; @@ -254,6 +255,7 @@ import com.cloud.vm.DiskProfile; import com.cloud.vm.UserVmManager; import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Sets; @@ -1148,6 +1150,86 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); } + @Override + public PrimaryDataStoreInfo changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException, InvalidParameterValueException, PermissionDeniedException { + Long id = cmd.getId(); + + Long accountId = cmd.getEntityOwnerId(); + if (!_accountMgr.isRootAdmin(accountId)) { + throw new PermissionDeniedException("Only root admin can perform this operation"); + } + + ScopeType scopeType = ScopeType.validateAndGetScopeType(cmd.getScope()); + if (scopeType != ScopeType.ZONE && scopeType != ScopeType.CLUSTER) { + throw new InvalidParameterValueException("Invalid scope " + scopeType.toString() + "for Primary storage"); + } + + StoragePoolVO primaryStorage = _storagePoolDao.findById(id); + if (primaryStorage == null) { + throw new IllegalArgumentException("Unable to find storage pool with ID: " + id); + } + + if (StoragePoolStatus.Disabled != primaryStorage.getStatus()) { + throw new InvalidParameterValueException("Primary storage should be disabled for this operation"); + } + + if (scopeType == primaryStorage.getScope()) { + throw new InvalidParameterValueException("Invalid scope change"); + } + + if (!primaryStorage.getStatus().equals(StoragePoolStatus.Disabled)) { + throw new InvalidParameterValueException("Scope of the Primary storage with id " + + primaryStorage.getUuid() + + " cannot be changed, as it is not in the Disabled state"); + } + + if (!primaryStorage.getStorageProviderName().equals(DataStoreProvider.DEFAULT_PRIMARY)) { + throw new InvalidParameterValueException("Primary storage scope change is only supported with " + + DataStoreProvider.DEFAULT_PRIMARY.toString() + " data store provider"); + } + + HypervisorType hypervisorType = primaryStorage.getHypervisor(); + Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Hyperv); + if (!supportedHypervisorTypes.contains(hypervisorType)) { + throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); + } + + String providerName = primaryStorage.getStorageProviderName(); + DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName); + PrimaryDataStoreLifeCycle lifeCycle = (PrimaryDataStoreLifeCycle) storeProvider.getDataStoreLifeCycle(); + DataStore primaryStore = _dataStoreMgr.getPrimaryDataStore(id); + + Long zoneId = primaryStorage.getDataCenterId(); + DataCenterVO zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("unable to find zone by id " + zoneId); + } + if (Grouping.AllocationState.Disabled == zone.getAllocationState()) { + throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId); + } + + if (scopeType == ScopeType.ZONE) { + ClusterScope clusterScope = new ClusterScope(primaryStorage.getClusterId(), null, zoneId); + lifeCycle.changeStoragePoolScopeToZone(primaryStore, clusterScope, hypervisorType); + + } else { + + Long clusterId = cmd.getClusterId(); + ClusterVO clusterVO = _clusterDao.findById(clusterId); + List states = Arrays.asList(State.Starting, State.Running, State.Stopping, State.Migrating, State.Restoring); + List volumesInOtherClusters = volumeDao.listByPoolIdVMStatesNotInCluster(clusterId, states, id); + if (volumesInOtherClusters.size() > 0) { + throw new CloudRuntimeException("Cannot change scope of the pool " + primaryStorage.getName() + " to cluster " + clusterVO.getName() + " as there are associated volumes present for other clusters"); + } + + ClusterScope clusterScope = new ClusterScope(clusterId, clusterVO.getPodId(), zoneId); + lifeCycle.changeStoragePoolScopeToCluster(primaryStore, clusterScope, hypervisorType); + } + + // maybe return a boolean instead + return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(primaryStorage.getId(), DataStoreRole.Primary); + } + @Override public void removeStoragePoolFromCluster(long hostId, String iScsiName, StoragePool storagePool) { final Map details = new HashMap<>(); diff --git a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java index c38cfc3b8320..bb7ded794860 100755 --- a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java +++ b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java @@ -431,6 +431,12 @@ public List listAllHostsInAllZonesByType(final Type type) { return null; } + @Override + public List listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType type, long dcId, long clusterId) { + // TODO Auto-generated method stub + return null; + } + /* (non-Javadoc) * @see com.cloud.resource.ResourceManager#listAvailHypervisorInZone(java.lang.Long, java.lang.Long) */ diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 17a70b423b39..4417660e78f6 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -59,6 +59,7 @@ "label.action.bulk.release.public.ip.address": "Bulk release public IP addresses", "label.action.cancel.maintenance.mode": "Cancel maintenance mode", "label.action.change.password": "Change password", +"label.action.change.primary.storage.scope": "Change primary storage scope", "label.action.configure.stickiness": "Stickiness", "label.action.copy.iso": "Copy ISO", "label.action.copy.snapshot": "Copy Snapshot", @@ -2447,6 +2448,8 @@ "message.action.patch.router": "Please confirm that you want to live patch the router.
This operation is equivalent updating the router packages and restarting the Network without cleanup.", "message.action.patch.systemvm": "Please confirm that you want to patch the System VM.", "message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped. Do you want to continue?", +"message.action.primary.storage.scope.cluster": "Change the primary storage scope from zone to cluster", +"message.action.primary.storage.scope.zone": "Change the primary storage scope from cluster to zone", "message.action.reboot.instance": "Please confirm that you want to reboot this Instance.", "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.", "message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.", @@ -2561,6 +2564,8 @@ "message.change.offering.for.volume.failed": "Change offering for the volume failed", "message.change.offering.for.volume.processing": "Changing offering for the volume...", "message.change.password": "Please change your password.", +"message.change.scope.failed": "Scope change failed", +"message.change.scope.processing": "Scope change in progress", "message.cluster.dedicated": "Cluster Dedicated", "message.cluster.dedication.released": "Cluster dedication released.", "message.config.health.monitor.failed": "Configure Health Monitor failed", @@ -3143,6 +3148,7 @@ "message.success.change.affinity.group": "Successfully changed affinity groups", "message.success.change.offering": "Successfully changed offering", "message.success.change.password": "Successfully changed password for User", +"message.success.change.scope": "Successfully changed scope for storage pool", "message.success.config.backup.schedule": "Successfully configured Instance backup schedule", "message.success.config.health.monitor": "Successfully Configure Health Monitor", "message.success.config.sticky.policy": "Successfully configured sticky policy", diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js index f222edeaf70b..addf5b07e7bb 100644 --- a/ui/src/config/section/infra/primaryStorages.js +++ b/ui/src/config/section/infra/primaryStorages.js @@ -133,12 +133,27 @@ export default { dataView: true, show: (record) => { return ['Maintenance', 'PrepareForMaintenance', 'ErrorInMaintenance'].includes(record.state) } }, + { + api: 'changeStoragePoolScope', + icon: 'arrow-right-outlined', + label: 'label.action.change.primary.storage.scope', + dataView: true, + popup: true, + show: (record) => { + return ( + record.state === 'Disabled' && + (record.hypervisor === 'Simulator' || record.hypervisor === 'KVM' || record.hypervisor === 'VMware') && + record.type === 'NetworkFilesystem' && + record.provider === 'DefaultPrimary' + ) + }, + component: shallowRef(defineAsyncComponent(() => import('@/views/infra/changeStoragePoolScope.vue'))) + }, { api: 'deleteStoragePool', icon: 'delete-outlined', label: 'label.action.delete.primary.storage', dataView: true, - args: ['forced'], show: (record) => { return (record.state === 'Down' || record.state === 'Maintenance' || record.state === 'Disconnected') }, displayName: (record) => { return record.name || record.displayName || record.id } } diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js index ea0abdc3506b..255712c32a64 100644 --- a/ui/src/core/lazy_lib/icons_use.js +++ b/ui/src/core/lazy_lib/icons_use.js @@ -21,6 +21,7 @@ import { ApiOutlined, AppstoreOutlined, ArrowDownOutlined, + ArrowRightOutlined, ArrowUpOutlined, ArrowsAltOutlined, AuditOutlined, @@ -180,6 +181,7 @@ export default { app.component('ApiOutlined', ApiOutlined) app.component('AppstoreOutlined', AppstoreOutlined) app.component('ArrowDownOutlined', ArrowDownOutlined) + app.component('ArrowRightOutlined', ArrowRightOutlined) app.component('ArrowUpOutlined', ArrowUpOutlined) app.component('ArrowsAltOutlined', ArrowsAltOutlined) app.component('AuditOutlined', AuditOutlined) diff --git a/ui/src/views/infra/changeStoragePoolScope.vue b/ui/src/views/infra/changeStoragePoolScope.vue new file mode 100644 index 000000000000..d3d4bd9f54c3 --- /dev/null +++ b/ui/src/views/infra/changeStoragePoolScope.vue @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + + + From d6aa3628cfc58bbc2593f57107eed67cbdbf10ba Mon Sep 17 00:00:00 2001 From: abh1sar Date: Tue, 2 Apr 2024 11:49:17 +0530 Subject: [PATCH 02/36] Added checks for Ceph/RBD --- .../com/cloud/storage/StorageService.java | 3 +- .../storage/ChangeStoragePoolScopeCmd.java | 5 ++- .../datastore/PrimaryDataStoreHelper.java | 32 ++++++++--------- ...oudStackPrimaryDataStoreLifeCycleImpl.java | 2 +- .../com/cloud/storage/StorageManagerImpl.java | 34 +++++++++++-------- .../config/section/infra/primaryStorages.js | 2 +- 6 files changed, 40 insertions(+), 38 deletions(-) diff --git a/api/src/main/java/com/cloud/storage/StorageService.java b/api/src/main/java/com/cloud/storage/StorageService.java index dc550963db68..95de92e81719 100644 --- a/api/src/main/java/com/cloud/storage/StorageService.java +++ b/api/src/main/java/com/cloud/storage/StorageService.java @@ -35,6 +35,7 @@ import com.cloud.exception.DiscoveryException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceInUseException; import com.cloud.exception.ResourceUnavailableException; import org.apache.cloudstack.api.command.admin.storage.heuristics.CreateSecondaryStorageSelectorCmd; @@ -129,5 +130,5 @@ public interface StorageService { ObjectStore updateObjectStore(Long id, UpdateObjectStoragePoolCmd cmd); - StoragePool changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException; + boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException, InvalidParameterValueException, PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java index 074a573125d6..08dad950ba91 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -31,7 +31,6 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; -import com.cloud.storage.StoragePool; import com.cloud.storage.StorageService; @APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool.", responseObject = SuccessResponse.class, @@ -62,8 +61,8 @@ public String getEventDescription() { @Override public void execute() { - StoragePool result = _storageService.changeStoragePoolScope(this); - if (result != null) { + boolean result = _storageService.changeStoragePoolScope(this); + if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); } else { 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 6fe913c9a6d5..e6fc548541e8 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 @@ -28,9 +28,6 @@ import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - 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.DataStoreManager; @@ -38,19 +35,21 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; import com.cloud.agent.api.StoragePoolInfo; import com.cloud.capacity.Capacity; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; -import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolStatus; -import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.Transaction; @@ -269,16 +268,15 @@ public boolean deletePrimaryDataStore(DataStore store) { return true; } - public DataStore switchToZone(DataStore store) { + public void switchToZone(DataStore store) { StoragePoolVO pool = dataStoreDao.findById(store.getId()); pool.setScope(ScopeType.ZONE); pool.setPodId(null); pool.setClusterId(null); dataStoreDao.update(pool.getId(), pool); - return dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary); - } + } - public DataStore switchToCluster(DataStore store, ClusterScope clusterScope) { + public void switchToCluster(DataStore store, ClusterScope clusterScope) { List hostPoolRecords = storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()).first(); StoragePoolVO pool = dataStoreDao.findById(store.getId()); @@ -286,16 +284,14 @@ public DataStore switchToCluster(DataStore store, ClusterScope clusterScope) { @Override public void doInTransactionWithoutResult(TransactionStatus status) { for (StoragePoolHostVO host : hostPoolRecords) { - storagePoolHostDao.deleteStoragePoolHostDetails(host.getHostId(), host.getPoolId()); - } - pool.setScope(ScopeType.CLUSTER); - pool.setPodId(clusterScope.getPodId()); - pool.setClusterId(clusterScope.getScopeId()); - dataStoreDao.update(pool.getId(), pool); + storagePoolHostDao.deleteStoragePoolHostDetails(host.getHostId(), host.getPoolId()); + } + pool.setScope(ScopeType.CLUSTER); + pool.setPodId(clusterScope.getPodId()); + pool.setClusterId(clusterScope.getScopeId()); + dataStoreDao.update(pool.getId(), pool); } }); - s_logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to cluster id=" + clusterScope.getScopeId()); - return dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Primary); } -} +} \ No newline at end of file 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 5f90476ea354..42a9446e9e15 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 @@ -585,4 +585,4 @@ public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clu dataStoreHelper.switchToCluster(store, clusterScope); return true; } -} +} \ No newline at end of file diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index d798addc4a26..1b4e0d084bc2 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1151,7 +1151,7 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I } @Override - public PrimaryDataStoreInfo changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException, InvalidParameterValueException, PermissionDeniedException { + public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws IllegalArgumentException, InvalidParameterValueException, PermissionDeniedException { Long id = cmd.getId(); Long accountId = cmd.getEntityOwnerId(); @@ -1169,6 +1169,24 @@ public PrimaryDataStoreInfo changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd throw new IllegalArgumentException("Unable to find storage pool with ID: " + id); } + if (!primaryStorage.getStorageProviderName().equals(DataStoreProvider.DEFAULT_PRIMARY)) { + throw new InvalidParameterValueException("Primary storage scope change is only supported with " + + DataStoreProvider.DEFAULT_PRIMARY.toString() + " data store provider"); + } + + if (!primaryStorage.getPoolType().equals(Storage.StoragePoolType.NetworkFilesystem) && + !primaryStorage.getPoolType().equals(Storage.StoragePoolType.RBD)) { + throw new InvalidParameterValueException("Primary storage scope change is not supported for protocol " + + primaryStorage.getPoolType().toString()); + } + + HypervisorType hypervisorType = primaryStorage.getHypervisor(); + Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Hyperv); + if (!supportedHypervisorTypes.contains(hypervisorType)) { + throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + + hypervisorType); + } + if (StoragePoolStatus.Disabled != primaryStorage.getStatus()) { throw new InvalidParameterValueException("Primary storage should be disabled for this operation"); } @@ -1183,17 +1201,6 @@ public PrimaryDataStoreInfo changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd " cannot be changed, as it is not in the Disabled state"); } - if (!primaryStorage.getStorageProviderName().equals(DataStoreProvider.DEFAULT_PRIMARY)) { - throw new InvalidParameterValueException("Primary storage scope change is only supported with " - + DataStoreProvider.DEFAULT_PRIMARY.toString() + " data store provider"); - } - - HypervisorType hypervisorType = primaryStorage.getHypervisor(); - Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Hyperv); - if (!supportedHypervisorTypes.contains(hypervisorType)) { - throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); - } - String providerName = primaryStorage.getStorageProviderName(); DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName); PrimaryDataStoreLifeCycle lifeCycle = (PrimaryDataStoreLifeCycle) storeProvider.getDataStoreLifeCycle(); @@ -1226,8 +1233,7 @@ public PrimaryDataStoreInfo changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd lifeCycle.changeStoragePoolScopeToCluster(primaryStore, clusterScope, hypervisorType); } - // maybe return a boolean instead - return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(primaryStorage.getId(), DataStoreRole.Primary); + return true; } @Override diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js index addf5b07e7bb..cf8e657b9bf2 100644 --- a/ui/src/config/section/infra/primaryStorages.js +++ b/ui/src/config/section/infra/primaryStorages.js @@ -143,7 +143,7 @@ export default { return ( record.state === 'Disabled' && (record.hypervisor === 'Simulator' || record.hypervisor === 'KVM' || record.hypervisor === 'VMware') && - record.type === 'NetworkFilesystem' && + (record.type === 'NetworkFilesystem' || record.type === 'RBD') && record.provider === 'DefaultPrimary' ) }, From 5a1e7bddca3123c033a63d80ee4402f790efa958 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Tue, 2 Apr 2024 16:50:57 +0530 Subject: [PATCH 03/36] Update op_host_capacity table on primary storage scope change --- .../java/com/cloud/capacity/CapacityVO.java | 8 +++---- .../datastore/PrimaryDataStoreHelper.java | 23 +++++++++++++++---- .../com/cloud/storage/StorageManagerImpl.java | 5 ++-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index 50c40134a911..af3ea12cfaf9 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -132,8 +132,8 @@ public Long getPodId() { return podId; } - public void setPodId(long podId) { - this.podId = new Long(podId); + public void setPodId(Long podId) { + this.podId = podId; } @Override @@ -141,8 +141,8 @@ public Long getClusterId() { return clusterId; } - public void setClusterId(long clusterId) { - this.clusterId = new Long(clusterId); + public void setClusterId(Long clusterId) { + this.clusterId = (clusterId); } @Override 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 e6fc548541e8..f9b306a22e07 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 @@ -270,15 +270,26 @@ public boolean deletePrimaryDataStore(DataStore store) { public void switchToZone(DataStore store) { StoragePoolVO pool = dataStoreDao.findById(store.getId()); - pool.setScope(ScopeType.ZONE); - pool.setPodId(null); - pool.setClusterId(null); - dataStoreDao.update(pool.getId(), pool); + CapacityVO capacity = _capacityDao.findByHostIdType(store.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED); + Transaction.execute(new TransactionCallbackNoReturn() { + public void doInTransactionWithoutResult(TransactionStatus status) { + pool.setScope(ScopeType.ZONE); + pool.setPodId(null); + pool.setClusterId(null); + dataStoreDao.update(pool.getId(), pool); + + capacity.setPodId(null); + capacity.setClusterId(null); + _capacityDao.update(capacity.getId(), capacity); + } + }); + s_logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to zone"); } public void switchToCluster(DataStore store, ClusterScope clusterScope) { List hostPoolRecords = storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()).first(); StoragePoolVO pool = dataStoreDao.findById(store.getId()); + CapacityVO capacity = _capacityDao.findByHostIdType(store.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED); Transaction.execute(new TransactionCallbackNoReturn() { @Override @@ -290,6 +301,10 @@ public void doInTransactionWithoutResult(TransactionStatus status) { pool.setPodId(clusterScope.getPodId()); pool.setClusterId(clusterScope.getScopeId()); dataStoreDao.update(pool.getId(), pool); + + capacity.setPodId(clusterScope.getPodId()); + capacity.setClusterId(clusterScope.getScopeId()); + _capacityDao.update(capacity.getId(), capacity); } }); s_logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to cluster id=" + clusterScope.getScopeId()); diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 1b4e0d084bc2..44423c3c84f8 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1181,10 +1181,9 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille } HypervisorType hypervisorType = primaryStorage.getHypervisor(); - Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Hyperv); + Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Simulator); if (!supportedHypervisorTypes.contains(hypervisorType)) { - throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " - + hypervisorType); + throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); } if (StoragePoolStatus.Disabled != primaryStorage.getStatus()) { From 5bd69953e8fb75ef1555c9decb3f7a910d3322e7 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Wed, 3 Apr 2024 10:55:50 +0530 Subject: [PATCH 04/36] Storage pool scope change integration test --- .../integration/smoke/test_primary_storage.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/test/integration/smoke/test_primary_storage.py b/test/integration/smoke/test_primary_storage.py index 477d3317ad69..b9a28c03ff4f 100644 --- a/test/integration/smoke/test_primary_storage.py +++ b/test/integration/smoke/test_primary_storage.py @@ -373,6 +373,145 @@ def __init__(self): "b": "NFS-B" } +class TestPrimaryStorageScope(cloudstackTestCase): + + def setUp(self): + + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.services = self.testClient.getParsedTestDataConfig() + self.cleanup = [] + self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) + self.pod = get_pod(self.apiclient, self.zone.id) + self.clusters = list_clusters(self.apiclient) + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="true") + def test_01_primary_storage_scope_change(self): + """Test primary storage pool scope change + """ + + cluster1 = self.clusters[0] + cluster2 = self.clusters[1] + storage = StoragePool.create(self.apiclient, + self.services["nfs"], + clusterid=cluster1.id, + zoneid=self.zone.id, + podid=self.pod.id + ) + self.cleanup.append(storage) + pool_id = self.dbclient.execute("select id from storage_pool where uuid=\"" + storage.id + "\"")[0][0] + + + self.debug("Created storage pool in cluster: %s" % cluster1.id) + + # Disable storage pool + cmd = updateStoragePool.updateStoragePoolCmd() + cmd.id = storage.id + cmd.enabled = False + self.apiclient.updateStoragePool(cmd) + + self.debug("Disabled storage pool : %s" % storage.id) + + # Change storage pool scope to Zone + cmd = changeStoragePoolScope.changeStoragePoolScopeCmd() + cmd.id = storage.id + cmd.scope = "ZONE" + self.apiclient.changeStoragePoolScope(cmd) + + self.debug("Changed scope of storage pool %s to zone" % storage.id) + + host2 = Host.list(self.apiclient, clusterid=cluster2.id, listall=True)[0] + host2_id = self.dbclient.execute("select id from host where uuid=\"" + host2.id + "\"")[0][0] + + pool_row = self.dbclient.execute("select cluster_id, pod_id, scope from storage_pool where id=" + str(pool_id))[0] + capacity_row = self.dbclient.execute("select cluster_id, pod_id from op_host_capacity where capacity_type=3 and host_id=" + str(pool_id))[0] + pool_host_rows = self.dbclient.execute("select id from storage_pool_host_ref where host_id=" + str(host2_id) + " and pool_id=" + str(pool_id)) + + self.assertIsNone( + pool_row[0], + "Cluster id not set to NULL for zone scope" + ) + self.assertIsNone( + pool_row[1], + "Pod id not set to NULL for zone scope" + ) + self.assertEqual( + pool_row[2], + "ZONE", + "Storage pool scope not changed to ZONE" + ) + self.assertIsNone( + capacity_row[0], + "Cluster id not set to NULL in the op_host_capacity table" + ) + self.assertIsNone( + capacity_row[1], + "Pod id not set to NULL in the op_host_capacity table" + ) + self.assertEqual( + len(pool_host_rows), + 1, + "Storage pool not added to the storage_pool_host_ref table for host on another cluster" + ) + + # Change storage pool scope to Cluster + cmd = changeStoragePoolScope.changeStoragePoolScopeCmd() + cmd.id = storage.id + cmd.scope = "CLUSTER" + cmd.clusterid = cluster1.id + self.apiclient.changeStoragePoolScope(cmd) + + self.debug("Changed scope of storage pool %s to cluster" % storage.id) + + pool_row = self.dbclient.execute("select cluster_id, pod_id, scope from storage_pool where id=" + str(pool_id))[0] + capacity_row = self.dbclient.execute("select cluster_id, pod_id from op_host_capacity where capacity_type=3 and host_id=" + str(pool_id))[0] + pool_host_rows = self.dbclient.execute("select id from storage_pool_host_ref where host_id=" + str(host2_id) + " and pool_id=" + str(pool_id)) + + self.assertIsNotNone( + pool_row[0], + "Cluster id should not be NULL for cluster scope" + ) + self.assertIsNotNone( + pool_row[1], + "Pod id should not be NULL for cluster scope" + ) + self.assertEqual( + pool_row[2], + "CLUSTER", + "Storage pool scope not changed to Cluster" + ) + self.assertIsNotNone( + capacity_row[0], + "Cluster id should not be NULL in the op_host_capacity table" + ) + self.assertIsNotNone( + capacity_row[1], + "Pod id set should not be NULL in the op_host_capacity table" + ) + self.assertEqual( + len(pool_host_rows), + 0, + "Storage pool not removed from the storage_pool_host_ref table for host on another cluster" + ) + + # Enable storage pool + cmd = updateStoragePool.updateStoragePoolCmd() + cmd.id = storage.id + cmd.enabled = True + response = self.apiclient.updateStoragePool(cmd) + self.assertEqual( + response.state, + "Up", + "Storage pool couldn't be enabled" + ) class TestStorageTags(cloudstackTestCase): From c5cbd5d685b14e986be98025ee7df6e38b824cb7 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Thu, 4 Apr 2024 12:36:41 +0530 Subject: [PATCH 05/36] pull 8875 : Addressed review comments --- .../storage/ChangeStoragePoolScopeCmd.java | 12 ++----- .../java/com/cloud/capacity/CapacityVO.java | 2 +- .../com/cloud/storage/dao/VolumeDaoImpl.java | 4 +-- .../datastore/PrimaryDataStoreHelper.java | 5 +-- ...oudStackPrimaryDataStoreLifeCycleImpl.java | 8 ++--- .../com/cloud/storage/StorageManagerImpl.java | 35 +++++++++++-------- .../integration/smoke/test_primary_storage.py | 1 - ui/public/locales/en.json | 2 +- .../config/section/infra/primaryStorages.js | 1 + 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java index 08dad950ba91..c694e886cdb1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -17,8 +17,6 @@ package org.apache.cloudstack.api.command.admin.storage; -import javax.inject.Inject; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -31,10 +29,9 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; -import com.cloud.storage.StorageService; @APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool.", responseObject = SuccessResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) + since= "4.19.1", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ChangeStoragePoolScopeCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true, description = "the Id of the storage pool") @@ -43,12 +40,9 @@ public class ChangeStoragePoolScopeCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.SCOPE, type = CommandType.STRING, required = true, description = "the scope of the storage: cluster or zone") private String scope; - @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "the CLuster ID to use for the storage pool's scope") + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "the Id of the cluster to use if scope is being set to Cluster") private Long clusterId; - @Inject - public StorageService _storageService; - @Override public String getEventType() { return EventTypes.EVENT_CHANGE_STORAGE_POOL_SCOPE; @@ -56,7 +50,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Change Storage Pool Scope"; + return "Change the scope to " + scope + " for storage pool " + this.id; } @Override diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index af3ea12cfaf9..29b58ddccd4d 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -142,7 +142,7 @@ public Long getClusterId() { } public void setClusterId(Long clusterId) { - this.clusterId = (clusterId); + this.clusterId = clusterId; } @Override diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index c0d129baed02..685fed44a770 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -513,7 +513,7 @@ public void init() { volumePoolNotInClusterSearch = createSearchBuilder(); volumePoolNotInClusterSearch.and("poolId", volumePoolNotInClusterSearch.entity().getPoolId(), Op.EQ); SearchBuilder vmSearch = _vmDao.createSearchBuilder(); - vmSearch.and("vmStateSearch", vmSearch.entity().getState(), Op.IN); + vmSearch.and("vmStates", vmSearch.entity().getState(), Op.IN); SearchBuilder hostSearch = _hostDao.createSearchBuilder(); hostSearch.and("clusterId", hostSearch.entity().getClusterId(), SearchCriteria.Op.NEQ); vmSearch.join("hostSearch", hostSearch, hostSearch.entity().getId(), vmSearch.entity().getHostId(), JoinType.INNER); @@ -871,7 +871,7 @@ public List listByIds(List ids) { public List listByPoolIdVMStatesNotInCluster(long clusterId, List states, long poolId) { SearchCriteria sc = volumePoolNotInClusterSearch.create(); sc.setParameters("poolId", poolId); - sc.setParameters("states", states); + sc.setParameters("vmStates", states); sc.setJoinParameters("hostSearch", "clusterId", clusterId); return listBy(sc, null); } 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 f9b306a22e07..d4d159c8b327 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 @@ -268,7 +268,7 @@ public boolean deletePrimaryDataStore(DataStore store) { return true; } - public void switchToZone(DataStore store) { + public void switchToZone(DataStore store, HypervisorType hypervisorType) { StoragePoolVO pool = dataStoreDao.findById(store.getId()); CapacityVO capacity = _capacityDao.findByHostIdType(store.getId(), Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED); Transaction.execute(new TransactionCallbackNoReturn() { @@ -276,6 +276,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { pool.setScope(ScopeType.ZONE); pool.setPodId(null); pool.setClusterId(null); + pool.setHypervisor(hypervisorType); dataStoreDao.update(pool.getId(), pool); capacity.setPodId(null); @@ -309,4 +310,4 @@ public void doInTransactionWithoutResult(TransactionStatus status) { }); s_logger.debug("Scope of storage pool id=" + pool.getId() + " is changed to cluster id=" + clusterScope.getScopeId()); } -} \ No newline at end of file +} 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 42a9446e9e15..77f2f65bd3f0 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 @@ -547,8 +547,7 @@ public void disableStoragePool(DataStore dataStore) { public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { List hosts = _resourceMgr.listAllHostsInOneZoneNotInClusterByHypervisor(hypervisorType, clusterScope.getZoneId(), clusterScope.getScopeId()); - s_logger.debug("In createPool. Attaching the pool to each of the hosts."); - List poolHosts = new ArrayList(); + s_logger.debug("Changing scope of the storage pool to Zone"); for (HostVO host : hosts) { try { storageMgr.connectHostToSharedPool(host.getId(), store.getId()); @@ -556,12 +555,13 @@ public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope cluste s_logger.warn("Unable to establish a connection between " + host + " and " + store, e); } } - dataStoreHelper.switchToZone(store); + dataStoreHelper.switchToZone(store, hypervisorType); return true; } public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { Pair, Integer> hostPoolRecords = _storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()); + s_logger.debug("Changing scope of the storage pool to Cluster"); HypervisorType hType = null; if (hostPoolRecords.second() > 0) { hType = getHypervisorType(hostPoolRecords.first().get(0).getHostId()); @@ -585,4 +585,4 @@ public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clu dataStoreHelper.switchToCluster(store, clusterScope); return true; } -} \ No newline at end of file +} diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 44423c3c84f8..e95b5b31add2 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1159,7 +1159,7 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille throw new PermissionDeniedException("Only root admin can perform this operation"); } - ScopeType scopeType = ScopeType.validateAndGetScopeType(cmd.getScope()); + ScopeType scopeType = EnumUtils.getEnumIgnoreCase(ScopeType.class, cmd.getScope()); if (scopeType != ScopeType.ZONE && scopeType != ScopeType.CLUSTER) { throw new InvalidParameterValueException("Invalid scope " + scopeType.toString() + "for Primary storage"); } @@ -1171,11 +1171,12 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille if (!primaryStorage.getStorageProviderName().equals(DataStoreProvider.DEFAULT_PRIMARY)) { throw new InvalidParameterValueException("Primary storage scope change is only supported with " - + DataStoreProvider.DEFAULT_PRIMARY.toString() + " data store provider"); + + DataStoreProvider.DEFAULT_PRIMARY + " data store provider"); } - if (!primaryStorage.getPoolType().equals(Storage.StoragePoolType.NetworkFilesystem) && - !primaryStorage.getPoolType().equals(Storage.StoragePoolType.RBD)) { + StoragePoolType poolType = primaryStorage.getPoolType(); + Set supportedStoragePoolTypes = Sets.newHashSet(StoragePoolType.NetworkFilesystem, StoragePoolType.RBD); + if (!supportedStoragePoolTypes.contains(poolType)) { throw new InvalidParameterValueException("Primary storage scope change is not supported for protocol " + primaryStorage.getPoolType().toString()); } @@ -1186,20 +1187,16 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); } - if (StoragePoolStatus.Disabled != primaryStorage.getStatus()) { - throw new InvalidParameterValueException("Primary storage should be disabled for this operation"); - } - - if (scopeType == primaryStorage.getScope()) { - throw new InvalidParameterValueException("Invalid scope change"); - } - if (!primaryStorage.getStatus().equals(StoragePoolStatus.Disabled)) { throw new InvalidParameterValueException("Scope of the Primary storage with id " + primaryStorage.getUuid() + " cannot be changed, as it is not in the Disabled state"); } + if (primaryStorage.getScope().equals(scopeType)) { + throw new InvalidParameterValueException("New scope must be different than the current scope"); + } + String providerName = primaryStorage.getStorageProviderName(); DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName); PrimaryDataStoreLifeCycle lifeCycle = (PrimaryDataStoreLifeCycle) storeProvider.getDataStoreLifeCycle(); @@ -1208,20 +1205,28 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille Long zoneId = primaryStorage.getDataCenterId(); DataCenterVO zone = _dcDao.findById(zoneId); if (zone == null) { - throw new InvalidParameterValueException("unable to find zone by id " + zoneId); + throw new InvalidParameterValueException("Unable to find zone by id " + zoneId); } if (Grouping.AllocationState.Disabled == zone.getAllocationState()) { throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId); } - if (scopeType == ScopeType.ZONE) { + if (scopeType.equals(ScopeType.ZONE)) { ClusterScope clusterScope = new ClusterScope(primaryStorage.getClusterId(), null, zoneId); lifeCycle.changeStoragePoolScopeToZone(primaryStore, clusterScope, hypervisorType); } else { - Long clusterId = cmd.getClusterId(); + if (clusterId == null) { + throw new IllegalArgumentException("Unable to find cluster with ID: " + clusterId); + } ClusterVO clusterVO = _clusterDao.findById(clusterId); + if (clusterVO == null) { + throw new InvalidParameterValueException("Unable to find cluster by id " + clusterId); + } + if (Grouping.AllocationState.Disabled == clusterVO.getAllocationState()) { + throw new PermissionDeniedException("Cannot perform this operation, Cluster is currently disabled: " + zoneId); + } List states = Arrays.asList(State.Starting, State.Running, State.Stopping, State.Migrating, State.Restoring); List volumesInOtherClusters = volumeDao.listByPoolIdVMStatesNotInCluster(clusterId, states, id); if (volumesInOtherClusters.size() > 0) { diff --git a/test/integration/smoke/test_primary_storage.py b/test/integration/smoke/test_primary_storage.py index b9a28c03ff4f..8793b0440a5c 100644 --- a/test/integration/smoke/test_primary_storage.py +++ b/test/integration/smoke/test_primary_storage.py @@ -409,7 +409,6 @@ def test_01_primary_storage_scope_change(self): self.cleanup.append(storage) pool_id = self.dbclient.execute("select id from storage_pool where uuid=\"" + storage.id + "\"")[0][0] - self.debug("Created storage pool in cluster: %s" % cluster1.id) # Disable storage pool diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4417660e78f6..c4b2052484d0 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2447,8 +2447,8 @@ "message.action.manage.cluster": "Please confirm that you want to manage the cluster.", "message.action.patch.router": "Please confirm that you want to live patch the router.
This operation is equivalent updating the router packages and restarting the Network without cleanup.", "message.action.patch.systemvm": "Please confirm that you want to patch the System VM.", -"message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped. Do you want to continue?", "message.action.primary.storage.scope.cluster": "Change the primary storage scope from zone to cluster", +"message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped. Do you want to continue?", "message.action.primary.storage.scope.zone": "Change the primary storage scope from cluster to zone", "message.action.reboot.instance": "Please confirm that you want to reboot this Instance.", "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.", diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js index cf8e657b9bf2..9fe491ed782c 100644 --- a/ui/src/config/section/infra/primaryStorages.js +++ b/ui/src/config/section/infra/primaryStorages.js @@ -154,6 +154,7 @@ export default { icon: 'delete-outlined', label: 'label.action.delete.primary.storage', dataView: true, + args: ['forced'], show: (record) => { return (record.state === 'Down' || record.state === 'Maintenance' || record.state === 'Disconnected') }, displayName: (record) => { return record.name || record.displayName || record.id } } From 0fab7a352441f41542e4a5a3e0af73dd3aa5b1e1 Mon Sep 17 00:00:00 2001 From: abh1sar Date: Sat, 6 Apr 2024 12:00:58 +0530 Subject: [PATCH 06/36] Pull 8875: remove storage checks, AbstractPrimayStorageLifeCycleImpl class --- .../storage/ChangeStoragePoolScopeCmd.java | 6 +- ...AbstractPrimaryDataStoreLifeCycleImpl.java | 127 ++++++++++++++++++ .../AdaptiveDataStoreLifeCycleImpl.java | 12 +- .../ElastistorPrimaryDataStoreLifeCycle.java | 12 +- .../DateraPrimaryDataStoreLifeCycle.java | 13 +- ...oudStackPrimaryDataStoreLifeCycleImpl.java | 44 +----- .../LinstorPrimaryDataStoreLifeCycleImpl.java | 12 +- .../NexentaPrimaryDataStoreLifeCycle.java | 14 +- .../ScaleIOPrimaryDataStoreLifeCycle.java | 12 +- .../SolidFirePrimaryDataStoreLifeCycle.java | 13 +- ...idFireSharedPrimaryDataStoreLifeCycle.java | 12 +- .../StorPoolPrimaryDataStoreLifeCycle.java | 12 +- .../com/cloud/storage/StorageManagerImpl.java | 52 +++---- ui/public/locales/en.json | 5 +- .../config/section/infra/primaryStorages.js | 12 +- ui/src/views/infra/changeStoragePoolScope.vue | 5 + 16 files changed, 198 insertions(+), 165 deletions(-) create mode 100644 engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AbstractPrimaryDataStoreLifeCycleImpl.java diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java index c694e886cdb1..c26b1db54d66 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -30,8 +30,10 @@ import com.cloud.event.EventTypes; -@APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool.", responseObject = SuccessResponse.class, - since= "4.19.1", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +@APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool. " + + "This feature is officially tested and supported for Hypervisors: KVM and VMware, Protocols: NFS and Ceph, and Storage Provider: DefaultPrimary. " + + "There might be extra steps involved to make this work for other hypervisors and storage options.", + responseObject = SuccessResponse.class, since= "4.19.1", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ChangeStoragePoolScopeCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true, description = "the Id of the storage pool") diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AbstractPrimaryDataStoreLifeCycleImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AbstractPrimaryDataStoreLifeCycleImpl.java new file mode 100644 index 000000000000..d43f91c0e4ce --- /dev/null +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AbstractPrimaryDataStoreLifeCycleImpl.java @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.datastore.lifecycle; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; +import org.apache.log4j.Logger; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.Pair; + +public class AbstractPrimaryDataStoreLifeCycleImpl { + private static final Logger s_logger = Logger.getLogger(AbstractPrimaryDataStoreLifeCycleImpl.class); + @Inject + AgentManager agentMgr; + @Inject + protected ResourceManager _resourceMgr; + @Inject + StorageManager storageMgr; + @Inject + PrimaryDataStoreHelper dataStoreHelper; + @Inject + protected HostDao _hostDao; + @Inject + protected StoragePoolHostDao _storagePoolHostDao; + + private HypervisorType getHypervisorType(long hostId) { + HostVO host = _hostDao.findById(hostId); + if (host != null) + return host.getHypervisorType(); + return HypervisorType.None; + } + + private List getPoolHostsList(ClusterScope clusterScope, HypervisorType hypervisorType) { + + List hosts = new ArrayList(); + + if (hypervisorType != null) { + hosts = _resourceMgr + .listAllHostsInOneZoneNotInClusterByHypervisor(hypervisorType, clusterScope.getZoneId(), clusterScope.getScopeId()); + } else { + List xenServerHosts = _resourceMgr + .listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType.XenServer, clusterScope.getZoneId(), clusterScope.getScopeId()); + List vmWareServerHosts = _resourceMgr + .listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType.VMware, clusterScope.getZoneId(), clusterScope.getScopeId()); + List kvmHosts = _resourceMgr. + listAllHostsInOneZoneNotInClusterByHypervisor(HypervisorType.KVM, clusterScope.getZoneId(), clusterScope.getScopeId()); + + hosts.addAll(xenServerHosts); + hosts.addAll(vmWareServerHosts); + hosts.addAll(kvmHosts); + } + return hosts; + } + + public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + List hosts = getPoolHostsList(clusterScope, hypervisorType); + s_logger.debug("Changing scope of the storage pool to Zone"); + for (HostVO host : hosts) { + try { + storageMgr.connectHostToSharedPool(host.getId(), store.getId()); + } catch (Exception e) { + s_logger.warn("Unable to establish a connection between " + host + " and " + store, e); + } + } + dataStoreHelper.switchToZone(store, hypervisorType); + return true; + } + + public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { + Pair, Integer> hostPoolRecords = _storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()); + s_logger.debug("Changing scope of the storage pool to Cluster"); + HypervisorType hType = null; + if (hostPoolRecords.second() > 0) { + hType = getHypervisorType(hostPoolRecords.first().get(0).getHostId()); + } + + StoragePool pool = (StoragePool) store; + for (StoragePoolHostVO host : hostPoolRecords.first()) { + DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool); + final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd); + + if (answer != null && answer.getResult()) { + if (HypervisorType.KVM != hType) { + break; + } + } else { + if (answer != null) { + s_logger.debug("Failed to delete storage pool: " + answer.getResult()); + } + } + } + dataStoreHelper.switchToCluster(store, clusterScope); + return true; + } +} diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java index db95b0c96ab3..f4d3f484cb37 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java @@ -62,7 +62,7 @@ /** * Manages the lifecycle of a Managed Data Store in CloudStack */ -public class AdaptiveDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { +public class AdaptiveDataStoreLifeCycleImpl extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { @Inject private PrimaryDataStoreDao _storagePoolDao; private static final Logger s_logger = Logger.getLogger(AdaptiveDataStoreLifeCycleImpl.class); @@ -404,14 +404,4 @@ public void disableStoragePool(DataStore store) { s_logger.info("Disabling storage pool [" + store.getName() + "]"); _dataStoreHelper.disable(store); } - - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } } diff --git a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java index 28b57f656281..2a98bedd667b 100644 --- a/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ElastistorPrimaryDataStoreLifeCycle.java @@ -65,7 +65,7 @@ import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.utils.exception.CloudRuntimeException; -public class ElastistorPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class ElastistorPrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger s_logger = Logger.getLogger(ElastistorPrimaryDataStoreLifeCycle.class); @Inject @@ -476,16 +476,6 @@ public void disableStoragePool(DataStore dataStore) { _dataStoreHelper.disable(dataStore); } - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - @SuppressWarnings("finally") @Override public boolean deleteDataStore(DataStore store) { diff --git a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java index 1ec8eddc8239..8f99b55cc450 100644 --- a/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/datera/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/DateraPrimaryDataStoreLifeCycle.java @@ -58,7 +58,7 @@ import java.util.List; import java.util.Map; -public class DateraPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class DateraPrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger s_logger = Logger.getLogger(DateraPrimaryDataStoreLifeCycle.class); @Inject @@ -397,12 +397,11 @@ public void disableStoragePool(DataStore dataStore) { @Override public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + /* + * We need to attach all VMware, Xenserver and KVM hosts in the zone. + * So pass hypervisorTpe as null. + */ + return super.changeStoragePoolScopeToZone(store, clusterScope, null); } private HypervisorType getHypervisorTypeForCluster(long clusterId) { 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 77f2f65bd3f0..8461a3364aac 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 @@ -45,7 +45,6 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.NumbersUtil; -import com.cloud.utils.Pair; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachineManager; @@ -73,7 +72,7 @@ import java.util.Map; import java.util.UUID; -public class CloudStackPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { +public class CloudStackPrimaryDataStoreLifeCycleImpl extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger s_logger = Logger.getLogger(CloudStackPrimaryDataStoreLifeCycleImpl.class); @Inject protected ResourceManager _resourceMgr; @@ -544,45 +543,4 @@ public void enableStoragePool(DataStore dataStore) { public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } - - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - List hosts = _resourceMgr.listAllHostsInOneZoneNotInClusterByHypervisor(hypervisorType, clusterScope.getZoneId(), clusterScope.getScopeId()); - s_logger.debug("Changing scope of the storage pool to Zone"); - for (HostVO host : hosts) { - try { - storageMgr.connectHostToSharedPool(host.getId(), store.getId()); - } catch (Exception e) { - s_logger.warn("Unable to establish a connection between " + host + " and " + store, e); - } - } - dataStoreHelper.switchToZone(store, hypervisorType); - return true; - } - - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - Pair, Integer> hostPoolRecords = _storagePoolHostDao.listByPoolIdNotInCluster(clusterScope.getScopeId(), store.getId()); - s_logger.debug("Changing scope of the storage pool to Cluster"); - HypervisorType hType = null; - if (hostPoolRecords.second() > 0) { - hType = getHypervisorType(hostPoolRecords.first().get(0).getHostId()); - } - - StoragePool pool = (StoragePool) store; - for (StoragePoolHostVO host : hostPoolRecords.first()) { - DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool); - final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd); - - if (answer != null && answer.getResult()) { - if (HypervisorType.KVM != hType) { - break; - } - } else { - if (answer != null) { - s_logger.debug("Failed to delete storage pool: " + answer.getResult()); - } - } - } - dataStoreHelper.switchToCluster(store, clusterScope); - return true; - } } diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java index d5425a40405d..bd4259368f8d 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java @@ -53,7 +53,7 @@ import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; import org.apache.log4j.Logger; -public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { +public class LinstorPrimaryDataStoreLifeCycleImpl extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreLifeCycleImpl.class); @Inject @@ -338,14 +338,4 @@ public void enableStoragePool(DataStore store) { public void disableStoragePool(DataStore store) { dataStoreHelper.disable(store); } - - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } } diff --git a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java index e2b40ffc2487..dec6212e6595 100644 --- a/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/lifecylce/NexentaPrimaryDataStoreLifeCycle.java @@ -30,6 +30,7 @@ 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.AbstractPrimaryDataStoreLifeCycleImpl; import org.apache.cloudstack.storage.datastore.util.NexentaUtil; import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; import org.apache.log4j.Logger; @@ -43,9 +44,9 @@ import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolAutomation; -import com.cloud.utils.exception.CloudRuntimeException; public class NexentaPrimaryDataStoreLifeCycle + extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger logger = Logger.getLogger(NexentaPrimaryDataStoreLifeCycle.class); @@ -180,12 +181,11 @@ public void disableStoragePool(DataStore dataStore) { @Override public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + /* + * We need to attach all VMware, Xenserver and KVM hosts in the zone. + * So pass hypervisorTpe as null. + */ + return super.changeStoragePoolScopeToZone(store, clusterScope, null); } @Override diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java index 5878eb664fe8..bb15f5c2a298 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java @@ -74,7 +74,7 @@ import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.exception.CloudRuntimeException; -public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class ScaleIOPrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger LOGGER = Logger.getLogger(ScaleIOPrimaryDataStoreLifeCycle.class); @Inject @@ -367,16 +367,6 @@ public void disableStoragePool(DataStore dataStore) { dataStoreHelper.disable(dataStore); } - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - @Override public boolean deleteDataStore(DataStore dataStore) { StoragePool storagePool = (StoragePool)dataStore; diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java index 5516c7b56e3c..60c131f7e6f4 100644 --- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java @@ -63,7 +63,7 @@ import com.google.common.base.Preconditions; -public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class SolidFirePrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger s_logger = Logger.getLogger(SolidFirePrimaryDataStoreLifeCycle.class); @Inject private CapacityManager _capacityMgr; @@ -390,11 +390,10 @@ public void disableStoragePool(DataStore dataStore) { @Override public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); + /* + * We need to attach all VMware, Xenserver and KVM hosts in the zone. + * So pass hypervisorTpe as null. + */ + return super.changeStoragePoolScopeToZone(store, clusterScope, null); } } diff --git a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java index 429160b9de90..a84943311bd5 100644 --- a/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/solidfire/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/SolidFireSharedPrimaryDataStoreLifeCycle.java @@ -72,7 +72,7 @@ import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; -public class SolidFireSharedPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class SolidFireSharedPrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger LOGGER = Logger.getLogger(SolidFireSharedPrimaryDataStoreLifeCycle.class); @Inject private AccountDao accountDao; @@ -770,14 +770,4 @@ public void enableStoragePool(DataStore dataStore) { public void disableStoragePool(DataStore dataStore) { primaryDataStoreHelper.disable(dataStore); } - - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java index eb9a48942ae5..5824ef621cb4 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/StorPoolPrimaryDataStoreLifeCycle.java @@ -60,7 +60,7 @@ import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.utils.exception.CloudRuntimeException; -public class StorPoolPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { +public class StorPoolPrimaryDataStoreLifeCycle extends AbstractPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { private static final Logger log = Logger.getLogger(StorPoolPrimaryDataStoreLifeCycle.class); @Inject @@ -318,14 +318,4 @@ public void disableStoragePool(DataStore dataStore) { log.debug("disableStoragePool"); dataStoreHelper.disable(dataStore); } - - @Override - public boolean changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } - - @Override - public boolean changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, HypervisorType hypervisorType) { - throw new CloudRuntimeException("Storage pool scope change not supported for this Storage Pool Provider"); - } } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index e95b5b31add2..502ff3a0bfaa 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -406,6 +406,9 @@ public void setDiscoverers(List discoverers) { private final Map hostListeners = new HashMap(); + private final Set zoneWidePoolSupportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, + HypervisorType.Hyperv, HypervisorType.LXC, HypervisorType.Any, HypervisorType.Simulator); + public boolean share(VMInstanceVO vm, List vols, HostVO host, boolean cancelPreviousShare) throws StorageUnavailableException { // if pool is in maintenance and it is the ONLY pool available; reject @@ -870,9 +873,7 @@ public PrimaryDataStoreInfo createPool(CreateStoragePoolCmd cmd) throws Resource throw new InvalidParameterValueException("Missing parameter hypervisor. Hypervisor type is required to create zone wide primary storage."); } - Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, - HypervisorType.Hyperv, HypervisorType.LXC, HypervisorType.Any, HypervisorType.Simulator); - if (!supportedHypervisorTypes.contains(hypervisorType)) { + if (!zoneWidePoolSupportedHypervisorTypes.contains(hypervisorType)) { throw new InvalidParameterValueException("Zone wide storage pool is not supported for hypervisor type " + hypervisor); } } else { @@ -1159,9 +1160,9 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille throw new PermissionDeniedException("Only root admin can perform this operation"); } - ScopeType scopeType = EnumUtils.getEnumIgnoreCase(ScopeType.class, cmd.getScope()); - if (scopeType != ScopeType.ZONE && scopeType != ScopeType.CLUSTER) { - throw new InvalidParameterValueException("Invalid scope " + scopeType.toString() + "for Primary storage"); + ScopeType newScope = EnumUtils.getEnumIgnoreCase(ScopeType.class, cmd.getScope()); + if (newScope != ScopeType.ZONE && newScope != ScopeType.CLUSTER) { + throw new InvalidParameterValueException("Invalid scope " + newScope.toString() + "for Primary storage"); } StoragePoolVO primaryStorage = _storagePoolDao.findById(id); @@ -1169,34 +1170,33 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille throw new IllegalArgumentException("Unable to find storage pool with ID: " + id); } - if (!primaryStorage.getStorageProviderName().equals(DataStoreProvider.DEFAULT_PRIMARY)) { - throw new InvalidParameterValueException("Primary storage scope change is only supported with " - + DataStoreProvider.DEFAULT_PRIMARY + " data store provider"); - } - - StoragePoolType poolType = primaryStorage.getPoolType(); - Set supportedStoragePoolTypes = Sets.newHashSet(StoragePoolType.NetworkFilesystem, StoragePoolType.RBD); - if (!supportedStoragePoolTypes.contains(poolType)) { - throw new InvalidParameterValueException("Primary storage scope change is not supported for protocol " - + primaryStorage.getPoolType().toString()); - } - - HypervisorType hypervisorType = primaryStorage.getHypervisor(); - Set supportedHypervisorTypes = Sets.newHashSet(HypervisorType.KVM, HypervisorType.VMware, HypervisorType.Simulator); - if (!supportedHypervisorTypes.contains(hypervisorType)) { - throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); - } - if (!primaryStorage.getStatus().equals(StoragePoolStatus.Disabled)) { throw new InvalidParameterValueException("Scope of the Primary storage with id " + primaryStorage.getUuid() + " cannot be changed, as it is not in the Disabled state"); } - if (primaryStorage.getScope().equals(scopeType)) { + ScopeType currentScope = primaryStorage.getScope(); + if (currentScope.equals(newScope)) { throw new InvalidParameterValueException("New scope must be different than the current scope"); } + HypervisorType hypervisorType; + if (currentScope.equals(ScopeType.CLUSTER)) { + /* + * For cluster wide primary storage the hypervisor type might not be set. + * So, get it from the clusterVO. + */ + Long clusterId = primaryStorage.getClusterId(); + ClusterVO clusterVO = _clusterDao.findById(clusterId); + hypervisorType = clusterVO.getHypervisorType(); + } else { + hypervisorType = primaryStorage.getHypervisor(); + } + if (!zoneWidePoolSupportedHypervisorTypes.contains(hypervisorType)) { + throw new InvalidParameterValueException("Primary storage scope change is not supported for hypervisor type " + hypervisorType); + } + String providerName = primaryStorage.getStorageProviderName(); DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(providerName); PrimaryDataStoreLifeCycle lifeCycle = (PrimaryDataStoreLifeCycle) storeProvider.getDataStoreLifeCycle(); @@ -1211,7 +1211,7 @@ public boolean changeStoragePoolScope(ChangeStoragePoolScopeCmd cmd) throws Ille throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId); } - if (scopeType.equals(ScopeType.ZONE)) { + if (newScope.equals(ScopeType.ZONE)) { ClusterScope clusterScope = new ClusterScope(primaryStorage.getClusterId(), null, zoneId); lifeCycle.changeStoragePoolScopeToZone(primaryStore, clusterScope, hypervisorType); diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index c4b2052484d0..38b9ccfc2b5f 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2447,9 +2447,9 @@ "message.action.manage.cluster": "Please confirm that you want to manage the cluster.", "message.action.patch.router": "Please confirm that you want to live patch the router.
This operation is equivalent updating the router packages and restarting the Network without cleanup.", "message.action.patch.systemvm": "Please confirm that you want to patch the System VM.", -"message.action.primary.storage.scope.cluster": "Change the primary storage scope from zone to cluster", +"message.action.primary.storage.scope.cluster": "Please confirm that you want to change the scope from zone to the specified cluster.
This operation will update the database and disconnect the storage pool from all hosts that were previously connected to the primary storage and are not part of the specified cluster.", +"message.action.primary.storage.scope.zone": "Please confirm that you want to change the scope from cluster to zone.
This operation will update the database and connect the storage pool to all hosts of the zone running the same hypervisor as set on the storage pool.", "message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all Instances using volumes from it to be stopped. Do you want to continue?", -"message.action.primary.storage.scope.zone": "Change the primary storage scope from cluster to zone", "message.action.reboot.instance": "Please confirm that you want to reboot this Instance.", "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.", "message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.", @@ -3330,6 +3330,7 @@ "message.volume.state.primary.storage.suitability": "The suitability of a primary storage for a volume depends on the disk offering of the volume and on the virtual machine allocation (if the volume is attached to a virtual machine).", "message.vr.alert.upon.network.offering.creation.l2": "As virtual routers are not created for L2 Networks, the compute offering will not be used.", "message.vr.alert.upon.network.offering.creation.others": "As none of the obligatory services for creating a virtual router (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) are enabled, the virtual router will not be created and the compute offering will not be used.", +"message.warn.change.primary.storage.scope": "This operation is supported and tested for the following configurations:
KVM - NFS/Ceph - DefaultPrimary
VMware - NFS - DefaultPrimary
*There might be extra steps involved to make this 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.zone.mtu.update": "Please note that this limit won't affect pre-existing Network’s MTU settings", diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js index 9fe491ed782c..d7e991b24664 100644 --- a/ui/src/config/section/infra/primaryStorages.js +++ b/ui/src/config/section/infra/primaryStorages.js @@ -140,11 +140,13 @@ export default { dataView: true, popup: true, show: (record) => { - return ( - record.state === 'Disabled' && - (record.hypervisor === 'Simulator' || record.hypervisor === 'KVM' || record.hypervisor === 'VMware') && - (record.type === 'NetworkFilesystem' || record.type === 'RBD') && - record.provider === 'DefaultPrimary' + return (record.state === 'Disabled' && + (record.hypervisor === 'KVM' || + record.hypervisor === 'VMware' || + record.hypervisor === 'HyperV' || + record.hypervisor === 'LXC' || + record.hypervisor === 'Any' || + record.hypervisor === 'Simulator') ) }, component: shallowRef(defineAsyncComponent(() => import('@/views/infra/changeStoragePoolScope.vue'))) diff --git a/ui/src/views/infra/changeStoragePoolScope.vue b/ui/src/views/infra/changeStoragePoolScope.vue index d3d4bd9f54c3..6633590a1bdc 100644 --- a/ui/src/views/infra/changeStoragePoolScope.vue +++ b/ui/src/views/infra/changeStoragePoolScope.vue @@ -30,6 +30,11 @@ + + +