/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.block.taskcompleter; import static java.util.Arrays.asList; import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.security.audit.AuditLogManager; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableBourneEvent; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager; import com.emc.storageos.volumecontroller.impl.monitoring.cim.enums.RecordType; import com.emc.storageos.volumecontroller.impl.smis.SRDFOperations.Mode; public class SRDFTaskCompleter extends TaskCompleter { /** * Reference to logger */ private static final Logger _logger = LoggerFactory .getLogger(SRDFTaskCompleter.class); private DbClient dbClient; protected List<Volume> volumeCache; /** * Constructor for specifying a combination of source and multiple target ID's. * * @param ids * @param opId */ public SRDFTaskCompleter(List<URI> ids, String opId) { super(Volume.class, ids, opId); } public SRDFTaskCompleter(URI sourceURI, URI targetURI, String opId) { super(Volume.class, asList(sourceURI, targetURI), opId); } @Override protected void complete(DbClient dbClient, Operation.Status status, ServiceCoded coded) throws DeviceControllerException { setDbClient(dbClient); setStatus(dbClient, status, coded); updateWorkflowStatus(status, coded); updateVolumeStatus(dbClient, status); updateConsistencyGroupTasks(dbClient, status, coded); } protected void setDbClient(DbClient dbClient) { this.dbClient = dbClient; } protected DbClient getDbClient() { return dbClient; } protected Volume getTargetVolume() { for (Volume v : getVolumes()) { if (!NullColumnValueGetter.isNullNamedURI(v.getSrdfParent()) && !v.getSrdfParent().getURI().toString().equalsIgnoreCase("null")) { return v; } } throw new IllegalStateException("Expected a target volume with an non-null SRDF parent"); } protected Volume getSourceVolume() { for (Volume v : getVolumes()) { if (v.getSrdfTargets() != null) { return v; } } throw new IllegalStateException("Expected a source volume with an non-null SRDF parent"); } protected List<Volume> getVolumes() { if (volumeCache == null) { volumeCache = getDbClient().queryObject(Volume.class, getIds()); } return volumeCache; } protected Set<String> getVolumeIds() { Set<String> volumeIds = new HashSet<String>(); for (Volume volume : volumeCache) { volumeIds.add(volume.getNativeGuid()); } return volumeIds; } /** * * @param dbClient * @param evtType * @param status * @param desc * @throws Exception */ public void recordBourneSRDFEvent(DbClient dbClient, URI volumeUri, String evtType, Operation.Status status, String desc) throws Exception { RecordableEventManager eventManager = new RecordableEventManager(); eventManager.setDbClient(dbClient); Volume volObj = dbClient.queryObject(Volume.class, volumeUri); RecordableBourneEvent event = ControllerUtils .convertToRecordableBourneEvent(volObj, evtType, desc, "", dbClient, ControllerUtils.BLOCK_EVENT_SERVICE, RecordType.Event.name(), ControllerUtils.BLOCK_EVENT_SOURCE); try { eventManager.recordEvents(event); _logger.info("Bourne {} event recorded", evtType); } catch (Exception ex) { _logger.error( "Failed to record event. Event description: {}. Error: ", evtType, ex); } } /** * Record block volume related event and audit * * @param dbClient db client * @param opType operation type * @param status operation status * @param extParam parameters array from which we could generate detail audit message */ public void recordSRDFOperation(DbClient dbClient, OperationTypeEnum opType, Operation.Status status, Object... extParam) { try { boolean opStatus = (Operation.Status.ready == status) ? true : false; String evType; evType = opType.getEvType(opStatus); String evDesc = opType.getDescription(); String opStage = AuditLogManager.AUDITOP_END; _logger.info("opType: {} detail: {}", opType.toString(), evType.toString() + ':' + evDesc); recordBourneSRDFEvent(dbClient, getId(), evType, status, evDesc); String id = (String) extParam[0]; switch (opType) { case CREATE_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case SUSPEND_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case DETACH_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case PAUSE_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case RESUME_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case FAILOVER_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case SWAP_SRDF_VOLUME: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case STOP_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; case SYNC_SRDF_LINK: AuditBlockUtil.auditBlock(dbClient, opType, opStatus, opStage, extParam); break; default: _logger.error("unrecognized SRDF operation type"); } } catch (Exception e) { _logger.error("Failed to record SRDF operation {}, err: {}", opType.toString(), e); } } protected Volume.LinkStatus getVolumeSRDFLinkStatusForSuccess() { return Volume.LinkStatus.OTHER; } /** * Setting access state is based on the personality and failover state of the volume, regardless of operation. * * @param v a volume impacted by this SRDF operation * @return volume access state for that volume. */ protected Volume.VolumeAccessState getVolumeAccessStateForSuccess(Volume v) { // If this volume is a source and exported to a host, the volume is write-disabled. Otherwise it is readwrite. if (v.getPersonality().equals(Volume.PersonalityTypes.SOURCE.toString()) && v.getLinkStatus().equals(Volume.LinkStatus.FAILED_OVER.name())) { // Check to see if it's exported URIQueryResultList exportGroups = new URIQueryResultList(); getDbClient().queryByConstraint(ContainmentConstraint. Factory.getBlockObjectExportGroupConstraint(v.getId()), exportGroups); if (exportGroups.iterator().hasNext()) { // A source volume that is in an export group is write-disabled or not-ready. return Volume.VolumeAccessState.NOT_READY; } else { return Volume.VolumeAccessState.READWRITE; } } else if (v.getPersonality().equals(Volume.PersonalityTypes.TARGET.toString()) && !v.getLinkStatus().equals(Volume.LinkStatus.FAILED_OVER.name())) { if (Mode.ACTIVE.equals(Mode.valueOf(v.getSrdfCopyMode()))) { // For Active mode target access state is always updated from the provider // after each operation so just use that. return Volume.VolumeAccessState.getVolumeAccessState(v.getAccessState()); } else { // A target volume in any state other than FAILED_OVER is write-disabled or not-ready. return Volume.VolumeAccessState.NOT_READY; } } // Any other state is READWRITE return Volume.VolumeAccessState.READWRITE; } protected void updateVolumeStatus(DbClient dbClient, Operation.Status status) { try { if (Operation.Status.ready.equals(status)) { List<Volume> volumes = dbClient.queryObject(Volume.class, getIds()); for (Volume v : volumes) { updateVolume(v); } dbClient.updateObject(volumes); _logger.info("Updated SRDF link status for volumes: {}", getIds()); } } catch (Exception e) { _logger.info("Not updating volume SRDF link status for volumes: {}", getIds(), e); } } private void updateVolume(Volume v) { if (v.isVPlexVolume(dbClient)) { // skip VPLEX volumes, as they delegate SRDF characteristics to their native volumes. return; } v.setLinkStatus(getVolumeSRDFLinkStatusForSuccess().name()); if (v.getPersonality() != null) { v.setAccessState(getVolumeAccessStateForSuccess(v).name()); } if (v.getSrdfTargets() != null) { List<URI> targetVolumeURIs = new ArrayList<>(); for (String targetId : v.getSrdfTargets()) { targetVolumeURIs.add(URI.create(targetId)); } List<Volume> targetVolumes = dbClient.queryObject(Volume.class, targetVolumeURIs); for (Volume targetVolume : targetVolumes) { targetVolume.setLinkStatus(getVolumeSRDFLinkStatusForSuccess().name()); targetVolume.setAccessState(getVolumeAccessStateForSuccess(targetVolume).name()); } dbClient.updateObject(targetVolumes); } } }