/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.smis.vmax;
import static com.emc.storageos.volumecontroller.impl.smis.ReplicationUtils.callEMCRefreshIfRequired;
import static java.text.MessageFormat.format;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.cim.CIMArgument;
import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.Volume.ReplicationState;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.exceptions.DeviceControllerErrors;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.ControllerServiceImpl;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.job.QueueJob;
import com.emc.storageos.volumecontroller.impl.smis.AbstractCloneOperations;
import com.emc.storageos.volumecontroller.impl.smis.ReplicationUtils;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants.SYNC_TYPE;
import com.emc.storageos.volumecontroller.impl.smis.job.SmisCloneRestoreJob;
import com.emc.storageos.volumecontroller.impl.smis.job.SmisCloneResyncJob;
import com.emc.storageos.volumecontroller.impl.smis.job.SmisCreateCGCloneJob;
import com.emc.storageos.volumecontroller.impl.utils.ConsistencyGroupUtils;
public class VmaxCloneOperations extends AbstractCloneOperations {
private static final Logger _log = LoggerFactory.getLogger(VmaxCloneOperations.class);
/**
* Should implement create of a clone from a source volume that is part of a
* consistency group.
*
* Implementation note: In this method we will kick of the asynchronous creation
* of the target devices required for the CG clones. Upon the successful
* device creation, the post operations will take place, which will include the
* creation of the target group and the group clone operation.
*
* @param storage [required] - StorageSystem object representing the array
* @param cloneList [required] - clone URI list
* @param createInactive whether the clone needs to to be created with sync_active=true/false
* @param taskCompleter - TaskCompleter object used for the updating operation status.
* @throws DeviceControllerException
*/
@Override
public void createGroupClone(StorageSystem storage, List<URI> cloneList,
Boolean createInactive, TaskCompleter taskCompleter) {
_log.info("START create group clone operation");
// Target group CIM Path
CIMObjectPath targetGroupPath = null;
// List of target device ids
List<String> targetDeviceIds = null;
// The source consistency group name
String sourceGroupName = null;
try {
final Volume first = _dbClient.queryObject(Volume.class, cloneList.get(0));
Volume sourceVolume = _dbClient.queryObject(Volume.class, first.getAssociatedSourceVolume());
sourceGroupName = ConsistencyGroupUtils.getSourceConsistencyGroupName(sourceVolume, _dbClient);
URI tenant = sourceVolume.getTenant().getURI();
TenantOrg tenantOrg = _dbClient.queryObject(TenantOrg.class, tenant);
String targetGroupLabel = generateLabel(tenantOrg, sourceVolume);
// CTRL-5640: ReplicationGroup may not be accessible after provider fail-over.
ReplicationUtils.checkReplicationGroupAccessibleOrFail(storage, sourceVolume, _dbClient, _helper, _cimPath);
final Map<String, List<Volume>> clonesBySizeMap = new HashMap<String, List<Volume>>();
List<Volume> clones = _dbClient.queryObject(Volume.class, cloneList);
// For 8.0 providers, no need to create target devices and
// target group separately for volumes in CG.
// They will be created as part of 'CreateGroupReplica' call.
// For 4.6 providers and VMAX3 arrays, target devices and target group will be
// created separately before 'CreateGroupReplica' call.
if (storage.checkIfVmax3() || !storage.getUsingSmis80()) {
targetDeviceIds = new ArrayList<String>();
for (Volume clone : clones) {
final URI poolId = clone.getPool();
Volume source = _dbClient.queryObject(Volume.class, clone.getAssociatedSourceVolume());
// Create target devices
final List<String> newDeviceIds = ReplicationUtils.createTargetDevices(storage, sourceGroupName,
clone.getLabel(),
createInactive,
1, poolId, clone.getCapacity(), source.getThinlyProvisioned(), source, taskCompleter, _dbClient, _helper,
_cimPath);
targetDeviceIds.addAll(newDeviceIds);
}
// Create target device group
targetGroupPath = ReplicationUtils.createTargetDeviceGroup(storage, sourceGroupName, targetDeviceIds, taskCompleter,
_dbClient, _helper, _cimPath,
SYNC_TYPE.CLONE);
}
// Create CG clone
CIMObjectPath job = VmaxGroupOperationsUtils.internalCreateGroupReplica(storage, sourceGroupName, targetGroupLabel,
targetGroupPath, createInactive, taskCompleter,
SYNC_TYPE.CLONE, _dbClient, _helper, _cimPath);
if (job != null) {
ControllerServiceImpl.enqueueJob(
new QueueJob(new SmisCreateCGCloneJob(job,
storage.getId(), !createInactive, taskCompleter)));
}
} catch (Exception e) {
final String errMsg = format(
"An exception occurred when trying to create clones for consistency group {0} on storage system {1}",
sourceGroupName, storage.getId());
_log.error(errMsg, e);
// Roll back changes
ReplicationUtils.rollbackCreateReplica(storage, targetGroupPath, targetDeviceIds, taskCompleter, _dbClient, _helper, _cimPath);
List<Volume> clones = _dbClient.queryObject(Volume.class, cloneList);
for (Volume clone : clones) {
clone.setInactive(true);
}
_dbClient.persistObject(clones);
ServiceError error = DeviceControllerErrors.smis.methodFailed("createGroupClones", e.getMessage());
taskCompleter.error(_dbClient, error);
}
}
/**
* This interface is for the clone activate in CG.
*
* @param storage [required] - StorageSystem object representing the array
* @param clones [required] - clone URIs
* @param taskCompleter - TaskCompleter object used for the updating operation status.
*/
@Override
public void activateGroupClones(StorageSystem storage, List<URI> clones, TaskCompleter taskCompleter) {
try {
_log.info("activateGroupClones operation START");
Volume cloneObj = _dbClient.queryObject(Volume.class, clones.get(0));
if (cloneObj.getSyncActive()) {
_log.warn("Trying to activate CG clone, which is already active",
cloneObj.getId().toString());
return;
}
Volume sourceVolume = _dbClient.queryObject(Volume.class, cloneObj.getAssociatedSourceVolume());
boolean isSuccess = VmaxGroupOperationsUtils.activateGroupReplicas(storage, sourceVolume, cloneObj,
SYNC_TYPE.CLONE, taskCompleter, _dbClient, _helper, _cimPath);
if (isSuccess) {
List<Volume> cloneList = new ArrayList<Volume>();
for (URI clone : clones) {
Volume theClone = _dbClient.queryObject(Volume.class, clone);
theClone.setSyncActive(true);
theClone.setRefreshRequired(true);
theClone.setReplicaState(ReplicationState.SYNCHRONIZED.name());
cloneList.add(theClone);
}
_dbClient.persistObject(cloneList);
taskCompleter.ready(_dbClient);
}
} catch (Exception e) {
_log.info("Problem making SMI-S call: ", e);
ServiceError error = DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage());
taskCompleter.error(_dbClient, error);
} finally {
_log.info("activateGroupClones operation END");
}
}
/**
* Implementation for restoring of clones in CG.
*
* @param storage [required] - StorageSystem object representing the array
* @param clones [required] - URIs representing the previously created clones
* @param taskCompleter - TaskCompleter object used for the updating operation status.
*/
@Override
@SuppressWarnings("rawtypes")
public void restoreGroupClones(StorageSystem storage, List<URI> clones, TaskCompleter taskCompleter) throws DeviceControllerException {
_log.info("START restore group clone operation");
try {
callEMCRefreshIfRequired(_dbClient, _helper, storage, clones);
Volume clone = _dbClient.queryObject(Volume.class, clones.get(0));
Volume sourceVol = _dbClient.queryObject(Volume.class, clone.getAssociatedSourceVolume());
String consistencyGroupName = ConsistencyGroupUtils.getSourceConsistencyGroupName(sourceVol, _dbClient);
String replicationGroupName = clone.getReplicationGroupInstance();
CIMObjectPath groupSynchronized = _cimPath.getGroupSynchronizedPath(storage, consistencyGroupName, replicationGroupName);
if (_helper.checkExists(storage, groupSynchronized, false, false) != null) {
CIMObjectPath cimJob = null;
CIMArgument[] restoreCGCloneInput = _helper.getRestoreFromReplicaInputArgumentsWithForce(groupSynchronized);
cimJob = _helper.callModifyReplica(storage, restoreCGCloneInput);
ControllerServiceImpl.enqueueJob(new QueueJob(new SmisCloneRestoreJob(cimJob, storage.getId(), taskCompleter)));
} else {
ServiceError error = DeviceControllerErrors.smis.unableToFindSynchPath(consistencyGroupName);
taskCompleter.error(_dbClient, error);
}
} catch (Exception e) {
String message =
String.format("Exception when trying to restoring clones from consistency group on array %s",
storage.getSerialNumber());
_log.error(message, e);
ServiceError error = DeviceControllerErrors.smis.methodFailed("restoreGroupClones", e.getMessage());
taskCompleter.error(_dbClient, error);
}
}
/**
* Implementation for resync clones in CG.
*
* @param storage [required] - StorageSystem object representing the array
* @param clones [required] - URIs representing the previously created clones
* @param taskCompleter - TaskCompleter object used for the updating operation status.
*/
@Override
@SuppressWarnings("rawtypes")
public void resyncGroupClones(StorageSystem storage, List<URI> clones, TaskCompleter taskCompleter) throws DeviceControllerException {
_log.info("START resync group clone operation");
try {
callEMCRefreshIfRequired(_dbClient, _helper, storage, clones);
Volume clone = _dbClient.queryObject(Volume.class, clones.get(0));
Volume sourceVol = _dbClient.queryObject(Volume.class, clone.getAssociatedSourceVolume());
String consistencyGroupName = ConsistencyGroupUtils.getSourceConsistencyGroupName(sourceVol, _dbClient);
String replicationGroupName = clone.getReplicationGroupInstance();
CIMObjectPath groupSynchronized = _cimPath.getGroupSynchronizedPath(storage, consistencyGroupName, replicationGroupName);
if (_helper.checkExists(storage, groupSynchronized, false, false) != null) {
CIMObjectPath cimJob = null;
CIMArgument[] resyncCGCloneInput = _helper.getResyncReplicaInputArguments(groupSynchronized);
cimJob = _helper.callModifyReplica(storage, resyncCGCloneInput);
ControllerServiceImpl.enqueueJob(new QueueJob(new SmisCloneResyncJob(cimJob, storage.getId(), taskCompleter)));
} else {
ServiceError error = DeviceControllerErrors.smis.unableToFindSynchPath(consistencyGroupName);
taskCompleter.error(_dbClient, error);
}
} catch (Exception e) {
String message =
String.format("Exception when trying to resync clones from consistency group on array %s",
storage.getSerialNumber());
_log.error(message, e);
taskCompleter.error(_dbClient, DeviceControllerException.exceptions.resynchronizeFullCopyFailed(e));
}
}
@Override
@SuppressWarnings("rawtypes")
public void fractureGroupClones(StorageSystem storage, List<URI> clones, TaskCompleter completer) {
_log.info("START fracture group clone operation");
try {
callEMCRefreshIfRequired(_dbClient, _helper, storage, clones);
CIMObjectPath groupSynchronized = ReplicationUtils.getCloneGroupSynchronizedPath(storage, clones.get(0),
_dbClient, _helper, _cimPath);
if (_helper.checkExists(storage, groupSynchronized, false, false) != null) {
if (_helper.groupHasReplicasInSplitState(storage, clones, Volume.class)) {
SmisCloneResyncJob job = new SmisCloneResyncJob(null, storage.getId(), completer);
CIMArgument[] resyncCGCloneInput = _helper.getResyncReplicaInputArguments(groupSynchronized);
_log.info("Resync group clones with mixed states");
_helper.callModifyReplicaSynchronously(storage, resyncCGCloneInput, job);
}
CIMArgument[] fractureCGCloneInput = _helper.getFractureMirrorInputArguments(groupSynchronized, null);
_helper.callModifyReplica(storage, fractureCGCloneInput);
List<Volume> cloneVolumes = _dbClient.queryObject(Volume.class, clones);
for (Volume theClone : cloneVolumes) {
theClone.setReplicaState(ReplicationState.SYNCHRONIZED.name());
}
_dbClient.persistObject(cloneVolumes);
completer.ready(_dbClient);
} else {
Volume clone = _dbClient.queryObject(Volume.class, clones.get(0));
ServiceError error = DeviceControllerErrors.smis.unableToFindSynchPath(clone.getReplicationGroupInstance());
completer.error(_dbClient, error);
}
} catch (Exception e) {
String message =
String.format("Exception when trying to fracture clones from consistency group on array %s",
storage.getSerialNumber());
_log.error(message, e);
completer.error(_dbClient, DeviceControllerException.exceptions.fractureFullCopyFailed(e));
}
}
@SuppressWarnings("rawtypes")
@Override
public void detachGroupClones(StorageSystem storage, List<URI> clones, TaskCompleter completer) {
_log.info("START detach group clone operation");
try {
callEMCRefreshIfRequired(_dbClient, _helper, storage, clones);
CIMObjectPath groupSynchronized = ReplicationUtils.getCloneGroupSynchronizedPath(storage, clones.get(0), _dbClient,
_helper, _cimPath);
if (_helper.checkExists(storage, groupSynchronized, false, false) != null) {
CIMArgument[] detachCGCloneInput = _helper.getDetachSynchronizationInputArguments(groupSynchronized);
_helper.callModifyReplica(storage, detachCGCloneInput);
List<Volume> cloneVolumes = _dbClient.queryObject(Volume.class, clones);
for (Volume theClone : cloneVolumes) {
ReplicationUtils.removeDetachedFullCopyFromSourceFullCopiesList(theClone, _dbClient);
theClone.setAssociatedSourceVolume(NullColumnValueGetter.getNullURI());
theClone.setReplicaState(ReplicationState.DETACHED.name());
}
_dbClient.persistObject(cloneVolumes);
if (completer != null) {
completer.ready(_dbClient);
}
} else {
Volume clone = _dbClient.queryObject(Volume.class, clones.get(0));
ServiceError error = DeviceControllerErrors.smis.unableToFindSynchPath(clone.getReplicationGroupInstance());
if (completer != null) {
completer.error(_dbClient, error);
}
}
} catch (Exception e) {
String message =
String.format("Exception when trying to detach clones from consistency group on array %s",
storage.getSerialNumber());
_log.error(message, e);
completer.error(_dbClient, DeviceControllerException.exceptions.detachVolumeFullCopyFailed(e));
}
}
@Override
public void establishVolumeCloneGroupRelation(StorageSystem storage, URI sourceVolume, URI clone, TaskCompleter completer) {
_log.info("establishVolumeCloneGroupRelation operation START");
try {
/**
* get groupPath for source volume
* get groupPath for clone
* get clones belonging to the same Replication Group
* get Element synchronizations between volumes and clones
* call CreateGroupReplicaFromElementSynchronizations
*/
Volume cloneObj = _dbClient.queryObject(Volume.class, clone);
Volume volumeObj = _dbClient.queryObject(Volume.class, sourceVolume);
CIMObjectPath srcRepSvcPath = _cimPath.getControllerReplicationSvcPath(storage);
String volumeGroupName = ConsistencyGroupUtils.getSourceConsistencyGroupName(volumeObj, _dbClient);
CIMObjectPath volumeGroupPath = _cimPath.getReplicationGroupPath(storage, volumeGroupName);
CIMObjectPath cloneGroupPath = _cimPath.getReplicationGroupPath(storage, cloneObj.getReplicationGroupInstance());
CIMObjectPath groupSynchronizedPath = _cimPath.getGroupSynchronized(volumeGroupPath, cloneGroupPath);
CIMInstance syncInstance = _helper.checkExists(storage, groupSynchronizedPath, false, false);
if (syncInstance == null) {
// get all clones belonging to a Replication Group. There may be multiple clones available for a Volume
List<Volume> fullCopies = ControllerUtils.
getFullCopiesPartOfReplicationGroup(cloneObj.getReplicationGroupInstance(), _dbClient);
List<CIMObjectPath> elementSynchronizations = new ArrayList<CIMObjectPath>();
for (Volume fullCopy : fullCopies) {
URI sourceVolumeURI = fullCopy.getAssociatedSourceVolume();
if (!NullColumnValueGetter.isNullURI(sourceVolumeURI)) {
Volume volume = _dbClient.queryObject(Volume.class, sourceVolumeURI);
elementSynchronizations.add(_cimPath.getStorageSynchronized(storage, volume,
storage, fullCopy));
}
}
_log.info("Creating Group synchronization between volume group and clone group");
CIMArgument[] inArgs = _helper.getCreateGroupReplicaFromElementSynchronizationsForSRDFInputArguments(volumeGroupPath,
cloneGroupPath, elementSynchronizations);
CIMArgument[] outArgs = new CIMArgument[5];
_helper.invokeMethod(storage, srcRepSvcPath,
SmisConstants.CREATE_GROUP_REPLICA_FROM_ELEMENT_SYNCHRONIZATIONS, inArgs, outArgs);
// No Job returned
} else {
_log.info("Link already established..");
}
completer.ready(_dbClient);
} catch (Exception e) {
_log.error(
"Failed to establish group relation between volume group and clone group. Volume: {}, Clone: {}",
sourceVolume, clone);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
}
}