/*
* Copyright (c) 2012-2015 iWave Software LLC
* All Rights Reserved
*/
package com.emc.sa.service.vipr.block;
import static com.emc.sa.service.ServiceParams.COPIES;
import static com.emc.sa.service.ServiceParams.HLU;
import static com.emc.sa.service.ServiceParams.HOST;
import static com.emc.sa.service.ServiceParams.MAX_PATHS;
import static com.emc.sa.service.ServiceParams.MIN_PATHS;
import static com.emc.sa.service.ServiceParams.PATHS_PER_INITIATOR;
import static com.emc.sa.service.ServiceParams.PROJECT;
import static com.emc.sa.service.ServiceParams.SNAPSHOTS;
import static com.emc.sa.service.ServiceParams.VIRTUAL_ARRAY;
import static com.emc.sa.service.ServiceParams.VOLUME;
import static com.emc.sa.service.ServiceParams.VOLUMES;
import static com.emc.sa.service.vipr.ViPRExecutionUtils.logInfo;
import static com.emc.sa.service.vipr.ViPRService.uris;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.emc.sa.engine.ExecutionUtils;
import com.emc.sa.engine.bind.Param;
import com.emc.sa.service.vipr.ViPRService;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.model.block.BlockObjectRestRep;
import com.emc.storageos.model.block.export.ExportGroupRestRep;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
*/
public class ExportBlockVolumeHelper {
@Param(HOST)
protected URI hostId;
@Param(value = VIRTUAL_ARRAY, required = false)
protected URI virtualArrayId;
@Param(PROJECT)
protected URI projectId;
@Param(value = VOLUMES, required = false)
protected List<String> volumeIds;
@Param(value = VOLUME, required = false)
protected String volumeId;
@Param(value = SNAPSHOTS, required = false)
protected List<String> snapshotIds;
@Param(value = COPIES, required = false)
protected List<String> copiesIds;
@Param(value = HLU, required = false)
protected Integer hlu;
@Param(value = MIN_PATHS, required = false)
protected Integer minPaths;
@Param(value = MAX_PATHS, required = false)
protected Integer maxPaths;
@Param(value = PATHS_PER_INITIATOR, required = false)
protected Integer pathsPerInitiator;
protected Host host;
protected Cluster cluster;
public void precheck() {
if (hlu == null) {
hlu = -1;
}
if (volumeId == null && volumeIds == null && snapshotIds == null && copiesIds == null) {
ExecutionUtils.fail("failTask.ExportBlockVolumeHelper.precheck", new Object[] {}, new Object[] { VOLUME, VOLUMES, SNAPSHOTS, COPIES });
}
precheckExportPathParameters(minPaths, maxPaths, pathsPerInitiator);
if (volumeIds == null || volumeIds.isEmpty() && volumeId != null) {
volumeIds = Collections.singletonList(volumeId);
}
if (BlockStorageUtils.isHost(hostId)) {
host = BlockStorageUtils.getHost(hostId);
}
else {
cluster = BlockStorageUtils.getCluster(hostId);
}
// Don't allow ViPR exports of block volumes (this may not fly as part of the create host services)
ViPRService.checkForBootVolumes(volumeIds);
BlockStorageUtils.checkEvents(host != null ? host : cluster);
}
/** convenience method for exporting volumes */
public List<ExportGroupRestRep> exportVolumes() {
return exportBlockResources(uris(volumeIds));
}
/**
* export the block resources identified by URIs in the given resource id list
*
* @param resourceIds the list of URIs which identify the block resources that need to be exported
* @return The list of export groups which have been created/updated
*/
public List<ExportGroupRestRep> exportBlockResources(List<URI> resourceIds) {
return exportBlockResources(resourceIds, null);
}
/**
* export the block resources identified by URIs in the given resource id list
*
* @param resourceIds the list of URIs which identify the block resources that need to be exported
* @param parentId the parent URI for the list of resourceIds
* @return The list of export groups which have been created/updated
*/
public List<ExportGroupRestRep> exportBlockResources(List<URI> resourceIds, URI parentId) {
// the list of exports to return
List<ExportGroupRestRep> exports = Lists.newArrayList();
List<URI> newVolumes = new ArrayList<URI>();
Map<URI, Set<URI>> addVolumeExports = Maps.newHashMap();
Map<URI, URI> addComputeResourceToExports = Maps.newHashMap();
// we will need to keep track of the current HLU number
Integer currentHlu = hlu;
// get a list of all block resources using the id list provided
List<BlockObjectRestRep> blockResources = BlockStorageUtils.getBlockResources(resourceIds, parentId);
URI virtualArrayId = null;
String exportName = cluster != null ? cluster.getLabel() : host.getHostName();
// Flag to indicate an empty ExportGroup object in ViPR, i.e., ExportGroup without any volumes and initiators in it.
boolean isEmptyExport = true;
ExportGroupRestRep export = null;
// For every block object,
// 1) check if there are existing ExportGroup object corresponding to the host/cluster.
// If yes, add the block object to the ExportGroup.
// 2) If there are no existing ViPR ExportGroup for the host/cluster, check if there is an
// ExportGroup object with name matching the host/cluster name.
// a) If the ExportGroup object is empty, add the block object and host/cluster to the ExportGroup.
// b) If the ExportGroup object is not empty, then create a new ExportGroup by appending a time stamp
// to the host/cluster name
// 3) If there is no ExportGroup found, create a new ExportGroup.
for (BlockObjectRestRep blockResource : blockResources) {
virtualArrayId = getVirtualArrayId(blockResource);
// see if we can find an export that uses this block resource
export = findExistingExportGroup(blockResource, virtualArrayId);
boolean createExport = export == null;
isEmptyExport = export != null && BlockStorageUtils.isEmptyExport(export);
// If did not find export group for the host/cluster, try find existing empty export with
// host/cluster name
if (export == null) {
export = BlockStorageUtils.findExportsByName(exportName, projectId, virtualArrayId);
isEmptyExport = export != null && BlockStorageUtils.isEmptyExport(export);
createExport = export == null || !isEmptyExport;
}
if (createExport) {
newVolumes.add(blockResource.getId());
}
// Export exists, check if volume belongs to it
else {
if (BlockStorageUtils.isVolumeInExportGroup(export, blockResource.getId())) {
logInfo("export.block.volume.contains.volume", export.getId(), blockResource.getId());
}
else {
updateExportVolumes(export, blockResource, addVolumeExports);
}
// Since the existing export can also be an empty export, also check if the host/cluster is present in the export.
// If not, add them.
if (isEmptyExport) {
URI computeResource = cluster != null ? cluster.getId() : host.getId();
addComputeResourceToExports.put(export.getId(), computeResource);
}
exports.add(export);
}
}
// If there is an existing non-empty export with the same name, append a time stamp to the name to make it unique
if (export != null && !isEmptyExport) {
exportName = exportName + BlockStorageUtils.UNDERSCORE + new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date());
}
// Bulk update multiple volumes to single export
List<URI> volumeIds = Lists.newArrayList();
for (Map.Entry<URI, Set<URI>> entry : addVolumeExports.entrySet()) {
volumeIds.addAll(entry.getValue());
}
Map<URI, Integer> volumeHlus = getVolumeHLUs(volumeIds);
for (Map.Entry<URI, Set<URI>> entry : addVolumeExports.entrySet()) {
BlockStorageUtils.addVolumesToExport(entry.getValue(), currentHlu, entry.getKey(), volumeHlus, minPaths, maxPaths,
pathsPerInitiator);
logInfo("export.block.volume.add.existing", entry.getValue(), entry.getKey());
if ((currentHlu != null) && (currentHlu > -1)) {
currentHlu += entry.getValue().size();
}
}
for (Map.Entry<URI, URI> entry : addComputeResourceToExports.entrySet()) {
if (cluster != null) {
BlockStorageUtils.addClusterToExport(entry.getKey(), cluster.getId(), minPaths, maxPaths, pathsPerInitiator);
logInfo("export.cluster.add.existing", entry.getValue(), entry.getKey());
} else {
BlockStorageUtils.addHostToExport(entry.getKey(), host.getId(), minPaths, maxPaths, pathsPerInitiator);
logInfo("export.host.add.existing", entry.getValue(), entry.getKey());
}
}
// Create new export with multiple volumes that don't belong to an export
if (!newVolumes.isEmpty()) {
volumeHlus = getVolumeHLUs(newVolumes);
URI exportId = null;
if (cluster != null) {
exportId = BlockStorageUtils.createClusterExport(exportName, projectId, virtualArrayId, newVolumes, currentHlu, cluster,
volumeHlus,
minPaths, maxPaths, pathsPerInitiator);
} else {
exportId = BlockStorageUtils.createHostExport(exportName, projectId, virtualArrayId, newVolumes, currentHlu, host, volumeHlus,
minPaths, maxPaths, pathsPerInitiator);
}
ExportGroupRestRep exportGroup = BlockStorageUtils.getExport(exportId);
// add this export to the list of exports we will return to the caller
exports.add(exportGroup);
}
// add host or cluster to the affected resources
if (host != null) {
ExecutionUtils.addAffectedResource(host.getId().toString());
} else if (cluster != null) {
ExecutionUtils.addAffectedResource(cluster.getId().toString());
}
// Clear the rollback at this point so later errors won't undo the exports
ExecutionUtils.clearRollback();
return exports;
}
private void updateExportVolumes(ExportGroupRestRep export, BlockObjectRestRep volume, Map<URI, Set<URI>> addVolumeExports) {
// Store mapping of export to volumes that will be bulk updated
Set<URI> value = addVolumeExports.get(export.getId());
if (value == null) {
value = new HashSet<URI>();
value.add(volume.getId());
} else {
value.add(volume.getId());
}
addVolumeExports.put(export.getId(), value);
}
private URI getVirtualArrayId(BlockObjectRestRep blockResource) {
// if we got a VArray from the form then we can just return that
if (virtualArrayId != null) {
return virtualArrayId;
}
else {
return BlockStorageUtils.getVirtualArrayId(blockResource);
}
}
private ExportGroupRestRep findExistingExportGroup(BlockObjectRestRep volume, URI virtualArrayId) {
if (cluster != null) {
return BlockStorageUtils.findExportByCluster(cluster, projectId, virtualArrayId, volume.getId());
}
// Attempt to find one (regardless of type) that contains the Volume
List<ExportGroupRestRep> exportGroups = BlockStorageUtils.findExportsContainingHost(host.getId(), projectId, virtualArrayId);
for (ExportGroupRestRep exportGroup : exportGroups) {
if (BlockStorageUtils.isVolumeInExportGroup(exportGroup, volume.getId())) {
return exportGroup;
}
}
// Didn't find it, so find a Host or Exclusive Type
for (ExportGroupRestRep exportGroup : exportGroups) {
if (!exportGroup.getType().equals("Cluster")) {
return exportGroup;
}
}
return null;
}
public URI getHostId() {
return hostId;
}
public List<String> getVolumeIds() {
return volumeIds;
}
protected Map<URI, Integer> getVolumeHLUs(List<URI> volumeIds) {
// only ExportVMwareBlockVolumeHelper supports setting HLUs for now
return Maps.newHashMap();
}
public static void precheckExportPathParameters(Integer minPaths, Integer maxPaths, Integer pathsPerInitiator) {
if (minPaths != null || maxPaths != null || pathsPerInitiator != null) {
if ((minPaths == null || maxPaths == null || pathsPerInitiator == null) || minPaths < 1 || maxPaths < 1
|| pathsPerInitiator < 1) {
ExecutionUtils.fail("failTask.exportPathParameters.precheck", new Object[] {}, new Object[] {});
} else if (minPaths > maxPaths) {
ExecutionUtils.fail("failTask.exportPathParameters.minPathsCheck", new Object[] {}, new Object[] {});
} else if (pathsPerInitiator > maxPaths) {
ExecutionUtils.fail("failTask.exportPathParameters.pathsPerInitiator", new Object[] {}, new Object[] {});
}
}
}
}