/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource.fullcopy;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.api.mapper.TaskMapper;
import com.emc.storageos.api.service.impl.placement.Scheduler;
import com.emc.storageos.api.service.impl.placement.StorageScheduler;
import com.emc.storageos.api.service.impl.placement.VolumeRecommendation;
import com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController;
import com.emc.storageos.blockorchestrationcontroller.VolumeDescriptor;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.VolumeRestRep;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.volumecontroller.BlockController;
import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper;
/**
* The default implementation for the block full copy API.
*/
public class DefaultBlockFullCopyApiImpl extends AbstractBlockFullCopyApiImpl {
// A reference to a logger.
private static final Logger s_logger = LoggerFactory.getLogger(DefaultBlockFullCopyApiImpl.class);
/**
* Constructor
*
* @param dbClient A reference to a database client.
* @param coordinator A reference to the coordinator client.
* @param scheduler A reference to the scheduler.
* @param fullCopyMgr A reference to the full copy manager.
*/
public DefaultBlockFullCopyApiImpl(DbClient dbClient, CoordinatorClient coordinator, Scheduler scheduler,
BlockFullCopyManager fullCopyMgr) {
super(dbClient, coordinator, scheduler, fullCopyMgr);
}
/**
* {@inheritDoc}
*/
@Override
public List<BlockObject> getAllSourceObjectsForFullCopyRequest(BlockObject fcSourceObj) {
return super.getAllSourceObjectsForFullCopyRequest(fcSourceObj);
}
/**
* {@inheritDoc}
*/
@Override
public void validateFullCopyCreateRequest(List<BlockObject> fcSourceObjList, int count) {
super.validateFullCopyCreateRequest(fcSourceObjList, count);
}
/**
* {@inheritDoc}
*/
@Override
public TaskList create(List<BlockObject> fcSourceObjList, VirtualArray varray,
String name, boolean createInactive, int count, String taskId) {
// Get the placement recommendations for the full copies and
// prepare the ViPR volumes to represent the full copies.
// TBD We are getting recommendations one at a time instead
// of for all full copies at the same time as was done
// previously. However, now we are allowing for creating
// full copies for multiple volume form a CG. These volumes
// could have different vpools and sizes. Therefore, I don't
// see how we can get them at the same time for all volumes
// as the capabilities could be different. I guess the
// possible result is that if the volumes are the same, they
// could be placed in the same storage pool and if the pool
// is approaching capacity, there may not actually be enough
// space in the recommended pool.
int sourceCounter = 0;
List<Volume> volumesList = new ArrayList<Volume>();
BlockObject aFCSource = null;
Map<URI, VirtualArray> vArrayCache = new HashMap<URI, VirtualArray>();
List<BlockObject> sortedSourceObjectList = sortFullCopySourceList(fcSourceObjList);
try {
for (BlockObject fcSourceObj : sortedSourceObjectList) {
// Make sure when there are multiple source objects,
// each full copy has a unique name.
aFCSource = fcSourceObj;
// volumes in VolumeGroup can be from different vArrays
varray = getVarrayFromCache(vArrayCache, fcSourceObj.getVirtualArray());
String copyName = null;
boolean inApplication = false;
if (aFCSource instanceof Volume && ((Volume) aFCSource).getApplication(_dbClient) != null) {
inApplication = true;
}
if (NullColumnValueGetter.isNotNullValue(fcSourceObj.getReplicationGroupInstance()) && inApplication) {
copyName = name + "-" + fcSourceObj.getReplicationGroupInstance()
+ (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : "");
} else {
copyName = name + (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : "");
}
VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(fcSourceObj, _dbClient);
VirtualPoolCapabilityValuesWrapper capabilities = getCapabilitiesForFullCopyCreate(
fcSourceObj, vpool, count);
List<VolumeRecommendation> placementRecommendations = getPlacementRecommendations(
fcSourceObj, capabilities, varray, vpool.getId());
volumesList.addAll(prepareClonesForEachRecommendation(copyName, name,
fcSourceObj, capabilities, createInactive, placementRecommendations));
}
} catch (Exception ex) {
handlePlacementFailure(volumesList);
throw ex;
}
// get volume descriptors
List<VolumeDescriptor> volumeDescriptors = prepareVolumeDescriptorsForFullCopy(volumesList, createInactive);
// get all tasks
TaskList tasks = getTasksForCreateFullCopy(aFCSource, volumesList, taskId);
try {
BlockOrchestrationController controller = getController(BlockOrchestrationController.class,
BlockOrchestrationController.BLOCK_ORCHESTRATION_DEVICE);
controller.createFullCopy(volumeDescriptors, taskId);
} catch (InternalException ie) {
handleFailedRequest(taskId, tasks, volumesList, ie, true);
}
return tasks;
}
/**
* creates volume descriptors based on the recommendations from placement
*
* @param volumes volume list that came from the placement algorithm
* @param createInactive flag to determine if the clone should be activated or not
* @return a list of volume descriptors
*/
private List<VolumeDescriptor> prepareVolumeDescriptorsForFullCopy(List<Volume> volumes, boolean createInactive) {
// Build up a list of VolumeDescriptors based on the volumes
final List<VolumeDescriptor> volumeDescriptors = new ArrayList<VolumeDescriptor>();
VirtualPoolCapabilityValuesWrapper vpoolCapabilities = new VirtualPoolCapabilityValuesWrapper();
vpoolCapabilities.put(VirtualPoolCapabilityValuesWrapper.REPLICA_CREATE_INACTIVE, new Boolean(createInactive).toString());
for (Volume volume : volumes) {
VolumeDescriptor desc = new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA,
volume.getStorageController(), volume.getId(),
volume.getPool(), volume.getConsistencyGroup(), vpoolCapabilities);
volumeDescriptors.add(desc);
}
return volumeDescriptors;
}
/**
* Get the placement recommendations for the passed full copy source.
*
* @param blockObject A reference to the full copy source.
* @param capabilities Encapsulates the copy capabilities
* @param varray A reference to the virtual array.
* @param vpoolURI The URI of the virtual pool for the source.
*
* @return A list of volume placement recommendations.
*/
private List<VolumeRecommendation> getPlacementRecommendations(
BlockObject blockObject, VirtualPoolCapabilityValuesWrapper capabilities,
VirtualArray varray, URI vpoolURI) {
// Find placement for block volume copies
VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, vpoolURI);
List<VolumeRecommendation> placementRecommendations = ((StorageScheduler) _scheduler)
.getRecommendationsForVolumeClones(varray, vPool, blockObject, capabilities);
if (placementRecommendations.isEmpty()) {
throw APIException.badRequests.invalidParameterNoStorageFoundForVolume(
varray.getId(), vPool.getId(), blockObject.getId());
}
return placementRecommendations;
}
/**
* Prepares a ViPR volume instance for each full copy.
*
* @param name The full copy name.
* @param cloneSetName
* @param blockObject The full copy source.
* @param capabilities The full copy capabilities.
* @param createInactive true to create the full copies inactive, false otherwise.
* @param placementRecommendations The placement recommendation for each full copy.
*
* @return A list of volumes representing the full copies.
*/
private List<Volume> prepareClonesForEachRecommendation(String name,
String cloneSetName, BlockObject blockObject, VirtualPoolCapabilityValuesWrapper capabilities,
Boolean createInactive, List<VolumeRecommendation> placementRecommendations) {
// Prepare clones for each recommendation
List<Volume> volumesList = new ArrayList<Volume>();
List<Volume> toUpdate = new ArrayList<Volume>();
boolean inApplication = false;
if (blockObject instanceof Volume && ((Volume) blockObject).getApplication(_dbClient) != null) {
inApplication = true;
}
int volumeCounter = (capabilities.getResourceCount() > 1) ? 1 : 0;
for (VolumeRecommendation recommendation : placementRecommendations) {
Volume volume = StorageScheduler.prepareFullCopyVolume(_dbClient, name,
blockObject, recommendation, volumeCounter, capabilities, createInactive);
// For Application, set the user provided clone name on all the clones to identify clone set
if (inApplication) {
volume.setFullCopySetName(cloneSetName);
toUpdate.add(volume);
}
volumesList.add(volume);
// set volume Id in the recommendation
recommendation.setId(volume.getId());
volumeCounter++;
}
// persist changes
if (!toUpdate.isEmpty()) {
_dbClient.updateObject(toUpdate);
}
return volumesList;
}
/**
* {@inheritDoc}
*/
@Override
public TaskList activate(BlockObject fcSourceObj, Volume fullCopyVolume) {
return super.activate(fcSourceObj, fullCopyVolume);
}
/**
* {@inheritDoc}
*/
@Override
public TaskList detach(BlockObject fcSourceObj, Volume fullCopyVolume) {
return super.detach(fcSourceObj, fullCopyVolume);
}
/**
* {@inheritDoc}
*/
@Override
public TaskList restoreSource(Volume sourceVolume, Volume fullCopyVolume) {
// Create the task list.
TaskList taskList = new TaskList();
// Create a unique task id.
String taskId = UUID.randomUUID().toString();
// If the source is in a CG, then we will restore the corresponding
// full copies for all the volumes in the CG. Since we did not allow
// full copies for volumes or snaps in CGs prior to Jedi, there should
// be a full copy for all volumes in the CG.
Map<URI, Volume> fullCopyMap = getFullCopySetMap(sourceVolume, fullCopyVolume);
Set<URI> fullCopyURIs = fullCopyMap.keySet();
// Get the id of the source volume.
URI sourceVolumeURI = sourceVolume.getId();
// Get the storage system for the source volume.
StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class,
sourceVolume.getStorageController());
URI sourceSystemURI = sourceSystem.getId();
// Create the restore task on the full copy volumes.
// The controller expects the task to be on the full
// copy even though the source is being restored.
// Not really sure why. Change this TBD
for (URI fullCopyURI : fullCopyURIs) {
Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI,
taskId, ResourceOperationTypeEnum.RESTORE_VOLUME_FULL_COPY);
fullCopyMap.get(fullCopyURI).getOpStatus().put(taskId, op);
TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask(
fullCopyMap.get(fullCopyURI), taskId, op);
taskList.getTaskList().add(fullCopyVolumeTask);
}
addConsistencyGroupTasks(Arrays.asList(sourceVolume), taskList, taskId,
ResourceOperationTypeEnum.RESTORE_CONSISTENCY_GROUP_FULL_COPY);
// Invoke the controller.
try {
BlockOrchestrationController controller = getController(BlockOrchestrationController.class,
BlockOrchestrationController.BLOCK_ORCHESTRATION_DEVICE);
controller.restoreFromFullCopy(sourceSystemURI, new ArrayList<URI>(fullCopyURIs), taskId);
} catch (InternalException ie) {
s_logger.error(String.format("Failed to restore source %s from full copy %s",
sourceVolumeURI, fullCopyVolume.getId()), ie);
handleFailedRequest(taskId, taskList,
new ArrayList<Volume>(fullCopyMap.values()), ie, false);
}
return taskList;
}
/**
* {@inheritDoc}
*/
@Override
public TaskList resynchronizeCopy(Volume sourceVolume, Volume fullCopyVolume) {
// Create the task list.
TaskList taskList = new TaskList();
// Create a unique task id.
String taskId = UUID.randomUUID().toString();
// If the source is in a CG, then we will resynchronize the corresponding
// full copies for all the volumes in the CG. Since we did not allow
// full copies for volumes or snaps in CGs prior to Jedi, there should
// be a full copy for all volumes in the CG.
Map<URI, Volume> fullCopyMap = getFullCopySetMap(sourceVolume, fullCopyVolume);
Set<URI> fullCopyURIs = fullCopyMap.keySet();
// Get the id of the source volume.
URI sourceVolumeURI = sourceVolume.getId();
// Get the storage system for the source volume.
StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class,
sourceVolume.getStorageController());
URI sourceSystemURI = sourceSystem.getId();
// Create the resynchronize task on the full copy volumes.
for (URI fullCopyURI : fullCopyURIs) {
Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI,
taskId, ResourceOperationTypeEnum.RESYNCHRONIZE_VOLUME_FULL_COPY);
fullCopyMap.get(fullCopyURI).getOpStatus().put(taskId, op);
TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask(
fullCopyMap.get(fullCopyURI), taskId, op);
taskList.getTaskList().add(fullCopyVolumeTask);
}
addConsistencyGroupTasks(Arrays.asList(sourceVolume), taskList, taskId,
ResourceOperationTypeEnum.RESYNCHRONIZE_CONSISTENCY_GROUP_FULL_COPY);
// Invoke the controller.
try {
BlockController controller = getController(BlockController.class,
sourceSystem.getSystemType());
controller.resyncFullCopy(sourceSystemURI, new ArrayList<URI>(fullCopyURIs),
Boolean.TRUE, taskId);
} catch (InternalException ie) {
s_logger.error(String.format("Failed to resynchronize full copy %s from source %s",
fullCopyVolume.getId(), sourceVolumeURI), ie);
handleFailedRequest(taskId, taskList,
new ArrayList<Volume>(fullCopyMap.values()), ie, false);
}
return taskList;
}
/**
* {@inheritDoc}
*/
@Override
public TaskList establishVolumeAndFullCopyGroupRelation(Volume sourceVolume, Volume fullCopyVolume) {
return super.establishVolumeAndFullCopyGroupRelation(sourceVolume, fullCopyVolume);
}
/**
* {@inheritDoc}
*/
@Override
public VolumeRestRep checkProgress(URI sourceURI, Volume fullCopyVolume) {
return super.checkProgress(sourceURI, fullCopyVolume);
}
/**
* Get the URI of the passed volumes.
*
* @param volumes A list of volumes.
*
* @return A list of the volume URIs.
*/
private List<URI> volumesToURIs(List<Volume> volumes) {
List<URI> uris = new ArrayList<URI>();
for (Volume v : volumes) {
uris.add(v.getId());
}
return uris;
}
}