Skip to content

Commit 4fd782c

Browse files
sureshanapartimprokopchuk
authored andcommitted
PowerFlex/ScaleIO - MDM and host SDC connection enhancements (apache#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 <[email protected]> Co-authored-by: mprokopchuk <[email protected]>
1 parent 8464bd3 commit 4fd782c

File tree

11 files changed

+611
-105
lines changed

11 files changed

+611
-105
lines changed

agent/src/main/java/com/cloud/agent/properties/AgentProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ public static class Property <T>{
845845
private T defaultValue;
846846
private Class<T> typeClass;
847847

848-
Property(String name, T value) {
848+
public Property(String name, T value) {
849849
init(name, value);
850850
}
851851

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.cloud.resource.CommandWrapper;
3232
import com.cloud.resource.ResourceWrapper;
3333
import com.cloud.storage.template.TemplateProp;
34+
import com.cloud.utils.exception.CloudRuntimeException;
3435

3536
@ResourceWrapper(handles = ModifyStoragePoolCommand.class)
3637
public final class LibvirtModifyStoragePoolCommandWrapper extends CommandWrapper<ModifyStoragePoolCommand, Answer, LibvirtComputingResource> {
@@ -49,11 +50,16 @@ public Answer execute(final ModifyStoragePoolCommand command, final LibvirtCompu
4950
return answer;
5051
}
5152

52-
final KVMStoragePool storagepool =
53-
storagePoolMgr.createStoragePool(command.getPool().getUuid(), command.getPool().getHost(), command.getPool().getPort(), command.getPool().getPath(), command.getPool()
54-
.getUserInfo(), command.getPool().getType(), command.getDetails());
55-
if (storagepool == null) {
56-
return new Answer(command, false, " Failed to create storage pool");
53+
final KVMStoragePool storagepool;
54+
try {
55+
storagepool =
56+
storagePoolMgr.createStoragePool(command.getPool().getUuid(), command.getPool().getHost(), command.getPool().getPort(), command.getPool().getPath(), command.getPool()
57+
.getUserInfo(), command.getPool().getType(), command.getDetails());
58+
if (storagepool == null) {
59+
return new Answer(command, false, " Failed to create storage pool");
60+
}
61+
} catch (CloudRuntimeException e) {
62+
return new Answer(command, false, String.format("Failed to create storage pool: %s", e.getLocalizedMessage()));
5763
}
5864

5965
final Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java

Lines changed: 156 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import java.io.IOException;
2323
import java.util.ArrayList;
2424
import java.util.Arrays;
25+
import java.util.Collection;
2526
import java.util.HashMap;
2627
import java.util.List;
2728
import java.util.Map;
2829
import java.util.UUID;
2930

31+
import com.cloud.agent.api.PrepareStorageClientCommand;
3032
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
3133
import org.apache.cloudstack.storage.datastore.manager.ScaleIOSDCManager;
3234
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
@@ -38,6 +40,7 @@
3840
import org.apache.cloudstack.utils.qemu.QemuImgException;
3941
import org.apache.cloudstack.utils.qemu.QemuImgFile;
4042
import org.apache.cloudstack.utils.qemu.QemuObject;
43+
import org.apache.commons.collections.MapUtils;
4144
import org.apache.commons.io.filefilter.WildcardFileFilter;
4245
import org.apache.logging.log4j.Logger;
4346
import org.apache.logging.log4j.LogManager;
@@ -149,7 +152,7 @@ public KVMPhysicalDisk getPhysicalDisk(String volumePath, KVMStoragePool pool) {
149152
@Override
150153
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, Storage.StoragePoolType type, Map<String, String> details, boolean isPrimaryStorage) {
151154
ScaleIOStoragePool storagePool = new ScaleIOStoragePool(uuid, host, port, path, type, details, this);
152-
if (details != null && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
155+
if (MapUtils.isNotEmpty(details) && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
153156
String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
154157
if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand)) {
155158
Ternary<Boolean, Map<String, String>, String> prepareStorageClientStatus = prepareStorageClient(uuid, details);
@@ -158,10 +161,49 @@ public KVMStoragePool createStoragePool(String uuid, String host, int port, Stri
158161
}
159162
}
160163
}
164+
165+
validateMdmState(details);
166+
161167
MapStorageUuidToStoragePool.put(uuid, storagePool);
162168
return storagePool;
163169
}
164170

171+
/**
172+
* Validate Storage Pool state to ensure it healthy and can operate requests.
173+
* There is observed situation where ScaleIO configuration file has different values than ScaleIO CLI.
174+
* Validation compares values from both drv_cfg.txt and drv_cfg CLI and throws exception if there is mismatch.
175+
*
176+
* @param details see {@link PrepareStorageClientCommand#getDetails()}
177+
* and {@link @UnprepareStorageClientCommand#getDetails()}, expected to contain
178+
* {@link ScaleIOSDCManager#ValidateMdmsOnConnect#key()}
179+
* @throws CloudRuntimeException in case if Storage Pool is not operate-able
180+
*/
181+
private void validateMdmState(Map<String, String> details) {
182+
String configKey = ScaleIOSDCManager.ValidateMdmsOnConnect.key();
183+
if (MapUtils.isEmpty(details) || !details.containsKey(configKey)) {
184+
logger.debug(String.format("Skipped PowerFlex MDMs validation as property %s not sent by Management Server", configKey));
185+
return;
186+
}
187+
188+
String configValue = details.get(configKey);
189+
190+
// be as much verbose as possible, otherwise it will be difficult to troubleshoot operational issue without logs
191+
if (StringUtils.isEmpty(configValue)) {
192+
logger.debug(String.format("Skipped PowerFlex MDMs validation as property %s sent by Management Server is empty", configKey));
193+
} else if (Boolean.valueOf(configValue).equals(Boolean.FALSE)) {
194+
logger.debug(String.format("Skipped PowerFlex MDMs validation as property %s received as %s", configKey, configValue));
195+
} else {
196+
Collection<String> mdmsFromConfigFile = ScaleIOUtil.getMdmsFromConfigFile();
197+
Collection<String> mdmsFromCliCmd = ScaleIOUtil.getMdmsFromCliCmd();
198+
if (!mdmsFromCliCmd.equals(mdmsFromConfigFile)) {
199+
String msg = String.format("PowerFlex MDM addresses from CLI and Configuration File doesn't match. " +
200+
"CLI values: %s, Configuration File values: %s", mdmsFromCliCmd, mdmsFromConfigFile);
201+
logger.warn(msg);
202+
throw new CloudRuntimeException(msg);
203+
}
204+
}
205+
}
206+
165207
@Override
166208
public boolean deleteStoragePool(String uuid) {
167209
ScaleIOStoragePool storagePool = (ScaleIOStoragePool) MapStorageUuidToStoragePool.get(uuid);
@@ -173,7 +215,7 @@ public boolean deleteStoragePool(String uuid) {
173215

174216
@Override
175217
public boolean deleteStoragePool(String uuid, Map<String, String> details) {
176-
if (details != null && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
218+
if (MapUtils.isNotEmpty(details) && details.containsKey(ScaleIOSDCManager.ConnectOnDemand.key())) {
177219
String connectOnDemand = details.get(ScaleIOSDCManager.ConnectOnDemand.key());
178220
if (connectOnDemand != null && !Boolean.parseBoolean(connectOnDemand)) {
179221
Pair<Boolean, String> unprepareStorageClientStatus = unprepareStorageClient(uuid, details);
@@ -259,7 +301,7 @@ public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<S
259301
volumePath = ScaleIOUtil.getVolumePath(volumePath);
260302

261303
int waitTimeInSec = DEFAULT_DISK_WAIT_TIME_IN_SECS;
262-
if (details != null && details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
304+
if (MapUtils.isNotEmpty(details) && details.containsKey(StorageManager.STORAGE_POOL_DISK_WAIT.toString())) {
263305
String waitTime = details.get(StorageManager.STORAGE_POOL_DISK_WAIT.toString());
264306
if (StringUtils.isNotEmpty(waitTime)) {
265307
waitTimeInSec = Integer.valueOf(waitTime).intValue();
@@ -607,28 +649,37 @@ public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(String
607649
}
608650

609651
if (!ScaleIOUtil.isSDCServiceActive()) {
652+
logger.debug("SDC service is not active on host, starting it");
610653
if (!ScaleIOUtil.startSDCService()) {
611654
return new Ternary<>(false, null, "Couldn't start SDC service on host");
612655
}
613656
}
614657

615-
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
658+
if (MapUtils.isNotEmpty(details) && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
616659
// Assuming SDC service is started, add mdms
617660
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
618661
String[] mdmAddresses = mdms.split(",");
619662
if (mdmAddresses.length > 0) {
620-
if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
663+
if (ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
621664
return new Ternary<>(true, getSDCDetails(details), "MDM added, no need to prepare the SDC client");
622665
}
623666

624-
ScaleIOUtil.addMdms(Arrays.asList(mdmAddresses));
625-
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
667+
ScaleIOUtil.addMdms(mdmAddresses);
668+
if (!ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
626669
return new Ternary<>(false, null, "Failed to add MDMs");
670+
} else {
671+
logger.debug(String.format("MDMs %s added to storage pool %s", mdms, uuid));
672+
applyMdmsChangeWaitTime(details);
627673
}
628674
}
629675
}
630676

631-
return new Ternary<>( true, getSDCDetails(details), "Prepared client successfully");
677+
Map<String, String> sdcDetails = getSDCDetails(details);
678+
if (MapUtils.isEmpty(sdcDetails)) {
679+
return new Ternary<>(false, null, "Couldn't get the SDC details on the host");
680+
}
681+
682+
return new Ternary<>(true, sdcDetails, "Prepared client successfully");
632683
}
633684

634685
public Pair<Boolean, String> unprepareStorageClient(String uuid, Map<String, String> details) {
@@ -642,40 +693,127 @@ public Pair<Boolean, String> unprepareStorageClient(String uuid, Map<String, Str
642693
return new Pair<>(true, "SDC service not enabled on host, no need to unprepare the SDC client");
643694
}
644695

645-
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
696+
if (MapUtils.isNotEmpty(details) && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
646697
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
647698
String[] mdmAddresses = mdms.split(",");
648699
if (mdmAddresses.length > 0) {
649-
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
700+
if (!ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
650701
return new Pair<>(true, "MDM not added, no need to unprepare the SDC client");
702+
} else {
703+
String configKey = ScaleIOSDCManager.BlockSdcUnprepareIfRestartNeededAndVolumesAreAttached.key();
704+
String configValue = details.get(configKey);
705+
706+
if (StringUtils.isEmpty(configValue)) {
707+
logger.debug(String.format("Configuration key %s not provided", configKey));
708+
} else {
709+
logger.debug(String.format("Configuration key %s provided as %s", configKey, configValue));
710+
}
711+
Boolean blockUnprepare = Boolean.valueOf(configValue);
712+
if (!ScaleIOUtil.isRemoveMdmCliSupported() // scini restart required when --remove_mdm is not supported
713+
&& !ScaleIOUtil.getVolumeIds().isEmpty()
714+
&& Boolean.TRUE.equals(blockUnprepare)) {
715+
return new Pair<>(false, "Failed to remove MDMs, SDC client requires service to be restarted, but there are Volumes attached to the Host");
716+
}
651717
}
652718

653-
ScaleIOUtil.removeMdms(Arrays.asList(mdmAddresses));
654-
if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
719+
// Immediate removal of MDMs after unmapping volume throws Error: "Volume is mappedKernel module rejects removing MDM"
720+
// Wait before removing MDMs for any volumes to get unmapped.
721+
applyMdmsChangeWaitTime(details);
722+
ScaleIOUtil.removeMdms(mdmAddresses);
723+
if (ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
655724
return new Pair<>(false, "Failed to remove MDMs, unable to unprepare the SDC client");
725+
} else {
726+
logger.debug(String.format("MDMs %s removed from storage pool %s", mdms, uuid));
727+
applyMdmsChangeWaitTime(details);
656728
}
657729
}
658730
}
659731

732+
/*
733+
* TODO:
734+
* 1. Verify on-demand is true
735+
* 2. If on-demand is true check whether other MDM addresses are still present
736+
* 3. If there are no MDM addresses, then stop SDC service.
737+
*/
738+
660739
return new Pair<>(true, "Unprepared SDC client successfully");
661740
}
662741

742+
/**
743+
* Check whether details map has wait time configured and do "apply wait time" pause before returning response
744+
* (to have ScaleIO changes applied).
745+
*
746+
* @param details see {@link PrepareStorageClientCommand#getDetails()}
747+
* and {@link @UnprepareStorageClientCommand#getDetails()}, expected to contain
748+
* {@link ScaleIOSDCManager#MdmsChangeApplyWaitTime#key()}
749+
*/
750+
private void applyMdmsChangeWaitTime(Map<String, String> details) {
751+
String configKey = ScaleIOSDCManager.MdmsChangeApplyWaitTime.key();
752+
if (MapUtils.isEmpty(details) || !details.containsKey(configKey)) {
753+
logger.debug(String.format("Apply wait time property %s not sent by Management Server, skip", configKey));
754+
return;
755+
}
756+
757+
String configValue = details.get(configKey);
758+
if (StringUtils.isEmpty(configValue)) {
759+
logger.debug(String.format("Apply wait time value not defined in property %s, skip", configKey));
760+
return;
761+
}
762+
long timeoutMs;
763+
try {
764+
timeoutMs = Long.parseLong(configValue);
765+
} catch (NumberFormatException e) {
766+
logger.warn(String.format("Invalid apply wait time value defined in property %s, skip", configKey), e);
767+
return;
768+
}
769+
if (timeoutMs < 1) {
770+
logger.warn(String.format("Apply wait time value is too small (%s ms), skipping", timeoutMs));
771+
return;
772+
}
773+
try {
774+
logger.debug(String.format("Waiting for %d ms as defined in property %s", timeoutMs, configKey));
775+
Thread.sleep(timeoutMs);
776+
} catch (InterruptedException e) {
777+
logger.warn(String.format("Waiting for %d ms interrupted", timeoutMs), e);
778+
}
779+
}
780+
663781
private Map<String, String> getSDCDetails(Map<String, String> details) {
664782
Map<String, String> sdcDetails = new HashMap<String, String>();
665-
if (details == null || !details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)) {
783+
if (MapUtils.isEmpty(details) || !details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)) {
666784
return sdcDetails;
667785
}
668786

669787
String storageSystemId = details.get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
670-
String sdcId = ScaleIOUtil.getSdcId(storageSystemId);
671-
if (sdcId != null) {
672-
sdcDetails.put(ScaleIOGatewayClient.SDC_ID, sdcId);
673-
} else {
788+
if (StringUtils.isEmpty(storageSystemId)) {
789+
return sdcDetails;
790+
}
791+
792+
int numberOfTries = 5;
793+
int timeBetweenTries = 1000; // Try more frequently (every sec) and return early when SDC Id or Guid found
794+
int attempt = 1;
795+
do {
796+
logger.debug("Get SDC details, attempt #{}", attempt);
797+
String sdcId = ScaleIOUtil.getSdcId(storageSystemId);
798+
if (sdcId != null) {
799+
sdcDetails.put(ScaleIOGatewayClient.SDC_ID, sdcId);
800+
return sdcDetails;
801+
}
802+
674803
String sdcGuId = ScaleIOUtil.getSdcGuid();
675804
if (sdcGuId != null) {
676805
sdcDetails.put(ScaleIOGatewayClient.SDC_GUID, sdcGuId);
806+
return sdcDetails;
677807
}
678-
}
808+
809+
try {
810+
Thread.sleep(timeBetweenTries);
811+
} catch (Exception ignore) {
812+
}
813+
numberOfTries--;
814+
attempt++;
815+
} while (numberOfTries > 0);
816+
679817
return sdcDetails;
680818
}
681819

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,12 @@ public void testUnprepareStorageClient_RemoveMDMFailed() {
194194
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
195195
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
196196
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
197+
when(Script.executeCommand(Mockito.eq("sed -i '/1.1.1.1\\,/d' /etc/emc/scaleio/drv_cfg.txt"))).thenReturn(new Pair<>(null, null));
197198
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
198199
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");
200+
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg"))).thenReturn(new Pair<>(null, null));
201+
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_vols"))).thenReturn(new Pair<>("", null));
202+
199203

200204
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
201205

plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public interface ScaleIOGatewayClient {
3838
String GATEWAY_API_PASSWORD = "powerflex.gw.password";
3939
String STORAGE_POOL_NAME = "powerflex.storagepool.name";
4040
String STORAGE_POOL_SYSTEM_ID = "powerflex.storagepool.system.id";
41+
/**
42+
* Storage Pool Metadata Management (MDM) IP address(es).
43+
*/
4144
String STORAGE_POOL_MDMS = "powerflex.storagepool.mdms";
4245
String SDC_ID = "powerflex.sdc.id";
4346
String SDC_GUID = "powerflex.sdc.guid";

0 commit comments

Comments
 (0)