/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.validators.smis.vmax;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.wbem.CloseableIterator;
import javax.wbem.WBEMException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.FCTN_VOLUME_URI_TO_STR;
import static com.google.common.collect.Collections2.transform;
import static java.lang.String.format;
/**
* Sub-class for {@link AbstractMultipleVmaxMaskValidator} in order to validate that a given
* {@link BlockObject} is not shared by another masking view out of management.
*/
class MultipleVmaxMaskForVolumesValidator<T extends BlockObject> extends AbstractMultipleVmaxMaskValidator<T> {
private static final Logger log = LoggerFactory.getLogger(MultipleVmaxMaskForVolumesValidator.class);
/**
* Default constructor.
*
* @param storage StorageSystem
* @param exportMask ExportMask
* @param blockObjects List of blockobjects to check.
* May be null, in which case all user added volumes from {@code exportMask} is used.
*/
MultipleVmaxMaskForVolumesValidator(StorageSystem storage, ExportMask exportMask, Collection<T> blockObjects) {
super(storage, exportMask, blockObjects);
}
/**
* Validation should fail if:
* 1. The associated mask has no ViPR export group
* 2. The storage group is shared between them
*
* @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 passed, false otherwise.
*/
@Override
protected boolean validate(BlockObject blockObject, CIMInstance mask, CIMInstance assocMask) throws WBEMException {
boolean isSharingStorageGroups;
boolean assocMaskHasExportGroup = false;
String name = (String) mask.getPropertyValue(SmisConstants.CP_DEVICE_ID);
String assocName = (String) assocMask.getPropertyValue(SmisConstants.CP_DEVICE_ID);
// Does ViPR know about this other mask?
ExportMask em = ExportMaskUtils.getExportMaskByName(getDbClient(), storage.getId(), assocName);
if (em != null) {
log.info("MV {} is tracked by {}", assocName, em.getId());
// Check if it's part of an ExportGroup
List<ExportGroup> exportGroups = CustomQueryUtility.queryActiveResourcesByConstraint(getDbClient(),
ExportGroup.class, ContainmentConstraint.Factory.getExportMaskExportGroupConstraint(em.getId()));
assocMaskHasExportGroup = !exportGroups.isEmpty();
log.info("MV {} has {} export group(s)", assocName, exportGroups.size());
} else {
log.info("MV {} is not tracked by any ExportMask", assocName);
}
// True, if associated export mask has no export group and it's sharing a storage group.
isSharingStorageGroups = isSharingStorageGroups(mask, assocMask);
log.info(format("MV %s is sharing a storage group with %s? %s", assocName, name, isSharingStorageGroups));
/*
* FIXME COP-24841 - Stop making dangerous assumptions about impacted masking views.
*
* Here, we may allow validation to pass based simply on the associated ExportMask being part of
* an ExportGroup. We assume that the orchestration layer has also generated a step to be run
* in parallel for removing the volume from this other mask. Instead, we should acquire
* an explicit list of impacted masking views and consider it when determining a pass/fail result.
*/
return assocMaskHasExportGroup || !isSharingStorageGroups;
}
private boolean isSharingStorageGroups(CIMInstance mask, CIMInstance assocMask) throws WBEMException {
Set<String> maskSGs = getStorageGroupIds(mask);
Set<String> assocMaskSGs = getStorageGroupIds(assocMask);
// Are the 2 sets disjointed? i.e. have no common elements
return !Collections.disjoint(maskSGs, assocMaskSGs);
}
private Set<String> getStorageGroupIds(CIMInstance mask) throws WBEMException {
CloseableIterator<CIMObjectPath> assocSG = null;
Set<String> instanceIds = Sets.newHashSet();
try {
assocSG = getHelper().getAssociatorNames(storage, mask.getObjectPath(), null,
SmisConstants.SE_DEVICE_MASKING_GROUP, null, null);
while (assocSG.hasNext()) {
CIMObjectPath next = assocSG.next();
instanceIds.add(next.getKeyValue(SmisConstants.CP_INSTANCE_ID).toString());
}
} finally {
if (assocSG != null) {
assocSG.close();
}
}
return instanceIds;
}
@Override
protected String getFriendlyId(BlockObject blockObject) {
return format("%s/%s", blockObject.getId(), blockObject.getNativeGuid());
}
@Override
protected CIMObjectPath getCIMObjectPath(BlockObject obj) {
return getCimPath().getBlockObjectPath(storage, obj);
}
/**
* Use this to access {@code blockObjects}, since it may be null.
* @return Collection of BlockObject instances.
*/
@SuppressWarnings("unchecked")
protected Collection<T> getDataObjects() {
if (dataObjects != null) {
// When first calling this method, caller has provided a list of block objects
// so we must ensure they actually exist in the mask itself.
checkRequestedVolumesBelongToMask();
return dataObjects;
}
// Caller did not provide a list of block objects, so use ExportMask userAddedVolumes instead.
StringMap userAddedVolumes = exportMask.getUserAddedVolumes();
List<URI> boURIs = Lists.newArrayList();
for (String boURI : userAddedVolumes.values()) {
boURIs.add(URI.create(boURI));
}
dataObjects = (Collection<T>) BlockObject.fetchAll(getDbClient(), boURIs);
return dataObjects;
}
private void checkRequestedVolumesBelongToMask() {
StringMap userAddedVolumes = exportMask.getUserAddedVolumes();
if (userAddedVolumes == null) {
return;
}
Collection<String> masKVolumeURIs = userAddedVolumes.values();
Collection<String> reqVolumeURIs = transform(dataObjects, FCTN_VOLUME_URI_TO_STR);
for (String reqVolumeURI : reqVolumeURIs) {
if (!masKVolumeURIs.contains(reqVolumeURI)) {
String msg = format("Requested volume %s does not belong in mask %s", reqVolumeURI, exportMask);
throw new IllegalArgumentException(msg);
}
}
}
}