/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.datadomain.restapi; import com.emc.storageos.datadomain.restapi.errorhandling.DataDomainApiException; import com.emc.storageos.datadomain.restapi.errorhandling.DataDomainResourceNotFoundException; import com.emc.storageos.services.restutil.RestClientItf; import com.emc.storageos.services.util.SecurityUtils; import com.google.gson.Gson; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.core.util.MultivaluedMapImpl; import com.emc.storageos.datadomain.restapi.model.*; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import java.net.URI; import java.util.List; public class DataDomainClient implements RestClientItf { private Client _client; private String _username; private String _password; private String _authToken; private URI _base; private static final int LOWEST_ERROR_STATUS = 300; private static Logger log = LoggerFactory.getLogger(DataDomainClient.class); /** * Constructor * * @param client A reference to a Jersey Apache HTTP client. * @param username The user to be authenticated. * @param password The user password for authentication. */ public DataDomainClient(URI baseURI, String username, String password, Client client) { _client = client; _base = baseURI; _username = username; _password = password; _authToken = ""; } @Override public ClientResponse get(URI uri) throws DataDomainApiException { URI requestURI = _base.resolve(uri); ClientResponse response = setResourceHeaders(_client.resource(requestURI)).get(ClientResponse.class); if (authenticationFailed(response)) { authenticate(); response = setResourceHeaders(_client.resource(requestURI)).get(ClientResponse.class); } checkResponse(uri, response); return response; } public ClientResponse get(URI uri, MultivaluedMap<String, String> params) throws DataDomainApiException { URI requestURI = _base.resolve(uri); ClientResponse response = setResourceHeaders(_client.resource(requestURI).queryParams(params)).get(ClientResponse.class); if (authenticationFailed(response)) { authenticate(); response = setResourceHeaders(_client.resource(requestURI).queryParams(params)).get(ClientResponse.class); } checkResponse(uri, response); return response; } @Override public ClientResponse put(URI uri, String body) throws DataDomainApiException { URI requestURI = _base.resolve(uri); ClientResponse response = setResourceHeaders(_client.resource(requestURI)).put(ClientResponse.class, body); if (authenticationFailed(response)) { authenticate(); response = setResourceHeaders(_client.resource(requestURI)).put(ClientResponse.class, body); } checkResponse(uri, response); return response; } @Override public ClientResponse post(URI uri, String body) throws DataDomainApiException { URI requestURI = _base.resolve(uri); ClientResponse response = setResourceHeaders(_client.resource(requestURI)).type(MediaType.APPLICATION_JSON) .post(ClientResponse.class, body); if (authenticationFailed(response)) { authenticate(); response = setResourceHeaders(_client.resource(requestURI)).type(MediaType.APPLICATION_JSON) .post(ClientResponse.class, body); } checkResponse(uri, response); return response; } @Override public ClientResponse delete(URI uri) throws DataDomainApiException { URI requestURI = _base.resolve(uri); ClientResponse response = setResourceHeaders(_client.resource(requestURI)).type(MediaType.APPLICATION_JSON) .delete(ClientResponse.class); if (authenticationFailed(response)) { authenticate(); response = setResourceHeaders(_client.resource(requestURI)).type(MediaType.APPLICATION_JSON) .delete(ClientResponse.class); } checkResponse(uri, response); return response; } /** * Close the client */ @Override public void close() { _client.destroy(); } private void authenticate() throws DataDomainApiException { DDAuthInfo authInfo = new DDAuthInfo(); authInfo.setPassword(_password); authInfo.setUsername(_username); String body = getJsonForEntity(authInfo); URI requestURI = _base.resolve(DataDomainApiConstants.URI_DATADOMAIN_AUTH); ClientResponse response = _client.resource(requestURI). type(MediaType.APPLICATION_JSON) .post(ClientResponse.class, body); if (response.getClientResponseStatus() != ClientResponse.Status.OK && response.getClientResponseStatus() != ClientResponse.Status.CREATED) { throw DataDomainApiException.exceptions.authenticationFailure(_base.toString()); } _authToken = response.getHeaders().getFirst(DataDomainApiConstants.AUTH_TOKEN); } private WebResource.Builder setResourceHeaders(WebResource resource) { return resource.header(DataDomainApiConstants.AUTH_TOKEN, _authToken); } private boolean authenticationFailed(ClientResponse response) { return response.getClientResponseStatus() == com.sun.jersey.api.client.ClientResponse.Status.UNAUTHORIZED; } private int checkResponse(URI uri, ClientResponse response) throws DataDomainApiException { int errorCode = response.getStatus(); if (errorCode >= LOWEST_ERROR_STATUS) { JSONObject obj = null; String msg; int ddCode; try { obj = response.getEntity(JSONObject.class); msg = obj.getString(DataDomainApiConstants.DETAILS); ddCode = obj.getInt(DataDomainApiConstants.CODE); } catch (Exception e) { throw DataDomainApiException.exceptions.jsonWriterReaderException(e); } log.error(String.format("DataDomain Rest API failed, DDCode: %d, Msg : %s", ddCode, msg)); if (ddCode == 404 || ddCode == 410) { throw DataDomainResourceNotFoundException.notFound.resourceNotFound(uri.toString(), msg); } else { throw DataDomainApiException.exceptions.failedResponseFromDataDomainMsg(uri, errorCode, msg, ddCode); } } else { return errorCode; } } private <T> String getJsonForEntity(T model) throws DataDomainApiException { try { return new Gson().toJson(model); /* * ObjectMapper mapper = new ObjectMapper(); * mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true); * return mapper.writeValueAsString(model); */ } catch (Exception e) { throw DataDomainApiException.exceptions.jsonWriterReaderException(e); } } private <T> T getResponseObject(Class<T> clazz, ClientResponse response) throws DataDomainApiException { try { JSONObject resp = response.getEntity(JSONObject.class); T respObject = new Gson().fromJson(SecurityUtils.sanitizeJsonString(resp.toString()), clazz); /* * ObjectMapper mapper = new ObjectMapper(); * mapper.configure(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE, true); * mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); * T respObject = mapper.readValue(response.getEntity(String.class),clazz); */ return respObject; } catch (Exception e) { throw DataDomainApiException.exceptions.jsonWriterReaderException(e); } } private ClientResponse doCreateMTree(String ddSystem, String mtreeName, long size) throws DataDomainApiException { DDMTreeCreate createParam = new DDMTreeCreate(); createParam.setName(mtreeName); createParam.setQuota(new DDQuotaConfig()); createParam.getQuota().setHardLimit(size); createParam.getQuota().setSoftLimit((long) (size * DataDomainApiConstants.DD_MTREE_SOFT_LIMIT)); ClientResponse response = post(DataDomainApiConstants.uriDataDomainMtrees(ddSystem), getJsonForEntity(createParam)); return response; } private ClientResponse modifyMTreeRetentionlock(String ddSystem, String mtreeId, Boolean enable, String mode) throws DataDomainApiException { DDQuotaConfig quotaConfig = null; DDRetentionLockSet retentionLockSet = new DDRetentionLockSet(enable, mode); DDMTreeModify modifyParam = new DDMTreeModify(quotaConfig, retentionLockSet); ClientResponse response = put(DataDomainApiConstants.uriDataDomainMtree(ddSystem, mtreeId), getJsonForEntity(modifyParam)); return response; } private ClientResponse doCreateExport(String ddSystem, DDExportCreate ddExport) throws DataDomainApiException { ClientResponse response = post(DataDomainApiConstants.uriDataDomainExports(ddSystem), getJsonForEntity(ddExport)); return response; } private ClientResponse doModifyExport(String ddSystem, String ddExportId, DDExportModify ddExportModify) throws DataDomainApiException { ClientResponse response = put(DataDomainApiConstants.uriDataDomainExport(ddSystem, ddExportId), getJsonForEntity(ddExportModify)); return response; } private ClientResponse doCreateShare(String ddSystem, DDShareCreate ddShare) throws DataDomainApiException { ClientResponse response = post(DataDomainApiConstants.uriDataDomainShares(ddSystem), getJsonForEntity(ddShare)); return response; } public DDMCInfoDetail getManagementSystemInfo() throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.URI_DATADOMAIN_SERVICE); return getResponseObject(DDMCInfoDetail.class, response); } public DDSystemList getManagedSystemList() throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.URI_DATADOMAIN_SYSTEM_LIST); return getResponseObject(DDSystemList.class, response); } public DDSystem getDDSystem(String system) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainSystem(system)); return getResponseObject(DDSystem.class, response); } public DDMCInfoDetail getDDSystemInfoDetail(String system) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainSystem(system)); return getResponseObject(DDMCInfoDetail.class, response); } public DDMTreeList getMTreeList(String system) throws DataDomainApiException { // As per DD team, the maximum mtrees that DDOS supports is 100. Hence we need // to request for page with size 100 so we get all MTrees. MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); queryParams.add("size", String.valueOf(DataDomainApiConstants.DD_MAX_MTREE_LIMIT)); ClientResponse response = get(DataDomainApiConstants.uriDataDomainMtrees(system), (MultivaluedMap<String, String>) queryParams); return getResponseObject(DDMTreeList.class, response); } public DDMTreeInfoDetail getMTree(String system, String mtree) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainMtree(system, mtree)); return getResponseObject(DDMTreeInfoDetail.class, response); } public DDMTreeInfo createMTree(String ddSystem, String mtreeName, long size, Boolean enableRetention, String retentionMode) throws DataDomainApiException { ClientResponse response = doCreateMTree(ddSystem, mtreeName, size); // The following lines are commented out for now because enabling/disabling // retention lock requires a special license // if (response.getStatus() < LOWEST_ERROR_STATUS) { // DDMTreeInfo ddMtree = getResponseObject(DDMTreeInfo.class, response); // response = modifyMTreeRetentionlock(ddSystem, ddMtree.getId(), // enableRetention, retentionMode); // } return getResponseObject(DDMTreeInfo.class, response); } public DDServiceStatus deleteMTree(String ddSystem, String mtreeId) throws DataDomainApiException { ClientResponse response = delete(DataDomainApiConstants.uriDataDomainMtree(ddSystem, mtreeId)); return getResponseObject(DDServiceStatus.class, response); } public DDMTreeInfo expandMTree(String ddSystem, String mtreeId, long newSize) throws DataDomainApiException { DDQuotaConfig quotaConfig = new DDQuotaConfig(); quotaConfig.setHardLimit(newSize); quotaConfig.setSoftLimit((long) (newSize * DataDomainApiConstants.DD_MTREE_SOFT_LIMIT)); DDRetentionLockSet retentionLockSet = null; DDMTreeModify modifyParam = new DDMTreeModify(quotaConfig, retentionLockSet); ClientResponse response = put(DataDomainApiConstants.uriDataDomainMtree(ddSystem, mtreeId), getJsonForEntity(modifyParam)); return getResponseObject(DDMTreeInfo.class, response); } public DDExportInfoDetail getExport(String ddSystem, String exportId) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainExport(ddSystem, exportId)); return getResponseObject(DDExportInfoDetail.class, response); } public DDExportInfo createExport(String ddSystem, String exportName, List<DDExportClient> exportClients) throws DataDomainApiException { DDExportCreate ddExportCreate = new DDExportCreate(exportName, exportClients); ClientResponse response = doCreateExport(ddSystem, ddExportCreate); return getResponseObject(DDExportInfo.class, response); } public DDExportInfo modifyExport(String ddSystem, String ddExportId, List<DDExportClientModify> ddExportClients) throws DataDomainApiException { DDExportModify ddExportModify = new DDExportModify(ddExportClients); ClientResponse response = doModifyExport(ddSystem, ddExportId, ddExportModify); return getResponseObject(DDExportInfo.class, response); } public DDServiceStatus deleteExport(String ddSystem, String ddExportId) throws DataDomainApiException { ClientResponse response = delete(DataDomainApiConstants.uriDataDomainExport(ddSystem, ddExportId)); return getResponseObject(DDServiceStatus.class, response); } public DDShareInfo createShare(String ddSystem, String shareName, String sharePath, int maxUsers, String description, String permissionType, String permission) throws DataDomainApiException { DDShareCreate ddShareCreate = new DDShareCreate(shareName, sharePath, maxUsers, description, permissionType, permission); ClientResponse response = doCreateShare(ddSystem, ddShareCreate); return getResponseObject(DDShareInfo.class, response); } public DDShareInfo modifyShare(String ddSystem, String ddShareId, String description) throws DataDomainApiException { DDShareModify ddShareModify = new DDShareModify(description); ClientResponse response = put(DataDomainApiConstants.uriDataDomainShare(ddSystem, ddShareId), getJsonForEntity(ddShareModify)); return getResponseObject(DDShareInfo.class, response); } public DDServiceStatus deleteShare(String ddSystem, String ddShareId) throws DataDomainApiException { ClientResponse response = delete(DataDomainApiConstants.uriDataDomainShare(ddSystem, ddShareId)); return getResponseObject(DDServiceStatus.class, response); } public DDShareInfoDetail getShare(String ddSystem, String shareId) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainShare(ddSystem, shareId)); return getResponseObject(DDShareInfoDetail.class, response); } public DDShareList getShares(String ddSystem) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainShares(ddSystem)); return getResponseObject(DDShareList.class, response); } public DDNetworkList getNetworks(String ddSystem) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainNetworks(ddSystem)); return getResponseObject(DDNetworkList.class, response); } public DDNetworkDetails getNetwork(String ddSystem, String network) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainNetwork(ddSystem, network)); return getResponseObject(DDNetworkDetails.class, response); } public DDExportList getExports(String ddSystem) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainExports(ddSystem)); return getResponseObject(DDExportList.class, response); } public DDSnapshot createSnapshot(String ddSystem, DDSnapshotCreate ddSnapshotCreate) throws DataDomainApiException { ClientResponse response = post(DataDomainApiConstants.uriDataDomainSnapshots(ddSystem), getJsonForEntity(ddSnapshotCreate)); return getResponseObject(DDSnapshot.class, response); } public DDServiceStatus deleteSnapshot(String ddSystem, String ddSnapshotId) throws DataDomainApiException { ClientResponse response = delete(DataDomainApiConstants.uriDataDomainSnapshot(ddSystem, ddSnapshotId)); return getResponseObject(DDServiceStatus.class, response); } public DDStatsCapacityInfos getSystemCapacityInfo(String ddSystem, int page, int size, DDStatsIntervalQuery interval, boolean requestedIntervalOnly, String sort, String queryFilter) throws DataDomainApiException { MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); if (page >= 0) { queryParams.add("page", String.valueOf(page)); } if (size > 0) { queryParams.add("size", String.valueOf(size)); } if (DDStatsIntervalQuery.isMember(interval)) { queryParams.add("interval", interval.toString()); } queryParams.add("requested_interval_only", String.valueOf(requestedIntervalOnly)); if ((sort != null) && (sort.endsWith(DataDomainApiConstants.COLLECTION_EPOCH))) { queryParams.add("sort", sort); } if ((queryFilter != null) && (queryFilter.startsWith(DataDomainApiConstants.COLLECTION_EPOCH))) { queryParams.add("filter", queryFilter); } ClientResponse response = get( DataDomainApiConstants.uriDataDomainSystemStatsCapacity(ddSystem), (MultivaluedMap<String, String>) queryParams); return getResponseObject(DDStatsCapacityInfos.class, response); } public DDStatsInfos getMTreeStatsInfos(String ddSystem, String mtreeId) throws DataDomainApiException { ClientResponse response = get(DataDomainApiConstants.uriDataDomainMtreeStats(ddSystem, mtreeId)); return getResponseObject(DDStatsInfos.class, response); } public DDMtreeCapacityInfos getMTreeCapacityInfo(String ddSystem, String mtreeId, int page, int pgSize, DDStatsDataViewQuery dataView, DDStatsIntervalQuery interval, boolean requestedIntervalOnly, String sort) throws DataDomainApiException { MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); if (page >= 0) { queryParams.add(DataDomainApiConstants.PAGE, String.valueOf(page)); } if (pgSize > 0) { queryParams.add(DataDomainApiConstants.PAGE_SIZE, String.valueOf(pgSize)); } if (DDStatsDataViewQuery.isMember(dataView)) { queryParams.add(DataDomainApiConstants.DATA_VIEW, dataView.toString()); } // interval is not supported now, but leaving the following lines here in case DD API evolves // if ((DDStatsIntervalQuery.isMember(interval)) && (dataView.equals(DDStatsDataViewQuery.delta))) { // queryParams.add(DataDomainApiConstants.INTERVAL, interval.toString()); // } if (dataView.equals(DDStatsDataViewQuery.delta)) { queryParams.add(DataDomainApiConstants.REQUESTED_INTERVAL_ONLY, String.valueOf(requestedIntervalOnly)); } if ((sort != null) && (sort.endsWith(DataDomainApiConstants.COLLECTION_EPOCH))) { queryParams.add(DataDomainApiConstants.SORT, sort); } ClientResponse response = get( DataDomainApiConstants.uriDataDomainMtreeStatsCapacity(ddSystem, mtreeId), (MultivaluedMap<String, String>) queryParams); return getResponseObject(DDMtreeCapacityInfos.class, response); } }