/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.hds.api;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.HttpStatus;
import org.milyn.payload.JavaResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.hds.HDSConstants;
import com.emc.storageos.hds.HDSException;
import com.emc.storageos.hds.model.Add;
import com.emc.storageos.hds.model.Delete;
import com.emc.storageos.hds.model.EchoCommand;
import com.emc.storageos.hds.model.Error;
import com.emc.storageos.hds.model.HostStorageDomain;
import com.emc.storageos.hds.model.Path;
import com.emc.storageos.hds.model.StorageArray;
import com.emc.storageos.hds.util.SmooksUtil;
import com.emc.storageos.hds.xmlgen.InputXMLGenerationClient;
import com.sun.jersey.api.client.ClientResponse;
/**
* This class is responsible to perform the Batch API operations using REST
* calls like POST to HiCommand DM server.
*
*/
public class HDSBatchApiExportManager {
/**
* Logger instance to log messages.
*/
private static final Logger log = LoggerFactory
.getLogger(HDSBatchApiExportManager.class);
private static final int MAX_RETRIES = 10;
private HDSApiClient hdsApiClient;
public HDSBatchApiExportManager(HDSApiClient hdsApiClient) {
this.hdsApiClient = hdsApiClient;
}
/**
* This method makes http POST call with a payload of bulk
* HostStorageDomains.
*
* @param systemId
* - SystemObjectID.
* @param hostGroups
* - List of HostStorageDomain objects.
* @return - Returns a List of created HostStorageDomain on Array.
* @throws Exception
* - In case processing errors.
*/
public List<HostStorageDomain> addHostStorageDomains(String systemId,
List<HostStorageDomain> hostGroups, String model) throws Exception {
InputStream responseStream = null;
List<HostStorageDomain> hsdList = null;
try {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Add addOp = new Add(HDSConstants.HOST_STORAGE_DOMAIN);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.ADD, addOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.HOSTGROUP_LIST, hostGroups);
String addHSDToSystemQuery = InputXMLGenerationClient
.getInputXMLString(
HDSConstants.BATCH_ADD_HSDS_TO_SYSTEM_OP,
attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
log.info("Batch query to create HostStorageDomains: {}",
addHSDToSystemQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
addHSDToSystemQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
verifyErrorPayload(javaResult);
hsdList = (List<HostStorageDomain>) javaResult
.getBean(HDSConstants.HSD_RESPONSE_BEAN_ID);
if (null == hsdList || hsdList.isEmpty()) {
throw HDSException.exceptions.notAbleToAddHSD(systemId);
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to add HostStorageDomains due to invalid response %1$s from server",
response.getStatus()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream addHostStorageDomains");
}
}
}
return hsdList;
}
/**
* This method makes a HTTP POST call to HiCommand with a payload of muliple
* HSD's and each with a set of WWNs. This method can only be used for FC
* HSD's.
*
* @param systemId
* - represents Storage System ObjectID.
* @param hsdList
* - List of HostStorageDomain objects.
* @return - List of HostStorageDomain objects with WWN's added.
* @throws Exception
* - In case of processing error.
*/
public List<HostStorageDomain> addWWNsToHostStorageDomain(String systemId,
List<HostStorageDomain> hsdList, String model) throws Exception {
InputStream responseStream = null;
List<HostStorageDomain> hsdResponseList = null;
try {
String addWWNToHSDsQuery = constructWWNQuery(systemId, hsdList, model);
log.info(
"batch query to add FC initiators to HostStorageDomains: {}",
addWWNToHSDsQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
addWWNToHSDsQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
verifyErrorPayload(javaResult);
hsdResponseList = (List<HostStorageDomain>) javaResult
.getBean(HDSConstants.HSD_RESPONSE_BEAN_ID);
if (null == hsdResponseList || hsdResponseList.isEmpty()) {
throw HDSException.exceptions
.notAbleToAddInitiatorsToHostStorageDomain(systemId);
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Batch query to add FC initiators to HSDs failed due to invalid response %1$s from server",
response.getStatus()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream");
}
}
}
return hsdResponseList;
}
/**
* This method makes a HTTP POST call to HiCommand with a payload of muliple
* HSD's and each with a set of ISCSINames. This method can only be used for
* ISCSI HSD's.
*
* @param systemId
* - represents Storage System ObjectID.
* @param hsdList
* - List of HostStorageDomain objects.
* @return - List of HostStorageDomain objects with ISCSINames's added.
* @throws Exception
* - In case of processing error.
*/
public List<HostStorageDomain> addISCSINamesToHostStorageDomain(
String systemId, List<HostStorageDomain> hsdList, String model) throws Exception {
InputStream responseStream = null;
List<HostStorageDomain> hsdResponseList = null;
try {
String addISCSINamesToHSDsQuery = constructISCSINamesQuery(
systemId, hsdList, model);
log.info(
"batch query to add ISCSI initiators to HostStorageDomains: {}",
addISCSINamesToHSDsQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
addISCSINamesToHSDsQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
verifyErrorPayload(javaResult);
hsdResponseList = (List<HostStorageDomain>) javaResult
.getBean(HDSConstants.HSD_RESPONSE_BEAN_ID);
if (null == hsdResponseList || hsdResponseList.isEmpty()) {
throw HDSException.exceptions
.notAbleToAddInitiatorsToHostStorageDomain(systemId);
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Batch query to add ISCSI initiators to HSDs failed due to invalid response %1$s from server",
response.getStatus()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream");
}
}
}
return hsdResponseList;
}
/**
* This method makes a HTTP POST call to add multiple LUN Paths using a
* batch query.
*
* @param systemId
* - Represents the storage system objectID.
* @param pathList
* - List of Path objects.
* @param model - model of the system
* @return - List of Path objects after successful creation.
* @throws Exception
* - Incase of processing Error.
*/
public List<Path> addLUNPathsToHSDs(String systemId, List<Path> pathList, String model)
throws Exception {
InputStream responseStream = null;
List<Path> pathResponseList = null;
try {
String addLUNQuery = constructAddLUNQuery(systemId, pathList, model);
log.info("Query to addLUN Query: {}", addLUNQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
addLUNQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
verifyErrorPayload(javaResult);
pathResponseList = (List<Path>) javaResult
.getBean(HDSConstants.PATHLIST_RESPONSE_BEANID);
if (null == pathResponseList || pathResponseList.isEmpty()) {
throw HDSException.exceptions.notAbleToAddVolumeToHSD(null,
systemId);
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to add Volume to HostStorageDomain due to invalid response %1$s from server",
response.getStatus()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream");
}
}
}
return pathResponseList;
}
/**
* This method makes a HTTP POST call to delete all HostStorageDomain's
* using a batch query.
*
* @param systemId
* - Represents storage system ObjectID.
* @param hsdList
* - List of HostStorageDomain objects to delete.
* @param model - Model of the system
*
* @throws Exception
*/
public void deleteBatchHostStorageDomains(String systemId,
List<HostStorageDomain> hsdList, String model) throws Exception {
InputStream responseStream = null;
try {
List<HostStorageDomain> unDeletedHSDs = new ArrayList<>();
unDeletedHSDs.addAll(hsdList);
boolean operationSucceeds = false;
int retryCount = 0;
StringBuilder errorDescriptionBuilder = new StringBuilder();
while (!operationSucceeds && retryCount < MAX_RETRIES) {
retryCount++;
String deleteHSDsQuery = constructDeleteHSDsQuery(systemId, unDeletedHSDs, model);
log.info("Batch Query to delete HSD's: {}", deleteHSDsQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
deleteHSDsQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
try {
verifyErrorPayload(javaResult);
operationSucceeds = true; // If no exception then operation succeeds
} catch (HDSException hdsException) {
Error error = javaResult.getBean(Error.class);
if (error != null && (error.getDescription().contains("2010")
|| error.getDescription().contains("5132") || error.getDescription().contains("7473"))) {
log.error("Error response recieved from HiCommandManger: {}", error.getDescription());
log.info("Exception from HICommand Manager recieved during delete operation, retrying operation {} time",
retryCount);
errorDescriptionBuilder.append("error ").append(retryCount).append(" : ").append(error.getDescription())
.append("-#####-");
Thread.sleep(60000); // Wait for a minute before retry
unDeletedHSDs.clear();
unDeletedHSDs.addAll(hsdList.stream().filter(hsd -> getHostStorageDomain(systemId, hsd.getObjectID()) != null)
.collect(Collectors.toList()));
if (unDeletedHSDs.isEmpty()) {
operationSucceeds = true; // Operation succeeded
log.info("Deleted {} LUN paths from system:{}",
hsdList.size(), systemId);
} else {
continue; // Retry the operation again if retry count not exceeded
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to delete HostStorageDomains due to invalid response %1$s from server",
response.getStatus()));
}
}
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to delete HostStorageDomains due to invalid response %1$s from server",
response.getStatus()));
}
}
if (!operationSucceeds) {// Delete operation failed ever after repeated retries
throw HDSException.exceptions
.invalidResponseFromHDS(String.format(
"Not able to delete HostStorageDomains due to repeated errors from HiCommand server, errors description are as %s",
errorDescriptionBuilder.toString()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream");
}
}
}
log.info("Batch Query to delete HSD's completed.");
}
/**
* Return the existing HSD's configured on the storage array.
*
* @param systemId
* @param type
* @return
* @throws Exception
*/
public HostStorageDomain getHostStorageDomain(String systemId, String hsdId) {
HostStorageDomain hsd = null;
try {
hsd = hdsApiClient.getHDSApiExportManager().getHostStorageDomain(systemId, hsdId);
} catch (Exception e) {//Nothing much can be done, So log exception and ignore the HSD in question
log.warn("Exception occured while getting HostStorageDomain", e);
}
return hsd;
}
/**
* This method makes HTTP POST call to delete LUN Paths's using a batch
* query from the storage system.
*
* @param systemId
* - represents the storage system objectID.
* @param pathList
* - List of Path objects to delete.
* @param model - Model of the system
* @throws Exception
* - Incase of processing error.
*/
public void deleteLUNPathsFromStorageSystem(String systemId,
List<Path> pathList, String model) throws Exception {
InputStream responseStream = null;
try {
boolean operationSucceeds = false;
int retryCount = 0;
StringBuilder errorDescriptionBuilder = new StringBuilder();
while (!operationSucceeds && retryCount < MAX_RETRIES) {
retryCount++;
String deleteLUNsQuery = constructRemoveLUNsQuery(systemId,
pathList, model);
log.info("Batch query to deleteLUNs Query: {}", deleteLUNsQuery);
URI endpointURI = hdsApiClient.getBaseURI();
ClientResponse response = hdsApiClient.post(endpointURI,
deleteLUNsQuery);
if (HttpStatus.SC_OK == response.getStatus()) {
responseStream = response.getEntityInputStream();
JavaResult javaResult = SmooksUtil.getParsedXMLJavaResult(
responseStream, HDSConstants.SMOOKS_CONFIG_FILE);
try {
verifyErrorPayload(javaResult);
operationSucceeds = true; //If no exception then operation succeeds
} catch (HDSException hdsException) {
Error error = javaResult.getBean(Error.class);
if (error != null && (error.getDescription().contains("2010")
|| error.getDescription().contains("5132") || error.getDescription().contains("7473"))) {
log.error("Error response recieved from HiCommandManger: {}", error.getDescription());
log.info("Exception from HICommand Manager recieved during delete operation, retrying operation {} time",
retryCount);
errorDescriptionBuilder.append("error ").append(retryCount).append(" : ").append(error.getDescription())
.append("-#####-");
Thread.sleep(60000); // Wait for a minute before retry
continue; // Retry the operation again if retry count not exceeded
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to delete LunPaths due to invalid response %1$s from server",
response.getStatus()));
}
}
log.info("Deleted {} LUN paths from system:{}",
pathList.size(), systemId);
} else {
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to delete Volume from HostGroups due to invalid response %1$s from server",
response.getStatus()));
}
}
if(!operationSucceeds) {// Delete operation failed ever after repeated retries
throw HDSException.exceptions
.invalidResponseFromHDS(String
.format("Not able to delete LunPaths due to repeated errors from HiCommand server, errors description are as %s",
errorDescriptionBuilder.toString()));
}
} finally {
if (null != responseStream) {
try {
responseStream.close();
} catch (IOException e) {
log.warn("IOException occurred while closing the response stream");
}
}
}
}
/**
* Constructs a batch query for given HSD's and each with a set of
* WorldWideName's to add. This query should be used to add FC initiators to
* the FC HSD.
*
* @param systemId
* - Represents the storage system objectID.
* @param hsdList
* - List of HostStorageDomain objects.
* @return
*/
private String constructISCSINamesQuery(String systemId,
List<HostStorageDomain> hsdList, String model) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Add addOp = new Add(HDSConstants.ISCSI_NAME_FOR_HSD_TARGET);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.ADD, addOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.HOSTGROUP_LIST, hsdList);
String addWWNQuery = InputXMLGenerationClient.getInputXMLString(
HDSConstants.BATCH_ADD_WWN_TO_HSD_OP, attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
return addWWNQuery;
}
/**
* Constructs a batch query for given HSD's and the WorldWideName's to add.
* This query should be used to add FC initiators to the FC HSD. This
* constructs the xml input string for multiple HSD's and each HSD with
* multiple WWN's.
*
* @param systemId
* - StorageSystem ObjectID.
* @param hsdList
* - List of HSD objects.
* @return - XML String to add HSD's with WWN's.
*/
private String constructWWNQuery(String systemId,
List<HostStorageDomain> hsdList, String model) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Add addOp = new Add(HDSConstants.ADD_WWN_TO_HSD_TARGET);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.ADD, addOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.HOSTGROUP_LIST, hsdList);
return InputXMLGenerationClient.getInputXMLString(
HDSConstants.BATCH_ADD_WWN_TO_HSD_OP, attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
}
/**
* Constructs a batch query for the given Path objects to remove LUN's from
* storage system.
*
* @param systemId
* - represents storage system objectID.
* @param pathList
* - List of Path objects.
* @return - XML String to remove the Paths from storage system
*/
private String constructRemoveLUNsQuery(String systemId, List<Path> pathList, String model) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Delete deleteOp = new Delete(HDSConstants.LUN_TARGET);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.DELETE, deleteOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.PATH_LIST, pathList);
return InputXMLGenerationClient.getInputXMLString(
HDSConstants.DELETE_PATH_FROM_HSD_OP, attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
}
/**
* Constructs a batch query for the given Path objects to add LUN's to
* storage system.
*
* @param systemId
* - represents storagesystem objectID.
* @param pathList
* - List of Path objects.
* @return - XML String to add LUN's to storage system.
*/
private String constructAddLUNQuery(String systemId, List<Path> pathList, String model) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Add addOp = new Add(HDSConstants.LUN_TARGET);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.ADD, addOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.PATH_LIST, pathList);
return InputXMLGenerationClient.getInputXMLString(
HDSConstants.ADD_PATH_TO_HSD_OP, attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
}
/**
* Constructs a batch query to delete given HSD's from the storage system.
*
* @param systemId
* - represents the storage system ObjectID.
* @param hsdList
* - List of HostStorageDomain objects.
* @return - XML string to delete the HostStorageDomain's
*/
private String constructDeleteHSDsQuery(String systemId,
List<HostStorageDomain> hsdList, String model) {
Map<String, Object> attributeMap = new HashMap<String, Object>();
StorageArray array = new StorageArray(systemId);
Delete deleteOp = new Delete(HDSConstants.HOST_STORAGE_DOMAIN);
attributeMap.put(HDSConstants.STORAGEARRAY, array);
attributeMap.put(HDSConstants.DELETE, deleteOp);
attributeMap.put(HDSConstants.MODEL, model);
attributeMap.put(HDSConstants.HOSTGROUP_LIST, hsdList);
return InputXMLGenerationClient.getInputXMLString(
HDSConstants.BATCH_DELETE_HSDS_FROM_SYSTEM, attributeMap,
HDSConstants.HITACHI_INPUT_XML_CONTEXT_FILE,
HDSConstants.HITACHI_SMOOKS_CONFIG_FILE);
}
/**
* Utility method to check if there are any errors or not.
*
* @param javaResult
* - java Result of the Parsed XML response.
* @throws Exception
* - In case of processing error.
*/
private void verifyErrorPayload(JavaResult javaResult) throws Exception {
EchoCommand command = javaResult.getBean(EchoCommand.class);
if (null == command
|| null == command.getStatus()
|| HDSConstants.FAILED_STR
.equalsIgnoreCase(command.getStatus())) {
Error error = javaResult.getBean(Error.class);
log.info(
"Error response received from Hitachi server for messageID",
command.getMessageID());
log.info(
"Hitachi command failed with error code:{} with message:{} for request:{}",
new Object[] { error.getCode().toString(),
error.getDescription(), error.getSource() });
throw HDSException.exceptions.errorResponseReceived(
error.getCode(), error.getDescription());
}
}
}