/*
* Copyright (c) 2008-2011 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.block.taskcompleter;
import static com.emc.storageos.db.client.constraint.ContainmentConstraint.Factory.getVolumesByConsistencyGroup;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
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.model.BlockSnapshot;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.ProtectionSet;
import com.emc.storageos.db.client.model.ProtectionSystem;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.VolumeGroup;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.exceptions.DeviceControllerExceptions;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.volumecontroller.AsyncTask;
import com.emc.storageos.volumecontroller.ControllerLockingService;
import com.emc.storageos.volumecontroller.TaskCompleter;
/**
* Locking-based task completer
*/
@XmlRootElement
public abstract class TaskLockingCompleter extends TaskCompleter {
private static final String LOCK_SEPARATOR = ":";
private static final Logger _logger = LoggerFactory.getLogger(TaskLockingCompleter.class);
private static final long serialVersionUID = -1520175533121538993L;
@XmlTransient
private String lockedName = null;
/**
* JAXB requirement
*/
public TaskLockingCompleter() {
}
public TaskLockingCompleter(Class clazz, URI id, String opId) {
super(clazz, id, opId);
}
public TaskLockingCompleter(Class clazz, List<URI> ids, String opId) {
super(clazz, ids, opId);
}
public TaskLockingCompleter(AsyncTask task) {
super(task);
}
/**
* Unlock the CG associated with the volumes in the operation.
*
* @param dbClient db client
* @param locker locker service
*/
protected void unlockCG(DbClient dbClient, ControllerLockingService locker) throws DeviceControllerException {
// Warn if a state is met where there is a lock defined, but the locker wasn't sent down. (programming error)
if (locker == null && lockedName != null) {
_logger.error(String.format(
"Completer is not freeing up lock: %s! This error will lead to a stray lock in the system and must be addressed",
lockedName));
throw DeviceControllerExceptions.recoverpoint.invalidUnlock(lockedName);
}
List<URI> volumeIds = new ArrayList<URI>();
for (URI id : getIds()) {
// If this is a snapshot object completer, get the volume ids from the snapshot.
if (URIUtil.isType(id, BlockSnapshot.class)) {
BlockSnapshot snapshot = dbClient.queryObject(BlockSnapshot.class, id);
volumeIds.add(snapshot.getParent().getURI());
} else if (URIUtil.isType(id, BlockConsistencyGroup.class)) {
List<Volume> cgVolumes = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient, Volume.class,
getVolumesByConsistencyGroup(getId()));
if (cgVolumes != null && !cgVolumes.isEmpty()) {
// Get the first volume in the CG
volumeIds.add(cgVolumes.get(0).getId());
}
} else {
volumeIds.add(id);
}
}
// Figure out the lock ID (rpSystemInstallationID:CGName)
if (locker != null && lockedName != null) {
for (URI id : volumeIds) {
Volume volume = dbClient.queryObject(Volume.class, id);
if (volume != null) {
// Volume's protection set will be set to a null URI value, not "null" itself after a delete.
if (volume.getProtectionController() != null && volume.getProtectionSet() != null) {
ProtectionSystem rpSystem = dbClient.queryObject(ProtectionSystem.class, volume.getProtectionController());
if (rpSystem != null) {
// Unlock the CG based on this volume
if (locker.releasePersistentLock(lockedName, _opId)) {
_logger.info("Released lock: " + lockedName);
lockedName = null;
break;
} else {
_logger.info("Failed to release lock: " + lockedName);
}
}
}
}
}
}
}
/**
* Lock the entire CG based on this volume.
*
* @param dbClient db client
* @param locker locker service
* @return true if lock was acquired
*/
public boolean lockCG(DbClient dbClient, ControllerLockingService locker) {
// Figure out the lock ID (rpSystemInstallationID:CGName)
URI volumeId = getId();
// If this is a snapshot object completer, get the volume id from the snapshot.
if (URIUtil.isType(getId(), BlockSnapshot.class)) {
BlockSnapshot snapshot = dbClient.queryObject(BlockSnapshot.class, getId());
volumeId = snapshot.getParent().getURI();
} else if (URIUtil.isType(getId(), BlockConsistencyGroup.class)) {
List<Volume> cgVolumes = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient, Volume.class,
getVolumesByConsistencyGroup(getId()));
if (cgVolumes != null && !cgVolumes.isEmpty()) {
// Get the first volume in the CG
volumeId = cgVolumes.get(0).getId();
}
}
// Figure out the lock ID (rpSystemInstallationID:CGName)
Volume volume = dbClient.queryObject(Volume.class, volumeId);
if (volume != null && locker != null) {
if (volume.getProtectionController() != null && volume.getProtectionSet() != null) {
ProtectionSystem rpSystem = dbClient.queryObject(ProtectionSystem.class, volume.getProtectionController());
ProtectionSet protectionSet = dbClient.queryObject(ProtectionSet.class, volume.getProtectionSet());
if (rpSystem != null && protectionSet != null && rpSystem.getInstallationId() != null && protectionSet.getLabel() != null) {
// Unlock the CG based on this volume
String lockName = rpSystem.getInstallationId() + LOCK_SEPARATOR + protectionSet.getLabel();
if (locker.acquirePersistentLock(lockName, _opId, 5)) {
_logger.info("Acquired lock: " + lockName);
lockedName = lockName;
return true;
} else {
_logger.info("Failed to acquire lock: " + lockName);
}
}
} else if (volume.getProtectionSet() == null) {
_logger.info("Lock not required, no CG in use");
lockedName = null;
return true;
}
}
return false;
}
/**
* This method will be called upon job execution finish with a locking controller.
* It is not expected that non-locking controllers will call this version, however we need a base
* method so we don't need to ship around TaskLockingCompleters all over the code.
*
* @param dbClient
* @param locker
* @param status
* @param coded
* @throws DeviceControllerException
*/
@Override
protected void complete(DbClient dbClient, ControllerLockingService locker, Operation.Status status, ServiceCoded coded)
throws DeviceControllerException {
unlockCG(dbClient, locker);
complete(dbClient, status, coded);
}
@Override
protected void complete(DbClient dbClient, Operation.Status status, ServiceCoded coded) throws DeviceControllerException {
updateConsistencyGroupTasks(dbClient, status, coded);
updateVolumeGroupTasks(dbClient, status, coded);
if (isNotifyWorkflow()) {
// If there is a workflow, update the step to complete.
updateWorkflowStatus(status, coded);
}
}
private void updateVolumeGroupTasks(DbClient dbClient, Operation.Status status, ServiceCoded coded) {
for (URI volumeGroupId : getVolumeGroupIds()) {
_logger.info("Updating volume group task: {}", volumeGroupId);
switch (status) {
case error:
setErrorOnDataObject(dbClient, VolumeGroup.class, volumeGroupId, coded);
break;
case ready:
setReadyOnDataObject(dbClient, VolumeGroup.class, volumeGroupId);
break;
}
}
}
}