/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.externaldevice;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.AbstractChangeTrackingSet;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
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.ExportPathParams;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringSetUtil;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.storagedriver.BlockStorageDriver;
import com.emc.storageos.storagedriver.DriverTask;
import com.emc.storageos.storagedriver.model.Initiator;
import com.emc.storageos.storagedriver.model.StoragePort;
import com.emc.storageos.storagedriver.model.StorageVolume;
import com.emc.storageos.storagedriver.storagecapabilities.CommonStorageCapabilities;
import com.emc.storageos.storagedriver.storagecapabilities.ExportPathsServiceOption;
import com.emc.storageos.storagedriver.storagecapabilities.StorageCapabilities;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.util.ConnectivityUtil;
import com.emc.storageos.util.ExportUtils;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.VolumeURIHLU;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportMaskAddInitiatorCompleter;
import com.emc.storageos.volumecontroller.impl.smis.ExportMaskOperations;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.emc.storageos.volumecontroller.placement.BlockStorageScheduler;
import com.google.common.base.Joiner;
/**
* Export operations for storage systems managed by drivers
*/
public class ExternalDeviceExportOperations implements ExportMaskOperations {
private static Logger log = LoggerFactory.getLogger(ExternalDeviceExportOperations.class);
private DbClient dbClient;
// Need this reference to get driver for device type.
private ExternalBlockStorageDevice externalDevice;
private BlockStorageScheduler blockScheduler;
public void setDbClient(DbClient dbClient) {
this.dbClient = dbClient;
}
public void setExternalDevice(ExternalBlockStorageDevice externalDevice) {
this.externalDevice = externalDevice;
}
public void setBlockScheduler(BlockStorageScheduler blockScheduler) {
this.blockScheduler = blockScheduler;
}
@Override
public void createExportMask(StorageSystem storage, URI exportMaskUri, VolumeURIHLU[] volumeURIHLUs, List<URI> targetURIList,
List<com.emc.storageos.db.client.model.Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException {
log.info("{} createExportMask START...", storage.getSerialNumber());
try {
log.info("createExportMask: Export mask id: {}", exportMaskUri);
log.info("createExportMask: volume-HLU pairs: {}", Joiner.on(',').join(volumeURIHLUs));
log.info("createExportMask: initiators: {}", Joiner.on(',').join(initiatorList));
log.info("createExportMask: assignments: {}", Joiner.on(',').join(targetURIList));
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
Set<URI> volumeUris = new HashSet<>();
for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) {
URI volumeURI = volumeURIHLU.getVolumeURI();
volumeUris.add(volumeURI);
}
// Prepare volumes
List<StorageVolume> driverVolumes = new ArrayList<>();
Map<String, String> driverVolumeToHLUMap = new HashMap<>();
Map<String, URI> volumeNativeIdToUriMap = new HashMap<>();
prepareVolumes(storage, volumeURIHLUs, driverVolumes, driverVolumeToHLUMap, volumeNativeIdToUriMap);
// Prepare initiators
List<Initiator> driverInitiators = new ArrayList<>();
prepareInitiators(initiatorList, exportGroup.forCluster(), driverInitiators);
// Prepare target storage ports
List<StoragePort> recommendedPorts = new ArrayList<>();
List<StoragePort> availablePorts = new ArrayList<>();
List<StoragePort> selectedPorts = new ArrayList<>();
// Prepare ports for driver call. Populate lists of recommended and available ports.
Map<String, com.emc.storageos.db.client.model.StoragePort> nativeIdToAvailablePortMap = new HashMap<>();
preparePorts(storage, exportMaskUri, targetURIList, recommendedPorts, availablePorts, nativeIdToAvailablePortMap);
ExportPathParams pathParams = blockScheduler.calculateExportPathParamForVolumes(volumeUris, exportGroup.getNumPaths(),
storage.getId(), exportGroupUri);
StorageCapabilities capabilities = new StorageCapabilities();
// Prepare num paths to send to driver
prepareCapabilities(pathParams, capabilities);
MutableBoolean usedRecommendedPorts = new MutableBoolean(true);
// Ready to call driver
DriverTask task = driver.exportVolumesToInitiators(driverInitiators, driverVolumes, driverVolumeToHLUMap, recommendedPorts,
availablePorts, capabilities, usedRecommendedPorts, selectedPorts);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
// If driver used recommended ports, we are done.
// Otherwise, if driver did not use recommended ports, we have to get ports selected by driver
// and use them in export mask and zones.
// We will verify driver selected ports against available ports list.
String msg = String.format("createExportMask -- Created export: %s . Used recommended ports: %s .", task.getMessage(),
usedRecommendedPorts);
log.info(msg);
log.info("Recommended ports: {}", recommendedPorts);
log.info("Available ports: {}", availablePorts);
log.info("Selected ports: {}", selectedPorts);
if (usedRecommendedPorts.isFalse()) {
// process driver selected ports
if (validateSelectedPorts(availablePorts, selectedPorts, pathParams.getMinPaths())) {
List<com.emc.storageos.db.client.model.StoragePort> selectedPortsForMask = new ArrayList<>();
for (StoragePort driverPort : selectedPorts) {
com.emc.storageos.db.client.model.StoragePort port = nativeIdToAvailablePortMap.get(driverPort.getNativeId());
selectedPortsForMask.add(port);
}
updateStoragePortsInExportMask(exportMask, exportGroup, selectedPortsForMask);
// Update volumes Lun Ids in export mask based on driver selection
for (String volumeNativeId : driverVolumeToHLUMap.keySet()) {
String targetLunId = driverVolumeToHLUMap.get(volumeNativeId);
URI volumeUri = volumeNativeIdToUriMap.get(volumeNativeId);
exportMask.getVolumes().put(volumeUri.toString(), targetLunId);
}
dbClient.updateObject(exportMask);
taskCompleter.ready(dbClient);
} else {
// selected ports are not valid. failure
String errorMsg = "createExportMask -- Ports selected by driver failed validation.";
log.error("createExportMask -- Ports selected by driver failed validation.");
ServiceError serviceError = ExternalDeviceException.errors.createExportMaskFailed("createExportMask", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} else {
// Used recommended ports.
// Update volumes Lun Ids in export mask based on driver selection
for (String volumeNativeId : driverVolumeToHLUMap.keySet()) {
String targetLunId = driverVolumeToHLUMap.get(volumeNativeId);
URI volumeUri = volumeNativeIdToUriMap.get(volumeNativeId);
exportMask.getVolumes().put(volumeUri.toString(), targetLunId);
}
dbClient.updateObject(exportMask);
taskCompleter.ready(dbClient);
}
} else {
String errorMsg = String.format("createExportMask -- Failed to create export: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.createExportMaskFailed("createExportMask", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("Problem in createExportMask: ", ex);
log.error("createExportMask -- Failed to create export mask. ", ex);
ServiceError serviceError = ExternalDeviceException.errors.createExportMaskFailed("createExportMask", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
log.info("{} createExportMask END...", storage.getSerialNumber());
}
@Override
public void deleteExportMask(StorageSystem storage, URI exportMaskUri, List<URI> volumeUrisList, List<URI> targetUris,
List<com.emc.storageos.db.client.model.Initiator> initiators, TaskCompleter taskCompleter) throws DeviceControllerException {
// Unexport export mask volumes from export mask initiators.
log.info("{} deleteExportMask START...", storage.getSerialNumber());
try {
log.info("deleteExportMask: Export mask id: {}", exportMaskUri);
if (volumeUrisList != null) {
log.info("deleteExportMask: volumes: {}", Joiner.on(',').join(volumeUrisList));
}
if (targetUris != null) {
log.info("deleteExportMask: assignments: {}", Joiner.on(',').join(targetUris));
}
if (initiators != null) {
log.info("deleteExportMask: initiators: {}", Joiner.on(',').join(initiators));
}
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
List<URI> volumeUris = new ArrayList<>();
StringMap maskVolumes = exportMask.getVolumes();
if (maskVolumes != null) {
for (String vol : maskVolumes.keySet()) {
URI volumeURI = URI.create(vol);
volumeUris.add(volumeURI);
}
}
StringSet maskInitiatorUris = exportMask.getInitiators();
List<String> initiatorUris = new ArrayList<>();
for (String initiatorUri : maskInitiatorUris) {
initiatorUris.add(initiatorUri);
}
log.info("Export mask existing initiators: {} ", Joiner.on(',').join(initiatorUris));
StringMap volumes = exportMask.getVolumes();
log.info("Export mask existing volumes: {} ", volumes != null ? Joiner.on(',').join(volumes.keySet()) : null);
// Prepare volumes.
List<StorageVolume> driverVolumes = new ArrayList<>();
prepareVolumes(storage, volumeUris, driverVolumes);
// Prepare initiators
List<Initiator> driverInitiators = new ArrayList<>();
Set<com.emc.storageos.db.client.model.Initiator> maskInitiators = ExportMaskUtils.getInitiatorsForExportMask(dbClient,
exportMask, null);
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
prepareInitiators(maskInitiators, exportGroup.forCluster(), driverInitiators);
// Ready to call driver
log.info("Initiators to call driver: {} ", maskInitiators);
log.info("Volumes to call driver: {} ", volumeUris);
DriverTask task = driver.unexportVolumesFromInitiators(driverInitiators, driverVolumes);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
String msg = String.format("Deleted export mask: %s.", task.getMessage());
log.info(msg);
taskCompleter.ready(dbClient);
} else {
String errorMsg = String.format("Failed to delete export mask: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.deleteExportMaskFailed("deleteExportMask", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("Problem in deleteExportMask: ", ex);
String errorMsg = String.format("Failed to delete export mask: %s .", ex.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.deleteExportMaskFailed("deleteExportMask", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
log.info("{} deleteExportMask END...", storage.getSerialNumber());
}
@Override
public void addInitiators(StorageSystem storage, URI exportMaskUri, List<URI> volumeURIs,
List<com.emc.storageos.db.client.model.Initiator> initiatorList, List<URI> targetURIList, TaskCompleter taskCompleter)
throws DeviceControllerException {
log.info("{} addInitiators START...", storage.getSerialNumber());
try {
log.info("addInitiators: Export mask id: {}", exportMaskUri);
if (volumeURIs != null) {
log.info("addInitiators: volumes : {}", Joiner.on(',').join(volumeURIs));
}
log.info("addInitiators: initiators : {}", Joiner.on(',').join(initiatorList));
log.info("addInitiators: targets : {}", Joiner.on(",").join(targetURIList));
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
List<URI> volumeUris = ExportMaskUtils.getVolumeURIs(exportMask);
// Prepare volumes
List<StorageVolume> driverVolumes = new ArrayList<>();
Map<String, String> driverVolumeToHLUMap = new HashMap<>();
Map<String, String> volumeNativeIdToUriMap = new HashMap<>();
prepareVolumes(storage, exportMask.getVolumes(), driverVolumes, driverVolumeToHLUMap, volumeNativeIdToUriMap);
// Prepare initiators
List<Initiator> driverInitiators = new ArrayList<>();
prepareInitiators(initiatorList, exportGroup.forCluster(), driverInitiators);
// Prepare target storage ports
List<StoragePort> recommendedPorts = new ArrayList<>();
List<StoragePort> availablePorts = new ArrayList<>();
List<StoragePort> selectedPorts = new ArrayList<>();
// Prepare ports for driver call. Populate lists of recommended and available ports.
Map<String, com.emc.storageos.db.client.model.StoragePort> nativeIdToAvailablePortMap = new HashMap<>();
preparePorts(storage, exportMaskUri, targetURIList, recommendedPorts, availablePorts, nativeIdToAvailablePortMap);
ExportPathParams pathParams = blockScheduler.calculateExportPathParamForVolumes(volumeUris, exportGroup.getNumPaths(),
storage.getId(), exportGroupUri);
StorageCapabilities capabilities = new StorageCapabilities();
// Prepare num paths to send to driver
prepareCapabilitiesForAddInitiators(pathParams, exportMask.getZoningMap(), exportGroup.getVirtualArray(), initiatorList,
capabilities);
MutableBoolean usedRecommendedPorts = new MutableBoolean(true);
// Ready to call driver
DriverTask task = driver.exportVolumesToInitiators(driverInitiators, driverVolumes, driverVolumeToHLUMap, recommendedPorts,
availablePorts, capabilities, usedRecommendedPorts, selectedPorts);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
// If driver used recommended ports, we are done.
// Otherwise, if driver did not use recommended ports, we have to get ports selected by driver
// and use them in export mask and zones.
// We will verify driver selected ports against available ports list.
String msg = String.format("addInitiators -- Added initiators: %s . Used recommended ports: %s .", task.getMessage(),
usedRecommendedPorts);
log.info(msg);
if (usedRecommendedPorts.isFalse()) {
// process driver selected ports
log.info("Ports selected by driver: {}", selectedPorts);
if (validateSelectedPorts(availablePorts, selectedPorts, pathParams.getPathsPerInitiator())) {
List<com.emc.storageos.db.client.model.StoragePort> selectedPortsForMask = new ArrayList<>();
URI varrayUri = exportGroup.getVirtualArray();
for (StoragePort driverPort : selectedPorts) {
com.emc.storageos.db.client.model.StoragePort port = nativeIdToAvailablePortMap.get(driverPort.getNativeId());
selectedPortsForMask.add(port);
}
updateStoragePortsForAddInitiators((ExportMaskAddInitiatorCompleter) taskCompleter, storage, exportMask,
initiatorList, selectedPortsForMask, varrayUri, pathParams);
taskCompleter.ready(dbClient);
} else {
// selected ports are not valid. failure
String errorMsg = "addInitiators -- Ports selected by driver failed validation.";
log.error("addInitiators -- Ports selected by driver failed validation.");
ServiceError serviceError = ExternalDeviceException.errors.addInitiatorsToExportMaskFailed("addInitiators",
errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} else {
// Used recommended ports.
taskCompleter.ready(dbClient);
}
} else {
String errorMsg = String.format("addInitiators -- Failed to add initiators to export mask: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.addInitiatorsToExportMaskFailed("addInitiators", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("addInitiators -- Failed to add initiators to export mask. ", ex);
ServiceError serviceError = ExternalDeviceException.errors.addInitiatorsToExportMaskFailed("addInitiators", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
log.info("{} addInitiators END...", storage.getSerialNumber());
}
@Override
public void removeInitiators(StorageSystem storage, URI exportMaskUri, List<URI> volumeURIList,
List<com.emc.storageos.db.client.model.Initiator> initiatorList, List<URI> targetURIList, TaskCompleter taskCompleter)
throws DeviceControllerException {
log.info("{} removeInitiators START...", storage.getSerialNumber());
try {
log.info("removeInitiators: Export mask id: {}", exportMaskUri);
if (volumeURIList != null) {
log.info("removeInitiators: volumes : {}", Joiner.on(',').join(volumeURIList));
}
log.info("removeInitiators: initiators : {}", Joiner.on(',').join(initiatorList));
log.info("removeInitiators: targets : {}", Joiner.on(',').join(targetURIList));
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
List<URI> volumeUris = ExportMaskUtils.getVolumeURIs(exportMask);
log.info("Export mask existing volumes: {} ", volumeUris);
// Prepare volumes
List<StorageVolume> driverVolumes = new ArrayList<>();
prepareVolumes(storage, volumeUris, driverVolumes);
// Prepare initiators
List<Initiator> driverInitiators = new ArrayList<>();
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
prepareInitiators(initiatorList, exportGroup.forCluster(), driverInitiators);
// Ready to call driver
DriverTask task = driver.unexportVolumesFromInitiators(driverInitiators, driverVolumes);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
String msg = String.format("Removed initiators from export mask: %s.", task.getMessage());
log.info(msg);
taskCompleter.ready(dbClient);
} else {
String errorMsg = String.format("Failed to remove initiators from export mask: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.removeInitiatorsFromExportMaskFailed("removeInitiators",
errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("Problem in removeInitiators: ", ex);
String errorMsg = String.format("Failed to remove initiators from export mask: %s .", ex.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.removeInitiatorsFromExportMaskFailed("removeInitiators", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
log.info("{} removeInitiators END...", storage.getSerialNumber());
}
@Override
public void addVolumes(StorageSystem storage, URI exportMaskUri, VolumeURIHLU[] volumeURIHLUs,
List<com.emc.storageos.db.client.model.Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException {
log.info("{} addVolumes START...", storage.getSerialNumber());
try {
log.info("addVolumes: Export mask id: {}", exportMaskUri);
log.info("addVolumes: New volumes to add: volume-HLU pairs: {}", Joiner.on(',').join(volumeURIHLUs));
if (initiatorList != null) {
log.info("addVolumes: initiators: {}", Joiner.on(',').join(initiatorList));
}
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
StringSet maskInitiators = exportMask.getInitiators();
List<String> maskInitiatorList = new ArrayList<>();
for (String initiatorUri : maskInitiators) {
maskInitiatorList.add(initiatorUri);
}
log.info("Export mask existing initiators: {} ", Joiner.on(',').join(maskInitiatorList));
StringSet storagePorts = exportMask.getStoragePorts();
List<URI> portList = new ArrayList<>();
for (String portUri : storagePorts) {
portList.add(URI.create(portUri));
}
log.info("Export mask existing storage ports: {} ", Joiner.on(',').join(portList));
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
Set<URI> volumeUris = new HashSet<>();
for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) {
URI volumeURI = volumeURIHLU.getVolumeURI();
volumeUris.add(volumeURI);
}
// Prepare volumes. We send to driver only new volumes for the export mask.
List<StorageVolume> driverVolumes = new ArrayList<>();
Map<String, String> driverVolumeToHLUMap = new HashMap<>();
Map<String, URI> volumeNativeIdToUriMap = new HashMap<>();
prepareVolumes(storage, volumeURIHLUs, driverVolumes, driverVolumeToHLUMap, volumeNativeIdToUriMap);
// Prepare initiators
Set<com.emc.storageos.db.client.model.Initiator> initiators = ExportMaskUtils.getInitiatorsForExportMask(dbClient, exportMask,
null);
List<Initiator> driverInitiators = new ArrayList<>();
prepareInitiators(initiators, exportGroup.forCluster(), driverInitiators);
// Prepare target storage ports
List<StoragePort> recommendedPorts = new ArrayList<>();
List<StoragePort> availablePorts = new ArrayList<>();
List<StoragePort> selectedPorts = new ArrayList<>();
// Prepare ports for driver call. Populate lists of recommended and available ports.
Map<String, com.emc.storageos.db.client.model.StoragePort> nativeIdToAvailablePortMap = new HashMap<>();
// We use existing ports in the mask as recommended ports.
preparePorts(storage, exportMaskUri, portList, recommendedPorts, availablePorts, nativeIdToAvailablePortMap);
log.info("varray ports: {}", nativeIdToAvailablePortMap);
// For add volumes to existing export mask, we do not allow storage port change in the mask.
// Only ports in the mask are available for driver call.
availablePorts = recommendedPorts;
ExportPathParams pathParams = blockScheduler.calculateExportPathParamForVolumes(volumeUris, exportGroup.getNumPaths(),
storage.getId(), exportGroupUri);
StorageCapabilities capabilities = new StorageCapabilities();
// Prepare num paths to send to driver
prepareCapabilities(pathParams, capabilities);
MutableBoolean usedRecommendedPorts = new MutableBoolean(true);
// Ready to call driver
DriverTask task = driver.exportVolumesToInitiators(driverInitiators, driverVolumes, driverVolumeToHLUMap, recommendedPorts,
availablePorts, capabilities, usedRecommendedPorts, selectedPorts);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
String msg = String.format("Created export for volumes: %s . Used recommended ports: %s .", task.getMessage(), usedRecommendedPorts);
log.info(msg);
log.info("Driver selected storage ports: {} ", Joiner.on(',').join(selectedPorts));
// If driver used recommended ports (the same ports as already in the mask), we are done.
// If driver did not use recommended ports, we will allow this to proceed only when auto san zoning is disabled, otherwise, if
// auto san zoning is enabled, we will fail the request.
if (usedRecommendedPorts.isFalse() && !selectedPorts.containsAll(recommendedPorts)) {
// for auto san zoning enabled we can not support case when selected ports do not include ports which are already in the mask
VirtualArray varray = dbClient.queryObject(VirtualArray.class, exportGroup.getVirtualArray());
log.info("AutoSanZoning for varray {} is {} ", varray.getLabel(), varray.getAutoSanZoning());
if (varray.getAutoSanZoning()) {
String errorMsg = String.format("AutoSanZoning is enabled and driver selected ports do not contain ports from the export mask: %s .",
task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.addVolumesToExportMaskFailed("addVolumes", errorMsg);
taskCompleter.error(dbClient, serviceError);
} else {
// auto san zoning is disabled --- add new selected ports to the mask
// we do not care about zoning map in this case
List<com.emc.storageos.db.client.model.StoragePort> selectedPortsForMask = new ArrayList<>();
for (StoragePort driverPort : selectedPorts) {
log.info("Driver selected port: {}", driverPort);
com.emc.storageos.db.client.model.StoragePort port = nativeIdToAvailablePortMap.get(driverPort.getNativeId());
if (port != null) {
// add all ports, StringSet in the mask will ignore duplicates
log.info("System port: {}", port);
selectedPortsForMask.add(port);
}
}
for (com.emc.storageos.db.client.model.StoragePort port : selectedPortsForMask) {
exportMask.addTarget(port.getId());
}
dbClient.updateObject(exportMask);
taskCompleter.ready(dbClient);
}
} else {
// Driver used recommended ports for a new volumes or all export mask ports are contained
// in the list of driver selected ports
// No port change in export mask is needed.
// Update volumes Lun Ids in export mask based on driver selection
for (String volumeNativeId : driverVolumeToHLUMap.keySet()) {
String targetLunId = driverVolumeToHLUMap.get(volumeNativeId);
URI volumeUri = volumeNativeIdToUriMap.get(volumeNativeId);
exportMask.getVolumes().put(volumeUri.toString(), targetLunId);
}
dbClient.updateObject(exportMask);
taskCompleter.ready(dbClient);
}
} else {
String errorMsg = String.format("Failed to add volumes to export mask: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.addVolumesToExportMaskFailed("addVolumes", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("Problem in addVolumes: ", ex);
String errorMsg = String.format("Failed to add volumes to export mask: %s .", ex.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.addVolumesToExportMaskFailed("addVolumes", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
log.info("{} addVolumes END...", storage.getSerialNumber());
}
@Override
public void removeVolumes(StorageSystem storage, URI exportMaskUri, List<URI> volumeUris,
List<com.emc.storageos.db.client.model.Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException {
log.info("{} removeVolumes START...", storage.getSerialNumber());
try {
log.info("removeVolumes: Export mask id: {}", exportMaskUri);
log.info("removeVolumes: volumes: {}", Joiner.on(',').join(volumeUris));
if (initiatorList != null) {
log.info("removeVolumes: impacted initiators: {}", Joiner.on(",").join(initiatorList));
}
BlockStorageDriver driver = externalDevice.getDriver(storage.getSystemType());
ExportMask exportMask = (ExportMask) dbClient.queryObject(exportMaskUri);
StringSet maskInitiators = exportMask.getInitiators();
List<String> maskInitiatorList = new ArrayList<>();
for (String initiatorUri : maskInitiators) {
maskInitiatorList.add(initiatorUri);
}
log.info("Export mask existing initiators: {} ", Joiner.on(",").join(maskInitiatorList));
// Prepare volumes. We send to driver only new volumes for the export mask.
List<StorageVolume> driverVolumes = new ArrayList<>();
prepareVolumes(storage, volumeUris, driverVolumes);
// Prepare initiators
Set<com.emc.storageos.db.client.model.Initiator> initiators = ExportMaskUtils.getInitiatorsForExportMask(dbClient, exportMask,
null);
List<Initiator> driverInitiators = new ArrayList<>();
// Get export group uri from task completer
URI exportGroupUri = taskCompleter.getId();
ExportGroup exportGroup = (ExportGroup) dbClient.queryObject(exportGroupUri);
prepareInitiators(initiators, exportGroup.forCluster(), driverInitiators);
// Ready to call driver
DriverTask task = driver.unexportVolumesFromInitiators(driverInitiators, driverVolumes);
// todo: need to implement support for async case.
if (task.getStatus() == DriverTask.TaskStatus.READY) {
String msg = String.format("Removed volumes from export: %s.", task.getMessage());
log.info(msg);
taskCompleter.ready(dbClient);
} else {
String errorMsg = String.format("Failed to remove volumes from export mask: %s .", task.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.deleteVolumesFromExportMaskFailed("removeVolumes", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
} catch (Exception ex) {
log.error("Problem in removeVolumes: ", ex);
String errorMsg = String.format("Failed to remove volumes from export mask: %s .", ex.getMessage());
log.error(errorMsg);
ServiceError serviceError = ExternalDeviceException.errors.deleteVolumesFromExportMaskFailed("removeVolumes", errorMsg);
taskCompleter.error(dbClient, serviceError);
}
log.info("{} removeVolumes END...", storage.getSerialNumber());
}
@Override
public Map<String, Set<URI>> findExportMasks(StorageSystem storage,
List<String> initiatorNames, boolean mustHaveAllPorts) throws DeviceControllerException {
// not supported. There are no common masking concepts. So, return null.
return null;
}
@Override
public Set<Integer> findHLUsForInitiators(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) {
// TODO Auto-generated method stub
return null;
}
@Override
public ExportMask refreshExportMask(StorageSystem storage, ExportMask mask) throws DeviceControllerException {
// No common masking concept for driver managed systems. Return export mask as is.
return mask;
}
@Override
public void updateStorageGroupPolicyAndLimits(StorageSystem storage, ExportMask exportMask, List<URI> volumeURIs,
VirtualPool newVirtualPool, boolean rollback, TaskCompleter taskCompleter) throws Exception {
throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
}
@Override
public Map<URI, Integer> getExportMaskHLUs(StorageSystem storage, ExportMask exportMask) {
return null;
}
/**
* Prepare new driver volumes for driver export request (Ex. in context of create a new export mask,
* or add new volumes to the existing mask).
*
* @param storage
* storage sytem
* @param volumeURIHLUs
* mapping of volume uri to volume hlu
* @param driverVolumes
* driver volumes (output)
* @param driverVolumeToHLUMap
* map of driver volumes to hlu values
* @param volumeNativeIdToUriMap
* map of volume native id to uri
*/
private void prepareVolumes(StorageSystem storage, VolumeURIHLU[] volumeURIHLUs, List<StorageVolume> driverVolumes,
Map<String, String> driverVolumeToHLUMap, Map<String, URI> volumeNativeIdToUriMap) {
for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) {
URI volumeURI = volumeURIHLU.getVolumeURI();
BlockObject volume = (BlockObject) dbClient.queryObject(volumeURI);
StorageVolume driverVolume = createDriverVolume(storage, volume);
driverVolumes.add(driverVolume);
// We send volume lun number to driver in decimal format.
Integer decimalHLU;
if (volumeURIHLU.getHLU().equals(ExportGroup.LUN_UNASSIGNED_STR)) { // cannot parse "ffffffff" with
// Integer.parseInt(volumeURIHLU.getHLU(),
// 16),
// got "NumberFormatException". Looks as
// Java error ???
decimalHLU = ExportGroup.LUN_UNASSIGNED;
} else {
decimalHLU = Integer.parseInt(volumeURIHLU.getHLU(), 16);
}
driverVolumeToHLUMap.put(driverVolume.getNativeId(), decimalHLU.toString());
volumeNativeIdToUriMap.put(driverVolume.getNativeId(), volumeURI);
}
log.info("prepareVolumes: volume-HLU pairs for driver: {}", driverVolumeToHLUMap);
}
/**
* Prepare existing export mask volumes to driver export request (Ex. in context of add/remove initiators for export
* masks.)
*
* @param storage
* storage system
* @param driverVolumes
* driver volumes (output)
* @param driverVolumeToHLUMap
* map of driver volumes to hlu values
* @param volumeNativeIdToUriMap
* map of volume native id to uri
*/
private void prepareVolumes(StorageSystem storage, Map<String, String> volumeUriToHluMap, List<StorageVolume> driverVolumes,
Map<String, String> driverVolumeToHLUMap, Map<String, String> volumeNativeIdToUriMap) {
for (Map.Entry<String, String> volumeUriToHlu : volumeUriToHluMap.entrySet()) {
String volumeURI = volumeUriToHlu.getKey();
BlockObject volume = (BlockObject) dbClient.queryObject(URIUtil.uri(volumeURI));
StorageVolume driverVolume = createDriverVolume(storage, volume);
driverVolumes.add(driverVolume);
driverVolumeToHLUMap.put(driverVolume.getNativeId(), volumeUriToHlu.getValue());
volumeNativeIdToUriMap.put(driverVolume.getNativeId(), volumeURI);
}
log.info("prepareVolumes: volume-HLU pairs for driver: {}", driverVolumeToHLUMap);
}
private void prepareVolumes(StorageSystem storage, List<URI> volumeUris, List<StorageVolume> driverVolumes) {
for (URI volumeUri : volumeUris) {
BlockObject volume = (BlockObject) dbClient.queryObject(volumeUri);
StorageVolume driverVolume = createDriverVolume(storage, volume);
driverVolumes.add(driverVolume);
}
}
private void prepareInitiators(Collection<com.emc.storageos.db.client.model.Initiator> initiatorList, boolean isClusterExport,
List<Initiator> driverInitiators) {
for (com.emc.storageos.db.client.model.Initiator initiator : initiatorList) {
Initiator driverInitiator = createDriverInitiator(initiator);
if (isClusterExport) {
driverInitiator.setInitiatorType(Initiator.Type.Cluster);
} else {
driverInitiator.setInitiatorType(Initiator.Type.Host);
}
driverInitiators.add(driverInitiator);
}
}
private void preparePorts(StorageSystem storage, URI exportMaskUri, List<URI> targetURIList, List<StoragePort> recommendedPorts,
List<StoragePort> availablePorts, Map<String, com.emc.storageos.db.client.model.StoragePort> nativeIdToAvailablePortMap) {
Iterator<com.emc.storageos.db.client.model.StoragePort> ports = dbClient
.queryIterativeObjects(com.emc.storageos.db.client.model.StoragePort.class, targetURIList);
while (ports.hasNext()) {
StoragePort driverPort = createDriverPort(storage, ports.next());
recommendedPorts.add(driverPort);
}
List<ExportGroup> exportGroups = ExportUtils.getExportGroupsForMask(exportMaskUri, dbClient);
URI varrayUri = exportGroups.get(0).getVirtualArray();
List<com.emc.storageos.db.client.model.StoragePort> varrayPorts = getStorageSystemVarrayStoragePorts(storage, varrayUri);
for (com.emc.storageos.db.client.model.StoragePort port : varrayPorts) {
if (port.getNativeId() != null) {
nativeIdToAvailablePortMap.put(port.getNativeId(), port);
} else {
// This is error condition. Each port for driver managed systems should have nativeId set at
// discovery time.
String errorMsg = String.format("No nativeId defined for port: storage system: %s, storagePort: %s .", storage.getId(),
port.getId());
log.error(errorMsg);
throw ExternalDeviceException.exceptions.noNativeIdDefinedForPort(storage.getNativeId(), port.getId().toString());
}
StoragePort driverPort = createDriverPort(storage, port);
availablePorts.add(driverPort);
}
}
private void prepareCapabilities(ExportPathParams pathParams, StorageCapabilities capabilities) {
ExportPathsServiceOption numPath = new ExportPathsServiceOption(pathParams.getMinPaths(), pathParams.getMaxPaths());
List<ExportPathsServiceOption> exportPathParams = new ArrayList<>();
exportPathParams.add(numPath);
CommonStorageCapabilities commonCapabilities = new CommonStorageCapabilities();
commonCapabilities.setExportPathParams(exportPathParams);
capabilities.setCommonCapabilitis(commonCapabilities);
}
private void prepareCapabilitiesForAddInitiators(ExportPathParams pathParams, StringSetMap existingZoningMap, URI varrayURI,
List<com.emc.storageos.db.client.model.Initiator> initiators, StorageCapabilities capabilities) {
int driverMaxPath;
StringSetMap zoningMap = new StringSetMap();
// Calculate existing paths (without new initiators).
int existingPaths = 0;
List<URI> initiatorUris = URIUtil.toUris(initiators);
for (Map.Entry<String, AbstractChangeTrackingSet<String>> entry : existingZoningMap.entrySet()) {
if (!initiatorUris.contains(URIUtil.uri(entry.getKey()))) {
zoningMap.put(entry.getKey(), entry.getValue());
}
}
Map<com.emc.storageos.db.client.model.Initiator, List<com.emc.storageos.db.client.model.StoragePort>> assignments = blockScheduler
.generateInitiatorsToStoragePortsMap(zoningMap, varrayURI);
existingPaths = assignments.values().size();
log.info("Existing path number in the export mask is {}", existingPaths);
// Calculate maxPath for the driver request.
// We try to balance two requirements: not violate maxPaths and to allocate at least enough paths
// for one new initiator. If we are over max path already or will be over max paths, when we allocate
// ppi number of new paths, we allocate only ppi new paths (a minimum).
// Otherwise we allocate the difference between max paths and existing paths
if (existingPaths + pathParams.getPathsPerInitiator() > pathParams.getMaxPaths()) {
driverMaxPath = pathParams.getPathsPerInitiator(); // we always need at least path-per-initiator ports
} else {
driverMaxPath = pathParams.getMaxPaths() - existingPaths;
}
// We assume that masking view meets min path before new initiators are added. We pass ppi as a minPath
// to driver, so we can zone at least one new initiator.
ExportPathsServiceOption numPath = new ExportPathsServiceOption(pathParams.getPathsPerInitiator(), driverMaxPath);
List<ExportPathsServiceOption> exportPathParams = new ArrayList<>();
exportPathParams.add(numPath);
CommonStorageCapabilities commonCapabilities = new CommonStorageCapabilities();
commonCapabilities.setExportPathParams(exportPathParams);
capabilities.setCommonCapabilitis(commonCapabilities);
}
/**
* Gets the list of storage ports of a storage system which belong to a virtual array.
*
* @param storage
* StorageSystem from which ports needs to be queried.
* @param varrayURI
* URI of the virtual array
* @return list of storage ports of storage system which belong to virtual array
*/
private List<com.emc.storageos.db.client.model.StoragePort> getStorageSystemVarrayStoragePorts(StorageSystem storage, URI varrayURI) {
log.debug("START - getStorageSystemVarrayStoragePorts");
// Get the list of storage ports of a storage system which are in a given varray
Map<URI, List<com.emc.storageos.db.client.model.StoragePort>> networkUriVsStoragePorts = ConnectivityUtil
.getStoragePortsOfTypeAndVArray(dbClient, storage.getId(), com.emc.storageos.db.client.model.StoragePort.PortType.frontend,
varrayURI);
List<com.emc.storageos.db.client.model.StoragePort> varrayTaggedStoragePorts = new ArrayList<>();
Set<URI> networkUriSet = networkUriVsStoragePorts.keySet();
for (URI nwUri : networkUriSet) {
List<com.emc.storageos.db.client.model.StoragePort> ports = networkUriVsStoragePorts.get(nwUri);
varrayTaggedStoragePorts.addAll(ports);
log.info("Varray Tagged Ports for network {} are {}", nwUri, ports);
}
log.debug("END - getStorageSystemVarrayStoragePorts");
return varrayTaggedStoragePorts;
}
private Boolean validateSelectedPorts(List<StoragePort> availablePorts, List<StoragePort> selectedPorts, int minPorts) {
boolean containmentCheck = availablePorts.containsAll(selectedPorts);
boolean numPathCheck = (selectedPorts.size() >= minPorts);
log.info(
String.format("Validation check for selected ports: containmentCheck: %s, minPorts: %s .", containmentCheck, numPathCheck));
return containmentCheck && numPathCheck;
}
private void updateStoragePortsInExportMask(ExportMask exportMask, ExportGroup exportGroup,
List<com.emc.storageos.db.client.model.StoragePort> storagePorts) {
// This method will update storage ports and zoning map in the export mask based on the storagePorts list.
// Clean all existing targets in the export mask and add new targets
List<URI> storagePortListFromMask = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts());
for (URI removeUri : storagePortListFromMask) {
exportMask.removeTarget(removeUri);
}
// Add new target ports
for (com.emc.storageos.db.client.model.StoragePort port : storagePorts) {
exportMask.addTarget(port.getId());
}
log.info("Ports in mask after add new ports: {}", exportMask.getStoragePorts());
// Update zoning map based on provided storage ports
blockScheduler.updateZoningMap(exportMask, exportGroup.getVirtualArray(), exportGroup.getId());
}
/**
* This method updates storage ports in export masks for new initiators, based on array selected ports.
*
* @param taskCompleter
* @param system
* @param exportMask
* @param initiatorList
* @param selectedPortsForMask
* @param virtualArray
* @param pathParams
*/
private void updateStoragePortsForAddInitiators(ExportMaskAddInitiatorCompleter taskCompleter, StorageSystem system,
ExportMask exportMask, List<com.emc.storageos.db.client.model.Initiator> initiatorList,
List<com.emc.storageos.db.client.model.StoragePort> selectedPortsForMask, URI virtualArray, ExportPathParams pathParams) {
// update storage ports in completer
List<URI> portUris = new ArrayList<>();
for (com.emc.storageos.db.client.model.StoragePort port : selectedPortsForMask) {
portUris.add(port.getId());
}
taskCompleter.setTargetURIs(portUris);
// update zoning map entries for new initiators
// remove old entries for the initiators from the zoning map
List<URI> initiatorUris = URIUtil.toUris(initiatorList);
for (URI initiatorUri : initiatorUris) {
exportMask.removeZoningMapEntry(initiatorUri.toString());
}
// add new entries for the initiators to the zoning map
// Build assignments for new added initiators.
Map<URI, List<URI>> assignments = blockScheduler.assignSelectedStoragePorts(system, selectedPortsForMask, virtualArray,
initiatorList, pathParams, exportMask.getZoningMap());
exportMask.addZoningMap(BlockStorageScheduler.getZoneMapFromAssignments(assignments));
dbClient.updateObject(exportMask);
}
/**
* Create driver block object
*
* @param storage
* @param volume
* @return
*/
private StorageVolume createDriverVolume(StorageSystem storage, BlockObject volume) {
StorageVolume driverVolume = new StorageVolume();
driverVolume.setStorageSystemId(storage.getNativeId());
driverVolume.setNativeId(volume.getNativeId());
driverVolume.setDeviceLabel(volume.getDeviceLabel());
if (!NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())) {
BlockConsistencyGroup cg = dbClient.queryObject(BlockConsistencyGroup.class, volume.getConsistencyGroup());
driverVolume.setConsistencyGroup(cg.getLabel());
}
return driverVolume;
}
private Initiator createDriverInitiator(com.emc.storageos.db.client.model.Initiator initiator) {
Initiator driverInitiator = new Initiator();
driverInitiator.setPort(initiator.getInitiatorPort());
driverInitiator.setHostName(initiator.getHostName());
driverInitiator.setClusterName(initiator.getClusterName());
driverInitiator.setNode(initiator.getInitiatorNode());
driverInitiator.setProtocol(Initiator.Protocol.valueOf(initiator.getProtocol()));
driverInitiator.setDisplayName(initiator.getLabel());
// set host OS type
driverInitiator.setHostOsType(Initiator.HostOsType.Other);
Host host = dbClient.queryObject(Host.class, initiator.getHost());
String hostType = host.getType();
for (Initiator.HostOsType driverInitiatorHostType : Initiator.HostOsType.values()) {
if (hostType.equals(driverInitiatorHostType.toString())) {
driverInitiator.setHostOsType(driverInitiatorHostType);
}
}
log.info("Initiator host OS type {}", driverInitiator.getHostOsType());
return driverInitiator;
}
private StoragePort createDriverPort(StorageSystem storage, com.emc.storageos.db.client.model.StoragePort port) {
StoragePort driverPort = new StoragePort();
driverPort.setNativeId(port.getNativeId());
driverPort.setStorageSystemId(storage.getNativeId());
driverPort.setPortName(port.getPortName());
driverPort.setDeviceLabel(port.getLabel());
driverPort.setPortGroup(port.getPortGroup());
return driverPort;
}
@Override
public void addPaths(StorageSystem storage, URI exportMask, Map<URI, List<URI>> newPaths, TaskCompleter taskCompleter)
throws DeviceControllerException {
throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
}
@Override
public void removePaths(StorageSystem storage, URI exportMask, Map<URI, List<URI>> adjustedPaths, Map<URI, List<URI>> removePaths, TaskCompleter taskCompleter)
throws DeviceControllerException {
throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
}
}