[PowerFlex] Add & Remove PowerFlex/ScaleIO MDMs for the storage SDC connections (#9903)

* Add & Remove PowerFlex/ScaleIO MDMs while preparing & unpreparing the storage SDC connections (instead of start & stop scini)

* Add/Remove MDM IP addresses during Host connection/disconnection to/from storage pool when powerflex.connect.on.demand is false

* unit test fixes

* Don't remove MDM IPs from SDC when any volumes mapped to SDC

* Don't remove MDM IPs when other pools of same ScaleIO/PowerFlex cluster are connected

* rebase fixes

* update changes, to not remove/disconnect MDMs on maintenance

* import fixes after rebase
This commit is contained in:
Suresh Kumar Anaparti 2025-05-15 12:42:13 +05:30 committed by GitHub
parent 64828f66e8
commit 572fc11a64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 767 additions and 176 deletions

View File

@ -46,6 +46,10 @@ public class ModifyStoragePoolAnswer extends Answer {
templateInfo = tInfo;
}
public ModifyStoragePoolAnswer(ModifyStoragePoolCommand cmd, boolean success, String details) {
super(cmd, success, details);
}
public void setPoolInfo(StoragePoolInfo poolInfo) {
this.poolInfo = poolInfo;
}

View File

@ -19,18 +19,22 @@
package com.cloud.agent.api;
import java.util.Map;
import com.cloud.storage.Storage.StoragePoolType;
public class UnprepareStorageClientCommand extends Command {
private StoragePoolType poolType;
private String poolUuid;
private Map<String, String> details;
public UnprepareStorageClientCommand() {
}
public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid) {
public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid, Map<String, String> details) {
this.poolType = poolType;
this.poolUuid = poolUuid;
this.details = details;
}
@Override
@ -45,4 +49,8 @@ public class UnprepareStorageClientCommand extends Command {
public String getPoolUuid() {
return poolUuid;
}
public Map<String, String> getDetails() {
return details;
}
}

View File

@ -45,4 +45,8 @@ public interface DataStoreDriver {
boolean canCopy(DataObject srcData, DataObject destData);
void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback);
default boolean canDisplayDetails() {
return true;
}
}

View File

