/*
* Copyright (c) 2008-2011 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.block.taskcompleter;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.model.ExportGroup;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.util.ExportUtils;
import com.emc.storageos.volumecontroller.BlockExportController;
/**
* Completer for {@link BlockExportController#exportGroupUpdate(URI, Map, List, List, List, String)}.
* In order for this call to be repeatable, the export group is updated only when the
* call. completes successfully. It is the completer task to update the export group
* when the job status is ready.
*
*/
public class ExportUpdateCompleter extends ExportTaskCompleter {
private static final Logger _log = LoggerFactory.getLogger(ExportUpdateCompleter.class);
private static final String EXPORT_UPDATED_MSG = "ExportGroup %s updated successfully";
private static final String EXPORT_UPDATE_FAILED_MSG = "Failed to update ExportGroup %s";
private Map<URI, Integer> _addedBlockObjects = new HashMap<URI, Integer>();
private Map<URI, Integer> _removedBlockObjects = new HashMap<URI, Integer>();
private Set<URI> _addedInitiators = new HashSet<>();
private Set<URI> _removedInitiators = new HashSet<>();
private Set<URI> _addedHosts = new HashSet<>();
private Set<URI> _removedHosts = new HashSet<>();
private Set<URI> _addedClusters = new HashSet<>();
private Set<URI> _removedClusters = new HashSet<>();
/**
* Constructor for export updates.
*
* @param egUri export group ID
* @param addedBlockObjects block objects to add
* @param removedBlockObjects block objects to remove
* @param addedInitiators initiators to add
* @param removedInitiators initiators to remove
* @param addedHosts hosts to add
* @param removedHosts hosts to remove
* @param addedClusters clusters to add
* @param removedClusters clusters to remove
* @param task task id
*/
public ExportUpdateCompleter(
URI egUri,
Map<URI, Integer> addedBlockObjects,
Map<URI, Integer> removedBlockObjects,
Set<URI> addedInitiators, Set<URI> removedInitiators,
Set<URI> addedHosts, Set<URI> removedHosts,
Set<URI> addedClusters, Set<URI> removedClusters,
String task) {
super(ExportGroup.class, egUri, task);
_addedBlockObjects = addedBlockObjects;
_removedBlockObjects = removedBlockObjects;
_addedInitiators = addedInitiators;
_removedInitiators = removedInitiators;
_addedHosts = addedHosts;
_removedHosts = removedHosts;
_addedClusters = addedClusters;
_removedClusters = removedClusters;
}
public ExportUpdateCompleter(URI egUri, String task) {
super(ExportGroup.class, egUri, task);
}
protected void complete(DbClient dbClient, Operation.Status status, ServiceCoded coded)
throws DeviceControllerException {
try {
ExportGroup exportGroup = dbClient.queryObject(ExportGroup.class, getId());
Operation operation = new Operation();
switch (status) {
case error:
operation.error(coded);
break;
case ready:
operation.ready();
break;
case suspended_no_error:
operation.suspendedNoError();
break;
case suspended_error:
operation.suspendedError(coded);
break;
default:
break;
}
exportGroup.getOpStatus().updateTaskStatus(getOpId(), operation);
// Update the export group data.
if (status.equals(Operation.Status.ready)) {
updateExportGroup(exportGroup, dbClient);
} else {
dbClient.updateObject(exportGroup);
}
if (Operation.isTerminalState(status) && needToRunExportGroupCleanup(dbClient)) {
// Clean stale references from EG if the status is either ready or error.
ExportUtils.cleanStaleReferences(exportGroup, dbClient);
}
_log.info("export_update completer: done");
_log.info(String.format("Done ExportMaskUpdate - Id: %s, OpId: %s, status: %s",
getId().toString(), getOpId(), status.name()));
recordBlockExportOperation(dbClient, OperationTypeEnum.UPDATE_EXPORT_GROUP, status, eventMessage(status, exportGroup),
exportGroup);
// Check to see if Export Group needs to be cleaned up
ExportUtils.checkExportGroupForCleanup(exportGroup, dbClient);
} catch (Exception e) {
_log.error(String.format("Failed updating status for ExportMaskUpdate - Id: %s, OpId: %s",
getId().toString(), getOpId()), e);
} finally {
super.complete(dbClient, status, coded);
}
}
private String eventMessage(Operation.Status status, ExportGroup exportGroup) {
return (status == Operation.Status.ready) ?
String.format(EXPORT_UPDATED_MSG, exportGroup.getLabel()) :
String.format(EXPORT_UPDATE_FAILED_MSG, exportGroup.getLabel());
}
/**
* Update the export group data.
*
* @param exportGroup the export group to be updated.
* @param dbClient {@link DbClient}
*/
private void updateExportGroup(ExportGroup exportGroup, DbClient dbClient) {
if (_addedInitiators != null) {
exportGroup.addInitiators(_addedInitiators);
}
if (_removedInitiators != null) {
exportGroup.removeInitiators(_removedInitiators);
}
if (_addedHosts != null) {
exportGroup.addHosts(_addedHosts);
}
if (_removedHosts != null) {
exportGroup.removeHosts(_removedHosts);
}
if (_addedClusters != null) {
exportGroup.addClusters(_addedClusters);
}
if (_removedClusters != null) {
exportGroup.removeClusters(_removedClusters);
}
if (_addedBlockObjects != null) {
exportGroup.addVolumes(_addedBlockObjects);
}
if (_removedBlockObjects != null) {
exportGroup.removeVolumes(_removedBlockObjects);
}
dbClient.updateObject(exportGroup);
}
/**
* If the initiators and hosts are added, but there are no volumes in the export Group, then no need to run.
* If the initiators and hosts and clusters are added, but there are no volumes in the export Group, then no need to run.
* If volumes are added, but there are no initiators, no need to cleanup
*
* This is needed to handle CLI scenarios like Create Empty Export Group, Add host to Export Group, Add Volume to Export Group.
* the 1st 2 cases doesn't need to run Cleanup, but the 3rd needs to, as real export happens.
*
* Other case : Create Empty Export Group, Add Volume to Export Group, Add Host to Export Group.
* the 1st 2 cases doesn't need to run Cleanup, but the 3rd needs to, as real export happens.
*
* @return
*/
private boolean needToRunExportGroupCleanup(DbClient dbClient) {
boolean needtoRunExportCleanupTask = true;
ExportGroup exportGroup = dbClient.queryObject(ExportGroup.class, getId());
if (null == exportGroup.getVolumes() || exportGroup.getVolumes().isEmpty()) {
if ((_addedInitiators != null && !_addedInitiators.isEmpty()) ||
(_addedHosts != null && !_addedHosts.isEmpty())
|| (_addedClusters != null && !_addedClusters.isEmpty())) {
_log.info(
"No need to run Export Clean up, as export Group contains no volumes and the request includes to add compute resource.");
needtoRunExportCleanupTask = false;
}
} else if (null == exportGroup.getInitiators() || exportGroup.getInitiators().isEmpty()) {
if (_addedBlockObjects != null && !_addedBlockObjects.isEmpty()) {
needtoRunExportCleanupTask = false;
_log.info(
"No need to run Export Clean up, as export Group contains no initiators and the request includes to add volumes.");
}
}
return needtoRunExportCleanupTask;
}
}