/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.validators.smis.vmax;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.FCTN_VOLUME_URI_TO_STR;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Lists.newArrayList;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.cim.CIMProperty;
import javax.wbem.CloseableIterator;
import javax.wbem.WBEMException;
import javax.wbem.client.WBEMClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.QueryResultList;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.volumecontroller.impl.smis.CIMPropertyFactory;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
/**
* Sub-class for {@link AbstractMultipleVmaxMaskValidator} in order to validate that a given
* {@link Initiator} is not shared by another masking view out of management.
*/
class MultipleVmaxMaskForInitiatorsValidator extends AbstractMultipleVmaxMaskValidator<Initiator> {
private static final String FAILING_MSG = "Failing validation: %s.";
private static final Logger log = LoggerFactory.getLogger(MultipleVmaxMaskForInitiatorsValidator.class);
private static final String INSTANCE_ID_PREFIX = "W-+-";
/**
* Default constructor.
*
* @param storage StorageSystem
* @param exportMask ExportMask
* @param initiators List of dataObjects to check.
* May be null, in which case all user added initiators from {@code exportMask} is used.
*/
MultipleVmaxMaskForInitiatorsValidator(StorageSystem storage, ExportMask exportMask,
Collection<Initiator> initiators) {
super(storage, exportMask, initiators);
}
/**
* Initiators are not to be shared across multiple masks, unless ALL the following
* criteria are met:
*
* <ol>
* <li>Associated mask is under ViPR management</li>
* <li>Both masks must be contained within the same ExportGroup</li>
* <li>Both masks must reference the same set of Initiators</li>
* </ol>
*
* @param mask The export mask in the ViPR request
* @param assocMask An export mask found to be associated with {@code mask}
* @return true if validation passes, false otherwise.
* @throws WBEMException
* @throws IllegalArgumentException if {@code mask} and {@code assocMask} are equal
*/
@Override
protected boolean validate(Initiator initiator, CIMInstance mask, CIMInstance assocMask) throws WBEMException {
if (mask.equals(assocMask)) {
throw new IllegalArgumentException("Mask instance parameters must not be equal");
}
String name = (String) mask.getPropertyValue(SmisConstants.CP_DEVICE_ID);
String assocName = (String) assocMask.getPropertyValue(SmisConstants.CP_DEVICE_ID);
log.info("MV {} is sharing an initiator with MV {}", name, assocName);
ExportMask exportMask = ExportMaskUtils.getExportMaskByName(getDbClient(), storage.getId(), assocName);
// Associated mask is under ViPR management
if (exportMask == null || hasExistingVolumes(getVolumesFromLunMaskingInstance(assocMask),exportMask)) {
logFailure("associated mask is not under ViPR management");
return false;
}
return true;
}
private boolean hasExistingVolumes(Set<String> volumesFromLunMaskingInstance, ExportMask exportMask) {
// TODO Auto-generated method stub
for(String volumeWWN : volumesFromLunMaskingInstance) {
if(!exportMask.hasUserAddedVolume(volumeWWN)) {
log.info("Mask {} has existing volumes", exportMask.getMaskName());
return true;
}
}
return false;
}
/**
* Get Volumes from the Masking view.
* @param client
* @param instance
* @return
* @throws WBEMException
*/
public Set<String> getVolumesFromLunMaskingInstance(CIMInstance instance) throws WBEMException {
Set<String> wwnList = new HashSet<String>();
CloseableIterator<CIMInstance> iterator = null;
try {
log.info(String.format("getVolumesFromLunMaskingInstance(%s)", instance.getObjectPath().toString()));
iterator = getHelper().getAssociatorInstances(storage, instance.getObjectPath(), null, SmisConstants.CIM_STORAGE_VOLUME, null, null,
SmisConstants.PS_EMCWWN);
while (iterator.hasNext()) {
CIMInstance cimInstance = iterator.next();
String wwn = CIMPropertyFactory.getPropertyValue(cimInstance, SmisConstants.CP_WWN_NAME);
wwnList.add(wwn);
}
log.info(String.format("getVolumesFromLunMaskingInstance(%s)", instance.getObjectPath().toString()));
} catch (WBEMException we) {
log.error("Caught an error will attempting to get volume list from " + "masking instance", we);
throw we;
} finally {
if (null != iterator) {
iterator.close();
}
}
return wwnList;
}
@Override
protected String getFriendlyId(Initiator initiator) {
return String.format("%s/%s", initiator.getId(), initiator.getInitiatorPort());
}
@Override
protected CIMObjectPath getCIMObjectPath(Initiator obj) throws Exception {
try {
String port = Initiator.normalizePort(obj.getInitiatorPort());
String instanceID = String.format("%s%s", INSTANCE_ID_PREFIX, port);
CIMObjectPath[] initiatorPaths = getCimPath().getInitiatorPaths(storage, new String[]{ instanceID });
if (initiatorPaths != null && initiatorPaths.length > 0) {
return initiatorPaths[0];
}
} catch (Exception e) {
log.error("Exception occurred getting Initiator CIM object path: {}", obj.getId());
throw e;
}
return null;
}
@Override
protected Collection<Initiator> getDataObjects() {
if (dataObjects != null) {
checkRequestedInitiatorsBelongToMask();
return dataObjects;
}
StringMap userAddedInitiators = exportMask.getUserAddedInitiators();
List<URI> initURIs = newArrayList();
if (userAddedInitiators == null || userAddedInitiators.isEmpty()) {
return Collections.emptyList();
}
for (String initURI : userAddedInitiators.values()) {
initURIs.add(URI.create(initURI));
}
dataObjects = getDbClient().queryObject(Initiator.class, initURIs);
return dataObjects;
}
private void checkRequestedInitiatorsBelongToMask() {
StringMap userAddedInitiators = exportMask.getUserAddedInitiators();
if (userAddedInitiators == null) {
return;
}
Collection<String> maskInitiatorURIs = userAddedInitiators.values();
Collection<String> reqInitiatorURIs = transform(dataObjects, FCTN_VOLUME_URI_TO_STR);
for (String reqInitiatorURI : reqInitiatorURIs) {
if (!maskInitiatorURIs.contains(reqInitiatorURI)) {
String msg = String.format("Requested initiator %s does not belong to mask %s", reqInitiatorURI, exportMask);
log.warn(msg);
// COP-27899: This check does not work well when initiator update operations fail to complete, or when
// external operations (outside of ViPR) remove initiators from a mask, and we're just trying to update
// our internal data structures.
//
// throw new IllegalArgumentException(msg);
}
}
}
private void logFailure(String reason) {
log.warn(String.format(FAILING_MSG, reason));
}
}