@ -145,6 +145,14 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver {
return false;
}
/**
* intended for managed storage
* returns true if the host can be disconnected from storage pool
*/
default boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool) {
return true;
}
/**
* Used by storage pools which want to keep VMs' information
* @return true if additional VM info is needed (intended for storage pools).

View File

@ -314,6 +314,8 @@ public interface StorageManager extends StorageService {
boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool);
boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool);
Host getHost(long hostId);
Host updateSecondaryStorage(long secStorageId, String newUrl);

View File

@ -30,6 +30,8 @@ public interface StoragePoolHostDao extends GenericDao<StoragePoolHostVO, Long>
public StoragePoolHostVO findByPoolHost(long poolId, long hostId);
List<StoragePoolHostVO> findByLocalPath(String path);
List<StoragePoolHostVO> listByHostStatus(long poolId, Status hostStatus);
List<Long> findHostsConnectedToPools(List<Long> poolIds);

View File

@ -45,6 +45,7 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase<StoragePoolHostVO, Lo
protected final SearchBuilder<StoragePoolHostVO> PoolSearch;
protected final SearchBuilder<StoragePoolHostVO> HostSearch;
protected final SearchBuilder<StoragePoolHostVO> PoolHostSearch;
protected final SearchBuilder<StoragePoolHostVO> LocalPathSearch;
protected SearchBuilder<StoragePoolHostVO> poolNotInClusterSearch;
@ -77,6 +78,9 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase<StoragePoolHostVO, Lo
PoolHostSearch.and("host_id", PoolHostSearch.entity().getHostId(), SearchCriteria.Op.EQ);
PoolHostSearch.done();
LocalPathSearch = createSearchBuilder();
LocalPathSearch.and("local_path", LocalPathSearch.entity().getLocalPath(), SearchCriteria.Op.EQ);
LocalPathSearch.done();
}
@PostConstruct
@ -117,6 +121,13 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase<StoragePoolHostVO, Lo
return findOneIncludingRemovedBy(sc);
}
@Override
public List<StoragePoolHostVO> findByLocalPath(String path) {
SearchCriteria<StoragePoolHostVO> sc = LocalPathSearch.create();
sc.setParameters("local_path", path);
return listBy(sc);
}
@Override
public List<StoragePoolHostVO> listByHostStatus(long poolId, Status hostStatus) {
TransactionLegacy txn = TransactionLegacy.currentTxn();

View File

@ -60,6 +60,8 @@ public interface PrimaryDataStoreDao extends GenericDao<StoragePoolVO, Long> {
StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule);
StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule, boolean displayDetails);
/**
* Find pool by name.
*
@ -103,6 +105,8 @@ public interface PrimaryDataStoreDao extends GenericDao<StoragePoolVO, Long> {
void updateDetails(long poolId, Map<String, String> details);
void removeDetails(long poolId);
Map<String, String> getDetails(long poolId);
List<String> searchForStoragePoolTags(long poolId);

View File

@ -296,14 +296,19 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
}
@Override
@DB
public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule) {
return persist(pool, details, tags, isTagARule, true);
}
@Override
@DB
public StoragePoolVO persist(StoragePoolVO pool, Map<String, String> details, List<String> tags, Boolean isTagARule, boolean displayDetails) {
TransactionLegacy txn = TransactionLegacy.currentTxn();
txn.start();
pool = super.persist(pool);
if (details != null) {
for (Map.Entry<String, String> detail : details.entrySet()) {
StoragePoolDetailVO vo = new StoragePoolDetailVO(pool.getId(), detail.getKey(), detail.getValue(), true);
StoragePoolDetailVO vo = new StoragePoolDetailVO(pool.getId(), detail.getKey(), detail.getValue(), displayDetails);
_detailsDao.persist(vo);
}
}
@ -570,6 +575,11 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
}
}
@Override
public void removeDetails(long poolId) {
_detailsDao.removeDetails(poolId);
}
@Override
public Map<String, String> getDetails(long poolId) {
return _detailsDao.listDetailsKeyPairs(poolId);

View File

@ -34,6 +34,7 @@ import org.apache.cloudstack.storage.datastore.api.SnapshotGroup;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
@ -301,7 +302,11 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
srcSnapshotDestVolumeMap.put(srcSnapshotVolumeId, destVolumeId);
}
String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for reverting VM snapshot: " + vmSnapshot.getName());
}
@ -380,7 +385,11 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
try {
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
StoragePoolVO storagePool = vmSnapshotHelper.getStoragePoolForVM(userVm);
String systemId = storagePoolDetailsDao.findDetail(storagePool.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(storagePool.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for deleting VM snapshot: " + vmSnapshot.getName());
}

View File

@ -29,13 +29,16 @@ import javax.inject.Inject;
import com.cloud.dc.dao.ClusterDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.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.DataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@ -78,6 +81,8 @@ public class PrimaryDataStoreHelper {
protected ClusterDao clusterDao;
@Inject
private AnnotationDao annotationDao;
@Inject
DataStoreProviderManager dataStoreProviderMgr;
public DataStore createPrimaryDataStore(PrimaryDataStoreParameters params) {
if(params == null)
@ -144,7 +149,17 @@ public class PrimaryDataStoreHelper {
storageTags.add(tag);
}
}
dataStoreVO = dataStoreDao.persist(dataStoreVO, details, storageTags, params.isTagARule());
boolean displayDetails = true;
DataStoreProvider storeProvider = dataStoreProviderMgr.getDataStoreProvider(params.getProviderName());
if (storeProvider != null) {
DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
if (storeDriver != null) {
displayDetails = storeDriver.canDisplayDetails();
}
}
dataStoreVO = dataStoreDao.persist(dataStoreVO, details, storageTags, params.isTagARule(), displayDetails);
return dataStoreMgr.getDataStore(dataStoreVO.getId(), DataStoreRole.Primary);
}

View File

@ -38,6 +38,17 @@ public final class LibvirtModifyStoragePoolCommandWrapper extends CommandWrapper
@Override
public Answer execute(final ModifyStoragePoolCommand command, final LibvirtComputingResource libvirtComputingResource) {
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
if (!command.getAdd()) {
boolean status = storagePoolMgr.deleteStoragePool(command.getPool().getType(), command.getPool().getUuid(), command.getDetails());
if (status) {
final ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(command, true, null);
return answer;
}
final ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(command, false, "Failed to delete storage pool");
return answer;
}
final KVMStoragePool storagepool =
storagePoolMgr.createStoragePool(command.getPool().getUuid(), command.getPool().getHost(), command.getPool().getPort(), command.getPool().getPath(), command.getPool()
.getUserInfo(), command.getPool().getType(), command.getDetails());
@ -47,7 +58,6 @@ public final class LibvirtModifyStoragePoolCommandWrapper extends CommandWrapper
final Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();
final ModifyStoragePoolAnswer answer = new ModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, storagepool.getDetails());
return answer;
}
}

View File

@ -34,7 +34,7 @@ public class LibvirtUnprepareStorageClientCommandWrapper extends CommandWrapper<
@Override
public Answer execute(UnprepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) {
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
Pair<Boolean, String> unprepareStorageClientResult = storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid());
Pair<Boolean, String> unprepareStorageClientResult = storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails());
if (!unprepareStorageClientResult.first()) {
String msg = unprepareStorageClientResult.second();
logger.debug("Couldn't unprepare storage client, due to: " + msg);

View File

@ -408,12 +408,26 @@ public class KVMStoragePoolManager {
public boolean deleteStoragePool(StoragePoolType type, String uuid) {
StorageAdaptor adaptor = getStorageAdaptor(type);
_haMonitor.removeStoragePool(uuid);
adaptor.deleteStoragePool(uuid);
if (type == StoragePoolType.NetworkFilesystem) {
_haMonitor.removeStoragePool(uuid);
}
boolean deleteStatus = adaptor.deleteStoragePool(uuid);;
synchronized (_storagePools) {
_storagePools.remove(uuid);
}
return true;
return deleteStatus;
}
public boolean deleteStoragePool(StoragePoolType type, String uuid, Map<String, String> details) {
StorageAdaptor adaptor = getStorageAdaptor(type);
if (type == StoragePoolType.NetworkFilesystem) {
_haMonitor.removeStoragePool(uuid);
}
boolean deleteStatus = adaptor.deleteStoragePool(uuid, details);
synchronized (_storagePools) {
_storagePools.remove(uuid);
}
return deleteStatus;
}
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, Storage.ProvisioningType provisioningType,
@ -477,11 +491,11 @@ public class KVMStoragePoolManager {
public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) {
StorageAdaptor adaptor = getStorageAdaptor(type);
return adaptor.prepareStorageClient(type, uuid, details);
return adaptor.prepareStorageClient(uuid, details);
}
public Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid) {
public Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) {
StorageAdaptor adaptor = getStorageAdaptor(type);
return adaptor.unprepareStorageClient(type, uuid);
return adaptor.unprepareStorageClient(uuid, details);
}
}

View File

@ -28,6 +28,7 @@ import java.util.Map;
import java.util.UUID;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.utils.cryptsetup.CryptSetup;
import org.apache.cloudstack.utils.cryptsetup.CryptSetupException;
@ -148,12 +149,37 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
@Override
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, Storage.StoragePoolType type, Map<String, String> details, boolean isPrimaryStorage) {
ScaleIOStoragePool storagePool = new ScaleIOStoragePool(uuid, host, port, path, type, details, this);
if (details != null && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand)) {
Ternary<Boolean, Map<String, String>, String> prepareStorageClientStatus = prepareStorageClient(uuid, details);
if (prepareStorageClientStatus.first()) {
details.putAll(prepareStorageClientStatus.second());
}
}
}
MapStorageUuidToStoragePool.put(uuid, storagePool);
return storagePool;
}
@Override
public boolean deleteStoragePool(String uuid) {
ScaleIOStoragePool storagePool = (ScaleIOStoragePool) MapStorageUuidToStoragePool.get(uuid);
if (storagePool != null) {
unprepareStorageClient(uuid, storagePool.getDetails());
}
return MapStorageUuidToStoragePool.remove(uuid) != null;
}
@Override
public boolean deleteStoragePool(String uuid, Map<String, String> details) {
if (details != null && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand)) {
Pair<Boolean, String> unprepareStorageClientStatus = unprepareStorageClient(uuid, details);
return MapStorageUuidToStoragePool.remove(uuid) != null && unprepareStorageClientStatus.first();
}
}
return MapStorageUuidToStoragePool.remove(uuid) != null;
}
@ -567,7 +593,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
qemu.resize(options, objects, usableSizeBytes);
}
public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(Storage.StoragePoolType type, String uuid, Map<String, String> details) {
public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(String uuid, Map<String, String> details) {
if (!ScaleIOUtil.isSDCServiceInstalled()) {
logger.debug("SDC service not installed on host, preparing the SDC client not possible");
return new Ternary<>(false, null, "SDC service not installed on host");
@ -584,14 +610,28 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
if (!ScaleIOUtil.startSDCService()) {
return new Ternary<>(false, null, "Couldn't start SDC service on host");
}
} else if (!ScaleIOUtil.restartSDCService()) {
return new Ternary<>(false, null, "Couldn't restart SDC service on host");
}
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
// Assuming SDC service is started, add mdms
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
String[] mdmAddresses = mdms.split(",");
if (mdmAddresses.length > 0) {
if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
return new Ternary<>(true, getSDCDetails(details), "MDM added, no need to prepare the SDC client");
}
ScaleIOUtil.addMdms(Arrays.asList(mdmAddresses));
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
return new Ternary<>(false, null, "Failed to add MDMs");
}
}
}
return new Ternary<>( true, getSDCDetails(details), "Prepared client successfully");
}
public Pair<Boolean, String> unprepareStorageClient(Storage.StoragePoolType type, String uuid) {
public Pair<Boolean, String> unprepareStorageClient(String uuid, Map<String, String> details) {
if (!ScaleIOUtil.isSDCServiceInstalled()) {
logger.debug("SDC service not installed on host, no need to unprepare the SDC client");
return new Pair<>(true, "SDC service not installed on host, no need to unprepare the SDC client");
@ -602,8 +642,19 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
return new Pair<>(true, "SDC service not enabled on host, no need to unprepare the SDC client");
}
if (!ScaleIOUtil.stopSDCService()) {
return new Pair<>(false, "Couldn't stop SDC service on host");
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
String[] mdmAddresses = mdms.split(",");
if (mdmAddresses.length > 0) {
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
return new Pair<>(true, "MDM not added, no need to unprepare the SDC client");
}
ScaleIOUtil.removeMdms(Arrays.asList(mdmAddresses));
if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
return new Pair<>(false, "Failed to remove MDMs, unable to unprepare the SDC client");
}
}
}
return new Pair<>(true, "Unprepared SDC client successfully");

View File

@ -44,6 +44,10 @@ public interface StorageAdaptor {
public boolean deleteStoragePool(String uuid);
public default boolean deleteStoragePool(String uuid, Map<String, String> details) {
return true;
}
public default KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
return createPhysicalDisk(name, pool, format, provisioningType, size, passphrase);
@ -127,22 +131,21 @@ public interface StorageAdaptor {
/**
* Prepares the storage client.
* @param type type of the storage pool
* @param uuid uuid of the storage pool
* @param details any details of the storage pool that are required for client preparation
* @return status, client details, & message in case failed
*/
default Ternary<Boolean, Map<String, String>, String> prepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) {
default Ternary<Boolean, Map<String, String>, String> prepareStorageClient(String uuid, Map<String, String> details) {
return new Ternary<>(true, new HashMap<>(), "");
}
/**
* Unprepares the storage client.
* @param type type of the storage pool
* @param uuid uuid of the storage pool
* @param details any details of the storage pool that are required for client unpreparation
* @return status, & message in case failed
*/
default Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid) {
default Pair<Boolean, String> unprepareStorageClient(String uuid, Map<String, String> details) {
return new Pair<>(true, "");
}
}

