/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.vplex.api;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.vplex.api.clientdata.VolumeInfo;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Provides utility methods.
*/
public class VPlexApiUtils {
// Logger reference.
private static Logger s_logger = LoggerFactory.getLogger(VPlexApiUtils.class);
/**
* Transforms the raw WWN format returned by the VPlex CLI.
*
* 0x1a2b3c4d5e6f7g8h -> 1A2B3C4D5E6F7G8H
* or
* REGISTERED_0x1a2b3c4d5e6f7g8h -> 1A2B3C4D5E6F7G8H
*
* @param rawWWN The raw WWN from the VPlex CLI.
*
* @return The formatted WWN.
*/
static String formatWWN(String rawWWN) {
if (rawWWN != null) {
// trim off the REGISTERED_ prefix if it's present
if (rawWWN.toUpperCase().startsWith(VPlexApiConstants.REGISTERED_INITIATOR_PREFIX)){
rawWWN = rawWWN.substring(VPlexApiConstants.REGISTERED_INITIATOR_PREFIX.length());
}
return rawWWN.substring(2).toUpperCase();
}
return rawWWN;
}
/**
* Extracts the attribute data from the passed JSON response and returns
* the attribute data as a Map of name/value pairs.
*
* @param response The response from a VPlex get request for a resource.
*
* @return A map of string/value pairs containing the attribute info.
*
* @throws VPlexApiException When an error occurs parsing the response.
*/
static Map<String, Object> getAttributesFromResponse(String response)
throws VPlexApiException {
Map<String, Object> attributeMap = new HashMap<String, Object>();
try {
JSONObject jsonObj = new JSONObject(response);
JSONObject respObj = jsonObj
.getJSONObject(VPlexApiConstants.RESPONSE_JSON_KEY);
JSONArray contextArray = respObj
.getJSONArray(VPlexApiConstants.CONTEXT_JSON_KEY);
for (int i = 0; i < contextArray.length(); i++) {
JSONObject contextObj = contextArray.getJSONObject(i);
JSONArray attributesArray = contextObj
.getJSONArray(VPlexApiConstants.ATTRIBUTES_JSON_KEY);
for (int j = 0; j < attributesArray.length(); j++) {
JSONObject attObj = attributesArray.getJSONObject(j);
String attName = attObj
.getString(VPlexApiConstants.ATTRIBUTE_NAME_JSON_KEY);
Object attValue = attObj
.get(VPlexApiConstants.ATTRIBUTE_VALUE_JSON_KEY);
attributeMap.put(attName, attValue);
}
}
} catch (Exception e) {
throw VPlexApiException.exceptions.failedExtractingAttributesFromResponse(response, e);
}
return attributeMap;
}
/**
* Extracts all children from the context objects in the passed request
* response.
*
* @param response The response from a VPlex GET request.
*
* @return A list of JSONObject specifying the children.
*
* @throws VPlexApiException When an error occurs processing the response.
*/
static <T extends VPlexResourceInfo> List<T> getChildrenFromResponse(
String baseResourcePath, String response, Class<T> clazz)
throws VPlexApiException {
List<T> children = new ArrayList<T>();
try {
JSONObject jsonObj = new JSONObject(response);
JSONObject respObj = jsonObj.getJSONObject(VPlexApiConstants.RESPONSE_JSON_KEY);
JSONArray contextArray = respObj.getJSONArray(VPlexApiConstants.CONTEXT_JSON_KEY);
for (int i = 0; i < contextArray.length(); i++) {
JSONObject contextObj = contextArray.getJSONObject(i);
JSONArray childArray = contextObj.getJSONArray(VPlexApiConstants.CHILDREN_JSON_KEY);
for (int j = 0; j < childArray.length(); j++) {
JSONObject childObj = childArray.getJSONObject(j);
T child = new Gson().fromJson(childObj.toString(), clazz);
child.setPath(baseResourcePath.substring(VPlexApiConstants.VPLEX_PATH
.length()) + child.getName());
children.add(child);
}
}
} catch (Exception e) {
throw VPlexApiException.exceptions.failedExtractingChildrenFromResponse(response, e);
}
return children;
}
/**
* Extracts VPlexResourceInfo resource objects from a list of JSON
* context objects returned by the VPLEX API.
*
* @param baseResourcePath the REST API Path to the resource
* @param response the response from the VPLEX API for processing
* @param clazz the VPlexResourceInfo resource object class
* @return a list of VPlexResourceInfo objects
*
* @throws VPlexApiException
*/
static <T extends VPlexResourceInfo> List<T> getResourcesFromResponseContext(
String baseResourcePath, String response, Class<T> clazz)
throws VPlexApiException {
List<T> resources = new ArrayList<T>();
try {
JSONObject jsonObj = new JSONObject(response);
JSONObject respObj = jsonObj
.getJSONObject(VPlexApiConstants.RESPONSE_JSON_KEY);
JSONArray contextArray = respObj
.getJSONArray(VPlexApiConstants.CONTEXT_JSON_KEY);
for (int i = 0; i < contextArray.length(); i++) {
JSONObject contextObj = contextArray.getJSONObject(i);
s_logger.debug("Parsing {}: {}", clazz.getName(), contextObj.toString());
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();
T resource = gson.fromJson(contextObj.toString(), clazz);
resource.setPath(contextObj.getString(VPlexApiConstants.PARENT_JSON_KEY) + VPlexApiConstants.SLASH + resource.getName());
resources.add(resource);
}
} catch (Exception e) {
s_logger.error(e.getLocalizedMessage(), e);
throw VPlexApiException.exceptions.failedToDeserializeJsonResponse(e.getLocalizedMessage());
}
return resources;
}
/**
* Sets the value of the attributes for the passed resource by extracting
* the values from the passed resource request response.
*
* @param responseStr The response for a resource GET request.
* @param resourceInfo The resource whose attribute value is set.
*
* @throws VPlexApiException If an error occurs setting the attribute
* values.
*/
@SuppressWarnings("rawtypes")
static void setAttributeValues(String responseStr, VPlexResourceInfo resourceInfo)
throws VPlexApiException {
try {
Map<String, Object> resourceAtts = getAttributesFromResponse(responseStr);
List<String> attributeFilters = resourceInfo.getAttributeFilters();
Iterator<Entry<String, Object>> attsIter = resourceAtts.entrySet().iterator();
while (attsIter.hasNext()) {
Entry<String, Object> entry = attsIter.next();
// Skip attributes we are not interested in.
if ((attributeFilters != null) && (!attributeFilters.isEmpty())
&& (!attributeFilters.contains(entry.getKey()))) {
continue;
}
Class[] parameterTypes;
Object attValObj = entry.getValue();
if (attValObj instanceof JSONArray) {
parameterTypes = new Class[] { List.class };
JSONArray attValArray = (JSONArray) attValObj;
List<String> attValList = new ArrayList<String>();
for (int i = 0; i < attValArray.length(); i++) {
attValList.add(attValArray.get(i).toString());
}
attValObj = attValList;
} else {
parameterTypes = new Class[] { String.class };
attValObj = attValObj.toString();
}
String setterName = resourceInfo.getAttributeSetterMethodName(entry.getKey());
Method m = resourceInfo.getClass().getMethod(setterName, parameterTypes);
m.invoke(resourceInfo, attValObj);
}
} catch (Exception e) {
throw VPlexApiException.exceptions.failedSettingAttributesForResource(resourceInfo.getName(), e);
}
}
/**
* Creates JSON formatted post data for passed arguments.
*
* @param argsMap A map of the POST data arguments.
* @param includeForce true to include the force argument, false otherwise.
*
* @return A JSONOBject containing the POST data.
*
* @throws VPlexApiException When an error occurs forming the post data.
*/
static <T extends VPlexResourceInfo> JSONObject createPostData(
Map<String, String> argsMap, boolean includeForce) throws VPlexApiException {
try {
StringBuilder argsBuilder = new StringBuilder();
if (argsMap != null) {
Iterator<Entry<String, String>> argsIter = argsMap.entrySet().iterator();
while (argsIter.hasNext()) {
Entry<String, String> entry = argsIter.next();
if (argsBuilder.length() != 0) {
argsBuilder.append(" ");
}
argsBuilder.append(entry.getKey());
argsBuilder.append(" ");
argsBuilder.append(entry.getValue());
}
}
if (includeForce) {
argsBuilder.append(" ");
argsBuilder.append(VPlexApiConstants.ARG_FORCE);
}
JSONObject postDataObject = new JSONObject();
postDataObject.put(VPlexApiConstants.POST_DATA_ARG_KEY, argsBuilder.toString());
return postDataObject;
} catch (Exception e) {
throw VPlexApiException.exceptions.failedCreatingPostDataForRequest(e);
}
}
/**
* Extracts the "custom-data" from the passed JSON response.
*
* @param response The response from a VPlex request.
*
* @return A string specifying the value for the custom data.
*
* @throws VPlexApiException When an error occurs parsing the response.
*/
static String getCustomDataFromResponse(String response) throws VPlexApiException {
String customData = null;
try {
JSONObject jsonObj = new JSONObject(response);
JSONObject respObj = jsonObj
.getJSONObject(VPlexApiConstants.RESPONSE_JSON_KEY);
customData = respObj.get(VPlexApiConstants.CUSTOM_DATA_JSON_KEY).toString();
} catch (Exception e) {
throw VPlexApiException.exceptions.failedGettingCustomDataFromResponse(response, e);
}
return customData;
}
/**
* Extracts the "exception" message from the passed JSON response.
*
* @param response The response from a VPlex request.
*
* @return A string specifying the value for the exception message.
*
* @throws VPlexApiException When an error occurs parsing the response.
*/
static String getExceptionMessageFromResponse(String response) throws VPlexApiException {
String exceptionMessage = null;
try {
JSONObject jsonObj = new JSONObject(response);
JSONObject respObj = jsonObj
.getJSONObject(VPlexApiConstants.RESPONSE_JSON_KEY);
exceptionMessage = respObj.get(VPlexApiConstants.EXCEPTION_MSG_JSON_KEY).toString();
} catch (Exception e) {
throw VPlexApiException.exceptions.failedGettingExceptionMsgFromResponse(response, e);
}
return exceptionMessage;
}
/**
* Extracts the cause returned in a failure response by the VPLEX.
*
* @param response The response from the VPLEX.
*
* @return The cause of the failure.
*/
static String getCauseOfFailureFromResponse(String response) {
// There is often multiple "cause:" in the response, and the last
// is the one that supplies the specific details that are most useful.
// For example, the first cause might be "Command Failed". The next
// cause might be "Failed to supply a value for parameter --foo".
// Then the last cause might be "Foo cannot begin with a numerical
// value", so the parameter is supplied. It just has an invalid value.
StringBuilder result = new StringBuilder();
String exceptionMessage = getExceptionMessageFromResponse(response);
if (exceptionMessage != null) {
String[] causes = exceptionMessage.split(VPlexApiConstants.CAUSE_DELIM);
if (causes != null && causes.length > 0) {
String cause = causes[causes.length - 1];
String[] lines = cause.split("\n");
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
result.append(line);
if (i < lines.length - 1) {
result.append(" ");
}
}
} else {
result.append(exceptionMessage);
}
}
return result.toString();
}
/**
* Simple puts the thread to sleep for the passed duration.
*
* @param duration How long to pause in milliseconds.
*/
static void pauseThread(long duration) {
try {
Thread.sleep(duration);
} catch (Exception e) {
s_logger.warn("Exception while trying to sleep", e);
}
}
/**
* ITLs fetch is required if the backend
* array has populated the ITLs in the VolumeInfo
*
* Note : Currently Cinder is using ITLs for volume lookup
*
* @param volInfo
* @return
*/
public static boolean isITLBasedSearch(VolumeInfo volumeInfo) {
return !volumeInfo.getITLs().isEmpty();
}
/**
* Determines if the passed volume name conforms to the default naming convention.
*
* @param volumeName The name of the volume.
* @param supportingDeviceName The name of the supporting device.
* @param isDistributed true if the volume is distributed, or false for local.
* @param claimedVolumeNames The names of the claimed storage volumes used by the virtual volumes.
*
* @return true if the volume name conforms to the default naming convention, false otherwise.
*/
public static boolean volumeHasDefaultNamingConvention(String volumeName, String supportingDeviceName,
boolean isDistributed, List<String> claimedVolumeNames) {
// First check that the supporting device conforms to the default naming convention.
if (!deviceHasDefaultNamingConvention(supportingDeviceName, isDistributed, claimedVolumeNames)) {
s_logger.info("Supporting device {} does conform to default naming convention", supportingDeviceName);
return false;
}
// The volume name must end with the expected virtual volume suffix.
if (!volumeName.endsWith(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX)) {
s_logger.info("Volume {} does not end with the expected suffix", volumeName);
return false;
}
// Verify the passed volume name is not simply the volume suffix.
if (volumeName.length() == VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX.length()) {
s_logger.info("Volume name {} consists only of the volume suffix", volumeName);
return false;
}
// Extract the value after before the suffix, which in the default naming convention
// is the supporting device name.
int endIndex = volumeName.length() - VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX.length();
if (!supportingDeviceName.equals(volumeName.substring(0, endIndex))) {
s_logger.info("Volume name {} does not conform to default naming convention", volumeName);
return false;
}
return true;
}
/**
* Determines if the passed supporting device name conforms to the default naming convention.
*
* @param supportingDeviceName The name of the supporting device.
* @param isDistributed true if the volume is distributed, or false for local.
* @param claimedVolumeNames The names of the claimed storage volumes used by the device.
*
* @return true if the device name conforms to the default naming convention, false otherwise.
*/
public static boolean deviceHasDefaultNamingConvention(String supportingDeviceName, boolean isDistributed, List<String> claimedVolumeNames) {
// Verify the passed supporting device name based on whether it is distributed or local.
if (isDistributed) {
return distributedDeviceHasDefaultNamingConvention(supportingDeviceName, claimedVolumeNames);
} else {
return localDeviceHasDefaultNamingConvention(supportingDeviceName, claimedVolumeNames);
}
}
/**
* Determines if the passed local device name conforms to the default naming convention.
*
* @param supportingDeviceName The name of the supporting device.
* @param claimedVolumeNames The names of the storage volumes used by the device.
*
* @return true if the device name conforms to the default naming convention, false otherwise.
*/
public static boolean localDeviceHasDefaultNamingConvention(String deviceName, List<String> claimedVolumeNames) {
// The local device name must start with the local device prefix.
if (!deviceName.startsWith(VPlexApiConstants.DEVICE_PREFIX)) {
s_logger.info("Local device {} does not start with the expected prefix", deviceName);
return false;
}
// Verify the passed device name is not simply the device prefix.
if (deviceName.equals(VPlexApiConstants.DEVICE_PREFIX.length())) {
s_logger.info("Local device name {} consists only of the device prefix", deviceName);
return false;
}
// There should only be a single claimed volume.
if (claimedVolumeNames.size() != 1) {
s_logger.info("Too many claimed volumes {} for local device {}", claimedVolumeNames, deviceName);
return false;
}
String claimedVolumeName = claimedVolumeNames.get(0);
// Extract the value after the prefix, which in the default naming convention
// is the claimed storage volume name.
int startIndex = VPlexApiConstants.DEVICE_PREFIX.length();
if (!claimedVolumeName.equals(deviceName.substring(startIndex))) {
s_logger.info("Local device name {} does not conform to default naming convention", deviceName);
return false;
}
return true;
}
/**
* Determines if the passed distributed device name conforms to the default naming convention.
*
* @param supportingDeviceName The name of the supporting device.
* @param claimedVolumeNames The names of the storage volumes used by the device.
*
* @return true if the device name conforms to the default naming convention, false otherwise.
*/
public static boolean distributedDeviceHasDefaultNamingConvention(String deviceName, List<String> claimedVolumeNames) {
// The distributed device must start with the expected prefix.
String distDevicePrefix = VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM;
if (!deviceName.startsWith(distDevicePrefix)) {
s_logger.info("Distributed device {} does not start with the expected prefix", deviceName);
return false;
}
// Verify the device name is not exactly the prefix.
if (deviceName.equals(distDevicePrefix)) {
s_logger.info("Distributed device {} consists only of the expected prefix", deviceName);
return false;
}
// Get the distributed device name without the prefix. This must consist of
// exactly two components separated by a delimiter.
String deviceNameNoPrefix = deviceName.substring(distDevicePrefix.length());
String[] deviceNameComponents = deviceNameNoPrefix.split(VPlexApiConstants.DIST_DEVICE_NAME_DELIM);
if (deviceNameComponents.length != 2) {
s_logger.info("Distributed device {} does not consist of exactly 2 components", deviceName);
return false;
}
// These two components must be the passed claimed storage volume names.
boolean allComponentsMatched = true;
for (String deviceNameComponent : deviceNameComponents) {
boolean match = false;
for (String claimedVolumeName : claimedVolumeNames)
if (deviceNameComponent.equals(claimedVolumeName)) {
match = true;
break;
}
if (!match) {
allComponentsMatched = false;
break;
}
}
if (!allComponentsMatched) {
s_logger.info("Distributed device name {} does not contain claimed volumes {}", deviceName, claimedVolumeNames);
return false;
}
return true;
}
}