This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 3220eb442a5 PowerFlex/ScaleIO - MDM and host SDC connection 
enhancements (#11047)
3220eb442a5 is described below

commit 3220eb442a5b2a1416b2c44a1112dc029dd35708
Author: Suresh Kumar Anaparti <sureshkumar.anapa...@gmail.com>
AuthorDate: Wed Jul 16 11:55:28 2025 +0530

    PowerFlex/ScaleIO - MDM and host SDC connection enhancements (#11047)
    
    * Cumulative enhancements fix for ScaleIO: MDM add/remove, Host 
prepare/unprepare, validate Storage Pool can be created in Agent.
    
    - Implemented validation to fail Host disconnect from Storage Pool if there 
are Volumes attached and SDC client MDM removal requires scini service to be 
restarted
    - Implemented Storage Pool validation by checking whether MDM addresses 
from configuration file and from memory (using CLI) matches, otherwise file 
ModifyStoragePool command.
    - Introduced configuration key to apply timeout after making MDM changes 
for ScaleIO: powerflex.mdm.change.apply.timeout.ms (default 1000ms)
    - Implemented logic to apply timeout after making MDM changes for ScaleIO 
in prepare and unprepare logic
    - Added detection of MDM removal support via CLI
    - If MDM removal support via CLI supported then use CLI, fall back to edit 
drv_cfg.txt and restart scini instead
    
    Co-authored-by: Suresh Kumar Anaparti <suresh.anapa...@shapeblue.com>
    Co-authored-by: mprokopchuk <mprokopc...@apple.com>
---
 .../cloud/agent/properties/AgentProperties.java    |   2 +-
 .../LibvirtModifyStoragePoolCommandWrapper.java    |  16 +-
 .../kvm/storage/ScaleIOStorageAdaptor.java         | 174 ++++++++--
 .../kvm/storage/ScaleIOStorageAdaptorTest.java     |   4 +
 .../datastore/client/ScaleIOGatewayClient.java     |   3 +
 .../ScaleIOPrimaryDataStoreLifeCycle.java          |  39 +--
 .../datastore/manager/ScaleIOSDCManager.java       |  37 +++
 .../datastore/manager/ScaleIOSDCManagerImpl.java   |  58 +++-
 .../datastore/provider/ScaleIOHostListener.java    |  12 +-
 .../storage/datastore/util/ScaleIOUtil.java        | 351 ++++++++++++++++++---
 .../main/java/com/cloud/utils/script/Script.java   |  20 ++
 11 files changed, 611 insertions(+), 105 deletions(-)

diff --git 
a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java 
b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
index 69537621673..b6e90160c07 100644
--- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
+++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
@@ -833,7 +833,7 @@ public class AgentProperties{
         private T defaultValue;
         private Class<T> typeClass;
 
-        Property(String name, T value) {
+        public Property(String name, T value) {
             init(name, value);
         }
 
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java
index 06883c708d9..990cefda8f3 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java
@@ -31,6 +31,7 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.storage.template.TemplateProp;
+import com.cloud.utils.exception.CloudRuntimeException;
 
 @ResourceWrapper(handles =  ModifyStoragePoolCommand.class)
 public final class LibvirtModifyStoragePoolCommandWrapper extends 
CommandWrapper<ModifyStoragePoolCommand, Answer, LibvirtComputingResource> {
@@ -49,11 +50,16 @@ public final class LibvirtModifyStoragePoolCommandWrapper 
extends CommandWrapper
             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());
-        if (storagepool == null) {
-            return new Answer(command, false, " Failed to create storage 
pool");
+        final KVMStoragePool storagepool;
+        try {
+            storagepool =
+                    
storagePoolMgr.createStoragePool(command.getPool().getUuid(), 
command.getPool().getHost(), command.getPool().getPort(), 
command.getPool().getPath(), command.getPool()
+                            .getUserInfo(), command.getPool().getType(), 
command.getDetails());
+            if (storagepool == null) {
+                return new Answer(command, false, " Failed to create storage 
pool");
+            }
+        } catch (CloudRuntimeException e) {
+            return new Answer(command, false, String.format("Failed to create 
storage pool: %s", e.getLocalizedMessage()));
         }
 
         final Map<String, TemplateProp> tInfo = new HashMap<String, 
TemplateProp>();
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
index 29c152f934f..efa8024a34b 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java
@@ -22,11 +22,13 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import com.cloud.agent.api.PrepareStorageClientCommand;
 import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
 import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
 import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
@@ -38,6 +40,7 @@ import org.apache.cloudstack.utils.qemu.QemuImg;
 import org.apache.cloudstack.utils.qemu.QemuImgException;
 import org.apache.cloudstack.utils.qemu.QemuImgFile;
 import org.apache.cloudstack.utils.qemu.QemuObject;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.io.filefilter.WildcardFileFilter;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
@@ -149,7 +152,7 @@ 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())) {
+        if (MapUtils.isNotEmpty(details) && 
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);
@@ -158,10 +161,49 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
                 }
             }
         }
+
+        validateMdmState(details);
+
         MapStorageUuidToStoragePool.put(uuid, storagePool);
         return storagePool;
     }
 
+    /**
+     * Validate Storage Pool state to ensure it healthy and can operate 
requests.
+     * There is observed situation where ScaleIO configuration file has 
different values than ScaleIO CLI.
+     * Validation compares values from both drv_cfg.txt and drv_cfg CLI and 
throws exception if there is mismatch.
+     *
+     * @param details see {@link PrepareStorageClientCommand#getDetails()}
+     *                and {@link @UnprepareStorageClientCommand#getDetails()}, 
expected to contain
+     *                {@link ScaleIOSDCManager#ValidateMdmsOnConnect#key()}
+     * @throws CloudRuntimeException in case if Storage Pool is not 
operate-able
+     */
+    private void validateMdmState(Map<String, String> details) {
+        String configKey = ScaleIOSDCManager.ValidateMdmsOnConnect.key();
+        if (MapUtils.isEmpty(details) || !details.containsKey(configKey)) {
+            logger.debug(String.format("Skipped PowerFlex MDMs validation as 
property %s not sent by Management Server", configKey));
+            return;
+        }
+
+        String configValue = details.get(configKey);
+
+        // be as much verbose as possible, otherwise it will be difficult to 
troubleshoot operational issue without logs
+        if (StringUtils.isEmpty(configValue)) {
+            logger.debug(String.format("Skipped PowerFlex MDMs validation as 
property %s sent by Management Server is empty", configKey));
+        } else if (Boolean.valueOf(configValue).equals(Boolean.FALSE)) {
+            logger.debug(String.format("Skipped PowerFlex MDMs validation as 
property %s received as %s", configKey, configValue));
+        } else {
+            Collection<String> mdmsFromConfigFile = 
ScaleIOUtil.getMdmsFromConfigFile();
+            Collection<String> mdmsFromCliCmd = 
ScaleIOUtil.getMdmsFromCliCmd();
+            if (!mdmsFromCliCmd.equals(mdmsFromConfigFile)) {
+                String msg = String.format("PowerFlex MDM addresses from CLI 
and Configuration File doesn't match. " +
+                        "CLI values: %s, Configuration File values: %s", 
mdmsFromCliCmd, mdmsFromConfigFile);
+                logger.warn(msg);
+                throw new CloudRuntimeException(msg);
+            }
+        }
+    }
+
     @Override
     public boolean deleteStoragePool(String uuid) {
         ScaleIOStoragePool storagePool = (ScaleIOStoragePool) 
MapStorageUuidToStoragePool.get(uuid);
@@ -173,7 +215,7 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
 
     @Override
     public boolean deleteStoragePool(String uuid, Map<String, String> details) 
{
-        if (details != null && 
details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
+        if (MapUtils.isNotEmpty(details) && 
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);
@@ -259,7 +301,7 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
         volumePath = ScaleIOUtil.getVolumePath(volumePath);
 
         int waitTimeInSec = DEFAULT_DISK_WAIT_TIME_IN_SECS;
-        if (details != null && 
details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
+        if (MapUtils.isNotEmpty(details) && 
details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
             String waitTime = 
details.get(StorageManager.STORAGE_POOL_DISK_WAIT.toString());
             if (StringUtils.isNotEmpty(waitTime)) {
                 waitTimeInSec = Integer.valueOf(waitTime).intValue();
@@ -607,28 +649,37 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
         }
 
         if (!ScaleIOUtil.isSDCServiceActive()) {
+            logger.debug("SDC service is not active on host, starting it");
             if (!ScaleIOUtil.startSDCService()) {
                 return new Ternary<>(false, null, "Couldn't start SDC service 
on host");
             }
         }
 
-        if (details != null && 
details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
+        if (MapUtils.isNotEmpty(details) && 
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])) {
+                if (ScaleIOUtil.isMdmPresent(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])) {
+                ScaleIOUtil.addMdms(mdmAddresses);
+                if (!ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
                     return new Ternary<>(false, null, "Failed to add MDMs");
+                } else {
+                    logger.debug(String.format("MDMs %s added to storage pool 
%s", mdms, uuid));
+                    applyMdmsChangeWaitTime(details);
                 }
             }
         }
 
-        return new Ternary<>( true, getSDCDetails(details), "Prepared client 
successfully");
+        Map<String, String> sdcDetails = getSDCDetails(details);
+        if (MapUtils.isEmpty(sdcDetails)) {
+            return new Ternary<>(false, null, "Couldn't get the SDC details on 
the host");
+        }
+
+        return new Ternary<>(true, sdcDetails, "Prepared client successfully");
     }
 
     public Pair<Boolean, String> unprepareStorageClient(String uuid, 
Map<String, String> details) {
@@ -642,40 +693,127 @@ public class ScaleIOStorageAdaptor implements 
StorageAdaptor {
             return new Pair<>(true, "SDC service not enabled on host, no need 
to unprepare the SDC client");
         }
 
-        if (details != null && 
details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
+        if (MapUtils.isNotEmpty(details) && 
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])) {
+                if (!ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
                     return new Pair<>(true, "MDM not added, no need to 
unprepare the SDC client");
+                } else {
+                    String configKey = 
ScaleIOSDCManager.BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached.key();
+                    String configValue = details.get(configKey);
+
+                    if (StringUtils.isEmpty(configValue)) {
+                        logger.debug(String.format("Configuration key %s not 
provided", configKey));
+                    } else {
+                        logger.debug(String.format("Configuration key %s 
provided as %s", configKey, configValue));
+                    }
+                    Boolean blockUnprepare = Boolean.valueOf(configValue);
+                    if (!ScaleIOUtil.isRemoveMdmCliSupported() // scini 
restart required when --remove_mdm is not supported
+                            && !ScaleIOUtil.getVolumeIds().isEmpty()
+                            && Boolean.TRUE.equals(blockUnprepare)) {
+                        return new Pair<>(false, "Failed to remove MDMs, SDC 
client requires service to be restarted, but there are Volumes attached to the 
Host");
+                    }
                 }
 
-                ScaleIOUtil.removeMdms(Arrays.asList(mdmAddresses));
-                if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
+                // Immediate removal of MDMs after unmapping volume throws 
Error: "Volume is mappedKernel module rejects removing MDM"
+                // Wait before removing MDMs for any volumes to get unmapped.
+                applyMdmsChangeWaitTime(details);
+                ScaleIOUtil.removeMdms(mdmAddresses);
+                if (ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
                     return new Pair<>(false, "Failed to remove MDMs, unable to 
unprepare the SDC client");
+                } else {
+                    logger.debug(String.format("MDMs %s removed from storage 
pool %s", mdms, uuid));
+                    applyMdmsChangeWaitTime(details);
                 }
             }
         }
 
+        /*
+         * TODO:
+         * 1. Verify on-demand is true
+         * 2. If on-demand is true check whether other MDM addresses are still 
present
+         * 3. If there are no MDM addresses, then stop SDC service.
+         */
+
         return new Pair<>(true, "Unprepared SDC client successfully");
     }
 
+    /**
+     * Check whether details map has wait time configured and do "apply wait 
time" pause before returning response
+     * (to have ScaleIO changes applied).
+     *
+     * @param details see {@link PrepareStorageClientCommand#getDetails()}
+     *                and {@link @UnprepareStorageClientCommand#getDetails()}, 
expected to contain
+     *                {@link ScaleIOSDCManager#MdmsChangeApplyWaitTime#key()}
+     */
+    private void applyMdmsChangeWaitTime(Map<String, String> details) {
+        String configKey = ScaleIOSDCManager.MdmsChangeApplyWaitTime.key();
+        if (MapUtils.isEmpty(details) || !details.containsKey(configKey)) {
+            logger.debug(String.format("Apply wait time property %s not sent 
by Management Server, skip", configKey));
+            return;
+        }
+
+        String configValue = details.get(configKey);
+        if (StringUtils.isEmpty(configValue)) {
+            logger.debug(String.format("Apply wait time value not defined in 
property %s, skip", configKey));
+            return;
+        }
+        long timeoutMs;
+        try {
+            timeoutMs = Long.parseLong(configValue);
+        } catch (NumberFormatException e) {
+            logger.warn(String.format("Invalid apply wait time value defined 
in property %s, skip", configKey), e);
+            return;
+        }
+        if (timeoutMs < 1) {
+            logger.warn(String.format("Apply wait time value is too small (%s 
ms), skipping", timeoutMs));
+            return;
+        }
+        try {
+            logger.debug(String.format("Waiting for %d ms as defined in 
property %s", timeoutMs, configKey));
+            Thread.sleep(timeoutMs);
+        } catch (InterruptedException e) {
+            logger.warn(String.format("Waiting for %d ms interrupted", 
timeoutMs), e);
+        }
+    }
+
     private Map<String, String> getSDCDetails(Map<String, String> details) {
         Map<String, String> sdcDetails = new HashMap<String, String>();
-        if (details == null || 
!details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID))  {
+        if (MapUtils.isEmpty(details) || 
!details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID))  {
             return sdcDetails;
         }
 
         String storageSystemId = 
details.get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
-        String sdcId = ScaleIOUtil.getSdcId(storageSystemId);
-        if (sdcId != null) {
-            sdcDetails.put(ScaleIOGatewayClient.SDC_ID, sdcId);
-        } else {
+        if (StringUtils.isEmpty(storageSystemId)) {
+            return sdcDetails;
+        }
+
+        int numberOfTries = 5;
+        int timeBetweenTries = 1000; // Try more frequently (every sec) and 
return early when SDC Id or Guid found
+        int attempt = 1;
+        do {
+            logger.debug("Get SDC details, attempt #{}", attempt);
+            String sdcId = ScaleIOUtil.getSdcId(storageSystemId);
+            if (sdcId != null) {
+                sdcDetails.put(ScaleIOGatewayClient.SDC_ID, sdcId);
+                return sdcDetails;
+            }
+
             String sdcGuId = ScaleIOUtil.getSdcGuid();
             if (sdcGuId != null) {
                 sdcDetails.put(ScaleIOGatewayClient.SDC_GUID, sdcGuId);
+                return sdcDetails;
             }
-        }
+
+            try {
+                Thread.sleep(timeBetweenTries);
+            } catch (Exception ignore) {
+            }
+            numberOfTries--;
+            attempt++;
+        } while (numberOfTries > 0);
+
         return sdcDetails;
     }
 
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
index 421fee09634..eddaa8f6499 100644
--- 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java
@@ -194,8 +194,12 @@ public class ScaleIOStorageAdaptorTest {
         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.executeCommand(Mockito.eq("sed -i '/1.1.1.1\\,/d' 
/etc/emc/scaleio/drv_cfg.txt"))).thenReturn(new Pair<>(null, null));
         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");
+        
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg"))).thenReturn(new
 Pair<>(null, null));
+        
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg 
--query_vols"))).thenReturn(new Pair<>("", null));
+
 
         Pair<Boolean, String> result = 
scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
 
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java
index 2dc5acffcbc..77a8677e6b1 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java
@@ -38,6 +38,9 @@ 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";
+    /**
+     * Storage Pool Metadata Management (MDM) IP address(es).
+     */
     String STORAGE_POOL_MDMS = "powerflex.storagepool.mdms";
     String SDC_ID = "powerflex.sdc.id";
     String SDC_GUID = "powerflex.sdc.guid";
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 461992be102..5917e7895ca 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
@@ -31,7 +31,6 @@ import java.util.stream.Collectors;
 import javax.inject.Inject;
 
 import com.cloud.host.HostVO;
-import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
 import org.apache.cloudstack.api.ApiConstants;
 import com.cloud.utils.StringUtils;
 import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
@@ -105,8 +104,6 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends 
BasePrimaryDataStoreLifeCy
     @Inject
     private AgentManager agentMgr;
     private ScaleIOSDCManager sdcManager;
-    @Inject
-    private StoragePoolAndAccessGroupMapDao storagePoolAndAccessGroupMapDao;
 
     public ScaleIOPrimaryDataStoreLifeCycle() {
         sdcManager = new ScaleIOSDCManagerImpl();
@@ -306,14 +303,19 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends 
BasePrimaryDataStoreLifeCy
 
     @Override
     public boolean maintain(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());
-            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");
+        Map<String, String> details = new HashMap<>();
+        StoragePoolVO storagePoolVO = 
primaryDataStoreDao.findById(store.getId());
+        if (storagePoolVO != null) {
+            sdcManager = ComponentContext.inject(sdcManager);
+            sdcManager.populateSdcSettings(details, 
storagePoolVO.getDataCenterId());
+            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");
+                }
             }
         }
 
@@ -324,14 +326,15 @@ public class ScaleIOPrimaryDataStoreLifeCycle extends 
BasePrimaryDataStoreLifeCy
 
     @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());
+        Map<String, String> details = new HashMap<>();
+        StoragePoolVO storagePoolVO = 
primaryDataStoreDao.findById(store.getId());
+        if (storagePoolVO != null) {
             sdcManager = ComponentContext.inject(sdcManager);
-            if (sdcManager.areSDCConnectionsWithinLimit(store.getId())) {
-                StoragePoolVO storagePoolVO = 
primaryDataStoreDao.findById(store.getId());
-                if (storagePoolVO != null) {
+            sdcManager.populateSdcSettings(details, 
storagePoolVO.getDataCenterId());
+            StoragePoolDetailVO systemIdDetail = 
storagePoolDetailsDao.findDetail(store.getId(), 
ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
+            if (systemIdDetail != null) {
+                details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, 
systemIdDetail.getValue());
+                if (sdcManager.areSDCConnectionsWithinLimit(store.getId())) {
                     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) {
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java
index b02732051b5..8529d5dac73 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManager.java
@@ -22,6 +22,8 @@ import org.apache.cloudstack.framework.config.ConfigKey;
 
 import com.cloud.host.Host;
 
+import java.util.Map;
+
 public interface ScaleIOSDCManager {
     ConfigKey<Boolean> ConnectOnDemand = new ConfigKey<>("Storage",
             Boolean.class,
@@ -32,6 +34,34 @@ public interface ScaleIOSDCManager {
             Boolean.TRUE,
             ConfigKey.Scope.Zone);
 
+    /**
+     * Timeout for Host to wait after MDM changes made on Host until changes 
will be applied.
+     * Needed to avoid cases when Storage Pool is not connected yet, but Agent 
already starts to use Storage Pool.
+     */
+    ConfigKey<Integer> MdmsChangeApplyWaitTime = new ConfigKey<>("Storage",
+            Integer.class,
+            "powerflex.mdm.change.apply.wait",
+            "3000",
+            "Time (in ms) to wait after MDM addition, and before & after MDM 
removal changes made on the Host, default value: 3000 ms",
+            Boolean.TRUE,
+            ConfigKey.Scope.Zone);
+
+    ConfigKey<Boolean> ValidateMdmsOnConnect = new ConfigKey<>("Storage",
+            Boolean.class,
+            "powerflex.mdm.validate.on.connect",
+            Boolean.FALSE.toString(),
+            "Flag to validate PowerFlex MDMs on the host, present in 
Configuration File and in CLI during storage pool registration in agent, 
default value: false",
+            Boolean.TRUE,
+            ConfigKey.Scope.Zone);
+
+    ConfigKey<Boolean> BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached = 
new ConfigKey<>("Storage",
+            Boolean.class,
+            "powerflex.block.sdc.unprepare",
+            Boolean.FALSE.toString(),
+            "Block storage client un-preparation if SDC service restart needed 
after PowerFlex MDM removal (i.e. no support for --remove_mdm in drv_cfg cmd) 
when there are Volumes mapped to the Host",
+            Boolean.TRUE,
+            ConfigKey.Scope.Zone);
+
     /**
      * Checks SDC connections limit.
      * @param storagePoolId the storage pool id
@@ -93,4 +123,11 @@ public interface ScaleIOSDCManager {
      * @return Comma-separated list of MDM IPs of the pool
      */
     String getMdms(long poolId);
+
+    /**
+     * Adds the SDC settings to the details map.
+     * @param details the details map to add the settings
+     * @param dataCenterId the datacenter id for the settings
+     */
+    void populateSdcSettings(Map<String, String> details, long dataCenterId);
 }
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java
index 79f82a1e0df..c13ad61a6cd 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/manager/ScaleIOSDCManagerImpl.java
@@ -183,12 +183,13 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
                     storagePoolHost.setLocalPath(sdcId);
                     storagePoolHostDao.update(storagePoolHost.getId(), 
storagePoolHost);
                 }
-            }
 
-            int waitTimeInSecs = 15; // Wait for 15 secs (usual tests with SDC 
service start took 10-15 secs)
-            if (isHostSdcConnected(sdcId, dataStore, waitTimeInSecs)) {
-                return sdcId;
+                int waitTimeInSecs = 15; // Wait for 15 secs (usual tests with 
SDC service start took 10-15 secs)
+                if (isHostSdcConnected(sdcId, dataStore, waitTimeInSecs)) {
+                    return sdcId;
+                }
             }
+
             return null;
         } finally {
             if (storageSystemIdLock != null) {
@@ -204,9 +205,10 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
 
     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<>();
+        Map<String, String> details = new HashMap<>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
         details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
+        populateSdcSettings(details, host.getDataCenterId());
         PrepareStorageClientCommand cmd = new 
PrepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), 
dataStore.getUuid(), details);
         int timeoutSeconds = 60;
         cmd.setWait(timeoutSeconds);
@@ -247,7 +249,7 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
         }
 
         if (StringUtils.isBlank(sdcId)) {
-            logger.warn("Couldn't retrieve PowerFlex storage SDC details from 
the host: {}, try (re)install SDC and restart agent", host);
+            logger.warn("Couldn't retrieve PowerFlex storage SDC details from 
the host: {}, add MDMs if On-demand connect disabled or try (re)install SDC & 
restart agent", host);
             return null;
         }
 
@@ -297,8 +299,7 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
             }
 
             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");
+                logger.debug("Cannot unprepare SDC, there are other pools of 
the same PowerFlex storage cluster with some volumes mapped to the host SDC");
                 return false;
             }
 
@@ -324,6 +325,7 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
         logger.debug(String.format("Unpreparing SDC on the host %s (%s)", 
host.getId(), host.getName()));
         Map<String,String> details = new HashMap<>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, mdms);
+        populateSdcSettings(details, host.getDataCenterId());
         UnprepareStorageClientCommand cmd = new 
UnprepareStorageClientCommand(((PrimaryDataStore) dataStore).getPoolType(), 
dataStore.getUuid(), details);
         int timeoutSeconds = 60;
         cmd.setWait(timeoutSeconds);
@@ -359,18 +361,28 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
             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));
+        try {
+            if (logger.isDebugEnabled()) {
+                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", host));
+                }
+            }
+
+            return !areVolumesMappedToPoolSdc(dataStore.getId(), sdcId);
+        } 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;
         }
+    }
 
+    private boolean areVolumesMappedToPoolSdc(long storagePoolId, String 
sdcId) throws Exception {
         try {
-            final ScaleIOGatewayClient client = 
getScaleIOClient(dataStore.getId());
-            return client.listVolumesMappedToSdc(sdcId).isEmpty();
+            final ScaleIOGatewayClient client = 
getScaleIOClient(storagePoolId);
+            return 
CollectionUtils.isNotEmpty(client.listVolumesMappedToSdc(sdcId));
         } 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;
+            logger.warn("Unable to check the volumes mapped to SDC of the 
pool: " + storagePoolId + ", due to " + e.getMessage());
+            throw e;
         }
     }
 
@@ -467,9 +479,23 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
 
     private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) 
throws Exception {
         StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
+        if (storagePool == null) {
+            throw new CloudRuntimeException("Unable to find the storage pool 
with id " + storagePoolId);
+        }
         return 
ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePool, 
storagePoolDetailsDao);
     }
 
+    @Override
+    public void populateSdcSettings(Map<String, String> details, long 
dataCenterId) {
+        if (details == null) {
+            details = new HashMap<>();
+        }
+
+        details.put(ScaleIOSDCManager.MdmsChangeApplyWaitTime.key(), 
String.valueOf(ScaleIOSDCManager.MdmsChangeApplyWaitTime.valueIn(dataCenterId)));
+        details.put(ScaleIOSDCManager.ValidateMdmsOnConnect.key(), 
String.valueOf(ScaleIOSDCManager.ValidateMdmsOnConnect.valueIn(dataCenterId)));
+        
details.put(ScaleIOSDCManager.BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached.key(),
 
String.valueOf(ScaleIOSDCManager.BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached.valueIn(dataCenterId)));
+    }
+
     @Override
     public String getConfigComponentName() {
         return ScaleIOSDCManager.class.getSimpleName();
@@ -477,6 +503,6 @@ public class ScaleIOSDCManagerImpl implements 
ScaleIOSDCManager, Configurable {
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey[]{ConnectOnDemand};
+        return new ConfigKey[]{ConnectOnDemand, MdmsChangeApplyWaitTime, 
ValidateMdmsOnConnect, BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached};
     }
 }
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java
index 0db329204e8..f169b581b2c 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java
@@ -27,7 +27,6 @@ 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.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
@@ -59,7 +58,6 @@ public class ScaleIOHostListener implements 
HypervisorHostListener {
     @Inject private DataStoreManager _dataStoreMgr;
     @Inject private HostDao _hostDao;
     @Inject private StoragePoolHostDao _storagePoolHostDao;
-    @Inject private PrimaryDataStoreDao _primaryDataStoreDao;
     @Inject private StoragePoolDetailsDao _storagePoolDetailsDao;
     private ScaleIOSDCManager _sdcManager = new ScaleIOSDCManagerImpl();
 
@@ -109,9 +107,10 @@ public class ScaleIOHostListener implements 
HypervisorHostListener {
         if (systemId == null) {
             throw new CloudRuntimeException("Failed to get the system id for 
PowerFlex storage pool " + storagePool.getName());
         }
-        Map<String,String> details = new HashMap<>();
+        Map<String, String> details = new HashMap<>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
         _sdcManager = ComponentContext.inject(_sdcManager);
+        _sdcManager.populateSdcSettings(details, host.getDataCenterId());
         if (_sdcManager.areSDCConnectionsWithinLimit(poolId)) {
             details.put(ScaleIOSDCManager.ConnectOnDemand.key(), 
String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
             String mdms = _sdcManager.getMdms(poolId);
@@ -120,7 +119,7 @@ public class ScaleIOHostListener implements 
HypervisorHostListener {
 
         ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, 
storagePool, storagePool.getPath(), details);
         ModifyStoragePoolAnswer answer  = sendModifyStoragePoolCommand(cmd, 
storagePool, host);
-        Map<String,String> poolDetails = answer.getPoolInfo().getDetails();
+        Map<String, String> poolDetails = answer.getPoolInfo().getDetails();
         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);
@@ -137,7 +136,7 @@ public class ScaleIOHostListener implements 
HypervisorHostListener {
         }
 
         if (StringUtils.isBlank(sdcId)) {
-            String msg = String.format("Couldn't retrieve PowerFlex storage 
SDC details from the host: %s, (re)install SDC and restart agent", host);
+            String msg = String.format("Couldn't retrieve PowerFlex storage 
SDC details from the host: %s, add MDMs if On-demand connect disabled or try 
(re)install SDC & restart agent", host);
             logger.warn(msg);
             _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, 
host.getDataCenterId(), host.getPodId(), "SDC details not found on host: " + 
host.getUuid(), msg);
             return null;
@@ -201,9 +200,10 @@ public class ScaleIOHostListener implements 
HypervisorHostListener {
         if (systemId == null) {
             throw new CloudRuntimeException("Failed to get the system id for 
PowerFlex storage pool " + storagePool.getName());
         }
-        Map<String,String> details = new HashMap<>();
+        Map<String, String> details = new HashMap<>();
         details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
         _sdcManager = ComponentContext.inject(_sdcManager);
+        _sdcManager.populateSdcSettings(details, host.getDataCenterId());
         if (_sdcManager.canUnprepareSDC(host, dataStore)) {
             details.put(ScaleIOSDCManager.ConnectOnDemand.key(), 
String.valueOf(ScaleIOSDCManager.ConnectOnDemand.valueIn(host.getDataCenterId())));
             String mdms = _sdcManager.getMdms(poolId);
diff --git 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java
 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java
index a8c02a90bf9..70ea6f429e7 100644
--- 
a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java
+++ 
b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/util/ScaleIOUtil.java
@@ -17,16 +17,27 @@
 
 package org.apache.cloudstack.storage.datastore.util;
 
-import java.util.List;
-
-import org.apache.commons.collections.CollectionUtils;
+import com.cloud.agent.properties.AgentProperties;
+import com.cloud.agent.properties.AgentPropertiesFileHandler;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
 
+import com.cloud.utils.Pair;
 import com.cloud.utils.UuidUtils;
 import com.cloud.utils.script.Script;
 import org.apache.commons.lang3.StringUtils;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class ScaleIOUtil {
     protected static Logger LOGGER = LogManager.getLogger(ScaleIOUtil.class);
 
@@ -39,7 +50,7 @@ public class ScaleIOUtil {
     public static final String VMSNAPSHOT_PREFIX = "vmsnap";
 
     public static final int IDENTIFIER_LENGTH = 16;
-    public static final Long MINIMUM_ALLOWED_IOPS_LIMIT = Long.valueOf(10);
+    public static final Long MINIMUM_ALLOWED_IOPS_LIMIT = 10L;
 
     public static final String DISK_PATH = "/dev/disk/by-id";
     public static final String DISK_NAME_PREFIX = "emc-vol-";
@@ -63,9 +74,17 @@ public class ScaleIOUtil {
     private static final String SDC_SERVICE_ENABLE_CMD = "systemctl enable 
scini";
 
     public static final String CONNECTED_SDC_COUNT_STAT = "ConnectedSDCCount";
+
+    /**
+     * Time (in seconds) to wait after SDC service 'scini' 
start/restart/stop.<br>
+     * Data type: Integer.<br>
+     * Default value: <code>3</code>
+     */
+    public static final AgentProperties.Property<Integer> 
SDC_SERVICE_ACTION_WAIT = new 
AgentProperties.Property<>("powerflex.sdc.service.wait", 3);
+
     /**
      * Cmd for querying volumes in SDC
-     * Sample output for cmd: drv_cfg --query_vols:
+     * Sample output for cmd {@code drv_cfg --query_vols}:
      * Retrieved 2 volume(s)
      * VOL-ID 6c33633100000009 MDM-ID 218ce1797566a00f
      * VOL-ID 6c3362a30000000a MDM-ID 218ce1797566a00f
@@ -74,14 +93,14 @@ public class ScaleIOUtil {
 
     /**
      * Cmd for querying guid in SDC
-     * Sample output for cmd: drv_cfg --query_guid:
+     * Sample output for cmd {@code drv_cfg --query_guid}:
      * B0E3BFB8-C20B-43BF-93C8-13339E85AA50
      */
     private static final String QUERY_GUID_CMD = "drv_cfg --query_guid";
 
     /**
      * Cmd for querying MDMs in SDC
-     * Sample output for cmd: drv_cfg --query_mdms:
+     * Sample output for cmd {@code drv_cfg --query_mdms}:
      * Retrieved 2 mdm(s)
      * MDM-ID 3ef46cbf2aaf5d0f SDC ID 6b18479c00000003 INSTALLATION ID 
68ab55462cbb3ae4 IPs [0]-x.x.x.x [1]-x.x.x.x
      * MDM-ID 2e706b2740ec200f SDC ID 301b852c00000003 INSTALLATION ID 
33f8662e7a5c1e6c IPs [0]-x.x.x.x [1]-x.x.x.x
@@ -89,61 +108,251 @@ 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 REMOVE_MDM_PARAMETER = "--remove_mdm";
+
+    /**
+     * Calls the kernel module to remove MDM.
+     */
+    private static final String REMOVE_MDMS_CMD = "drv_cfg " + 
REMOVE_MDM_PARAMETER;
+
+    /**
+     * Command to get back "Usage" response. As of now it is just drv_cfg 
without parameters.
+     */
+    private static final String USAGE_CMD = "drv_cfg";
+
     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 Command - sed -i '/x.x.x.x\,/d' /etc/emc/scaleio/drv_cfg.txt
+     */
+    private static final String REMOVE_MDM_CMD_TEMPLATE = "sed -i '/%s\\,/d' 
%s";
+
+    /**
+     * Patterns to parse {@link ScaleIOUtil#DRV_CFG_FILE} and {@code 
--query_mdms} command output.
+     * The format is:
+     * MDM-ID {HEX_ID} SDC ID {HEX_ID} INSTALLATION ID {HEX_ID} IPs 
[{DEC_INC_NUMBER}]-{IP_ADDRESS} [{DEC_INC_NUMBER}]-{IP_ADDRESS} ...
+     */
+    private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\r?\\n");
+    /**
+     * Pattern to find "IPs" substring in {@link ScaleIOUtil#QUERY_MDMS_CMD} 
command output.
+     * The output format is:
+     * MDM-ID {HEX_ID} SDC ID {HEX_ID} INSTALLATION ID {HEX_ID} IPs 
[{DEC_INC_NUMBER}]-{IP_ADDRESS} [{DEC_INC_NUMBER}]-{IP_ADDRESS} ...
+     */
+    private static final Pattern USAGE_IPS_LINE_PATTERN = 
Pattern.compile("IPs\\s*(.*)$");
+    /**
+     * Pattern to find individual IP address in {@link 
ScaleIOUtil#QUERY_MDMS_CMD} command output.
+     */
+    private static final Pattern USAGE_IP_TOKEN_PATTERN = 
Pattern.compile("(\\s*\\[\\d\\]\\-)([^\\s$]+)");
+
+    /**
+     * Pattern to find Volume ID in {@link  ScaleIOUtil#QUERY_VOLUMES_CMD}
+     */
+    private static final Pattern VOLUME_ID_TOKEN_PATTERN = 
Pattern.compile("VOL-ID\\s*([^\\s$]+)");
+    /**
+     * Pattern to find MDM entries line in {@link ScaleIOUtil#DRV_CFG_FILE}.
+     */
+    private static final Pattern DRV_CFG_MDM_LINE_PATTERN = 
Pattern.compile("^mdm\\s*([0-9A-F:\\.,]+)$", Pattern.CASE_INSENSITIVE);
+    /**
+     * Pattern to split comma separated string of IP addresses (space aware).
+     */
+    private static final Pattern DRV_CFG_MDM_IPS_PATTERN = 
Pattern.compile("\\s*,\\s*");
+
+    public static boolean addMdms(String... mdmAddresses) {
+        if (mdmAddresses.length < 1) {
+            return false;
         }
         // 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");
+        String command = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.ADD_MDMS_CMD;
+        command += " --ip " + String.join(",", mdmAddresses);
+        command += " --file " + DRV_CFG_FILE;
+        return runCmd(command);
+    }
+
+    /**
+     * Remove MDM via ScaleIO via CLI.
+     *
+     * @param mdmAddress MDM address to remove
+     * @return true if IP address successfully removed
+     */
+    private static boolean removeMdm(String mdmAddress) {
+        // Sample Cmd - /opt/emc/scaleio/sdc/bin/drv_cfg --remove_mdm --ip 
x.x.x.x,x.x.x.x --file /etc/emc/scaleio/drv_cfg.txt
+        String command = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.REMOVE_MDMS_CMD;
+        command += " --ip " + String.join(",", mdmAddress);
+        command += " --file " + DRV_CFG_FILE;
+        return runCmd(command);
+    }
+
+    /**
+     * Run command, log command result and return {@link Boolean#TRUE} if 
command succeeded.
+     * FIXME: may need to do refactoring and replace static method calls with 
dynamic.
+     */
+    private static boolean runCmd(String command) {
+        Pair<String, String> result = Script.executeCommand(command);
+        String stdOut = result.first();
+        String stdErr = result.second();
+        boolean succeeded = StringUtils.isEmpty(stdErr);
+        if (succeeded) {
+            LOGGER.debug(String.format("Successfully executed command '%s': 
%s", command, stdOut));
+        } else {
+            LOGGER.warn(String.format("Failed to execute command '%s': %s", 
command, stdErr));
         }
+        return succeeded;
     }
 
-    public static void removeMdms(List<String> mdmAddresses) {
-        if (CollectionUtils.isEmpty(mdmAddresses)) {
-            return;
+    /**
+     * Remove MDMs either via ScaleIO CLI (if supported) or by updating 
configuration file.
+     *
+     * @param mdmAddresses MDM addresses
+     * @return returns {@link Boolean#TRUE} if changes were applied to the 
configuration
+     */
+    public static boolean removeMdms(String... mdmAddresses) {
+        if (mdmAddresses.length < 1) {
+            return false;
         }
-        // (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 changesApplied = false;
+        boolean removeMdmCliSupported = isRemoveMdmCliSupported();
         boolean restartSDC = false;
-        String removeMdmsCmdFormat = "sed -i '/%s\\,/d' %s";
         for (String mdmAddress : mdmAddresses) {
-            if (mdmAdded(mdmAddress)) {
-                restartSDC = true;
+            // continue to next address if current MDM is not present in 
configuration
+            if (!isMdmPresent(mdmAddress)) {
+                continue;
+            }
+            // remove MDM via CLI if it is supported
+            if (removeMdmCliSupported) {
+                if (removeMdm(mdmAddress)) {
+                    changesApplied = true;
+                }
+            } else {
+                String command = String.format(REMOVE_MDM_CMD_TEMPLATE, 
mdmAddress, DRV_CFG_FILE);
+                String stdErr = Script.executeCommand(command).second();
+                if(StringUtils.isEmpty(stdErr)) {
+                    // restart SDC needed only if configuration file modified 
manually (not by CLI)
+                    restartSDC = true;
+                    changesApplied = true;
+                } else {
+                    LOGGER.error(String.format("Failed to remove MDM %s from 
%s: %s", mdmAddress, DRV_CFG_FILE, stdErr));
+                }
             }
-            String removeMdmsCmd = String.format(removeMdmsCmdFormat, 
mdmAddress, DRV_CFG_FILE);
-            Script.runSimpleBashScript(removeMdmsCmd);
         }
         if (restartSDC) {
             restartSDCService();
         }
+        return changesApplied;
     }
 
-    public static boolean mdmAdded(String mdmAddress) {
+    /**
+     * Returns MDM entries from {@link ScaleIOUtil#DRV_CFG_FILE}.
+     */
+    public static Collection<String> getMdmsFromConfigFile() {
+        List<String> configFileLines;
+        try {
+            configFileLines = Files.readAllLines(Path.of(DRV_CFG_FILE));
+        } catch (IOException e) {
+            LOGGER.error(String.format("Failed to read MDMs from %s", 
DRV_CFG_FILE), e);
+            return List.of();
+        }
+        Set<String> mdms = new LinkedHashSet<>();
+        for (String line : configFileLines) {
+            Matcher mdmLineMatcher = DRV_CFG_MDM_LINE_PATTERN.matcher(line);
+            if(mdmLineMatcher.find() && mdmLineMatcher.groupCount() > 0) {
+                String mdmLine = mdmLineMatcher.group(1);
+                String[] mdmValues = DRV_CFG_MDM_IPS_PATTERN.split(mdmLine);
+                mdms.addAll(Arrays.asList(mdmValues));
+            }
+        }
+        return mdms;
+    }
+    /**
+     * Returns Volume Ids from {@link ScaleIOUtil#DRV_CFG_FILE}.
+     */
+    public static Collection<String> getVolumeIds() {
+        String command = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.QUERY_VOLUMES_CMD;
+        Pair<String, String> result = Script.executeCommand(command);
+        String stdOut = result.first();
+
+        Set<String> volumeIds = new LinkedHashSet<>();
+        String[] stdOutLines = NEW_LINE_PATTERN.split(stdOut);
+        for (String line : stdOutLines) {
+            Matcher volumeIdMatcher = VOLUME_ID_TOKEN_PATTERN.matcher(line);
+            if (volumeIdMatcher.find() && volumeIdMatcher.groupCount() > 0) {
+                volumeIds.add(volumeIdMatcher.group(1));
+            }
+        }
+        return volumeIds;
+    }
+
+    /**
+     * Returns MDM entries from CLI Cmd using {@code --query_mdms}.
+     */
+    public static Collection<String> getMdmsFromCliCmd() {
+        String command = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.QUERY_MDMS_CMD;
+        Pair<String, String> result = Script.executeCommand(command);
+        String stdOut = result.first();
+
+        Set<String> mdms = new LinkedHashSet<>();
+        String[] stdOutLines = NEW_LINE_PATTERN.split(stdOut);
+        for (String line : stdOutLines) {
+            Matcher ipsLineMatcher = USAGE_IPS_LINE_PATTERN.matcher(line);
+            if (ipsLineMatcher.find() && ipsLineMatcher.groupCount() > 0) {
+                String ipToken = ipsLineMatcher.group(1);
+                Matcher ipMatcher = USAGE_IP_TOKEN_PATTERN.matcher(ipToken);
+                while (ipMatcher.find()) {
+                    if (ipMatcher.groupCount() > 1) {
+                        mdms.add(ipMatcher.group(2));
+                    }
+                }
+            }
+        }
+        return mdms;
+    }
+
+    /**
+     * Returns {@link Boolean#TRUE} if ScaleIO CLI tool (drv_cfg) supports 
MDMs removal.
+     */
+    public static boolean isRemoveMdmCliSupported() {
+        /*
+         * New version of drv_cfg supports remove mdm API.
+         * Instead of defining supported version and checking it, the logic is 
to check drv_cfg "Usage" output
+         * and see whether remove_mdm command supported.
+         * The "Usage" returned if tool executed without parameters or with 
invalid parameters.
+         */
+        String command = SDC_HOME_PATH + "/bin/" + USAGE_CMD;
+
+        Pair<String, String> result = Script.executeCommand(command);
+        String stdOut = result.first();
+        String stdErr = result.second();
+
+        /*
+         * Check whether stderr or stdout contains mdm removal "--remove_mdm" 
parameter.
+         *
+         * Current version returns "Usage" in stderr, check stdout as well in 
case this will be changed in the future,
+         * as returned "Usage" is not an error.
+         */
+        return (stdOut + stdErr).toLowerCase().contains(REMOVE_MDM_PARAMETER);
+    }
+
+    /**
+     * Returns true if provided MDM address is present in configuration.
+     */
+    public static boolean isMdmPresent(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;
+
+        return StringUtils.isNotBlank(result) && result.contains(mdmAddress);
     }
 
     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";
         String sdcHomeCmd = String.format(sdcHomePropertyCmdFormat, 
SDC_HOME_PARAMETER, AGENT_PROPERTIES_FILE, SDC_HOME_PARAMETER);
-
         String result = Script.runSimpleBashScript(sdcHomeCmd);
+        String sdcHomePath;
         if (result == null) {
-            LOGGER.warn("Failed to get sdc home path from agent.properties, 
fallback to default path");
+            sdcHomePath = DEFAULT_SDC_HOME_PATH;
+            LOGGER.warn(String.format("Failed to get sdc home path from 
agent.properties, fallback to default path %s", sdcHomePath));
         } else {
             sdcHomePath = result;
         }
@@ -151,7 +360,7 @@ public class ScaleIOUtil {
         return sdcHomePath;
     }
 
-    public static final void rescanForNewVolumes() {
+    public static void rescanForNewVolumes() {
         // Detecting new volumes
         String rescanCmd = ScaleIOUtil.SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.RESCAN_CMD;
 
@@ -161,7 +370,7 @@ public class ScaleIOUtil {
         }
     }
 
-    public static final String getSystemIdForVolume(String volumeId) {
+    public static String getSystemIdForVolume(String volumeId) {
         //query_vols outputs "VOL-ID <VolumeID> MDM-ID <SystemID>" for a 
volume with ID: <VolumeID>
         String queryDiskCmd = SDC_HOME_PATH + "/bin/" + 
ScaleIOUtil.QUERY_VOLUMES_CMD;
         queryDiskCmd += "|grep " + volumeId + "|awk '{print $4}'";
@@ -225,7 +434,7 @@ public class ScaleIOUtil {
         return result;
     }
 
-    public static final String getVolumePath(String volumePathWithName) {
+    public static String getVolumePath(String volumePathWithName) {
         if (StringUtils.isEmpty(volumePathWithName)) {
             return volumePathWithName;
         }
@@ -237,7 +446,7 @@ public class ScaleIOUtil {
         return volumePathWithName;
     }
 
-    public static final String updatedPathWithVolumeName(String volumePath, 
String volumeName) {
+    public static String updatedPathWithVolumeName(String volumePath, String 
volumeName) {
         if (StringUtils.isAnyEmpty(volumePath, volumeName)) {
             return volumePath;
         }
@@ -267,16 +476,76 @@ public class ScaleIOUtil {
 
     public static boolean startSDCService() {
         int exitValue = 
Script.runSimpleBashScriptForExitValue(SDC_SERVICE_START_CMD);
-        return exitValue == 0;
+        if (exitValue != 0) {
+            return false;
+        }
+        waitForSdcServiceActionToComplete();
+        return true;
     }
 
     public static boolean stopSDCService() {
         int exitValue = 
Script.runSimpleBashScriptForExitValue(SDC_SERVICE_STOP_CMD);
-        return exitValue == 0;
+        if (exitValue != 0) {
+            return false;
+        }
+        waitForSdcServiceActionToComplete();
+        return true;
     }
 
     public static boolean restartSDCService() {
         int exitValue = 
Script.runSimpleBashScriptForExitValue(SDC_SERVICE_RESTART_CMD);
-        return exitValue == 0;
+        if (exitValue != 0) {
+            return false;
+        }
+        waitForSdcServiceActionToComplete();
+        return true;
+    }
+
+    private static void waitForSdcServiceActionToComplete() {
+        // Wait for the SDC service to settle after start/restart/stop and 
reaches a stable state
+        int waitTimeInSecs = 
AgentPropertiesFileHandler.getPropertyValue(SDC_SERVICE_ACTION_WAIT);
+        if (waitTimeInSecs < 0) {
+            waitTimeInSecs = SDC_SERVICE_ACTION_WAIT.getDefaultValue();
+        }
+        try {
+            LOGGER.debug(String.format("Waiting for %d secs after SDC service 
action, to reach a stable state", waitTimeInSecs));
+            Thread.sleep(waitTimeInSecs * 1000L);
+        } catch (InterruptedException ignore) {
+        }
+    }
+
+    /**
+     * Represents {@link ScaleIOUtil#DRV_CFG_FILE} MDM entry (SDC and 
Installation Ids are skipped).
+     */
+    public static class MdmEntry {
+        private String mdmId;
+        private Collection<String> ips;
+
+        /**
+         * MDM entry constructor.
+         *
+         * @param mdmId MDM Id
+         * @param ips   IP Addresses
+         */
+        public MdmEntry(String mdmId, Collection<String> ips) {
+            this.mdmId = mdmId;
+            this.ips = ips;
+        }
+
+        public String getMdmId() {
+            return mdmId;
+        }
+
+        public void setMdmId(String mdmId) {
+            this.mdmId = mdmId;
+        }
+
+        public Collection<String> getIps() {
+            return ips;
+        }
+
+        public void setIps(Collection<String> ips) {
+            this.ips = ips;
+        }
     }
 }
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java 
b/utils/src/main/java/com/cloud/utils/script/Script.java
index a1104b37c27..7ba2bb1b4d9 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -671,6 +671,26 @@ public class Script implements Callable<String> {
         return runScript(getScriptForCommandRun(command));
     }
 
+    /**
+     * Execute command and return standard output and standard error.
+     *
+     * @param command OS command to be executed
+     * @return {@link Pair} with standard output as first and standard error 
as second field
+     */
+    public static Pair<String, String> executeCommand(String command) {
+        // wrap command into bash
+        Script script = new Script("/bin/bash");
+        script.add("-c");
+        script.add(command);
+
+        // parse all lines from the output
+        OutputInterpreter.AllLinesParser parser = new 
OutputInterpreter.AllLinesParser();
+        String stdErr = script.execute(parser);
+        String stdOut = parser.getLines();
+        LOGGER.debug(String.format("Command [%s] result - stdout: [%s], 
stderr: [%s]", command, stdOut, stdErr));
+        return new Pair<>(stdOut, stdErr);
+    }
+
     public static int executeCommandForExitValue(long timeout, String... 
command) {
         return runScriptForExitValue(getScriptForCommandRun(timeout, command));
     }

Reply via email to