/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.vnxe.requests;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.vnxe.VNXeConstants;
import com.emc.storageos.vnxe.VNXeException;
import com.emc.storageos.vnxe.models.ParamBase;
import com.emc.storageos.vnxe.models.VNXeCommandJob;
import com.emc.storageos.vnxe.models.VNXeCommandResult;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/*
* This is the base class for request sending to KittyHawk/VNXe server
*/
public class KHRequests<T> {
private static final Logger _logger = LoggerFactory.getLogger(KHRequests.class);
protected MultivaluedMap<String, String> _queryParams;
protected String _url;
protected String _fields;
protected KHClient _client;
private WebResource _resource;
private Set<NewCookie> _requestCookies = new HashSet<NewCookie>();
private static final String AUTH_TOKEN = "Cookie";
private static final String CLIENT_HEADER = "X-EMC-REST-CLIENT";
private static final String EMC_CSRF_HEADER = "EMC-CSRF-TOKEN";
private static final String GET_REQUEST = "GET";
private static final String POST_REQUEST = "POST";
private static final String DELETE_REQUEST = "DELETE";
private static final String CONNECTION = "Connection";
private static final String CLOSE = "close";
public KHRequests(KHClient client) {
_client = client;
_resource = client.getResource();
}
protected KHClient getClient() {
return _client;
}
public void setQueryParameters(MultivaluedMap<String, String> queryParams) {
if (_queryParams != null) {
for (String key : queryParams.keySet()) {
List<String> values = queryParams.get(key);
for (String value : values) {
_queryParams.add(key, value);
}
}
} else {
_queryParams = queryParams;
}
}
public void unsetQueryParameters() {
_queryParams = null;
}
public WebResource buildResource(WebResource resource) {
return resource.path(_url);
}
/*
* Build the request with headers and cookies
*/
protected WebResource.Builder buildRequest(WebResource.Builder builder) {
builder = builder.header(CLIENT_HEADER, "true");
if (_client.getEmcCsrfToken() != null) {
_logger.debug("EMC-CSRF-TOKEN is:: " + _client.getEmcCsrfToken());
builder.header(EMC_CSRF_HEADER, _client.getEmcCsrfToken());
}
builder.header(CONNECTION, CLOSE);
Set<NewCookie> cookies = null;
if (!_requestCookies.isEmpty()) {
cookies = _requestCookies;
} else {
cookies = _client.getCookie();
}
if (cookies != null && !cookies.isEmpty()) {
StringBuilder buildCookies = new StringBuilder();
int n = 0;
for (NewCookie cookie : cookies) {
if (n == 0) {
buildCookies.append(cookie.toString());
} else {
buildCookies.append(";");
buildCookies.append(cookie.toString());
}
n++;
}
_logger.debug("setting the cookie:" + buildCookies.toString());
builder = builder.header(AUTH_TOKEN, buildCookies.toString());
}
builder = builder.accept(MediaType.APPLICATION_JSON_TYPE);
builder = builder.type(MediaType.APPLICATION_JSON_TYPE);
return builder;
}
protected WebResource addQueryParameters(WebResource resource) {
if (_queryParams == null) {
return resource; // no query parameters
}
_logger.debug("_queryParams:" + _queryParams);
return resource.queryParams(_queryParams);
}
/*
* get a list type of request
* e.g. GET /api/types/system/instances
*
* @param resource WebResource
*
* @param valueType class type
*
* @return list of objects
*
* @throws VnxeException unexpectedDataError
*/
public List<T> getDataForObjects(Class<T> valueType)
throws VNXeException {
_logger.info("getting data: {}", _url);
ClientResponse response = sendGetRequest(_resource);
String emcCsrfToken = response.getHeaders().getFirst(EMC_CSRF_HEADER);
if (emcCsrfToken != null) {
saveEmcCsrfToken(emcCsrfToken);
}
saveClientCookies();
String resString = response.getEntity(String.class);
_logger.info("got data: " + resString);
JSONObject res;
List<T> returnedObjects = new ArrayList<T>();
try {
res = new JSONObject(resString);
if (res != null) {
JSONArray entries = res.getJSONArray(VNXeConstants.ENTRIES);
if (entries != null && entries.length() > 0) {
for (int i = 0; i < entries.length(); i++) {
JSONObject entry = entries.getJSONObject(i);
JSONObject object = (JSONObject) entry.get(VNXeConstants.CONTENT);
if (object != null) {
String objectString = object.toString();
ObjectMapper mapper = new ObjectMapper();
try {
T returnedObject = mapper.readValue(objectString, valueType);
returnedObjects.add(returnedObject);
} catch (JsonParseException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(objectString, e);
} catch (JsonMappingException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(objectString, e);
} catch (IOException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(objectString, e);
}
}
}
}
}
} catch (JSONException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(resString, e);
}
return returnedObjects;
}
/*
* get one instance request
* e.g. GET /api/instances/nasServer/<id>
*
* @param resource WebResource
*
* @param Class<T> class type
*
* @throws VnexException unexpectedDataError
*/
public T getDataForOneObject(Class<T> valueType) throws VNXeException {
_logger.debug("getting data: " + _url);
ClientResponse response = sendGetRequest(_resource);
String emcCsrfToken = response.getHeaders().getFirst(EMC_CSRF_HEADER);
if (emcCsrfToken != null) {
saveEmcCsrfToken(emcCsrfToken);
}
saveClientCookies();
String resString = response.getEntity(String.class);
_logger.debug("got data: " + resString);
JSONObject res;
String objectString = null;
T returnedObject = null;
try {
res = new JSONObject(resString);
if (res != null) {
JSONObject object = (JSONObject) res.get(VNXeConstants.CONTENT);
if (object != null) {
objectString = object.toString();
ObjectMapper mapper = new ObjectMapper();
try {
returnedObject = mapper.readValue(objectString, valueType);
} catch (JsonParseException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + objectString, e);
} catch (JsonMappingException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + objectString, e);
} catch (IOException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", objectString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + objectString, e);
}
}
}
} catch (JSONException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + objectString, e);
}
return returnedObject;
}
/*
* Send POST request to KittyHawk server, and handle redirect/cookies
*
* @param resource webResource
*
* @param ParamBase parameters for post
*
* @throws VNXeException
*/
public ClientResponse postRequest(ParamBase param) throws VNXeException {
_logger.debug("post data: " + _url);
ObjectMapper mapper = new ObjectMapper();
String parmString = null;
if (param != null) {
try {
parmString = mapper.writeValueAsString(param);
_logger.debug("Content of the post: {}", parmString);
} catch (JsonGenerationException e) {
_logger.error("Post request param is not valid. ", e);
throw VNXeException.exceptions.vnxeCommandFailed("Post request param is not valid.", e);
} catch (JsonMappingException e) {
_logger.error("Post request param is not valid. ", e);
throw VNXeException.exceptions.vnxeCommandFailed("Post request param is not valid.", e);
} catch (IOException e) {
_logger.error("Post request param is not valid. ", e);
throw VNXeException.exceptions.vnxeCommandFailed("Post request param is not valid.", e);
}
}
ClientResponse response = buildRequest(addQueryParameters(buildResource(_resource))
.getRequestBuilder()).entity(parmString).post(ClientResponse.class);
Status statusCode = response.getClientResponseStatus();
if (statusCode == ClientResponse.Status.CREATED
|| statusCode == ClientResponse.Status.ACCEPTED
|| statusCode == ClientResponse.Status.OK
|| statusCode == ClientResponse.Status.NO_CONTENT) {
return response;
} else if (statusCode == ClientResponse.Status.UNAUTHORIZED) {
authenticate();
response = buildRequest(addQueryParameters(buildResource(_resource))
.getRequestBuilder()).entity(parmString).post(ClientResponse.class);
;
statusCode = response.getClientResponseStatus();
if (statusCode == ClientResponse.Status.OK
|| statusCode == ClientResponse.Status.ACCEPTED
|| statusCode == ClientResponse.Status.NO_CONTENT
|| statusCode == ClientResponse.Status.CREATED) {
return response;
}
}
int redirectTimes = 1;
// handle redirect
while (response.getClientResponseStatus() == ClientResponse.Status.FOUND &&
redirectTimes < VNXeConstants.REDIRECT_MAX) {
String code = response.getClientResponseStatus().toString();
String data = response.getEntity(String.class);
_logger.debug("Returned code: {}, returned data {}", code, data);
WebResource newResource = handelRedirect(response);
if (newResource != null) {
response = buildRequest(newResource.getRequestBuilder()).entity(parmString).post(ClientResponse.class);
redirectTimes++;
} else {
// could not find the redirect url, return
_logger.error(String.format("The post request to: %s failed with: %s %s", _url,
response.getClientResponseStatus().toString(), response.getEntity(String.class)));
throw VNXeException.exceptions.unexpectedDataError("Got redirect status code, but could not get redirected URL");
}
}
if (redirectTimes >= VNXeConstants.REDIRECT_MAX) {
_logger.error("redirected too many times for the request {}", _url);
throw VNXeException.exceptions.unexpectedDataError("Redirected too many times while sending the request for " + _url);
}
checkResponse(response, POST_REQUEST);
return response;
}
public VNXeCommandJob postRequestAsync(ParamBase param) {
setAsyncMode();
ClientResponse response = postRequest(param);
VNXeCommandJob job;
String resString = response.getEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
try {
job = mapper.readValue(resString, VNXeCommandJob.class);
} catch (JsonParseException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + resString, e);
} catch (JsonMappingException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + resString, e);
} catch (IOException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError("unexpected data returned:" + resString, e);
}
if (job != null) {
_logger.info("submitted the job: " + job.getId());
} else {
_logger.warn("No job returned.");
}
return job;
}
public VNXeCommandResult postRequestSync(ParamBase param) {
ClientResponse response = postRequest(param);
if (response.getClientResponseStatus() == ClientResponse.Status.NO_CONTENT) {
VNXeCommandResult result = new VNXeCommandResult();
result.setSuccess(true);
return result;
}
String resString = response.getEntity(String.class);
_logger.debug("KH API returned: {} ", resString);
JSONObject res;
String objectString = null;
VNXeCommandResult returnedObject = null;
try {
res = new JSONObject(resString);
if (res != null) {
JSONObject object = (JSONObject) res.get(VNXeConstants.CONTENT);
if (object != null) {
objectString = object.toString();
ObjectMapper mapper = new ObjectMapper();
try {
returnedObject = mapper.readValue(objectString, VNXeCommandResult.class);
returnedObject.setSuccess(true);
} catch (JsonParseException e) {
_logger.error(String.format("unexpected data returned: %s", objectString), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", objectString), e);
} catch (JsonMappingException e) {
_logger.error(String.format("unexpected data returned: %s", objectString), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", objectString), e);
} catch (IOException e) {
_logger.error(String.format("unexpected data returned: %s", objectString), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", objectString), e);
}
}
}
} catch (JSONException e) {
_logger.error(String.format("unexpected data returned: %s from: %s", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", objectString), e);
}
return returnedObject;
}
/*
* Send GET request to KittyHawk server, and handle redirect/cookies
*/
private ClientResponse sendGetRequest(WebResource resource) throws VNXeException {
_logger.info("getting data: {} ", _url);
if (_client.isUnity() == true) {
setFields();
}
ClientResponse response = buildRequest(addQueryParameters(buildResource(resource))
.getRequestBuilder()).get(ClientResponse.class);
Status statusCode = response.getClientResponseStatus();
_logger.info(response.getStatus() + ":" + response.toString());
if (statusCode == ClientResponse.Status.OK) {
String emcCsrfToken = response.getHeaders().getFirst(EMC_CSRF_HEADER);
if (emcCsrfToken != null) {
saveEmcCsrfToken(emcCsrfToken);
}
saveClientCookies();
return response;
} else if (response.getClientResponseStatus() == ClientResponse.Status.UNAUTHORIZED) {
authenticate();
response = buildRequest(addQueryParameters(buildResource(resource))
.getRequestBuilder()).get(ClientResponse.class);
;
}
int redirectTimes = 1;
while (response.getClientResponseStatus() == ClientResponse.Status.FOUND &&
redirectTimes < VNXeConstants.REDIRECT_MAX) {
String code = response.getClientResponseStatus().toString();
String data = response.getEntity(String.class);
_logger.debug("Returned code: {}, returned data:", code, data);
WebResource newResource = handelRedirect(response);
if (newResource != null) {
response = buildRequest(newResource.getRequestBuilder()).get(ClientResponse.class);
redirectTimes++;
} else {
// could not find the redirect url, return
_logger.error(String.format("The post request to: %s failed with: %s %s", _url,
response.getClientResponseStatus().toString(), response.getEntity(String.class)));
throw VNXeException.exceptions.unexpectedDataError("Got redirect status code, but could not get redirected URL");
}
}
if (redirectTimes >= VNXeConstants.REDIRECT_MAX) {
_logger.error("redirected too many times for the request {}", _url);
throw VNXeException.exceptions.unexpectedDataError("Redirected too many times while sending the request for " + _url);
}
checkResponse(response, GET_REQUEST);
List<NewCookie> cookies = response.getCookies();
if (cookies != null && !cookies.isEmpty()) {
_requestCookies.addAll(cookies);
}
saveClientCookies();
String emcCsrfToken = response.getHeaders().getFirst(EMC_CSRF_HEADER);
if (emcCsrfToken != null) {
saveEmcCsrfToken(emcCsrfToken);
}
return response;
}
/*
* Handle request redirects, adding cookies and setting the redirect URL
*/
private WebResource handelRedirect(ClientResponse response) {
URI url = response.getLocation();
List<NewCookie> cookies = response.getCookies();
if (cookies != null && !cookies.isEmpty()) {
_requestCookies.addAll(cookies);
} else {
_logger.debug("no cookies");
}
if (url != null) {
_logger.debug("redirected url: {}", url.toString());
WebResource resource = _client.getResource(url.toString());
return resource;
} else {
_logger.error("Could not find redirected url");
return null;
}
}
/*
* Send DELETE request to KittyHawk server, and handle redirect/cookies
*
* @param resource webResource
*
* @param param parameters for delete
*
* @throws VNXeException
*/
public ClientResponse deleteRequest(Object param) throws VNXeException {
_logger.debug("delete data: " + _url);
ClientResponse response = sendDeleteRequest(param);
Status statusCode = response.getClientResponseStatus();
if (statusCode == ClientResponse.Status.OK
|| statusCode == ClientResponse.Status.ACCEPTED
|| statusCode == ClientResponse.Status.NO_CONTENT) {
return response;
} else if (response.getClientResponseStatus() == ClientResponse.Status.UNAUTHORIZED) {
authenticate();
response = sendDeleteRequest(param);
statusCode = response.getClientResponseStatus();
if (statusCode == ClientResponse.Status.OK
|| statusCode == ClientResponse.Status.ACCEPTED
|| statusCode == ClientResponse.Status.NO_CONTENT) {
return response;
}
}
int redirectTimes = 1;
// handle redirect
while (response.getClientResponseStatus() == ClientResponse.Status.FOUND &&
redirectTimes < VNXeConstants.REDIRECT_MAX) {
String code = response.getClientResponseStatus().toString();
String data = response.getEntity(String.class);
_logger.debug("Returned code: {} returned data: ", code, data);
WebResource resource = handelRedirect(response);
if (resource != null) {
response = buildRequest(resource.getRequestBuilder()).entity(param).delete(ClientResponse.class);
redirectTimes++;
} else {
// could not find the redirect url, return
_logger.error(String.format("The post request to: %s failed with: %s %s", _url,
response.getClientResponseStatus().toString(), response.getEntity(String.class)));
throw VNXeException.exceptions.unexpectedDataError("Got redirect status code, but could not get redirected URL");
}
}
if (redirectTimes >= VNXeConstants.REDIRECT_MAX) {
_logger.error("redirected too many times for the request {} ", _url);
throw VNXeException.exceptions.unexpectedDataError("Redirected too many times while sending the request for " + _url);
}
checkResponse(response, DELETE_REQUEST);
return response;
}
/*
* save cookies in KHClient for next request
*/
private void saveClientCookies() {
if (!_requestCookies.isEmpty()) {
_client.setCookie(_requestCookies);
}
}
/*
* save EMC_CSRF_TOKEN for next POST or PUT request
*/
private void saveEmcCsrfToken(String emcCsrfToken) {
if (emcCsrfToken != null) {
_logger.debug("Saving CSRF token: " + emcCsrfToken);
_client.setEmcCsrfToken(emcCsrfToken);
}
}
/*
* Send DELETE request to KittyHawk server in async mode
*
* @param resource webResource
*
* @param param parameters for delete
*
* @return VNXeCommandJob
*
* @throws VNXeException
*/
public VNXeCommandJob deleteRequestAsync(Object param) throws VNXeException {
_logger.debug("delete data: " + _url);
setAsyncMode();
ClientResponse response = deleteRequest(param);
VNXeCommandJob job;
String resString = response.getEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
try {
job = mapper.readValue(resString, VNXeCommandJob.class);
} catch (JsonParseException e) {
_logger.error(String.format("unexpected data returned: %s from: %s ", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", resString), e);
} catch (JsonMappingException e) {
_logger.error(String.format("unexpected data returned: %s from: %s ", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", resString), e);
} catch (IOException e) {
_logger.error(String.format("unexpected data returned: %s from: %s ", resString, _url), e);
throw VNXeException.exceptions.unexpectedDataError(String.format("unexpected data returned: %s", resString), e);
}
if (job != null) {
_logger.info("submitted the deleting file system job: {} ", job.getId());
}
return job;
}
protected void setAsyncMode() {
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add(VNXeConstants.TIMEOUT, "0");
setQueryParameters(queryParams);
}
protected void setFilter(String filter) {
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add(VNXeConstants.FILTER, filter);
setQueryParameters(queryParams);
}
protected void setFields() {
_logger.info("Setting fields:" + _fields);
if (_fields != null) {
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add(VNXeConstants.FIELDS, _fields);
setQueryParameters(queryParams);
}
}
private void authenticate() {
// calling a GET operation would authenticate the client again.
_client.setCookie(null);
_client.setEmcCsrfToken(null);
StorageSystemRequest req = new StorageSystemRequest(_client);
req.get();
}
private ClientResponse sendDeleteRequest(Object param) {
ClientResponse response = null;
if (param != null) {
response = buildRequest(addQueryParameters(buildResource(_resource))
.getRequestBuilder()).entity(param).delete(ClientResponse.class);
} else {
response = buildRequest(addQueryParameters(buildResource(_resource))
.getRequestBuilder()).delete(ClientResponse.class);
}
return response;
}
private void checkResponse(ClientResponse response, String requestType) {
Status status = response.getClientResponseStatus();
if (status != ClientResponse.Status.OK &&
status != ClientResponse.Status.ACCEPTED &&
status != ClientResponse.Status.NO_CONTENT) {
// error
if (status == ClientResponse.Status.UNAUTHORIZED) {
throw VNXeException.exceptions.authenticationFailure(_url.toString());
}
String code = null;
code = Integer.toString(response.getStatus());
StringBuilder errorBuilder = new StringBuilder();
errorBuilder.append(requestType).append(" request to:");
errorBuilder.append(_url);
errorBuilder.append(" failed with status code: ");
errorBuilder.append(code);
errorBuilder.append(" ");
errorBuilder.append("message: ");
String msg = response.getEntity(String.class);
errorBuilder.append(msg);
_logger.error(errorBuilder.toString());
throw VNXeException.exceptions.vnxeCommandFailed(_url, code, msg);
}
}
}