2222import java .io .IOException ;
2323import java .util .ArrayList ;
2424import java .util .Arrays ;
25+ import java .util .Collection ;
2526import java .util .HashMap ;
2627import java .util .List ;
2728import java .util .Map ;
2829import java .util .UUID ;
2930
31+ import com .cloud .agent .api .PrepareStorageClientCommand ;
3032import org .apache .cloudstack .storage .datastore .client .ScaleIOGatewayClient ;
3133import org .apache .cloudstack .storage .datastore .manager .ScaleIOSDCManager ;
3234import org .apache .cloudstack .storage .datastore .util .ScaleIOUtil ;
3840import org .apache .cloudstack .utils .qemu .QemuImgException ;
3941import org .apache .cloudstack .utils .qemu .QemuImgFile ;
4042import org .apache .cloudstack .utils .qemu .QemuObject ;
43+ import org .apache .commons .collections .MapUtils ;
4144import org .apache .commons .io .filefilter .WildcardFileFilter ;
4245import org .apache .logging .log4j .Logger ;
4346import 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
0 commit comments