/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.smis; import static com.emc.storageos.volumecontroller.impl.smis.ReplicationUtils.callEMCRefreshIfRequired; import static com.emc.storageos.volumecontroller.impl.smis.SmisConstants.CREATE_LIST_REPLICA; import static com.emc.storageos.volumecontroller.impl.smis.SmisConstants.JOB; import static com.google.common.base.Strings.isNullOrEmpty; 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.CIMObjectPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.BlockMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.BlockSnapshotSession; import com.emc.storageos.db.client.model.DiscoveredDataObject.Type; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.SynchronizationState; 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.CustomQueryUtility; import com.emc.storageos.db.client.util.NameGenerator; 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.ReplicaOperations; 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.job.SmisBlockDeleteListReplicaJob; import com.emc.storageos.volumecontroller.impl.smis.job.SmisCreateListReplicaJob; public abstract class AbstractReplicaOperations implements ReplicaOperations { private static final Logger _log = LoggerFactory.getLogger(AbstractReplicaOperations.class); protected DbClient _dbClient; protected SmisCommandHelper _helper; protected CIMObjectPathFactory _cimPath; protected NameGenerator _nameGenerator; public void setCimObjectPathFactory(CIMObjectPathFactory cimObjectPathFactory) { _cimPath = cimObjectPathFactory; } public void setDbClient(DbClient dbClient) { _dbClient = dbClient; } public void setSmisCommandHelper(SmisCommandHelper smisCommandHelper) { _helper = smisCommandHelper; } public void setNameGenerator(NameGenerator nameGenerator) { _nameGenerator = nameGenerator; } @Override public void createListReplica(StorageSystem storage, List<URI> replicaList, Boolean createInactive, TaskCompleter taskCompleter) { _log.info("createListReplica operation START"); List<String> targetDeviceIds = new ArrayList<String>(); try { List<String> sourceIds = new ArrayList<String>(); List<String> labels = new ArrayList<String>(); Map<String, URI> srcNativeIdToReplicaUriMap = new HashMap<String, URI>(); Map<String, String> tgtToSrcMap = new HashMap<String, String>(); String replicaGroupName = null; String sessionName = null; Volume source = null; boolean isThinlyProvisioned = false; for (URI replicaURI : replicaList) { BlockObject replica = BlockObject.fetch(_dbClient, replicaURI); source = (Volume) _helper.getSource(replica); // Use the existing replica group instance name for the new snaps to add. TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, source.getTenant().getURI()); int maxLength = SmisConstants.MAX_VOLUME_NAME_LENGTH; if (storage.getUsingSmis80() && URIUtil.isType(replicaURI, BlockSnapshot.class)) { maxLength = SmisConstants.MAX_SMI80_SNAPSHOT_NAME_LENGTH; } String label = _nameGenerator.generate(tenant.getLabel(), replica.getLabel(), replicaURI.toString(), '-', maxLength); labels.add(label); replicaGroupName = replica.getReplicationGroupInstance(); if (sessionName == null) { sessionName = getSnapshotSessionNameFromReplicaGroupName(replicaGroupName, storage.getId()); } String sourceNativeId = source.getNativeId(); isThinlyProvisioned = source.getThinlyProvisioned(); sourceIds.add(sourceNativeId); srcNativeIdToReplicaUriMap.put(sourceNativeId, replica.getId()); if (storage.deviceIsType(Type.vnxblock)) { // need to create target devices first final URI poolId = source.getPool(); final List<String> newDeviceIds = ReplicationUtils.createTargetDevices(storage, replicaGroupName, label, createInactive, 1, poolId, source.getCapacity(), isThinlyProvisioned, null, taskCompleter, _dbClient, _helper, _cimPath); targetDeviceIds.addAll(newDeviceIds); tgtToSrcMap.put(newDeviceIds.get(0), source.getNativeId()); } } int syncType = getSyncType(replicaList.get(0)); CIMObjectPath[] sourceVolumePaths = _cimPath.getVolumePaths(storage, sourceIds.toArray(new String[sourceIds.size()])); CIMObjectPath[] targetDevicePaths = _cimPath.getVolumePaths(storage, targetDeviceIds.toArray(new String[targetDeviceIds.size()])); CIMObjectPath targetVPSnapPoolPath = null; if (syncType == SmisConstants.SNAPSHOT_VALUE && !volumeHasSnapshot(source)) { targetVPSnapPoolPath = ReplicationUtils.getTargetPoolForVPSnapCreation(storage, null, replicaGroupName, isThinlyProvisioned, _dbClient, _helper, _cimPath); } CIMArgument[] inArgs = _helper.getCreateListReplicaInputArguments(storage, sourceVolumePaths, targetDevicePaths, labels, syncType, replicaGroupName, sessionName, createInactive, targetVPSnapPoolPath); CIMArgument[] outArgs = new CIMArgument[5]; CIMObjectPath replicationSvc = _cimPath.getControllerReplicationSvcPath(storage); _helper.invokeMethod(storage, replicationSvc, CREATE_LIST_REPLICA, inArgs, outArgs); CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, JOB); ControllerServiceImpl.enqueueJob( new QueueJob(new SmisCreateListReplicaJob(job, storage.getId(), srcNativeIdToReplicaUriMap, tgtToSrcMap, syncType, !createInactive, taskCompleter))); } catch (Exception e) { final String errMsg = format( "An exception occurred when trying to create list replica on storage system {0}", storage.getId()); _log.error(errMsg, e); // Roll back changes ReplicationUtils.rollbackCreateReplica(storage, null, targetDeviceIds, taskCompleter, _dbClient, _helper, _cimPath); List<? extends BlockObject> replicas = BlockObject.fetch(_dbClient, replicaList); for (BlockObject replica : replicas) { replica.setInactive(true); } _dbClient.updateObject(replicas); ServiceError error = DeviceControllerErrors.smis.methodFailed("createListReplica", e.getMessage()); taskCompleter.error(_dbClient, error); } _log.info("createListReplica operation END"); } /** * Checks if the request is for second snapshot creation for the same volume. * This is a temporary fix for COP-20864 (OPT# 497150). * TODO remove this method and condition check once the OPT is resolved. * * @param source the volume * @return true, if is second snapshot request */ private boolean volumeHasSnapshot(Volume source) { int snapshotCount = 0; if (source != null) { URIQueryResultList snapshotURIs = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint( source.getId()), snapshotURIs); List<BlockSnapshot> snapshots = _dbClient.queryObject(BlockSnapshot.class, snapshotURIs); for (BlockSnapshot snapshot : snapshots) { if (snapshot != null && !snapshot.getInactive() && !isNullOrEmpty(snapshot.getNativeId())) { // snapshot created on array snapshotCount++; } } } return (snapshotCount > 0) ? true : false; } @Override public void detachListReplica(StorageSystem storage, List<URI> replicaList, TaskCompleter taskCompleter) { _log.info("detachListReplica operation START"); try { List<? extends BlockObject> replicas = BlockObject.fetchAll(_dbClient, replicaList); modifyListReplica(storage, replicaList, replicas, SmisConstants.DETACH_VALUE, SmisConstants.NON_COPY_STATE); for (BlockObject replica : replicas) { if (replica instanceof BlockMirror) { BlockMirror mirror = (BlockMirror) replica; mirror.setReplicaState(ReplicationState.DETACHED.name()); mirror.setConsistencyGroup(NullColumnValueGetter.getNullURI()); mirror.setReplicationGroupInstance(NullColumnValueGetter.getNullStr()); mirror.setSynchronizedInstance(NullColumnValueGetter.getNullStr()); mirror.setSyncState(NullColumnValueGetter.getNullStr()); Volume volume = _dbClient.queryObject(Volume.class, mirror.getSource()); if (volume.getMirrors() != null) { volume.getMirrors().remove(mirror.getId().toString()); _dbClient.updateObject(volume); } } else if (replica instanceof Volume) { Volume clone = (Volume) replica; ReplicationUtils.removeDetachedFullCopyFromSourceFullCopiesList(clone, _dbClient); clone.setAssociatedSourceVolume(NullColumnValueGetter.getNullURI()); clone.setReplicaState(ReplicationState.DETACHED.name()); } } _dbClient.updateObject(replicas); taskCompleter.ready(_dbClient); } catch (Exception e) { _log.error("Problem making SMI-S call", e); ServiceError error = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(_dbClient, error); } _log.info("detachListReplica operation END"); } @Override public void fractureListReplica(StorageSystem storage, List<URI> replicaList, Boolean sync, TaskCompleter taskCompleter) { _log.info("fractureListReplica operation START"); try { List<? extends BlockObject> replicas = BlockObject.fetch(_dbClient, replicaList); int operation = (sync != null && sync) ? SmisConstants.SPLIT_VALUE : SmisConstants.FRACTURE_VALUE; int copyState = (operation == SmisConstants.SPLIT_VALUE) ? SmisConstants.SPLIT : SmisConstants.FRACTURED; modifyListReplica(storage, replicaList, replicas, operation, copyState); for (BlockObject replica : replicas) { if (replica instanceof BlockMirror) { ((BlockMirror) replica).setSyncState(SynchronizationState.FRACTURED.name()); } else if (replica instanceof Volume) { ((Volume) replica).setReplicaState(ReplicationState.SYNCHRONIZED.name()); } } _dbClient.updateObject(replicas); taskCompleter.ready(_dbClient); } catch (Exception e) { _log.error("Problem making SMI-S call", e); ServiceError error = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(_dbClient, error); } _log.info("fractureListReplica operation END"); } @Override public void deleteListReplica(StorageSystem storage, List<URI> replicaList, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("deleteListReplica operation START"); if (!((storage.getUsingSmis80() && storage.deviceIsType(Type.vmax)) || storage.deviceIsType(Type.vnxblock))) { throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported(); } try { String[] deviceIds = _helper.getBlockObjectNativeIds(replicaList); if (storage.checkIfVmax3()) { for (String deviceId : deviceIds) { _helper.removeVolumeFromParkingSLOStorageGroup(storage, deviceId, false); _log.info("Done invoking remove volume {} from parking SLO storage group", deviceId); } } CIMObjectPath[] devicePaths = _cimPath.getVolumePaths(storage, deviceIds); CIMObjectPath configSvcPath = _cimPath.getConfigSvcPath(storage); CIMArgument[] inArgs = null; if (storage.deviceIsType(Type.vnxblock)) { inArgs = _helper.getReturnElementsToStoragePoolArguments(devicePaths); } else { inArgs = _helper.getReturnElementsToStoragePoolArguments(devicePaths, SmisConstants.CONTINUE_ON_NONEXISTENT_ELEMENT); } CIMArgument[] outArgs = new CIMArgument[5]; _helper.invokeMethod(storage, configSvcPath, SmisConstants.RETURN_ELEMENTS_TO_STORAGE_POOL, inArgs, outArgs); CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, SmisConstants.JOB); ControllerServiceImpl.enqueueJob(new QueueJob(new SmisBlockDeleteListReplicaJob(job, storage.getId(), taskCompleter))); } catch (Exception e) { _log.error("Problem making SMI-S call: ", e); ServiceError serviceError = DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage()); taskCompleter.error(_dbClient, serviceError); } } /** * Invoke modifyListSynchronization for synchronized operations, e.g. fracture, detach, etc. * * @param storage * @param replicaList * @param operation * @param copyState * @throws Exception */ @SuppressWarnings("rawtypes") private void modifyListReplica(StorageSystem storage, List<URI> replicaList, List<? extends BlockObject> replicas, int operation, int copyState) throws Exception { callEMCRefreshIfRequired(_dbClient, _helper, storage, replicaList); List<CIMObjectPath> syncPaths = new ArrayList<CIMObjectPath>(); for (BlockObject replica : replicas) { BlockObject source = _helper.getSource(replica); CIMObjectPath syncObject = _cimPath.getStorageSynchronized(storage, source, storage, replica); if (_helper.checkExists(storage, syncObject, false, false) == null) { _log.error("Storage synchronized instance is not available for replica {}", replica.getLabel()); throw DeviceControllerException.exceptions.synchronizationInstanceNull(replica.getLabel()); } syncPaths.add(syncObject); } CIMArgument[] inArgs = _helper.getModifyListReplicaInputArguments(syncPaths.toArray(new CIMObjectPath[] {}), operation, copyState); _helper.callModifyListReplica(storage, inArgs); } protected int getSyncType(URI uri) { int syncType; if (URIUtil.isType(uri, BlockMirror.class)) { syncType = SmisConstants.MIRROR_VALUE; } else if (URIUtil.isType(uri, BlockSnapshot.class)) { syncType = SmisConstants.SNAPSHOT_VALUE; } else { syncType = SmisConstants.CLONE_VALUE; } return syncType; } /** * Given a replication group name of a SnapVx linked target group, determine the session name * by finding any member of the group that is associated to the BlockSnapshotSession instance. * * @param replicaGroupName Linked target replication group name. * @return SnapVx session name. */ private String getSnapshotSessionNameFromReplicaGroupName(String replicaGroupName, URI storage) { List<BlockSnapshot> snapshots = ControllerUtils.getSnapshotsPartOfReplicationGroup(replicaGroupName, storage, _dbClient); for (BlockSnapshot snapshot : snapshots) { List<BlockSnapshotSession> sessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getLinkedTargetSnapshotSessionConstraint(snapshot.getId())); if (sessions != null && !sessions.isEmpty()) { return sessions.get(0).getSessionLabel(); } } return SmisConstants.DEFAULT_REPLICATION_SETTING_DATA_ELEMENT_NAME; } }