/******************************************************************************* * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package org.cloudifysource.esc.driver.provisioning.openstack; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.core.MediaType; import org.cloudifysource.esc.driver.provisioning.openstack.rest.TokenAccess; import org.cloudifysource.esc.driver.provisioning.openstack.rest.TokenServiceCatalog; import org.cloudifysource.esc.driver.provisioning.openstack.rest.TokenServiceCatalogEndpoint; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.LoggingFilter; /** * A base class for openstack clients.<br /> * It handle tokens. * * @author victor * @since 2.7.0 * */ public abstract class OpenStackBaseClient { private static final long TOKEN_TIMEOUT_MARGING = 60000L; protected static final int CODE_OK_200 = 200; protected static final int CODE_OK_204 = 204; private static final Logger WIRE_LOGGER = Logger.getLogger("openstack.wire"); private static final Logger logger = Logger.getLogger(OpenStackBaseClient.class.getName()); private final byte[] webResourceMutex = new byte[0]; private String endpoint; private String username; private String password; private String tenant; private TokenAccess token; private String region; private Client serviceClient; private WebResource serviceWebResource; public OpenStackBaseClient() { } public OpenStackBaseClient(final String endpoint, final String username, final String password, final String tenant, final String region) throws OpenstackJsonSerializationException { this.endpoint = endpoint; this.username = username; this.password = password; this.tenant = tenant; this.region = region; } /** * Destroy the client. <br /> */ public void close() { synchronized (this.webResourceMutex) { if (this.serviceClient != null) { this.serviceClient.destroy(); } } } private synchronized void renewTokenIfNeeded() throws OpenstackJsonSerializationException { if (this.isTokenExpiredSoon()) { if (logger.isLoggable(Level.FINEST)) { logger.finest("Token not found or expired. Request a new token"); } this.initToken(); } } private boolean isTokenExpiredSoon() { if (token == null) { return true; } long current = System.currentTimeMillis() + TOKEN_TIMEOUT_MARGING; long tokenExpires = token.getToken().getExpires().getTime(); return current > tokenExpires; } /** * Initialize Openstack token. * * @throws OpenstackJsonSerializationException * A problem occurs when requesting Openstack server. */ private void initToken() throws OpenstackJsonSerializationException { final Client client = Client.create(); if (WIRE_LOGGER.isLoggable(Level.FINE)) { client.addFilter(new LoggingFilter(WIRE_LOGGER)); } try { if (logger.isLoggable(Level.FINE)) { logger.fine("Request openstack " + this.getServiceType() + " new token."); } if (this.endpoint == null) { throw new IllegalStateException("No endpoint defined"); } final WebResource webResource = client.resource(this.endpoint); final String tokenJsonRequest = "{\"auth\":{\"passwordCredentials\":" + "{\"username\": \"%s\", \"password\":\"%s\"}, \"tenantName\":\"%s\"}}"; final String input = String.format(tokenJsonRequest, username, password, tenant); final String response = webResource.path("tokens").accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON_TYPE).post(String.class, input); this.token = JsonUtils.unwrapRootToObject(TokenAccess.class, response, false); } finally { if (client != null) { client.destroy(); } } } /** * Get the token id. It will renew it if needed. * * @return The token id. * @throws OpenstackJsonSerializationException * If a serialization issue occurs with Openstack request/response. */ protected String getTokenId() throws OpenstackJsonSerializationException { this.renewTokenIfNeeded(); return this.token.getToken().getId(); } /** * Get the tenant id, if token not null. * * @return The tenant id, or null if token is null * @throws OpenstackJsonSerializationException * If a serialization issue occurs with Openstack request/response. */ protected String getTenantId() throws OpenstackJsonSerializationException { String tenantId = null; if (token != null) { tenantId = token.getToken().getTenant().getId(); } return tenantId; } /** * Return the WebResource pre configured with the endpoint. * * @return The pre configured WebResource. * @throws OpenstackException * A problem occurs when requesting Openstack server. */ protected WebResource getWebResource() throws OpenstackException { synchronized (this.webResourceMutex) { if (this.serviceWebResource == null) { this.renewTokenIfNeeded(); this.endpoint = this.getEndpoint(); this.serviceClient = Client.create(); if (WIRE_LOGGER.isLoggable(Level.FINE)) { this.serviceClient.addFilter(new LoggingFilter(WIRE_LOGGER)); } this.serviceWebResource = this.serviceClient.resource(endpoint); if (logger.isLoggable(Level.FINE)) { logger.fine("Openstack endpoint: " + endpoint); } } return serviceWebResource; } } private String getEndpoint() throws OpenstackException { String endpoint = null; endpoint = this.getEndpointByName(); if (endpoint == null) { endpoint = this.getEndpointByType(); } if (endpoint == null) { throw new OpenstackException("Cannot find endpoint for service '" + this.getServiceType() + "' in Openstack service catalog."); } return endpoint; } /** * The name of the service the Openstack's catalog. * * @return The name of the service in Openstack's catalog. */ protected abstract String getServiceName(); /** * The type of the service the Openstack's catalog. * * @return The type of the service in Openstack's catalog. */ protected abstract String getServiceType(); /** * Retrieves the service endpoint by type. * * @return The endpoint */ protected String getEndpointByType() { final String endpointType = this.getServiceType(); if (logger.isLoggable(Level.FINEST)) { logger.finest("Trying to get service endpoint by type '" + endpointType + "'"); } for (final TokenServiceCatalog tsc : this.token.getServiceCatalog()) { if (endpointType.equals(tsc.getType())) { for (final TokenServiceCatalogEndpoint endpoint : tsc.getEndpoints()) { if (this.region.equals(endpoint.getRegion())) { return endpoint.getPublicURL(); } } } } return null; } /** * Retrieves the service endpoint by name. * * @return The endpoint */ private String getEndpointByName() { final String endpointName = this.getServiceName(); if (logger.isLoggable(Level.FINEST)) { logger.finest("Trying to get service endpoint by name '" + endpointName + "'"); } if (endpointName != null) { for (final TokenServiceCatalog tsc : this.token.getServiceCatalog()) { if (endpointName.equals(tsc.getName())) { for (final TokenServiceCatalogEndpoint endpoint : tsc.getEndpoints()) { if (this.region.equals(endpoint.getRegion())) { return endpoint.getPublicURL(); } } } } } return null; } /** * Translate {@link UniformInterfaceException} to {@link OpenstackServerException} to get an accurate error message. * * @param e * The {@link UniformInterfaceException} to translate * @return The translated {@link OpenstackServerException}. */ protected OpenstackServerException createOpenstackServerException(final UniformInterfaceException e) { final ClientResponse client = e.getResponse(); final String responseMessage = client.getEntity(String.class); return new OpenstackServerException(client.getStatus(), responseMessage, e); } /** * Perform a get query with parameters. * * @param path * The URI path of the request. * @param params * The parameters for the request. The array must be formatted as [key, value, key, value,...]. * @return The response of the request. * @throws OpenstackException * If an error occurs during the request. */ protected String doGet(final String path, final String[] params) throws OpenstackException { try { if (params != null && params.length % 2 != 0) { throw new IllegalArgumentException("Paramters array missing an element:" + Arrays.asList(params)); } WebResource webResource = this.getWebResource(); webResource = webResource.path(path); if (params != null) { for (int i = 0; i < params.length - 1; i += 2) { webResource = webResource.queryParam(params[i], params[i + 1]); } } if (logger.isLoggable(Level.FINER)) { logger.finer("GET '" + webResource + "'"); } final String response = webResource.type(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON) .header("X-Auth-Token", this.getTokenId()) .get(String.class); if (logger.isLoggable(Level.FINEST)) { logger.finest("GET '" + webResource + "' got response: " + response); } return response; } catch (final UniformInterfaceException e) { throw this.createOpenstackServerException(e); } } /** * Perform a get query. * * @param path * The URI path of the request. * @return The response of the request. * @throws OpenstackException * If an error occurs during the request. */ protected String doGet(final String path) throws OpenstackException { return this.doGet(path, null); } /** * Perform a delete query. * * @param path * The URI path of the request. * @param expectedStatus * The expected returned status code. * @throws OpenstackException * If an error occurs during the request. */ protected void doDelete(final String path, final int expectedStatus) throws OpenstackException { try { final WebResource webResource = this.getWebResource(); if (logger.isLoggable(Level.FINER)) { logger.finer("DELETE request: '" + webResource + "'"); } final ClientResponse response = webResource.path(path) .type(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON) .header("X-Auth-Token", this.getTokenId()) .delete(ClientResponse.class); if (logger.isLoggable(Level.FINEST)) { logger.finest("DELETE '" + webResource + "' got response status: " + response.getStatus()); } if (expectedStatus != response.getStatus()) { final String entity = response.getEntity(String.class); throw new OpenstackServerException(expectedStatus, response.getStatus(), entity); } } catch (final UniformInterfaceException e) { throw this.createOpenstackServerException(e); } } /** * Perform a post request. * * @param path * The URI path of the request. * @param input * The body of the request. * @return The response of the request. * @throws OpenstackException * If an error occurs during the request. */ protected String doPost(final String path, final String input) throws OpenstackException { try { final WebResource webResource = this.getWebResource().path(path); if (logger.isLoggable(Level.FINER)) { logger.finer("POST '" + webResource + "' with body: '" + input + "'"); } final String response = webResource.type(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON) .header("X-Auth-Token", this.getTokenId()) .post(String.class, input); if (logger.isLoggable(Level.FINEST)) { logger.finest("POST '" + webResource + "' with body: '" + input + "' got response: " + response); } return response; } catch (final UniformInterfaceException e) { throw this.createOpenstackServerException(e); } } /** * Perform a put request. * * @param path * The URI path of the request. * @param input * The body of the request. * @return The response of the request. * @throws OpenstackException * If an error occurs during the request. */ protected String doPut(final String path, final String input) throws OpenstackException { try { final WebResource webResource = this.getWebResource().path(path); if (logger.isLoggable(Level.FINER)) { logger.finer("PUT '" + webResource + "' with body: '" + input + "'"); } final String response = webResource .type(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON) .header("X-Auth-Token", this.getTokenId()) .put(String.class, input); if (logger.isLoggable(Level.FINEST)) { logger.finest("PUT '" + webResource + "' with body: '" + input + "' got response: " + response); } return response; } catch (final UniformInterfaceException e) { throw this.createOpenstackServerException(e); } } }