| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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.apache.cloudstack.cloudian.client; |
| |
| import java.io.IOException; |
| import java.net.SocketTimeoutException; |
| import java.security.KeyManagementException; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.X509TrustManager; |
| |
| import org.apache.cloudstack.api.ApiErrorCode; |
| import org.apache.cloudstack.api.ServerApiException; |
| import org.apache.cloudstack.utils.security.SSLUtils; |
| import org.apache.http.HttpHost; |
| import org.apache.http.HttpResponse; |
| import org.apache.http.HttpStatus; |
| import org.apache.http.auth.AuthScope; |
| import org.apache.http.auth.Credentials; |
| import org.apache.http.auth.UsernamePasswordCredentials; |
| import org.apache.http.client.AuthCache; |
| import org.apache.http.client.CredentialsProvider; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.client.config.RequestConfig; |
| import org.apache.http.client.methods.HttpDelete; |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.client.methods.HttpPost; |
| import org.apache.http.client.methods.HttpPut; |
| import org.apache.http.client.protocol.HttpClientContext; |
| import org.apache.http.conn.ConnectTimeoutException; |
| import org.apache.http.conn.ssl.NoopHostnameVerifier; |
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |
| import org.apache.http.entity.StringEntity; |
| import org.apache.http.impl.auth.BasicScheme; |
| import org.apache.http.impl.client.BasicAuthCache; |
| import org.apache.http.impl.client.BasicCredentialsProvider; |
| import org.apache.http.impl.client.HttpClientBuilder; |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.utils.nio.TrustAllManager; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import com.google.common.base.Strings; |
| |
| public class CloudianClient { |
| private static final Logger LOG = Logger.getLogger(CloudianClient.class); |
| |
| private final HttpClient httpClient; |
| private final HttpClientContext httpContext; |
| private final String adminApiUrl; |
| |
| public CloudianClient(final String host, final Integer port, final String scheme, final String username, final String password, final boolean validateSSlCertificate, final int timeout) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { |
| final CredentialsProvider provider = new BasicCredentialsProvider(); |
| provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); |
| final HttpHost adminHost = new HttpHost(host, port, scheme); |
| final AuthCache authCache = new BasicAuthCache(); |
| authCache.put(adminHost, new BasicScheme()); |
| |
| this.adminApiUrl = adminHost.toURI(); |
| this.httpContext = HttpClientContext.create(); |
| this.httpContext.setCredentialsProvider(provider); |
| this.httpContext.setAuthCache(authCache); |
| |
| final RequestConfig config = RequestConfig.custom() |
| .setConnectTimeout(timeout * 1000) |
| .setConnectionRequestTimeout(timeout * 1000) |
| .setSocketTimeout(timeout * 1000) |
| .build(); |
| |
| if (!validateSSlCertificate) { |
| final SSLContext sslcontext = SSLUtils.getSSLContext(); |
| sslcontext.init(null, new X509TrustManager[]{new TrustAllManager()}, new SecureRandom()); |
| final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); |
| this.httpClient = HttpClientBuilder.create() |
| .setDefaultCredentialsProvider(provider) |
| .setDefaultRequestConfig(config) |
| .setSSLSocketFactory(factory) |
| .build(); |
| } else { |
| this.httpClient = HttpClientBuilder.create() |
| .setDefaultCredentialsProvider(provider) |
| .setDefaultRequestConfig(config) |
| .build(); |
| } |
| } |
| |
| private void checkAuthFailure(final HttpResponse response) { |
| if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { |
| final Credentials credentials = httpContext.getCredentialsProvider().getCredentials(AuthScope.ANY); |
| LOG.error("Cloudian admin API authentication failed, please check Cloudian configuration. Admin auth principal=" + credentials.getUserPrincipal() + ", password=" + credentials.getPassword() + ", API url=" + adminApiUrl); |
| throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "Cloudian backend API call unauthorized, please ask your administrator to fix integration issues."); |
| } |
| } |
| |
| private void checkResponseOK(final HttpResponse response) { |
| if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { |
| LOG.debug("Requested Cloudian resource does not exist"); |
| return; |
| } |
| if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { |
| throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to find the requested resource and get valid response from Cloudian backend API call, please ask your administrator to diagnose and fix issues."); |
| } |
| } |
| |
| private boolean checkEmptyResponse(final HttpResponse response) throws IOException { |
| return response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT || |
| response.getEntity() == null || |
| response.getEntity().getContent() == null); |
| } |
| |
| private void checkResponseTimeOut(final Exception e) { |
| if (e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException) { |
| throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Operation timed out, please try again."); |
| } |
| } |
| |
| private HttpResponse delete(final String path) throws IOException { |
| final HttpResponse response = httpClient.execute(new HttpDelete(adminApiUrl + path), httpContext); |
| checkAuthFailure(response); |
| return response; |
| } |
| |
| private HttpResponse get(final String path) throws IOException { |
| final HttpResponse response = httpClient.execute(new HttpGet(adminApiUrl + path), httpContext); |
| checkAuthFailure(response); |
| return response; |
| } |
| |
| private HttpResponse post(final String path, final Object item) throws IOException { |
| final ObjectMapper mapper = new ObjectMapper(); |
| final String json = mapper.writeValueAsString(item); |
| final StringEntity entity = new StringEntity(json); |
| final HttpPost request = new HttpPost(adminApiUrl + path); |
| request.setHeader("Content-type", "application/json"); |
| request.setEntity(entity); |
| final HttpResponse response = httpClient.execute(request, httpContext); |
| checkAuthFailure(response); |
| return response; |
| } |
| |
| private HttpResponse put(final String path, final Object item) throws IOException { |
| final ObjectMapper mapper = new ObjectMapper(); |
| final String json = mapper.writeValueAsString(item); |
| final StringEntity entity = new StringEntity(json); |
| final HttpPut request = new HttpPut(adminApiUrl + path); |
| request.setHeader("Content-type", "application/json"); |
| request.setEntity(entity); |
| final HttpResponse response = httpClient.execute(request, httpContext); |
| checkAuthFailure(response); |
| return response; |
| } |
| |
| //////////////////////////////////////////////////////// |
| //////////////// Public APIs: User ///////////////////// |
| //////////////////////////////////////////////////////// |
| |
| public boolean addUser(final CloudianUser user) { |
| if (user == null) { |
| return false; |
| } |
| LOG.debug("Adding Cloudian user: " + user); |
| try { |
| final HttpResponse response = put("/user", user); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to add Cloudian user due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| |
| public CloudianUser listUser(final String userId, final String groupId) { |
| if (Strings.isNullOrEmpty(userId) || Strings.isNullOrEmpty(groupId)) { |
| return null; |
| } |
| LOG.debug("Trying to find Cloudian user with id=" + userId + " and group id=" + groupId); |
| try { |
| final HttpResponse response = get(String.format("/user?userId=%s&groupId=%s", userId, groupId)); |
| checkResponseOK(response); |
| if (checkEmptyResponse(response)) { |
| return null; |
| } |
| final ObjectMapper mapper = new ObjectMapper(); |
| return mapper.readValue(response.getEntity().getContent(), CloudianUser.class); |
| } catch (final IOException e) { |
| LOG.error("Failed to list Cloudian user due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return null; |
| } |
| |
| public List<CloudianUser> listUsers(final String groupId) { |
| if (Strings.isNullOrEmpty(groupId)) { |
| return new ArrayList<>(); |
| } |
| LOG.debug("Trying to list Cloudian users in group id=" + groupId); |
| try { |
| final HttpResponse response = get(String.format("/user/list?groupId=%s&userType=all&userStatus=active", groupId)); |
| checkResponseOK(response); |
| if (checkEmptyResponse(response)) { |
| return new ArrayList<>(); |
| } |
| final ObjectMapper mapper = new ObjectMapper(); |
| return Arrays.asList(mapper.readValue(response.getEntity().getContent(), CloudianUser[].class)); |
| } catch (final IOException e) { |
| LOG.error("Failed to list Cloudian users due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return new ArrayList<>(); |
| } |
| |
| public boolean updateUser(final CloudianUser user) { |
| if (user == null) { |
| return false; |
| } |
| LOG.debug("Updating Cloudian user: " + user); |
| try { |
| final HttpResponse response = post("/user", user); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to update Cloudian user due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| |
| public boolean removeUser(final String userId, final String groupId) { |
| if (Strings.isNullOrEmpty(userId) || Strings.isNullOrEmpty(groupId)) { |
| return false; |
| } |
| LOG.debug("Removing Cloudian user with user id=" + userId + " in group id=" + groupId); |
| try { |
| final HttpResponse response = delete(String.format("/user?userId=%s&groupId=%s", userId, groupId)); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to remove Cloudian user due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| |
| ///////////////////////////////////////////////////////// |
| //////////////// Public APIs: Group ///////////////////// |
| ///////////////////////////////////////////////////////// |
| |
| public boolean addGroup(final CloudianGroup group) { |
| if (group == null) { |
| return false; |
| } |
| LOG.debug("Adding Cloudian group: " + group); |
| try { |
| final HttpResponse response = put("/group", group); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to add Cloudian group due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| |
| public CloudianGroup listGroup(final String groupId) { |
| if (Strings.isNullOrEmpty(groupId)) { |
| return null; |
| } |
| LOG.debug("Trying to find Cloudian group with id=" + groupId); |
| try { |
| final HttpResponse response = get(String.format("/group?groupId=%s", groupId)); |
| checkResponseOK(response); |
| if (checkEmptyResponse(response)) { |
| return null; |
| } |
| final ObjectMapper mapper = new ObjectMapper(); |
| return mapper.readValue(response.getEntity().getContent(), CloudianGroup.class); |
| } catch (final IOException e) { |
| LOG.error("Failed to list Cloudian group due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return null; |
| } |
| |
| public List<CloudianGroup> listGroups() { |
| LOG.debug("Trying to list Cloudian groups"); |
| try { |
| final HttpResponse response = get("/group/list"); |
| checkResponseOK(response); |
| if (checkEmptyResponse(response)) { |
| return new ArrayList<>(); |
| } |
| final ObjectMapper mapper = new ObjectMapper(); |
| return Arrays.asList(mapper.readValue(response.getEntity().getContent(), CloudianGroup[].class)); |
| } catch (final IOException e) { |
| LOG.error("Failed to list Cloudian groups due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return new ArrayList<>(); |
| } |
| |
| public boolean updateGroup(final CloudianGroup group) { |
| if (group == null) { |
| return false; |
| } |
| LOG.debug("Updating Cloudian group: " + group); |
| try { |
| final HttpResponse response = post("/group", group); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to remove group due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| |
| public boolean removeGroup(final String groupId) { |
| if (Strings.isNullOrEmpty(groupId)) { |
| return false; |
| } |
| LOG.debug("Removing Cloudian group id=" + groupId); |
| try { |
| final HttpResponse response = delete(String.format("/group?groupId=%s", groupId)); |
| return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; |
| } catch (final IOException e) { |
| LOG.error("Failed to remove group due to:", e); |
| checkResponseTimeOut(e); |
| } |
| return false; |
| } |
| } |