/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.cinder;
import java.io.IOException;
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.cinder.CinderConstants;
import com.emc.storageos.cinder.model.VolumeAttachResponse;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.CompatibilityStatus;
import com.emc.storageos.db.client.model.DiscoveredDataObject.DiscoveryStatus;
import com.emc.storageos.db.client.model.StorageHADomain;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StoragePort.OperationalStatus;
import com.emc.storageos.db.client.model.StoragePort.PortType;
import com.emc.storageos.db.client.model.StorageProtocol;
import com.emc.storageos.db.client.model.StorageProtocol.Block;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
import com.emc.storageos.volumecontroller.impl.StoragePortAssociationHelper;
/**
* This class will be used to perform cinder storage port operations
*
* 1. Modify : To modify the existing storage port with the actual identifier after the
* volume export/attach goes through fine, the response of the export/attach would
* give us the actual port information.
*
* 2. Create : To create additional ports for a storage system after the volume export/
* attach goes through fine, the response for FC would contain the host WWPN to storage
* port WWPN mapping. For iSCSI, if the successful export returns new IQN which is not
* present in the ViPR, it will be enumerated as new IP port.
*
*
*/
public class CinderStoragePortOperations
{
private static final Logger logger = LoggerFactory.getLogger(CinderStoragePortOperations.class);
private StorageSystem storageSystem = null;
private DbClient dbClient = null;
private List<StoragePort> allStoragePortsList = null;
private List<StoragePort> newStoragePortsList = new ArrayList<StoragePort>();
private List<StoragePort> modifiedStoragePortsList = new ArrayList<StoragePort>();;
// Map that contains instances of CinderStoragePortOperations
private static Map<URI, CinderStoragePortOperations> instancesMap = new HashMap<URI, CinderStoragePortOperations>();
private CinderStoragePortOperations(StorageSystem system, DbClient dbc)
{
storageSystem = system;
dbClient = dbc;
}
/**
* Gets the instance from the map if already created, otherwise creates one
*
* @param system
* @param response
* @return
*/
public static CinderStoragePortOperations getInstance(StorageSystem system, DbClient dbc)
{
CinderStoragePortOperations instance = instancesMap.get(system.getId());
if (null == instance)
{
synchronized (instancesMap)
{
if (null == instance)
{
instance = new CinderStoragePortOperations(system, dbc);
instancesMap.put(system.getId(), instance);
}
}
}
return instance;
}
/**
* Invokes the FC or iSCSI ports operation
* based on the type of the export/attach operation
*
* @param attachResponse
* @throws IOException
*/
public void invoke(VolumeAttachResponse attachResponse)
{
logger.info("Cinder Storage Port Invoke Operation Started for" +
" the storage system : {}", storageSystem.getId());
synchronized (this)
{
try
{
// Get the transport type
String protocolType = attachResponse.connection_info.driver_volume_type;
Map<String, List<String>> initiatorTargetMap = null;
if (CinderConstants.ATTACH_RESPONSE_FC_TYPE.equalsIgnoreCase(protocolType))
{
initiatorTargetMap = attachResponse.connection_info.data.initiator_target_map;
if (null != initiatorTargetMap && !initiatorTargetMap.isEmpty()) {
logger.debug("FC Initiator and Target mappings : {} ", initiatorTargetMap.toString());
performFCOperation(initiatorTargetMap);
}
}
String iqn = null;
if (CinderConstants.ATTACH_RESPONSE_ISCSI_TYPE.equalsIgnoreCase(protocolType))
{
iqn = attachResponse.connection_info.data.target_iqn;
logger.debug("iSCSI target IQN is :{}", iqn);
performISCSIOperation(iqn);
}
// Update the port to network associations for modified ports and newly created ports.
if (!modifiedStoragePortsList.isEmpty())
{
StoragePortAssociationHelper.updatePortAssociations(modifiedStoragePortsList, dbClient);
}
if (!newStoragePortsList.isEmpty())
{
StoragePortAssociationHelper.updatePortAssociations(newStoragePortsList, dbClient);
}
} catch (Exception e) {
logger.error("There is an error while creating/modifying ports after export/attach," +
" Reason:" + e.getMessage(), e);
} finally {
// clear modified and new ports list
modifiedStoragePortsList.clear();
newStoragePortsList.clear();
}
}
logger.info("Cinder Storage Port Invoke Operation completed for" +
" the storage system :{} ", storageSystem.getId());
}
/**
* Creates or modifies IP/iSCSI ports
*
* Steps-
* 1. Get all iSCSI ports of the storage system.
* 2. If single port is present -
* a) Check if it contains valid IQN as port network Id.
* b) If it does not contain the valid IQN, then just update its port network id.
* c) If it contains the valid IQN as port network id and that port network id
* does not equal the one received in the response, then create new port.
* 3. If there are more than one ports
* a) Form a list of port network ids by iterating all storage port instances.
* b) Check if the IQN is present in the list constructed in 3.a.
* c) If it is not present in the list 3.a, then create a new port.
*
* @param iqn
*/
private void performISCSIOperation(String iqn)
{
logger.info("Start iSCSI Ports create/modify operations," +
" for storage system : {} ", storageSystem.getId());
List<StoragePort> iscsiPorts = getISCSIPorts();
if (iscsiPorts.size() == 1)
{
// If there is only a port, check if the networkId is the IQN
// If it is not IQN, then update it with the IQN received in the response.
StoragePort singlePort = iscsiPorts.get(0);
String portNetworkId = singlePort.getPortNetworkId();
if (!StorageProtocol.checkInitiator(Block.iSCSI.name(), null, portNetworkId))
{
modify(singlePort, iqn);
}
else
{
// It is IQN, check if the port's IQN matches with the IQN received
// If it does not match, just create the new IP port.
if (!portNetworkId.equalsIgnoreCase(iqn))
{
create(iqn, StorageProtocol.Transport.IP.name());
}
}
}
else
{
List<String> portNetworkIdsList = new ArrayList<String>();
// If there are more than 1 ports, construct a list of portNetworkIds
for (StoragePort port : iscsiPorts)
{
portNetworkIdsList.add(port.getPortNetworkId());
}
// Now, check if the IQN in the response is already present in the
// existing portNetworkIds list. If it is not present, then create new IP port.
if (!portNetworkIdsList.contains(iqn))
{
create(iqn, StorageProtocol.Transport.IP.name());
}
}
logger.info("End iSCSI Ports create/modify operations," +
" for storage system :{}", storageSystem.getId());
}
/**
* Creates or modifies FC ports
*
* Steps-
* 1. Merge all target WWNs into a single list.
* 2. Get all FC ports of storage system.
* 3. From the list constructed in step 1, remove all existing port network Ids
* 4. Create storage port instances for the remaining WWNs in the merged list.
*
* @param initiatorTargetMap
*/
private void performFCOperation(Map<String, List<String>> initiatorTargetMap)
{
logger.info("Start FC Ports create/modify operations," +
" for storage system:{} ", storageSystem.getId());
// Merge all target wwns into a single list
Set<String> mergedWwnSet = new HashSet<String>();
Set<String> keys = initiatorTargetMap.keySet();
for (String key : keys)
{
List<String> wwnList = initiatorTargetMap.get(key);
for (String wwn : wwnList)
{
String formattedWwn = addColon(wwn);
mergedWwnSet.add(formattedWwn);
}
}
// Get all existing FC storage ports
List<StoragePort> fcPorts = getFCPorts();
// Remove the existing storage port wwns from the merged list
for (StoragePort fcPort : fcPorts)
{
String portNetworkId = fcPort.getPortNetworkId();
if (mergedWwnSet.contains(portNetworkId))
{
mergedWwnSet.remove(portNetworkId);
}
}
// Now, create new ports for each item present in the final mergedWwnList
for (String targetStoragePortWWN : mergedWwnSet)
{
create(targetStoragePortWWN, StorageProtocol.Transport.FC.name());
}
logger.info("End FC Ports create/modify operations," +
" for storage system : {}", storageSystem.getId());
}
/**
* Modify the port with new port network Id.
*
* @param port
* @param portNetworkId
*/
private void modify(StoragePort port, String portNetworkId)
{
port.setPortNetworkId(portNetworkId);
dbClient.persistObject(port);
modifiedStoragePortsList.add(port);
}
/**
* Creates an instance of new storage port
* and associates it with the storage system.
*
* @param portNetworkId
* @param transportType
*/
private void create(String portNetworkId, String transportType)
{
logger.info("Start create storage port for portNetworkId={}" +
" and transportType={}", portNetworkId, transportType);
StorageHADomain adapter = CinderUtils.getStorageAdapter(storageSystem, dbClient);
StoragePort port = new StoragePort();
port.setId(URIUtil.createId(StoragePort.class));
port.setStorageDevice(storageSystem.getId());
String portName = generatePortName();
logger.debug("New storage port name is = {}", portName);
String nativeGuid = NativeGUIDGenerator.generateNativeGuid(storageSystem,
portName, NativeGUIDGenerator.PORT);
port.setNativeGuid(nativeGuid);
port.setPortNetworkId(portNetworkId);
port.setStorageHADomain(adapter.getId());
port.setRegistrationStatus(DiscoveredDataObject.RegistrationStatus.REGISTERED
.toString());
// always treat it as a frontend port
port.setPortType(PortType.frontend.name());
port.setOperationalStatus(OperationalStatus.OK.toString());
port.setTransportType(transportType);
port.setLabel(portName);
port.setPortName(portName);
port.setPortGroup("Cinder-PortGroup");
port.setCompatibilityStatus(CompatibilityStatus.COMPATIBLE.name());
port.setDiscoveryStatus(DiscoveryStatus.VISIBLE.name());
dbClient.createObject(port);
// Add it to the new ports list
newStoragePortsList.add(port);
// Add it to the local list
allStoragePortsList.add(port);
logger.info("End create storage port for portNetworkId={}" +
" and transportType={}", portNetworkId, transportType);
}
/**
* Gets list of active ports belonging to a storage system.
*
* @return
*/
private List<StoragePort> getStoragePortList(boolean isRefresh)
{
logger.debug("Start getStoragePortList");
if (null == allStoragePortsList || isRefresh)
{
allStoragePortsList = new ArrayList<StoragePort>();
URIQueryResultList storagePortURIs = new URIQueryResultList();
URI sysid = storageSystem.getId();
dbClient.queryByConstraint(ContainmentConstraint.Factory.
getStorageDeviceStoragePortConstraint(sysid),
storagePortURIs);
Iterator<URI> storagePortsIter = storagePortURIs.iterator();
while (storagePortsIter.hasNext())
{
URI storagePortURI = storagePortsIter.next();
StoragePort storagePort = dbClient.queryObject(StoragePort.class,
storagePortURI);
if (storagePort != null && !storagePort.getInactive())
{
allStoragePortsList.add(storagePort);
}
}
}
logger.debug("End getStoragePortList");
return allStoragePortsList;
}
private List<StoragePort> filterPortsByType(String transportType)
{
List<StoragePort> filteredList = new ArrayList<StoragePort>();
List<StoragePort> allPorts = getStoragePortList(false);
for (StoragePort port : allPorts)
{
if (transportType.equals(port.getTransportType()))
{
filteredList.add(port);
}
}
return filteredList;
}
/*
* Filters FC ports from all port list
*/
private List<StoragePort> getFCPorts()
{
return filterPortsByType(StorageProtocol.Transport.FC.name());
}
/*
* Filters iSCSI ports from all port list
*/
private List<StoragePort> getISCSIPorts()
{
return filterPortsByType(StorageProtocol.Transport.IP.name());
}
/**
* Generates the name for storage port.
*
* All Cinder storage system ports will be named as
*
* First port would have been named as "DEFAULT" during the
* first discovery, then onwards ports will be named as.
*
* 'Cinder Storage Port:0'
* 'Cinder Storage Port:1'
* 'Cinder Storage Port:2'
* and so on ....
*
* @return
*/
private String generatePortName()
{
StringBuffer portName = new StringBuffer("Cinder Storage Port:");
int portCount = allStoragePortsList.size();
portName = portName.append(String.valueOf(portCount - 1));
return portName.toString();
}
/**
* Formats the WWN by adding colon to it.
*
* @param wwn
* @return
*/
private String addColon(String wwn)
{
StringBuffer buf = new StringBuffer();
char[] charArray = wwn.toCharArray();
int count = 0;
for (char c : charArray)
{
if (count != 0 && count % 2 == 0)
{
buf.append(CinderConstants.COLON);
}
buf.append(c);
count++;
}
return buf.toString().toUpperCase();
}
}