/*
* Copyright (c) 2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.smis.job;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import javax.cim.CIMArgument;
import javax.cim.CIMDataType;
import javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.cim.CIMProperty;
import javax.cim.UnsignedInteger16;
import javax.wbem.CloseableIterator;
import javax.wbem.WBEMException;
import javax.wbem.client.WBEMClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.Volume.PersonalityTypes;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.exceptions.DeviceControllerErrors;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.volumecontroller.JobContext;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.ControllerServiceImpl;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
import com.emc.storageos.volumecontroller.impl.smis.CIMConnectionFactory;
import com.emc.storageos.volumecontroller.impl.smis.CIMObjectPathFactory;
import com.emc.storageos.volumecontroller.impl.smis.CIMPropertyFactory;
import com.emc.storageos.volumecontroller.impl.smis.SmisCommandHelper;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
import com.emc.storageos.volumecontroller.impl.smis.SmisStorageDevice;
import com.emc.storageos.volumecontroller.impl.smis.SmisUtils;
import com.emc.storageos.workflow.WorkflowException;
/**
* This class will have a base implementation of the
* updateStatus for create volume operations.
*/
public abstract class SmisAbstractCreateVolumeJob extends SmisReplicaCreationJobs {
private static final Logger _log = LoggerFactory.getLogger(SmisAbstractCreateVolumeJob.class);
private URI _storagePool;
private CIMObjectPathFactory _cimPath;
public void setCimPath(CIMObjectPathFactory _cimPath) {
this._cimPath = _cimPath;
}
public SmisAbstractCreateVolumeJob(CIMObjectPath cimJob, URI storageSystem, URI storagePool, TaskCompleter taskCompleter, String name) {
super(cimJob, storageSystem, taskCompleter, name);
_storagePool = storagePool;
}
/**
* Called to update the job status when the volume create job completes.
* <p/>
* This is common update code for volume create operations.
*
* @param jobContext The job context.
*/
@Override
public void updateStatus(JobContext jobContext) throws Exception {
CloseableIterator<CIMObjectPath> iterator = null;
DbClient dbClient = jobContext.getDbClient();
JobStatus jobStatus = getJobStatus();
try {
if (jobStatus == JobStatus.IN_PROGRESS) {
return;
}
int volumeCount = 0;
String opId = getTaskCompleter().getOpId();
StringBuilder logMsgBuilder = new StringBuilder(String.format("Updating status of job %s to %s", opId, jobStatus.name()));
CIMConnectionFactory cimConnectionFactory = jobContext.getCimConnectionFactory();
WBEMClient client = getWBEMClient(dbClient, cimConnectionFactory);
iterator = client.associatorNames(getCimJob(), null, SmisConstants.CIM_STORAGE_VOLUME, null, null);
Calendar now = Calendar.getInstance();
// If terminal state update storage pool capacity and remove reservation for volumes capacity
// from pool's reserved capacity map.
if (jobStatus == JobStatus.SUCCESS || jobStatus == JobStatus.FAILED || jobStatus == JobStatus.FATAL_ERROR) {
SmisUtils.updateStoragePoolCapacity(dbClient, client, _storagePool);
StoragePool pool = dbClient.queryObject(StoragePool.class, _storagePool);
StringMap reservationMap = pool.getReservedCapacityMap();
for (URI volumeId : getTaskCompleter().getIds()) {
// remove from reservation map
reservationMap.remove(volumeId.toString());
}
dbClient.persistObject(pool);
}
if (jobStatus == JobStatus.SUCCESS) {
List<URI> volumes = new ArrayList<URI>();
while (iterator.hasNext()) {
CIMObjectPath volumePath = iterator.next();
CIMProperty<String> deviceID = (CIMProperty<String>) volumePath
.getKey(SmisConstants.CP_DEVICE_ID);
String nativeID = deviceID.getValue();
URI volumeId = getTaskCompleter().getId(volumeCount++);
volumes.add(volumeId);
persistVolumeNativeID(dbClient, volumeId, nativeID, now);
processVolume(jobContext, volumePath, nativeID, volumeId, client, dbClient, logMsgBuilder, now);
}
// Add Volumes to Consistency Group (if needed)
addVolumesToConsistencyGroup(jobContext, volumes);
} else if (jobStatus == JobStatus.FAILED) {
if (iterator.hasNext()) {
while (iterator.hasNext()) {
CIMObjectPath volumePath = iterator.next();
CIMProperty<String> deviceID = (CIMProperty<String>) volumePath
.getKey(SmisConstants.CP_DEVICE_ID);
String nativeID = deviceID.getValue();
URI volumeId = getTaskCompleter().getId(volumeCount++);
if ((nativeID != null) && (nativeID.length() != 0)) {
persistVolumeNativeID(dbClient, volumeId, nativeID, now);
processVolume(jobContext, volumePath, nativeID, volumeId,
client, dbClient, logMsgBuilder, now);
} else {
logMsgBuilder.append("\n");
logMsgBuilder.append(String.format(
"Task %s failed to create volume: %s", opId, volumeId));
Volume volume = dbClient.queryObject(Volume.class, volumeId);
volume.setInactive(true);
dbClient.persistObject(volume);
}
}
} else {
for (URI id : getTaskCompleter().getIds()) {
logMsgBuilder.append("\n");
logMsgBuilder.append(String.format(
"Task %s failed to create volume: %s", opId, id.toString()));
Volume volume = dbClient.queryObject(Volume.class, id);
volume.setInactive(true);
dbClient.persistObject(volume);
}
}
}
_log.info(logMsgBuilder.toString());
} catch (Exception e) {
_log.error("Caught an exception while trying to updateStatus for SmisCreateVolumeJob", e);
setPostProcessingErrorStatus("Encountered an internal error during volume create job status processing : " + e.getMessage());
} finally {
super.updateStatus(jobContext);
if (iterator != null) {
iterator.close();
}
}
}
/**
* This method can be implemented by the derived class for
* specific updates or processing for a derived class.
*
* Default behavior simply updates the deviceLabel name for the single volume that was created.
*
* @param storageSystem [in] - StorageSystem for the Volume
* @param dbClient [in] - Client for reading/writing from/to database.
* @param client [in] - WBEMClient for accessing SMI-S provider data
* @param volume [in] - Reference to Bourne's Volume object
* @param volumePath [in] - Name reference to the SMI-S side volume object
*/
protected void specificProcessing(StorageSystem storageSystem, DbClient dbClient, WBEMClient client, Volume volume, CIMInstance volumeInstance,
CIMObjectPath volumePath) {
String elementName = CIMPropertyFactory.getPropertyValue(volumeInstance, SmisConstants.CP_ELEMENT_NAME);
volume.setDeviceLabel(elementName);
volume.setCompressionRatio(SmisUtils.getCompressionRatioForVolume(volumeInstance));
}
/**
* Processes a newly created volume.
*
* @param jobContext The job context.
* @param volumePath The CIM object path for the volume.
* @param nativeID The native volume identifier.
* @param volumeId The Bourne volume id.
* @param client The CIM client.
* @param dbClient the database client.
* @param logMsgBuilder Holds a log message.
* @param creationTime Holds the date-time for the volume creation
* @throws java.io.IOException When an error occurs querying the database.
* @throws WorkflowException
*/
private void processVolume(JobContext jobContext, CIMObjectPath volumePath, String nativeID,
URI volumeId, WBEMClient client, DbClient dbClient,
StringBuilder logMsgBuilder, Calendar creationTime) throws Exception, IOException, DeviceControllerException, WBEMException {
Volume volume = dbClient.queryObject(Volume.class, volumeId);
// If the volume is inactive, the job failed while the asynchronous work was running.
// Honor that the job failed and do not commit the volume, which would make it active again.
if (volume.getInactive()) {
if (logMsgBuilder.length() != 0) {
logMsgBuilder.append("\n");
}
logMsgBuilder.append(String.format(
"Create volume job failed and volume set to inactive. Volume was likely created successfully and will be left on the array for ingestion. NativeId: %s, URI: %s",
nativeID,
getTaskCompleter().getId()));
return;
}
CIMInstance volumeInstance = commonVolumeUpdate(dbClient, client, volume, volumePath);
URI storageSystemURI = volume.getStorageController();
StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, storageSystemURI);
if (volume.getIsComposite() && _cimPath != null) {
// need to set meta volume member size (required for volume expansion).
// this call is context dependent --- this is why we check for cimPath be set.
// first, get meta members list;
// second, get second member and get its size (the first member is a head and it will show size of meta
// volume itself);
// third, set this size as meta volume size in vipr volume
ArrayList<CIMArgument> list = new ArrayList<CIMArgument>();
CIMArgument<CIMObjectPath> volumeReference = new CIMArgument<CIMObjectPath>(SmisConstants.CP_THE_ELEMENT,
CIMDataType.getDataType(volumePath), volumePath);
// set request type to "children"
CIMArgument<UnsignedInteger16> requestType = new CIMArgument<UnsignedInteger16>("RequestType", CIMDataType.UINT16_T,
new UnsignedInteger16(2));
list.add(volumeReference);
list.add(requestType);
CIMArgument[] inArgs = {};
inArgs = list.toArray(inArgs);
CIMArgument[] outArgs = new CIMArgument[5];
CIMObjectPath elementCompositionServicePath = _cimPath.getElementCompositionSvcPath(storageSystem);
SmisCommandHelper helper = jobContext.getSmisCommandHelper();
StorageSystem forProvider = helper.getStorageSystemForProvider(storageSystem, volume);
helper.invokeMethod(forProvider, elementCompositionServicePath,
"GetCompositeElements", inArgs, outArgs);
// get member volumes from output
CIMObjectPath[] metaMembersPaths = (CIMObjectPath[]) _cimPath.getFromOutputArgs(outArgs, "OutElements");
// get meta member size. use second member --- the first member will show size of meta volume itself.
CIMObjectPath metaMemberPath = metaMembersPaths[1];
CIMInstance cimVolume = helper.getInstance(forProvider, metaMemberPath, false,
false, new String[] { SmisConstants.CP_CONSUMABLE_BLOCKS, SmisConstants.CP_BLOCK_SIZE });
CIMProperty consumableBlocks = cimVolume.getProperty(SmisConstants.CP_CONSUMABLE_BLOCKS);
CIMProperty blockSize = cimVolume.getProperty(SmisConstants.CP_BLOCK_SIZE);
// calculate size = consumableBlocks * block size
Long size = Long.valueOf(consumableBlocks.getValue().toString()) * Long.valueOf(blockSize.getValue().toString());
// set member size for meta volume (required for volume expansion)
volume.setMetaMemberSize(size);
_log.info(String.format("Meta member info: blocks --- %s, block size --- %s, size --- %s .", consumableBlocks.getValue()
.toString(),
blockSize.getValue().toString(), size));
}
specificProcessing(storageSystem, dbClient, client, volume, volumeInstance, volumePath);
dbClient.updateObject(volume);
if (logMsgBuilder.length() != 0) {
logMsgBuilder.append("\n");
}
logMsgBuilder.append(String.format(
"Created volume successfully .. NativeId: %s, URI: %s", nativeID,
getTaskCompleter().getId()));
}
/**
* This method saves the native ID info and creation time for the volume object. The native ID is the key
* identifier for the volume instance on the SMI-S side. We need to immediately persist it, so that if
* when further post-processing of the volume encounters some error, we would be able to have some
* reference to the volume and we could attempt to delete it.
*
* @param volumeID - [IN] URI of Volume
*/
private void persistVolumeNativeID(DbClient dbClient, URI volumeId, String nativeID, Calendar creationTime) throws IOException {
Volume volume = dbClient.queryObject(Volume.class, volumeId);
volume.setCreationTime(creationTime);
volume.setNativeId(nativeID);
volume.setNativeGuid(NativeGUIDGenerator.generateNativeGuid(dbClient, volume));
dbClient.updateObject(volume);
}
/**
* This is common volume attribute update code. Consider this the place to make updates
* for the Bourne's volume object based on the references to the array based object
* irrespective of what type of volume creation it is.
*
* @param dbClient [in] - Client for reading/writing from/to database.
* @param client [in] - WBEMClient for accessing SMI-S provider data
* @param volume [in] - Reference to Bourne's Volume object
* @param volumePath [in] - Name reference to the SMI-S side volume object
* @param nativeID [in] - NativeID extracted from create, will be set on volume
* @param creationTime [in] - Create time of the volume, will be set on volume
* @return CIMInstance - Reference to SMI-S side volume that can be used to retrieve
* data about the volume from the array side.
*/
private CIMInstance commonVolumeUpdate(DbClient dbClient, WBEMClient client, Volume volume, CIMObjectPath volumePath) {
CIMInstance volumeInstance = null;
try {
_log.info("Executing CIMInstance update for volume {} at volume path {}", volume.forDisplay(), volumePath);
volumeInstance = client.getInstance(volumePath, true, false, null);
if (volumeInstance != null) {
String alternateName = CIMPropertyFactory.getPropertyValue(volumeInstance, SmisConstants.CP_NAME);
volume.setAlternateName(alternateName);
String wwn = CIMPropertyFactory.getPropertyValue(volumeInstance, SmisConstants.CP_WWN_NAME);
volume.setWWN(wwn.toUpperCase());
volume.setProvisionedCapacity(getProvisionedCapacityInformation(client, volumeInstance));
volume.setAllocatedCapacity(getAllocatedCapacityInformation(client, volumeInstance));
String accessState = CIMPropertyFactory.getPropertyValue(volumeInstance, SmisConstants.CP_ACCESS);
String[] statusDescriptions = CIMPropertyFactory.getPropertyArray(volumeInstance, SmisConstants.CP_STATUS_DESCRIPTIONS);
// Look for NOT_READY in status descriptions
List<String> statusDescriptionList = Arrays.asList(statusDescriptions);
// If this volume is managed by RP, RP owns the volume access field.
if (!volume.checkForRp()) {
volume.setAccessState(SmisUtils.generateAccessState(accessState, statusDescriptionList));
}
}
volume.setInactive(false);
} catch (Exception e) {
_log.error("Caught an exception while trying to update attributes for volume {} and volume path {}",
volume.forDisplay(), volumePath, e);
// If we could not get the common attributes, for whatever reason, we will mark it as a non-retryable failure
setPostProcessingFailedStatus("Caught an exception while trying to update volume attributes: " + e.getMessage());
}
return volumeInstance;
}
/**
*
* This method will redirect to the appropriate SmiStorageDevice object to make the
* call to add the volumes to the consistency group. This operation should be done
* after the volumes has been successfully created (i.e., there's a deviceNativeId
* for the volumes).
*
* @param jobContext [required] - JobContext object
* @param volumesIds [required] - Volumes to add
* @throws DeviceControllerException
*/
private void addVolumesToConsistencyGroup(JobContext jobContext, List<URI> volumesIds) throws DeviceControllerException {
if (volumesIds == null || volumesIds.isEmpty()) {
return;
}
try {
final DbClient dbClient = jobContext.getDbClient();
// Get volumes from database
final List<Volume> volumes = dbClient.queryObject(Volume.class, volumesIds);
// All the volumes will be in the same consistency group
final URI consistencyGroupId = volumes.get(0).getConsistencyGroup();
BlockConsistencyGroup consistencyGroup = null;
if (consistencyGroupId != null) {
// Get consistency group and storage system from database
consistencyGroup = dbClient
.queryObject(BlockConsistencyGroup.class, consistencyGroupId);
}
if (consistencyGroup == null) {
_log.info(String.format("Skipping step addVolumesToConsistencyGroup: volumes %s do not reference a consistency group.",
volumesIds.toString()));
return;
}
final StorageSystem storage = dbClient.queryObject(StorageSystem.class,
getStorageSystemURI());
final SmisStorageDevice storageDevice = (SmisStorageDevice) ControllerServiceImpl.
getBean(SmisCommandHelper.getSmisStorageDeviceName(storage));
// Add the new volumes to the consistency group unless:
// 1. The volume is a RP+VPlex target/journal backing volume
// 2. The volume does not have a ReplicationGroupInstance field
List<Volume> volumesToAddToCG = new ArrayList<Volume>();
String rgName = null;
for (URI volumeId : volumesIds) {
Volume volume = dbClient.queryObject(Volume.class, volumeId);
if (!RPHelper.isAssociatedToRpVplexType(volume, dbClient, PersonalityTypes.TARGET, PersonalityTypes.METADATA) &&
NullColumnValueGetter.isNotNullValue(volume.getReplicationGroupInstance())) {
rgName = volume.getReplicationGroupInstance();
volumesToAddToCG.add(volume);
} else {
_log.info(String.format(
"Skipping step addVolumesToConsistencyGroup: Volume %s (%s) does not reference an existing consistency group on array %s.",
volume.getLabel(), volume.getId(), volume.getStorageController()));
}
}
if (volumesToAddToCG.isEmpty()) {
_log.info("Skipping step addVolumesToConsistencyGroup: Volumes are not part of a consistency group");
return;
}
storageDevice.addVolumesToConsistencyGroup(storage, consistencyGroup, volumesToAddToCG, rgName, getTaskCompleter());
} catch (Exception e) {
_log.error("Problem making SMI-S call: ", e);
ServiceError error = DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage());
getTaskCompleter().error(jobContext.getDbClient(), error);
}
}
}