View File

@ -14,6 +14,10 @@
package com.cloud.hypervisor.kvm.resource.wrapper;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -45,10 +49,13 @@ public class LibvirtUnprepareStorageClientCommandWrapperTest {
UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class);
Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid);
Map<String, String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
Mockito.when(cmd.getDetails()).thenReturn(details);
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(true, ""));
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Pair<>(true, ""));
UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);
@ -60,10 +67,13 @@ public class LibvirtUnprepareStorageClientCommandWrapperTest {
UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class);
Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid);
Map<String, String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
Mockito.when(cmd.getDetails()).thenReturn(details);
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(false, "Unprepare storage client failed"));
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Pair<>(false, "Unprepare storage client failed"));
UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);

View File

@ -34,7 +34,6 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageLayer;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
@ -73,7 +72,7 @@ public class ScaleIOStorageAdaptorTest {
public void testPrepareStorageClient_SDCServiceNotInstalled() {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(4);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(poolUuid, new HashMap<>());
Assert.assertFalse(result.first());
Assert.assertNull(result.second());
@ -86,41 +85,13 @@ public class ScaleIOStorageAdaptorTest {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(1);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl enable scini"))).thenReturn(1);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(poolUuid, new HashMap<>());
Assert.assertFalse(result.first());
Assert.assertNull(result.second());
Assert.assertEquals("SDC service not enabled on host", result.third());
}
@Test
public void testPrepareStorageClient_SDCServiceNotRestarted() {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(1);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
Assert.assertFalse(result.first());
Assert.assertNull(result.second());
Assert.assertEquals("Couldn't restart SDC service on host", result.third());
}
@Test
public void testPrepareStorageClient_SDCServiceRestarted() {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
Assert.assertTrue(result.first());
Assert.assertNotNull(result.second());
Assert.assertTrue(result.second().isEmpty());
}
@Test
public void testPrepareStorageClient_SDCServiceNotStarted() {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
@ -128,7 +99,7 @@ public class ScaleIOStorageAdaptorTest {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(1);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl start scini"))).thenReturn(1);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(poolUuid, new HashMap<>());
Assert.assertFalse(result.first());
Assert.assertNull(result.second());
@ -149,7 +120,7 @@ public class ScaleIOStorageAdaptorTest {
String sdcId = "301b852c00000003";
when(ScaleIOUtil.getSdcId(systemId)).thenReturn(sdcId);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(poolUuid, details);
Assert.assertTrue(result.first());
Assert.assertNotNull(result.second());
@ -172,7 +143,8 @@ public class ScaleIOStorageAdaptorTest {
when(ScaleIOUtil.getSdcId(systemId)).thenReturn(null);
when(ScaleIOUtil.getSdcGuid()).thenReturn(sdcGuid);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(poolUuid, details);
Assert.assertTrue(result.first());
Assert.assertNotNull(result.second());
Assert.assertEquals(sdcGuid, result.second().get(ScaleIOGatewayClient.SDC_GUID));
@ -181,9 +153,10 @@ public class ScaleIOStorageAdaptorTest {
@Test
public void testUnprepareStorageClient_SDCServiceNotInstalled() {
Map<String, String> details = new HashMap<>();
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(4);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
Assert.assertTrue(result.first());
Assert.assertEquals("SDC service not installed on host, no need to unprepare the SDC client", result.second());
@ -191,35 +164,42 @@ public class ScaleIOStorageAdaptorTest {
@Test
public void testUnprepareStorageClient_SDCServiceNotEnabled() {
Map<String, String> details = new HashMap<>();
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(1);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
Assert.assertTrue(result.first());
Assert.assertEquals("SDC service not enabled on host, no need to unprepare the SDC client", result.second());
}
@Test
public void testUnprepareStorageClient_SDCServiceNotStopped() {
public void testUnprepareStorageClient_MDMNotAdded() {
Map<String, String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(1);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-3.3.3.3 [1]-4.4.4.4");
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
Assert.assertFalse(result.first());
Assert.assertEquals("Couldn't stop SDC service on host", result.second());
Assert.assertTrue(result.first());
Assert.assertEquals("MDM not added, no need to unprepare the SDC client", result.second());
}
@Test
public void testUnprepareStorageClient_SDCServiceStopped() {
public void testUnprepareStorageClient_RemoveMDMFailed() {
Map<String, String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(0);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-1.1.1.1 [1]-2.2.2.2");
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
Assert.assertTrue(result.first());
Assert.assertFalse(result.first());
Assert.assertEquals("Failed to remove MDMs, unable to unprepare the SDC client", result.second());
}
}

View File

@ -0,0 +1,48 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.datastore.api;
public class StorageConfiguration {
String systemId;
Long mdmPort;
String[] mdmAddresses;
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
public Long getMdmPort() {
return mdmPort;
}
public void setMdmPort(Long mdmPort) {
this.mdmPort = mdmPort;
}
public String[] getMdmAddresses() {
return mdmAddresses;
}
public void setMdmAddresses(String[] mdmAddresses) {
this.mdmAddresses = mdmAddresses;
}
}

View File

@ -17,12 +17,15 @@
package org.apache.cloudstack.storage.datastore.api;
import java.util.List;
public class StoragePool {
String id;
String name;
String mediaType;
String protectionDomainId;
String systemId;
List<String> mdmAddresses;
StoragePoolStatistics statistics;
public String getId() {
@ -65,6 +68,14 @@ public class StoragePool {
this.systemId = systemId;
}
public List<String> getMdmAddresses() {
return mdmAddresses;
}
public void setMdmAddresses(List<String> mdmAddresses) {
this.mdmAddresses = mdmAddresses;
}
public StoragePoolStatistics getStatistics() {
return statistics;
}

View File

@ -38,6 +38,7 @@ public interface ScaleIOGatewayClient {
String GATEWAY_API_PASSWORD = "powerflex.gw.password";
String STORAGE_POOL_NAME = "powerflex.storagepool.name";
String STORAGE_POOL_SYSTEM_ID = "powerflex.storagepool.system.id";
String STORAGE_POOL_MDMS = "powerflex.storagepool.mdms";
String SDC_ID = "powerflex.sdc.id";
String SDC_GUID = "powerflex.sdc.guid";
@ -91,4 +92,6 @@ public interface ScaleIOGatewayClient {
boolean haveConnectedSdcs();
boolean isSdcConnected(String sdcId);
boolean isSdcConnectedByIP(String ipAddress);
List<String> getMdmAddresses();
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.cloud.storage.StoragePool;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
@ -76,11 +77,23 @@ public class ScaleIOGatewayClientConnectionPool {
synchronized (gatewayClients) {
client = gatewayClients.get(storagePoolId);
if (client == null) {
final String url = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_ENDPOINT).getValue();
final String encryptedUsername = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME).getValue();
final String username = DBEncryptionUtil.decrypt(encryptedUsername);
final String encryptedPassword = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue();
final String password = DBEncryptionUtil.decrypt(encryptedPassword);
String url = null;
StoragePoolDetailVO urlDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_ENDPOINT);
if (urlDetail != null) {
url = urlDetail.getValue();
}
String username = null;
StoragePoolDetailVO encryptedUsernameDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME);
if (encryptedUsernameDetail != null) {
final String encryptedUsername = encryptedUsernameDetail.getValue();
username = DBEncryptionUtil.decrypt(encryptedUsername);
}
String password = null;
StoragePoolDetailVO encryptedPasswordDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD);
if (encryptedPasswordDetail != null) {
final String encryptedPassword = encryptedPasswordDetail.getValue();
password = DBEncryptionUtil.decrypt(encryptedPassword);
}
final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.valueIn(storagePoolId);
final int clientMaxConnections = StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.valueIn(storagePoolId);

View File

@ -34,6 +34,7 @@ import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import org.apache.cloudstack.storage.datastore.api.StorageConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
@ -1126,6 +1127,15 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient {
return false;
}
@Override
public List<String> getMdmAddresses() {
StorageConfiguration storageConfiguration = get("/Configuration", StorageConfiguration.class);
if (storageConfiguration != null && storageConfiguration.getMdmAddresses().length > 0) {
return Arrays.asList(storageConfiguration.getMdmAddresses());
}
return new ArrayList<>();
}
private String getConnectionManagerStats() {
StringBuilder sb = new StringBuilder();
sb.append("\n").append("Client Connection Manager Stats => ");

View File

@ -268,7 +268,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
if (client.listVolumesMappedToSdc(sdcId).isEmpty()) {
sdcManager = ComponentContext.inject(sdcManager);
sdcManager.stopSDC(host, dataStore);
sdcManager.unprepareSDC(host, dataStore);
}
} catch (Exception e) {
logger.warn("Failed to revoke access due to: " + e.getMessage(), e);
@ -296,7 +296,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volumePath), sdcId);
if (client.listVolumesMappedToSdc(sdcId).isEmpty()) {
sdcManager = ComponentContext.inject(sdcManager);
sdcManager.stopSDC(host, dataStore);
sdcManager.unprepareSDC(host, dataStore);
}
} catch (Exception e) {
logger.warn("Failed to revoke access due to: " + e.getMessage(), e);
@ -1112,7 +1112,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
volumeDetails.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
volumeDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
return volumeDetails;
@ -1522,6 +1526,37 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
return sdcManager.areSDCConnectionsWithinLimit(pool.getId());
}
@Override
public boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool) {
if (host == null || pool == null) {
return false;
}
StoragePoolHostVO poolHostVO = storagePoolHostDao.findByPoolHost(pool.getId(), host.getId());
if (poolHostVO == null) {
return false;
}
final String sdcId = poolHostVO.getLocalPath();
if (StringUtils.isBlank(sdcId)) {
return false;
}
List<StoragePoolHostVO> poolHostVOsBySdc = storagePoolHostDao.findByLocalPath(sdcId);
if (CollectionUtils.isNotEmpty(poolHostVOsBySdc) && poolHostVOsBySdc.size() > 1) {
logger.debug(String.format("There are other connected pools with the same SDC of the host %s, shouldn't disconnect SDC", host));
return false;
}
try {
final ScaleIOGatewayClient client = getScaleIOClient(pool);
return client.listVolumesMappedToSdc(sdcId).isEmpty();
} catch (Exception e) {
logger.warn("Unable to check whether the host: " + host.getId() + " can be disconnected from storage pool: " + pool.getId() + ", due to " + e.getMessage(), e);
return false;
}
}
private void alertHostSdcDisconnection(Host host) {
if (host == null) {
return;
@ -1591,4 +1626,9 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
public boolean zoneWideVolumesAvailableWithoutClusterMotion() {
return true;
}
@Override
public boolean canDisplayDetails() {
return false;
}
}

View File

@ -18,35 +18,6 @@
*/
package org.apache.cloudstack.storage.datastore.lifecycle;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
import com.cloud.utils.StringUtils;
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.storage.datastore.api.StoragePoolStatistics;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
import org.apache.commons.collections.CollectionUtils;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.capacity.CapacityManager;
@ -63,9 +34,41 @@ import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolAutomation;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.template.TemplateManager;
import com.cloud.utils.StringUtils;
import com.cloud.utils.UriUtils;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.storage.datastore.api.StoragePoolStatistics;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManagerImpl;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
import org.apache.commons.collections.CollectionUtils;
import javax.inject.Inject;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
@Inject
@ -94,8 +97,10 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCy
private TemplateManager templateMgr;
@Inject
private AgentManager agentMgr;
private ScaleIOSDCManager sdcManager;
public ScaleIOPrimaryDataStoreLifeCycle() {
sdcManager = new ScaleIOSDCManagerImpl();
}
private org.apache.cloudstack.storage.datastore.api.StoragePool findStoragePool(String url, String username, String password, String storagePoolName) {
@ -112,6 +117,8 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCy
String systemId = client.getSystemId(pool.getProtectionDomainId());
pool.setSystemId(systemId);
List<String> mdmAddresses = client.getMdmAddresses();
pool.setMdmAddresses(mdmAddresses);
return pool;
}
}
@ -239,6 +246,7 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCy
details.put(ScaleIOGatewayClient.GATEWAY_API_PASSWORD, DBEncryptionUtil.encrypt(gatewayPassword));
details.put(ScaleIOGatewayClient.STORAGE_POOL_NAME, storagePoolName);
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, scaleIOPool.getSystemId());
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, org.apache.commons.lang3.StringUtils.join(scaleIOPool.getMdmAddresses(), ","));
parameters.setDetails(details);
return dataStoreHelper.createPrimaryDataStore(parameters);
@ -288,15 +296,43 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCy
@Override
public boolean maintain(DataStore store) {
storagePoolAutomation.maintain(store);
Map<String,String> details = new HashMap<>();
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(store.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemIdDetail.getValue());
StoragePoolDetailVO mdmsDetail = storagePoolDetailsDao.findDetail(store.getId(), ScaleIOGatewayClient.STORAGE_POOL_MDMS);
if (mdmsDetail != null) {
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdmsDetail.getValue());
details.put(ScaleIOSDCManager.ConnectOnDemand.key(), "false");
}
}
storagePoolAutomation.maintain(store, details);
dataStoreHelper.maintain(store);
return true;
}
@Override
public boolean cancelMaintain(DataStore store) {
Map<String,String> details = new HashMap<>();
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(store.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemIdDetail.getValue());
sdcManager = ComponentContext.inject(sdcManager);
if (sdcManager.areSDCConnectionsWithinLimit(store.getId())) {
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(store.getId());
if (storagePoolVO != null) {
details.put(ScaleIOSDCManager.ConnectOnDemand.key(), String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(storagePoolVO.getDataCenterId())));
StoragePoolDetailVO mdmsDetail = storagePoolDetailsDao.findDetail(store.getId(), ScaleIOGatewayClient.STORAGE_POOL_MDMS);
if (mdmsDetail != null) {
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdmsDetail.getValue());
}
}
}
}
dataStoreHelper.cancelMaintain(store);
storagePoolAutomation.cancelMaintain(store);
storagePoolAutomation.cancelMaintain(store, details);
return true;
}
@ -314,7 +350,12 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends BasePrimaryDataStoreLifeCy
public boolean deleteDataStore(DataStore dataStore) {
if (cleanupDatastore(dataStore)) {
ScaleIOGatewayClientConnectionPool.getInstance().removeClient(dataStore);
return dataStoreHelper.deletePrimaryDataStore(dataStore);
boolean isDeleted = dataStoreHelper.deletePrimaryDataStore(dataStore);
if (isDeleted) {
primaryDataStoreDao.removeDetails(dataStore.getId());
}
return isDeleted;
}
return false;
}

View File

@ -18,10 +18,20 @@
package org.apache.cloudstack.storage.datastore.manager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.framework.config.ConfigKey;
import com.cloud.host.Host;
public interface ScaleIOSDCManager {
ConfigKey<Boolean> ConnectOnDemand = new ConfigKey<>("Storage",
Boolean.class,
"powerflex.connect.on.demand",
Boolean.FALSE.toString(),
"When true, connects PowerFlex client on Host when first Volume is mapped to SDC & client connections configured 'storage.pool.connected.clients.limit' are within the limit and disconnects when last Volume is unmapped from SDC; " +
"and When false, connects PowerFlex client on Host when host connects to storage pool & client connections configured 'storage.pool.connected.clients.limit' are within the limit and disconnects when host disconnects from storage pool & no volumes mapped to SDC.",
Boolean.TRUE,
ConfigKey.Scope.Zone);
/**
* Checks SDC connections limit.
* @param storagePoolId the storage pool id
@ -30,18 +40,57 @@ public interface ScaleIOSDCManager {
boolean areSDCConnectionsWithinLimit(Long storagePoolId);
/**
* Prepares/starts the SDC on the host.
* Returns connected SDC Id.
* @param host the host
* @param dataStore the datastore
* @return SDC Id of the host
*/
String getConnectedSdc(Host host, DataStore dataStore);
/**
* Prepares the SDC on the host (adds the MDM IPs to SDC, starts scini service if required).
* @param host the host
* @param dataStore the datastore
* @return SDC Id of the host if SDC is successfully prepared-ed on the host
*/
String prepareSDC(Host host, DataStore dataStore);
/**
* Stops the SDC on the host.
* Unprepares the SDC on the host (removes the MDM IPs from SDC, restarts scini service).
* @param host the host
* @param dataStore the datastore
* @return true if SDC stopped on the host
* @return true if SDC is successfully unprepared-ed on the host
*/
boolean stopSDC(Host host, DataStore dataStore);
boolean unprepareSDC(Host host, DataStore dataStore);
/**
* Checks if the SDC can be unprepared on the host (don't remove MDM IPs from SDC if any volumes mapped to SDC).
* @param host the host
* @param dataStore the datastore
* @return true if SDC can be unprepared on the host
*/
boolean canUnprepareSDC(Host host, DataStore dataStore);
/**
* Returns the SDC Id of the host for the pool.
* @param sdcGuid the SDC GUID
* @param dataStore the datastore
* @return SDC Id of the host for the pool
*/
String getHostSdcId(String sdcGuid, DataStore dataStore);
/**
* Returns the connection status of host SDC of the pool.
* @param sdcId the SDC id
* @param dataStore the datastore
* @return true if Host SDC is connected to the pool
*/
boolean isHostSdcConnected(String sdcId, DataStore dataStore, int waitTimeInSecs);
/**
* Returns the comma-separated list of MDM IPs of the pool.
* @param poolId the pool id
* @return Comma-separated list of MDM IPs of the pool
*/
String getMdms(long poolId);
}

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.storage.datastore.manager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@ -30,8 +31,10 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@ -58,15 +61,6 @@ import com.cloud.utils.exception.CloudRuntimeException;
public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
private Logger logger = LogManager.getLogger(getClass());
static ConfigKey<Boolean> ConnectOnDemand = new ConfigKey<>("Storage",
Boolean.class,
"powerflex.connect.on.demand",
Boolean.FALSE.toString(),
"Connect PowerFlex client on Host when first Volume is mapped to SDC and disconnect when last Volume is unmapped from SDC," +
" otherwise no action (that is connection remains in the same state whichever it is, connected or disconnected).",
Boolean.TRUE,
ConfigKey.Scope.Zone);
@Inject
AgentManager agentManager;
@Inject
@ -91,6 +85,7 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
try {
int connectedClientsLimit = StorageManager.STORAGE_POOL_CONNECTED_CLIENTS_LIMIT.valueIn(storagePoolId);
if (connectedClientsLimit <= 0) {
logger.debug(String.format("SDC connections limit (unlimited) on PowerFlex Storage with pool id: %d", storagePoolId));
return true;
}
@ -121,7 +116,11 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
return getConnectedSdc(host, dataStore);
}
String systemId = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Unable to prepare SDC, failed to get the system id for PowerFlex storage pool: " + dataStore.getName());
}
@ -168,7 +167,8 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
throw new CloudRuntimeException(errorMsg);
}
sdcId = prepareSDCOnHost(host, dataStore, systemId);
String mdms = getMdms(dataStore.getId());
sdcId = prepareSDCOnHost(host, dataStore, systemId, mdms);
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId);
if (StringUtils.isBlank(sdcId)) {
@ -186,7 +186,7 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
}
int waitTimeInSecs = 15; // Wait for 15 secs (usual tests with SDC service start took 10-15 secs)
if (hostSdcConnected(sdcId, dataStore, waitTimeInSecs)) {
if (isHostSdcConnected(sdcId, dataStore, waitTimeInSecs)) {
return sdcId;
}
return null;
@ -202,10 +202,11 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
}
}
private String prepareSDCOnHost(Host host, DataStore dataStore, String systemId) {
private String prepareSDCOnHost(Host host, DataStore dataStore, String systemId, String mdms) {
logger.debug("Preparing SDC on the host {}", host);
Map<String,String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
PrepareStorageClientCommand cmd = new PrepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), dataStore.getUuid(), details);
int timeoutSeconds = 60;
cmd.setWait(timeoutSeconds);
@ -254,13 +255,17 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
}
@Override
public boolean stopSDC(Host host, DataStore dataStore) {
public boolean unprepareSDC(Host host, DataStore dataStore) {
if (Boolean.FALSE.equals(ConnectOnDemand.valueIn(host.getDataCenterId()))) {
logger.debug(String.format("On-demand connect/disconnect config %s disabled in the zone %d, no need to unprepare SDC", ConnectOnDemand.key(), host.getDataCenterId()));
return true;
}
String systemId = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(dataStore.getId(), ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Unable to unprepare SDC, failed to get the system id for PowerFlex storage pool: " + dataStore);
}
@ -284,10 +289,29 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
String sdcId = getConnectedSdc(host, dataStore);
if (StringUtils.isBlank(sdcId)) {
logger.debug("SDC not connected, no need to unprepare it");
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(dataStore.getId(), host.getId());
if (storagePoolHost != null) {
storagePoolHostDao.deleteStoragePoolHostDetails(host.getId(), dataStore.getId());
}
return true;
}
return unprepareSDCOnHost(host, dataStore);
if (!canUnprepareSDC(host, dataStore)) {
logger.debug("Cannot unprepare SDC, there might be other connected pools of same PowerFlex storage cluster," +
"or some volumes mapped to the SDC that belongs to any of the storage pools of the PowerFlex storage cluster");
return false;
}
String mdms = getMdms(dataStore.getId());;
boolean unprepareSDCStatus = unprepareSDCOnHost(host, dataStore, mdms);
if (unprepareSDCStatus) {
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(dataStore.getId(), host.getId());
if (storagePoolHost != null) {
storagePoolHostDao.deleteStoragePoolHostDetails(host.getId(), dataStore.getId());
}
}
return unprepareSDCStatus;
} finally {
if (lock != null) {
lock.unlock();
@ -296,9 +320,11 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
}
}
private boolean unprepareSDCOnHost(Host host, DataStore dataStore) {
private boolean unprepareSDCOnHost(Host host, DataStore dataStore, String mdms) {
logger.debug(String.format("Unpreparing SDC on the host %s (%s)", host.getId(), host.getName()));
UnprepareStorageClientCommand cmd = new UnprepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), dataStore.getUuid());
Map<String,String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
UnprepareStorageClientCommand cmd = new UnprepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), dataStore.getUuid(), details);
int timeoutSeconds = 60;
cmd.setWait(timeoutSeconds);
@ -317,7 +343,39 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
return true;
}
private String getHostSdcId(String sdcGuid, DataStore dataStore ) {
@Override
public boolean canUnprepareSDC(Host host, DataStore dataStore) {
if (host == null || dataStore == null) {
return false;
}
StoragePoolHostVO poolHostVO = storagePoolHostDao.findByPoolHost(dataStore.getId(), host.getId());
if (poolHostVO == null) {
return false;
}
final String sdcId = poolHostVO.getLocalPath();
if (StringUtils.isBlank(sdcId)) {
return false;
}
List<StoragePoolHostVO> poolHostVOsBySdc = storagePoolHostDao.findByLocalPath(sdcId);
if (CollectionUtils.isNotEmpty(poolHostVOsBySdc) && poolHostVOsBySdc.size() > 1) {
logger.debug(String.format("There are other connected pools with the same SDC of the host %s, shouldn't unprepare SDC", host));
return false;
}
try {
final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
return client.listVolumesMappedToSdc(sdcId).isEmpty();
} catch (Exception e) {
logger.warn("Unable to check whether the SDC of the pool: " + dataStore.getId() + " can be unprepared on the host: " + host.getId() + ", due to " + e.getMessage(), e);
return false;
}
}
@Override
public String getHostSdcId(String sdcGuid, DataStore dataStore) {
try {
logger.debug("Try to get host SDC Id for pool: {}, with SDC guid {}", dataStore, sdcGuid);
ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
@ -328,7 +386,8 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
}
}
private String getConnectedSdc(Host host, DataStore dataStore) {
@Override
public String getConnectedSdc(Host host, DataStore dataStore) {
long poolId = dataStore.getId();
long hostId = host.getId();
@ -351,7 +410,8 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
return null;
}
private boolean hostSdcConnected(String sdcId, DataStore dataStore, int waitTimeInSecs) {
@Override
public boolean isHostSdcConnected(String sdcId, DataStore dataStore, int waitTimeInSecs) {
long poolId = dataStore.getId();
logger.debug(String.format("Waiting (for %d secs) for the SDC %s of the pool %s to connect",
waitTimeInSecs, sdcId, dataStore));
@ -369,6 +429,32 @@ public class ScaleIOSDCManagerImpl implements ScaleIOSDCManager, Configurable {
return isHostSdcConnected(sdcId, poolId);
}
@Override
public String getMdms(long poolId) {
String mdms = null;
StoragePoolDetailVO mdmsDetail = storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_MDMS);
if (mdmsDetail != null) {
mdms = mdmsDetail.getValue();
}
if (StringUtils.isNotBlank(mdms)) {
return mdms;
}
try {
final ScaleIOGatewayClient client = getScaleIOClient(poolId);
List<String> mdmAddresses = client.getMdmAddresses();
if (CollectionUtils.isNotEmpty(mdmAddresses)) {
mdms = StringUtils.join(mdmAddresses, ",");
StoragePoolDetailVO storagePoolDetailVO = new StoragePoolDetailVO(poolId, ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms, false);
storagePoolDetailsDao.persist(storagePoolDetailVO);
}
return mdms;
} catch (Exception e) {
logger.error("Failed to get MDMs", e);
throw new CloudRuntimeException("Failed to fetch PowerFlex MDM details");
}
}
private boolean isHostSdcConnected(String sdcId, long poolId) {
try {
final ScaleIOGatewayClient client = getScaleIOClient(poolId);

View File

@ -18,21 +18,20 @@
*/
package org.apache.cloudstack.storage.datastore.provider;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
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.HypervisorHostListener;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManagerImpl;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
@ -49,6 +48,7 @@ import com.cloud.storage.DataStoreRole;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolHostVO;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.exception.CloudRuntimeException;
public class ScaleIOHostListener implements HypervisorHostListener {
@ -61,6 +61,7 @@ public class ScaleIOHostListener implements HypervisorHostListener {
@Inject private StoragePoolHostDao _storagePoolHostDao;
@Inject private PrimaryDataStoreDao _primaryDataStoreDao;
@Inject private StoragePoolDetailsDao _storagePoolDetailsDao;
private ScaleIOSDCManager _sdcManager = new ScaleIOSDCManagerImpl();
@Override
public boolean hostAdded(long hostId) {
@ -75,9 +76,10 @@ public class ScaleIOHostListener implements HypervisorHostListener {
return false;
}
StoragePool storagePool = (StoragePool)_dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
DataStore dataStore = _dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
StoragePool storagePool = (StoragePool) dataStore;
StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId);
String sdcId = getSdcIdOfHost(host, storagePool);
String sdcId = getSdcIdOfHost(host, dataStore);
if (StringUtils.isBlank(sdcId)) {
if (storagePoolHost != null) {
_storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId);
@ -95,15 +97,26 @@ public class ScaleIOHostListener implements HypervisorHostListener {
return true;
}
private String getSdcIdOfHost(HostVO host, StoragePool storagePool) {
private String getSdcIdOfHost(HostVO host, DataStore dataStore) {
StoragePool storagePool = (StoragePool) dataStore;
long hostId = host.getId();
long poolId = storagePool.getId();
String systemId = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
String systemId = null;
StoragePoolDetailVO systemIdDetail = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool " + storagePool.getName());
}
Map<String,String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
_sdcManager = ComponentContext.inject(_sdcManager);
if (_sdcManager.areSDCConnectionsWithinLimit(poolId)) {
details.put(ScaleIOSDCManager.ConnectOnDemand.key(), String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
String mdms = _sdcManager.getMdms(poolId);
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
}
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, storagePool, storagePool.getPath(), details);
ModifyStoragePoolAnswer answer = sendModifyStoragePoolCommand(cmd, storagePool, host);
@ -111,7 +124,7 @@ public class ScaleIOHostListener implements HypervisorHostListener {
if (MapUtils.isEmpty(poolDetails)) {
String msg = String.format("PowerFlex storage SDC details not found on the host: %s, (re)install SDC and restart agent", host);
logger.warn(msg);
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC not found on host: " + host.getUuid(), msg);
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC details not found on host: " + host.getUuid(), msg);
return null;
}
@ -120,7 +133,7 @@ public class ScaleIOHostListener implements HypervisorHostListener {
sdcId = poolDetails.get(ScaleIOGatewayClient.SDC_ID);
} else if (poolDetails.containsKey(ScaleIOGatewayClient.SDC_GUID)) {
String sdcGuid = poolDetails.get(ScaleIOGatewayClient.SDC_GUID);
sdcId = getHostSdcId(sdcGuid, poolId);
sdcId = _sdcManager.getHostSdcId(sdcGuid, dataStore);
}
if (StringUtils.isBlank(sdcId)) {
@ -130,47 +143,85 @@ public class ScaleIOHostListener implements HypervisorHostListener {
return null;
}
return sdcId;
}
private String getHostSdcId(String sdcGuid, long poolId) {
StoragePoolVO storagePool = _primaryDataStoreDao.findById(poolId);
try {
logger.debug(String.format("Try to get host SDC Id for pool: %s, with SDC guid %s", storagePool, sdcGuid));
ScaleIOGatewayClient client = ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePool, _storagePoolDetailsDao);
return client.getSdcIdByGuid(sdcGuid);
} catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) {
logger.error(String.format("Failed to get host SDC Id for pool: %s", storagePool), e);
throw new CloudRuntimeException(String.format(
"Failed to establish connection with PowerFlex Gateway to get host SDC Id for pool: %s",
storagePool));
if (details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand) && !_sdcManager.isHostSdcConnected(sdcId, dataStore, 15)) {
logger.warn("SDC not connected on the host: " + hostId);
String msg = "SDC not connected on the host: " + hostId + ", reconnect the SDC to MDM and restart agent";
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "SDC not connected on host: " + host.getUuid(), msg);
return null;
}
}
return sdcId;
}
private ModifyStoragePoolAnswer sendModifyStoragePoolCommand(ModifyStoragePoolCommand cmd, StoragePool storagePool, HostVO host) {
Answer answer = _agentMgr.easySend(host.getId(), cmd);
if (answer == null) {
throw new CloudRuntimeException("Unable to get an answer to the modify storage pool command (" + storagePool.getName() + ")");
throw new CloudRuntimeException(String.format("Unable to get an answer to the modify storage pool command (add: %s) for PowerFlex storage pool %s, sent to host %s",
cmd.getAdd(), getStoragePoolDetails(storagePool), host));
}
if (!answer.getResult()) {
String msg = "Unable to attach PowerFlex storage pool " + storagePool + " to host " + host.getUuid();
if (cmd.getAdd()) {
String msg = "Unable to attach PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " to the host " + host;
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
throw new CloudRuntimeException("Unable to establish a connection from agent to PowerFlex storage pool " + storagePool + " due to " + answer.getDetails() +
" (" + storagePool.getId() + ")");
throw new CloudRuntimeException("Unable to connect to PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " due to " + answer.getDetails());
} else {
String msg = "Unable to detach PowerFlex storage pool " + getStoragePoolDetails(storagePool) + " from the host " + host;
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, storagePool.getDataCenterId(), storagePool.getPodId(), msg, msg);
}
}
assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; PowerFlex Storage Pool = " + storagePool.getId() + " Host = " + host;
assert (answer instanceof ModifyStoragePoolAnswer) : "ModifyStoragePoolAnswer expected ; PowerFlex Storage Pool = " + storagePool + " Host = " + host;
return (ModifyStoragePoolAnswer) answer;
}
@Override
public boolean hostDisconnected(long hostId, long poolId) {
// SDC ID is getting updated upon host connect, no need to delete the storage_pool_host_ref entry
HostVO host = _hostDao.findById(hostId);
if (host == null) {
logger.error("Failed to disconnect host by HostListener as host was not found with id : " + hostId);
return false;
}
DataStore dataStore = _dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
StoragePool storagePool = (StoragePool) dataStore;
String systemId = null;
StoragePoolDetailVO systemIdDetail = _storagePoolDetailsDao.findDetail(poolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
if (systemIdDetail != null) {
systemId = systemIdDetail.getValue();
}
if (systemId == null) {
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool " + storagePool.getName());
}
Map<String,String> details = new HashMap<>();
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
_sdcManager = ComponentContext.inject(_sdcManager);
if (_sdcManager.canUnprepareSDC(host, dataStore)) {
details.put(ScaleIOSDCManager.ConnectOnDemand.key(), String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
String mdms = _sdcManager.getMdms(poolId);
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
}
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(false, storagePool, storagePool.getPath(), details);
ModifyStoragePoolAnswer answer = sendModifyStoragePoolCommand(cmd, storagePool, host);
if (!answer.getResult()) {
logger.error("Failed to disconnect storage pool: " + storagePool + " and host: " + hostId);
return false;
}
StoragePoolHostVO storagePoolHost = _storagePoolHostDao.findByPoolHost(poolId, hostId);
if (storagePoolHost != null) {
_storagePoolHostDao.deleteStoragePoolHostDetails(hostId, poolId);
}
logger.info("Connection removed between storage pool: " + storagePool + " and host: " + hostId);
return true;
}
@ -188,4 +239,12 @@ public class ScaleIOHostListener implements HypervisorHostListener {
public boolean hostEnabled(long hostId) {
return true;
}
private String getStoragePoolDetails(StoragePool storagePool) {
String poolDetails = "";
if (storagePool != null) {
poolDetails = String.format("%s (id: %d, uuid: %s)", storagePool.getName(), storagePool.getId(), storagePool.getUuid());
}
return poolDetails;
}
}

View File

@ -17,6 +17,9 @@
package org.apache.cloudstack.storage.datastore.util;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
@ -85,6 +88,54 @@ public class ScaleIOUtil {
*/
private static final String QUERY_MDMS_CMD = "drv_cfg --query_mdms";
private static final String ADD_MDMS_CMD = "drv_cfg --add_mdm";
private static final String DRV_CFG_FILE = "/etc/emc/scaleio/drv_cfg.txt";
public static void addMdms(List<String> mdmAddresses) {
if (CollectionUtils.isEmpty(mdmAddresses)) {
return;
}
// Sample Cmd - /opt/emc/scaleio/sdc/bin/drv_cfg --add_mdm --ip x.x.x.x,x.x.x.x --file /etc/emc/scaleio/drv_cfg.txt
String addMdmsCmd = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + ScaleIOUtil.ADD_MDMS_CMD;
addMdmsCmd += " --ip " + String.join(",", mdmAddresses);
addMdmsCmd += " --file " + DRV_CFG_FILE;
String result = Script.runSimpleBashScript(addMdmsCmd);
if (result == null) {
LOGGER.warn("Failed to add mdms");
}
}
public static void removeMdms(List<String> mdmAddresses) {
if (CollectionUtils.isEmpty(mdmAddresses)) {
return;
}
// (i) Remove MDMs from config file (ii) Restart scini
// Sample Cmd - sed -i '/x.x.x.x\,/d' /etc/emc/scaleio/drv_cfg.txt
boolean restartSDC = false;
String removeMdmsCmdFormat = "sed -i '/%s\\,/d' %s";
for (String mdmAddress : mdmAddresses) {
if (mdmAdded(mdmAddress)) {
restartSDC = true;
}
String removeMdmsCmd = String.format(removeMdmsCmdFormat, mdmAddress, DRV_CFG_FILE);
Script.runSimpleBashScript(removeMdmsCmd);
}
if (restartSDC) {
restartSDCService();
}
}
public static boolean mdmAdded(String mdmAddress) {
//query_mdms outputs "MDM-ID <System/MDM-Id> SDC ID <SDC-Id> INSTALLATION ID <Installation-Id> IPs [0]-x.x.x.x [1]-x.x.x.x" for a MDM with ID: <MDM-Id>
String queryMdmsCmd = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + ScaleIOUtil.QUERY_MDMS_CMD;
queryMdmsCmd += "|grep " + mdmAddress;
String result = Script.runSimpleBashScript(queryMdmsCmd);
if (StringUtils.isNotBlank(result) && result.contains(mdmAddress)) {
return true;
}
return false;
}
public static String getSdcHomePath() {
String sdcHomePath = DEFAULT_SDC_HOME_PATH;
String sdcHomePropertyCmdFormat = "sed -n '/%s/p' '%s' 2>/dev/null | sed 's/%s=//g' 2>/dev/null";

View File

@ -22,6 +22,7 @@ package org.apache.cloudstack.storage.datastore.lifecycle;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@ -163,7 +164,7 @@ public class ScaleIOPrimaryDataStoreLifeCycleTest {
@Test
public void testMaintain() {
final DataStore store = mock(DataStore.class);
when(storagePoolAutomation.maintain(any(DataStore.class))).thenReturn(true);
when(storagePoolAutomation.maintain(any(DataStore.class), anyMap())).thenReturn(true);
when(dataStoreHelper.maintain(any(DataStore.class))).thenReturn(true);
final boolean result = scaleIOPrimaryDataStoreLifeCycleTest.maintain(store);
assertThat(result).isTrue();
@ -173,7 +174,7 @@ public class ScaleIOPrimaryDataStoreLifeCycleTest {
public void testCancelMaintain() {
final DataStore store = mock(DataStore.class);
when(dataStoreHelper.cancelMaintain(any(DataStore.class))).thenReturn(true);
when(storagePoolAutomation.cancelMaintain(any(DataStore.class))).thenReturn(true);
when(storagePoolAutomation.cancelMaintain(any(DataStore.class), anyMap())).thenReturn(true);
final boolean result = scaleIOPrimaryDataStoreLifeCycleTest.cancelMaintain(store);
assertThat(result).isTrue();
}

View File

@ -2923,6 +2923,17 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
return storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver)storeDriver).canHostPrepareStoragePoolAccess(host, pool);
}
@Override
public boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool) {
if (pool == null || !pool.isManaged()) {
return true;
}
DataStoreProvider storeProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName());
DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
return storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver)storeDriver).canDisconnectHostFromStoragePool(host, pool);
}
@Override
@DB
public Host getHost(long hostId) {

View File

@ -18,10 +18,16 @@
*/
package com.cloud.storage;
import java.util.Map;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
public interface StoragePoolAutomation {
public boolean maintain(DataStore store);
boolean maintain(DataStore store);
public boolean cancelMaintain(DataStore store);
boolean maintain(DataStore store, Map<String,String> details);
boolean cancelMaintain(DataStore store);
boolean cancelMaintain(DataStore store, Map<String,String> details);
}

View File

@ -31,14 +31,15 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManag
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.logging.log4j.Logger;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.ModifyStoragePoolCommand;
import com.cloud.agent.api.ModifyStoragePoolAnswer;
import com.cloud.agent.api.ModifyStoragePoolCommand;
import com.cloud.alert.AlertManager;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@ -112,6 +113,11 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
@Override
public boolean maintain(DataStore store) {
return maintain(store, null);
}
@Override
public boolean maintain(DataStore store, Map<String,String> details) {
Long userId = CallContext.current().getCallingUserId();
User user = _userDao.findById(userId);
Account account = CallContext.current().getCallingAccount();
@ -161,6 +167,9 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
// remove heartbeat
for (HostVO host : hosts) {
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(false, storagePool);
if (MapUtils.isNotEmpty(details) && storageManager.canDisconnectHostFromStoragePool(host, storagePool)) {
cmd.setDetails(details);
}
final Answer answer = agentMgr.easySend(host.getId(), cmd);
if (answer == null || !answer.getResult()) {
if (logger.isDebugEnabled()) {
@ -299,6 +308,11 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
@Override
public boolean cancelMaintain(DataStore store) {
return cancelMaintain(store, null);
}
@Override
public boolean cancelMaintain(DataStore store, Map<String,String> details) {
// Change the storage state back to up
Long userId = CallContext.current().getCallingUserId();
User user = _userDao.findById(userId);
@ -327,7 +341,10 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
Pair<Map<String, String>, Boolean> nfsMountOpts = storageManager.getStoragePoolNFSMountOpts(pool, null);
// add heartbeat
for (HostVO host : hosts) {
ModifyStoragePoolCommand msPoolCmd = new ModifyStoragePoolCommand(true, pool, nfsMountOpts.first());
ModifyStoragePoolCommand msPoolCmd = new ModifyStoragePoolCommand(true, pool);
if (MapUtils.isNotEmpty(details)) {
msPoolCmd.setDetails(details);
}
final Answer answer = agentMgr.easySend(host.getId(), msPoolCmd);
if (answer == null || !answer.getResult()) {
if (logger.isDebugEnabled()) {