/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.block;
import java.net.URI;
import java.util.ArrayList;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.Controller;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.ExportGroup.ExportGroupType;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StorageProtocol;
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.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringSetUtil;
import com.emc.storageos.exceptions.DeviceControllerExceptions;
import com.emc.storageos.locking.LockTimeoutValue;
import com.emc.storageos.locking.LockType;
import com.emc.storageos.networkcontroller.impl.NetworkScheduler;
import com.emc.storageos.plugins.common.Constants;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.util.NetworkLite;
import com.emc.storageos.volumecontroller.BlockStorageDevice;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.ControllerLockingUtil;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportMaskOnlyRemoveVolumeCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportTaskCompleter;
import com.emc.storageos.volumecontroller.placement.PlacementUtils;
import com.emc.storageos.volumecontroller.placement.StoragePortsAllocator;
import com.emc.storageos.volumecontroller.placement.StoragePortsAllocator.PortAllocationContext;
import com.emc.storageos.volumecontroller.placement.StoragePortsAssigner;
import com.emc.storageos.vplex.api.VPlexApiException;
import com.emc.storageos.workflow.Workflow;
import com.emc.storageos.workflow.Workflow.Method;
import com.emc.storageos.workflow.WorkflowService;
import com.emc.storageos.workflow.WorkflowStepCompleter;
public class VplexXtremIOMaskingOrchestrator extends XtremIOMaskingOrchestrator implements
VplexBackEndMaskingOrchestrator, Controller {
private static final Logger _log = LoggerFactory.getLogger(VplexXtremIOMaskingOrchestrator.class);
private boolean simulation = false;
private static final int XTREMIO_NUM_PORT_GROUP = 1;
private static final int MAXIMUM_NUMBER_OF_STORAGE_PORTS_PER_SET = 4;
private static final int REQUIRED_MINIMUM_NUMBER_OF_STORAGE_PORTS_PER_SET = 2;
private static final int DEFAULT_NUMBER_OF_PATHS_PER_VPLEX_DIRECTOR = 4;
private static final int MINIMUM_NUMBER_OF_PATHS_PER_VPLEX_DIRECTOR = 2;
private int vplexDirectorCount;
private int xtremIOXbricksCount;
BlockDeviceController _blockController = null;
WorkflowService _workflowService = null;
public VplexXtremIOMaskingOrchestrator() {
}
public void setBlockDeviceController(BlockDeviceController blockController) {
this._blockController = blockController;
}
public VplexXtremIOMaskingOrchestrator(DbClient dbClient, BlockDeviceController controller) {
this._dbClient = dbClient;
this._blockController = controller;
}
public void setVplexDirectorCount(int vplexDirectorCount) {
this.vplexDirectorCount = vplexDirectorCount;
}
@Override
public Map<URI, ExportMask> readExistingExportMasks(StorageSystem storage,
BlockStorageDevice device, List<Initiator> initiators) {
// This will cause the VPlexBackendManager to generate an ExportMask
// for the first volume and then reuse it by finding it from the
// database for subsequent volumes.
// Use this , if you really don't care about the # masks created on
// theArray
return new HashMap<URI, ExportMask>();
}
@Override
public ExportMask refreshExportMask(StorageSystem storage, BlockStorageDevice device,
ExportMask mask) {
// Use this ,if you really don't care about the details of existing
// masks on Array.
super.refreshExportMask(storage, device, mask);
return mask;
}
@Override
public void suggestExportMasksForPlacement(
StorageSystem storage, BlockStorageDevice device, List<Initiator> initiators,
ExportMaskPlacementDescriptor placementDescriptor) {
super.suggestExportMasksForPlacement(storage, device, initiators, placementDescriptor);
}
@Override
public Set<Map<URI, List<List<StoragePort>>>> getPortGroups(
Map<URI, List<StoragePort>> allocatablePorts, Map<URI, NetworkLite> networkMap,
URI varrayURI, int nInitiatorGroups, Map<URI, Map<String, Integer>> switchToPortNumber,
Map<URI, PortAllocationContext> contextMap) {
/**
* Number of Port Group for XtremIO is always one.
* - If multiple port groups, each VPLEX Director's initiators will be mapped to multiple ports
*
* Single Port Group contains different set of storage ports for each network,
* so that each VPLEX director's initiators will map to different port set.
*
* why allocatePorts() not required:
* allocatePorts() would return required number of storage ports from a network from unique X-bricks.
* But we need to select storage ports uniquely across X-bricks & StorageControllers and we need
* to make use of all storage ports.
*
*/
Set<Map<URI, List<List<StoragePort>>>> portGroups = new HashSet<Map<URI, List<List<StoragePort>>>>();
StringSet netNames = new StringSet();
// Order the networks from those with fewest ports to those with the most ports.
List<URI> orderedNetworks = orderNetworksByNumberOfPorts(allocatablePorts);
for (URI networkURI : orderedNetworks) {
netNames.add(networkMap.get(networkURI).getLabel());
}
_log.info("Calculating PortGroups for Networks: {}", netNames.toString());
StoragePortsAllocator allocator = new StoragePortsAllocator();
// Determine if we should check connectivity from the varray.auto_san_zoning
boolean sanZoningEnabled = false;
if (!simulation) {
VirtualArray varray = _dbClient.queryObject(VirtualArray.class, varrayURI);
if (varray != null && NetworkScheduler.isZoningRequired(_dbClient, varray)) {
sanZoningEnabled = true;
}
}
/**
* Till all storage ports been processed:
* -- get a set of 4 storage ports selected equally across networks
* -- add this set into network to port List map (each port set within a network will be mapped for different
* directors)
*/
Map<URI, List<List<StoragePort>>> useablePorts = new HashMap<URI, List<List<StoragePort>>>();
Set<String> usedPorts = new HashSet<String>();
// map of selected X-brick to Storage Controllers across all networks
Map<String, List<String>> xBricksToSelectedSCs = new HashMap<String, List<String>>();
// map of network to selected X-bricks
Map<URI, List<String>> networkToSelectedXbricks = new HashMap<URI, List<String>>();
do {
Map<URI, List<StoragePort>> useablePortsSet = getUsablePortsSet(allocatablePorts, orderedNetworks, usedPorts,
xBricksToSelectedSCs, networkToSelectedXbricks, networkMap, allocator, sanZoningEnabled, switchToPortNumber,
contextMap);
if (useablePortsSet == null) {
// if requirement not satisfied
break;
}
for (URI networkURI : useablePortsSet.keySet()) {
if (!useablePorts.containsKey(networkURI)) {
useablePorts.put(networkURI, new ArrayList<List<StoragePort>>());
}
useablePorts.get(networkURI).add(useablePortsSet.get(networkURI));
}
} while (!isAllPortsLooped(orderedNetworks, allocatablePorts, usedPorts));
int numPG = XTREMIO_NUM_PORT_GROUP;
_log.info(String.format("Number of Port Groups: %d", numPG));
portGroups.add(useablePorts);
_log.info("Selected network to ports set: {}", useablePorts.entrySet());
// get number of X-bricks from selected ports
xtremIOXbricksCount = getXbricksCount(useablePorts);
return portGroups;
}
/**
* Returns a Set of Storage Ports selected equally across networks. Minimum of 2 and maximum of 4 storage ports.
* It returns null when all storage ports have been processed and the minimum requirement is not met.
*
* @param allocatablePorts
* the allocatable ports
* @param orderedNetworks
* the ordered networks
* @param usedPorts
* the used ports
* @param networkToSelectedXbricks
* @param xBricksToSelectedSCs
* @param networkMap
* @param allocator
* Storage Ports Allocator
* @param sanZoningEnabled
* on vArray
* @return the usable ports set
*/
private Map<URI, List<StoragePort>> getUsablePortsSet(Map<URI, List<StoragePort>> allocatablePorts, List<URI> orderedNetworks,
Set<String> usedPorts, Map<String, List<String>> xBricksToSelectedSCs, Map<URI, List<String>> networkToSelectedXbricks,
Map<URI, NetworkLite> networkMap, StoragePortsAllocator allocator, boolean sanZoningEnabled,
Map<URI, Map<String, Integer>> switchToPortNumber,
Map<URI, PortAllocationContext> contextMap) {
Map<URI, List<StoragePort>> useablePorts = new HashMap<URI, List<StoragePort>>();
Set<String> usedPortsSet = new HashSet<String>();
Set<String> portsSelected = new HashSet<String>(usedPorts);
do {
int previousSize = usedPortsSet.size();
Iterator<URI> networkItr = orderedNetworks.iterator();
while (networkItr.hasNext() && usedPortsSet.size() < MAXIMUM_NUMBER_OF_STORAGE_PORTS_PER_SET) {
URI networkURI = networkItr.next();
_log.debug(String.format("network: %s, xBricksToSelectedSCs: %s, networkToSelectedXbricks: %s",
networkURI, xBricksToSelectedSCs.entrySet(), networkToSelectedXbricks.get(networkURI)));
NetworkLite net = networkMap.get(networkURI);
// Determine if we should check connectivity from the Network's varray.auto_san_zoning
boolean checkConnectivity = sanZoningEnabled && !StorageProtocol.Transport.IP.name().equals(net.getTransportType());
Map<String, Integer> switchNumberMap = null;
PortAllocationContext context = null;
if (switchToPortNumber != null) {
switchNumberMap = switchToPortNumber.get(networkURI);
}
if (contextMap != null) {
context = contextMap.get(networkURI);
}
StoragePort port = getNetworkPortUniqueXbrick(networkURI, allocatablePorts.get(networkURI),
portsSelected, networkToSelectedXbricks, xBricksToSelectedSCs, allocator, checkConnectivity,
switchNumberMap, context);
_log.debug("Port selected {} for network {}", port != null ? port.getPortName() : null, networkURI);
if (port != null) {
usedPortsSet.add(port.getPortName());
portsSelected.add(port.getPortName());
if (!useablePorts.containsKey(networkURI)) {
useablePorts.put(networkURI, new ArrayList<StoragePort>());
}
useablePorts.get(networkURI).add(port);
}
}
// If No ports have been selected in this round, then clear the X-bricks map
if (previousSize == usedPortsSet.size()) {
xBricksToSelectedSCs.clear();
networkToSelectedXbricks.clear();
}
_log.debug("Ports selected so far : {}", usedPortsSet);
} while (usedPortsSet.size() < MAXIMUM_NUMBER_OF_STORAGE_PORTS_PER_SET
&& !isAllPortsLooped(orderedNetworks, allocatablePorts, portsSelected));
_log.info("Set Done: Ports selected in this set: {}", usedPortsSet);
if (usedPortsSet.size() < REQUIRED_MINIMUM_NUMBER_OF_STORAGE_PORTS_PER_SET) {
return null; // requirement not met
}
// if usedPortsSet.size() >= 2, satisfies minimum requirement, min 2 paths
// add to all usedPorts list
usedPorts.addAll(usedPortsSet);
return useablePorts;
}
/**
* Gets a storage port for the given network from Unique X-brick/SC.
*
* @param networkURI
* the network uri
* @param storagePorts
* the storage ports
* @param usedPorts
* the used ports
* @param networkToSelectedXbricks
* the network to selected x-bricks
* @param xBricksToSelectedSCs
* the x-bricks to selected SCs
* @param allocator
* Storage Port Allocator
* @param checkConnectivity
* @return the network port unique x brick
*/
private StoragePort getNetworkPortUniqueXbrick(URI networkURI, List<StoragePort> storagePorts, Set<String> usedPorts,
Map<URI, List<String>> networkToSelectedXbricks, Map<String, List<String>> xBricksToSelectedSCs,
StoragePortsAllocator allocator, boolean checkConnectivity, Map<String, Integer> switchNumberMap,
PortAllocationContext context) {
/**
* Input:
* -List of network's storage ports;
* -X-bricks already chosen for this network;
* -X-bricks already chosen for all networks with StorageControllers (SC) chosen:
*
* Choose a storage port based on below logic:
* -See if there is a port from X-brick other than allNetworkXbricks (select different SC for the selected
* X-brick)
* -If not, see if there is a port from X-brick other than networkXbricks (select different SC for the selected
* X-brick)
*/
StoragePort port = null;
if (networkToSelectedXbricks.get(networkURI) == null) {
networkToSelectedXbricks.put(networkURI, new ArrayList<String>());
}
boolean isSwitchAffinity = false;
Map<StoragePort, String> portSwitchMap = null;
if (switchNumberMap != null) {
// switch affinity is on
isSwitchAffinity = true;
if (!simulation) {
portSwitchMap = getPortSwitchMap(storagePorts);
} else if (simulation && context != null) {
portSwitchMap = allocator.getPortSwitchMap(context);
}
}
List<StoragePort> uniqueXBrickPorts = new ArrayList<StoragePort>();
for (StoragePort sPort : storagePorts) {
// Do not choose a port that has already been chosen
if (!usedPorts.contains(sPort.getPortName()) && isPortConnected(allocator, sPort, checkConnectivity)) {
String[] splitArray = sPort.getPortGroup().split(Constants.HYPHEN);
String xBrick = splitArray[0];
String sc = splitArray[1];
// select port from unique X-brick/SC
if (!xBricksToSelectedSCs.containsKey(xBrick)) {
if (isSwitchAffinity) {
uniqueXBrickPorts.add(sPort);
} else {
port = sPort;
addSCToXbrick(xBricksToSelectedSCs, xBrick, sc);
networkToSelectedXbricks.get(networkURI).add(xBrick);
break;
}
}
}
}
if (!uniqueXBrickPorts.isEmpty()) {
port = getSwitchAffinityPort(uniqueXBrickPorts, switchNumberMap, portSwitchMap, networkToSelectedXbricks,
xBricksToSelectedSCs, networkURI);
}
if (port == null) {
uniqueXBrickPorts.clear();
for (StoragePort sPort : storagePorts) {
// Do not choose a port that has already been chosen
if (!usedPorts.contains(sPort.getPortName()) && isPortConnected(allocator, sPort, checkConnectivity)) {
String[] splitArray = sPort.getPortGroup().split(Constants.HYPHEN);
String xBrick = splitArray[0];
String sc = splitArray[1];
// select port from unique X-brick/SC for this network
if (!networkToSelectedXbricks.get(networkURI).contains(xBrick)
&& (xBricksToSelectedSCs.get(xBrick) == null || !xBricksToSelectedSCs.get(xBrick).contains(sc))) {
if (isSwitchAffinity) {
uniqueXBrickPorts.add(sPort);
} else {
port = sPort;
addSCToXbrick(xBricksToSelectedSCs, xBrick, sc);
networkToSelectedXbricks.get(networkURI).add(xBrick);
break;
}
}
}
}
if (!uniqueXBrickPorts.isEmpty()) {
port = getSwitchAffinityPort(uniqueXBrickPorts, switchNumberMap, portSwitchMap, networkToSelectedXbricks,
xBricksToSelectedSCs, networkURI);
}
}
return port;
}
/**
* Checks if storage port is connected depends on Network's varray.auto_san_zoning.
*
*/
private boolean isPortConnected(StoragePortsAllocator allocator, StoragePort sPort, boolean checkConnectivity) {
if (checkConnectivity && (PlacementUtils.getSwitchName(sPort, _dbClient) == null)) {
return false;
}
return true;
}
/**
* Adds the selected Storage controller to X-brick in the xBricksToSelectedSCs map.
*/
private void addSCToXbrick(Map<String, List<String>> xBricksToSelectedSCs, String xBrick, String storageController) {
if (xBricksToSelectedSCs.get(xBrick) == null) {
xBricksToSelectedSCs.put(xBrick, new ArrayList<String>());
}
if (!xBricksToSelectedSCs.get(xBrick).contains(storageController)) {
xBricksToSelectedSCs.get(xBrick).add(storageController);
}
}
/**
* Checks if is all given ports have been processed or selected.
*/
private boolean isAllPortsLooped(List<URI> orderedNetworks, Map<URI, List<StoragePort>> allocatablePorts, Set<String> usedPorts) {
for (URI networkURI : orderedNetworks) {
for (StoragePort port : allocatablePorts.get(networkURI)) {
if (!usedPorts.contains(port.getPortName())) {
return false;
}
}
}
return true;
}
/**
* Order the networks from those with least ports to those with most ports.
*
* @param allocatablePorts
* -- Map of Network URI to list of ports
* @return ordered list of Network URIs
*/
private List<URI> orderNetworksByNumberOfPorts(Map<URI, List<StoragePort>> allocatablePorts) {
List<URI> orderedNetworks = new ArrayList<URI>();
Map<Integer, Set<URI>> numPortsToNetworkSet = new HashMap<Integer, Set<URI>>();
for (URI networkURI : allocatablePorts.keySet()) {
int numPorts = allocatablePorts.get(networkURI).size();
if (numPortsToNetworkSet.get(numPorts) == null) {
numPortsToNetworkSet.put(numPorts, new HashSet<URI>());
}
numPortsToNetworkSet.get(numPorts).add(networkURI);
}
for (Set<URI> networkURIs : numPortsToNetworkSet.values()) {
orderedNetworks.addAll(networkURIs);
}
return orderedNetworks;
}
/**
* Gets the number of X-bricks from the selected ports.
*
* @param useablePorts
* the port groups
* @return the xbricks count
*/
private int getXbricksCount(Map<URI, List<List<StoragePort>>> useablePorts) {
Set<String> xBricks = new HashSet<String>();
for (List<List<StoragePort>> portSet : useablePorts.values()) {
for (List<StoragePort> ports : portSet) {
for (StoragePort port : ports) {
xBricks.add(port.getPortGroup().split(Constants.HYPHEN)[0]);
}
}
}
return xBricks.size();
}
private List<StoragePort> allocatePorts(StoragePortsAllocator allocator,
List<StoragePort> candidatePorts, int portsRequested, NetworkLite net, URI varrayURI,
Map<String, Integer> switchToPortNumber, PortAllocationContext context) {
return VPlexBackEndOrchestratorUtil.allocatePorts(allocator, candidatePorts, portsRequested, net, varrayURI,
simulation, _blockScheduler, _dbClient, switchToPortNumber, context);
}
@Override
public StringSetMap configureZoning(Map<URI, List<List<StoragePort>>> portGroup,
Map<String, Map<URI, Set<Initiator>>> initiatorGroup, Map<URI, NetworkLite> networkMap,
StoragePortsAssigner assigner, Map<URI, String> initiatorSwitchMap,
Map<URI, Map<String, List<StoragePort>>> switchStoragePortsMap,
Map<URI, String> portSwitchMap) {
StringSetMap zoningMap = new StringSetMap();
// Set up a map to track port usage so that we can use all ports more or less equally.
Map<StoragePort, Integer> portUsage = new HashMap<StoragePort, Integer>();
// Iterate through each of the directors, matching each of its initiators
// with one port. This will ensure not to violate four paths per director.
// select number of paths per VPLEX director
// if X-bricks count is less than director count, choose only 2 initiators from each director
// leaving other initiators for future scale of X-bricks
int pathsPerDirector = DEFAULT_NUMBER_OF_PATHS_PER_VPLEX_DIRECTOR; // default 4 initiators in director
if (xtremIOXbricksCount < vplexDirectorCount) {
pathsPerDirector = MINIMUM_NUMBER_OF_PATHS_PER_VPLEX_DIRECTOR;
}
_log.info(String.format("VPLEX Directors: %s, X-bricks: %s, Number of paths per VPLEX Director: %s", vplexDirectorCount,
xtremIOXbricksCount, pathsPerDirector));
boolean isSwitchAffinity = false;
if (initiatorSwitchMap != null && !initiatorSwitchMap.isEmpty() &&
switchStoragePortsMap != null && !switchStoragePortsMap.isEmpty()) {
isSwitchAffinity = true;
}
int directorNumber = 1;
for (String director : initiatorGroup.keySet()) {
// split initiators across networks depending on number of paths per director
int numberOfNetworksForDirector = initiatorGroup.get(director).keySet().size();
int initiatorsPerNetworkForDirector = pathsPerDirector / numberOfNetworksForDirector;
_log.info("Number of Initiators that must be chosen per network for a director: {}", initiatorsPerNetworkForDirector);
for (URI networkURI : initiatorGroup.get(director).keySet()) {
int numberOfInitiatorsPerNetwork = 0;
NetworkLite net = networkMap.get(networkURI);
for (Initiator initiator : initiatorGroup.get(director).get(networkURI)) {
// If there are no ports on the initiators network, too bad...
if (portGroup.get(networkURI) == null) {
_log.info(String.format("%s -> no ports in network",
initiator.getInitiatorPort()));
continue;
}
// if desired number of initiator paths chosen for network
if (numberOfInitiatorsPerNetwork >= initiatorsPerNetworkForDirector) {
_log.info(String.format("Maximum paths per network %s (%d) reached for Director %s",
net.getLabel(), numberOfInitiatorsPerNetwork, director));
break;
}
List<StoragePort> assignablePorts = null;
if (isSwitchAffinity) {
// find the ports with the same switch as the initiator
String switchName = initiatorSwitchMap.get(initiator.getId());
if (!switchName.equals(NullColumnValueGetter.getNullStr())) {
Map<String, List<StoragePort>>switchMap = switchStoragePortsMap.get(networkURI);
if (switchMap != null) {
List<StoragePort> switchPorts = switchMap.get(switchName);
if (switchPorts != null && !switchPorts.isEmpty()) {
_log.info(String.format("Found the same switch ports, switch is %s", switchName));
assignablePorts = switchPorts;
}
}
}
}
List<StoragePort> portList = getStoragePortSetForDirector(portGroup.get(networkURI), directorNumber);
if (assignablePorts != null) {
assignablePorts.retainAll(portList);
}
if (assignablePorts == null || assignablePorts.isEmpty()) {
assignablePorts = portList;
}
// find a port for the initiator
StoragePort storagePort = VPlexBackEndOrchestratorUtil.assignPortToInitiator(assigner,
assignablePorts, net, initiator, portUsage, null);
if (storagePort != null) {
_log.info(String.format("%s %s %s %s -> %s %s %s", director, net.getLabel(),
initiator.getInitiatorPort(), initiatorSwitchMap.get(initiator.getId()), storagePort.getPortNetworkId(),
storagePort.getPortName(), portSwitchMap.get(storagePort.getId())));
StringSet ports = new StringSet();
ports.add(storagePort.getId().toString());
zoningMap.put(initiator.getId().toString(), ports);
numberOfInitiatorsPerNetwork++;
} else {
_log.info(String.format("A port could not be assigned for %s %s %s", director, net.getLabel(),
initiator.getInitiatorPort()));
}
}
}
directorNumber++;
}
return zoningMap;
}
/**
* Gets the storage port set for director.
*
* @param list
* the list
* @param directorNumber
* the director number
* @return the storage port set for director
*/
private List<StoragePort> getStoragePortSetForDirector(List<List<StoragePort>> list, int directorNumber) {
List<StoragePort> portsSetForDirector = null;
Iterator<List<StoragePort>> itr = list.iterator();
for (int i = 1; i <= directorNumber; i++) {
if (!itr.hasNext()) {
itr = list.iterator();
}
portsSetForDirector = itr.next();
}
return portsSetForDirector;
}
@Override
public Method createOrAddVolumesToExportMaskMethod(URI arrayURI, URI exportGroupURI, URI exportMaskURI,
Map<URI, Integer> volumeMap, List<URI> initiatorURIs, TaskCompleter completer) {
return new Workflow.Method("createOrAddVolumesToExportMask", arrayURI,
exportGroupURI, exportMaskURI, volumeMap, initiatorURIs, completer);
}
@Override
public void createOrAddVolumesToExportMask(URI arrayURI, URI exportGroupURI, URI exportMaskURI,
Map<URI, Integer> volumeMap, List<URI> initiatorURIs, TaskCompleter completer, String stepId) {
try {
WorkflowStepCompleter.stepExecuting(stepId);
StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI);
ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI);
// If the exportMask isn't found, or has been deleted, fail, ask user to retry.
if (exportMask == null || exportMask.getInactive()) {
_log.info(String.format("ExportMask %s deleted or inactive, failing", exportMaskURI));
ServiceError svcerr = VPlexApiException.errors
.createBackendExportMaskDeleted(exportMaskURI.toString(), arrayURI.toString());
WorkflowStepCompleter.stepFailed(stepId, svcerr);
return;
}
// Protect concurrent operations by locking {host, array} dupple.
// Lock will be released when workflow step completes.
List<String> lockKeys = ControllerLockingUtil.getHostStorageLockKeys(
_dbClient, ExportGroupType.Host,
StringSetUtil.stringSetToUriList(exportMask.getInitiators()), arrayURI);
getWorkflowService().acquireWorkflowStepLocks(stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT));
BlockStorageDevice device = _blockController.getDevice(array.getSystemType());
if (!exportMask.hasAnyVolumes()) {
// We are creating this ExportMask on the hardware! (Maybe not
// the first time though...)
// Fetch the Initiators
List<Initiator> initiators = new ArrayList<Initiator>();
for (String initiatorId : exportMask.getInitiators()) {
Initiator initiator = _dbClient.queryObject(Initiator.class,
URI.create(initiatorId));
if (initiator != null) {
initiators.add(initiator);
}
}
// Fetch the targets
List<URI> targets = new ArrayList<URI>();
for (String targetId : exportMask.getStoragePorts()) {
targets.add(URI.create(targetId));
}
if (volumeMap != null) {
for (URI volume : volumeMap.keySet()) {
exportMask.addVolume(volume, volumeMap.get(volume));
}
}
_dbClient.persistObject(exportMask);
device.doExportCreate(array, exportMask, volumeMap, initiators, targets,
completer);
} else {
List<Initiator> initiators = new ArrayList<Initiator>();
for (String initiatorId : exportMask.getInitiators()) {
Initiator initiator = _dbClient.queryObject(Initiator.class,
URI.create(initiatorId));
if (initiator != null) {
initiators.add(initiator);
}
}
device.doExportAddVolumes(array, exportMask, initiators, volumeMap, completer);
}
} catch (Exception ex) {
_log.error("Failed to create or add volumes to export mask for vmax: ", ex);
VPlexApiException vplexex = DeviceControllerExceptions.vplex
.addStepsForCreateVolumesFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, vplexex);
}
}
@Override
public Method deleteOrRemoveVolumesFromExportMaskMethod(URI arrayURI,
URI exportGroupURI, URI exportMaskURI,
List<URI> volumes, List<URI> initiatorURIs) {
return new Workflow.Method("deleteOrRemoveVolumesFromExportMask", arrayURI,
exportGroupURI, exportMaskURI, volumes, initiatorURIs);
}
@Override
public void deleteOrRemoveVolumesFromExportMask(URI arrayURI, URI exportGroupURI, URI exportMaskURI,
List<URI> volumes, List<URI> initiatorURIs, String stepId) {
ExportTaskCompleter completer = null;
try {
completer = new ExportMaskOnlyRemoveVolumeCompleter(exportGroupURI,
exportMaskURI, volumes, stepId);
WorkflowStepCompleter.stepExecuting(stepId);
StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI);
BlockStorageDevice device = _blockController.getDevice(array.getSystemType());
ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI);
// If the exportMask isn't found, or has been deleted, nothing to do.
if (exportMask == null || exportMask.getInactive()) {
_log.info(String.format("ExportMask %s inactive, returning success", exportMaskURI));
completer.ready(_dbClient);
return;
}
// Protect concurrent operations by locking {host, array} dupple.
// Lock will be released when workflow step completes.
List<String> lockKeys = ControllerLockingUtil.getHostStorageLockKeys(
_dbClient, ExportGroupType.Host,
StringSetUtil.stringSetToUriList(exportMask.getInitiators()), arrayURI);
getWorkflowService().acquireWorkflowStepLocks(stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT));
// Determine if we're deleting the last volume in the mask.
StringMap maskVolumesMap = exportMask.getVolumes();
Set<String> remainingVolumes = new HashSet<String>();
List<URI> passedVolumesInMask = new ArrayList<>(volumes);
if (maskVolumesMap != null) {
remainingVolumes.addAll(maskVolumesMap.keySet());
}
for (URI volume : volumes) {
remainingVolumes.remove(volume.toString());
// Remove any volumes from the volume list that are no longer
// in the export mask. When a failure occurs removing a backend
// volume from a mask, the rollback method will try and remove it
// again. However, in the case of a distributed volume, one side
// may have succeeded, so we will try and remove it again. Previously,
// this was not a problem. However, new validation exists at the
// block level that checks to make sure the volume to remove is
// actually in the mask, which now causes a failure when you remove
// it a second time. So, we check here and remove any volumes that
// are not in the mask to handle this condition.
if ((maskVolumesMap != null) && (!maskVolumesMap.keySet().contains(volume.toString()))){
passedVolumesInMask.remove(volume);
}
}
// None of the volumes is in the export mask, so we are done.
if (passedVolumesInMask.isEmpty()) {
_log.info("None of these volumes {} are in export mask {}", volumes, exportMask.forDisplay());
completer.ready(_dbClient);
return;
}
// If it is last volume and there are no existing volumes, delete the ExportMask.
if (remainingVolumes.isEmpty()
&& !exportMask.hasAnyExistingVolumes()) {
device.doExportDelete(array, exportMask, passedVolumesInMask, initiatorURIs, completer);
} else {
List<Initiator> initiators = null;
if (initiatorURIs != null && !initiatorURIs.isEmpty()) {
initiators = _dbClient.queryObject(Initiator.class, initiatorURIs);
}
device.doExportRemoveVolumes(array, exportMask, passedVolumesInMask, initiators, completer);
}
completer.ready(_dbClient);
} catch (Exception ex) {
_log.error("Failed to delete or remove volumes to export mask for vmax: ", ex);
VPlexApiException vplexex = DeviceControllerExceptions.vplex
.addStepsForCreateVolumesFailed(ex);
completer.error(_dbClient, vplexex);
}
}
public void setSimulation(boolean simulation) {
this.simulation = simulation;
}
public WorkflowService getWorkflowService() {
return _workflowService;
}
@Override
public void setWorkflowService(WorkflowService _workflowService) {
this._workflowService = _workflowService;
}
/**
* Get storage port to switch name map
*
* @param storagePorts all storage ports
* @return the map of storage port to switch name
*/
private Map<StoragePort, String> getPortSwitchMap(List<StoragePort> storagePorts) {
Map<StoragePort, String> result = new HashMap<StoragePort, String>();
for (StoragePort port: storagePorts) {
String switchName = PlacementUtils.getSwitchName(port, _dbClient);
if (switchName != null && !switchName.isEmpty()) {
result.put(port, switchName);
}
}
return result;
}
/**
* Get the first storage port whose switch name exists in the switchNumberMap if possible, otherwise, return the first
* port in the input port list.
*
* @param ports ports to be selected
* @param switchNumberMap switch name to number of ports map
* @param portSwitchMap port to switch name map
* @param networkToSelectedXbricks will be updated after the port is selected
* @param xBricksToSelectedSCs will be updated after the port is selected
* @return
*/
private StoragePort getSwitchAffinityPort(List<StoragePort>ports, Map<String, Integer>switchNumberMap,
Map<StoragePort, String> portSwitchMap, Map<URI, List<String>> networkToSelectedXbricks,
Map<String, List<String>> xBricksToSelectedSCs, URI netURI) {
StoragePort result = null;
if (portSwitchMap == null || switchNumberMap == null || ports == null || ports.isEmpty()) {
return result;
}
for (StoragePort port : ports) {
String switchName = portSwitchMap.get(port);
if (switchName == null) {
continue;
}
Integer count = switchNumberMap.get(switchName);
if (count != null && count > 0) {
result = port;
// update the switchNumberMap
if (count > 1) {
switchNumberMap.put(switchName, count--);
} else {
switchNumberMap.remove(switchName);
}
break;
}
}
if (result == null) {
result = ports.get(0);
_log.info(String.format("Not found the same switch port: %s", result.getPortName()));
}
String[] splitArray = result.getPortGroup().split(Constants.HYPHEN);
String xBrick = splitArray[0];
String sc = splitArray[1];
addSCToXbrick(xBricksToSelectedSCs, xBrick, sc);
networkToSelectedXbricks.get(netURI).add(xBrick);
return result;
}
}