/* * Copyright [2013] [Cloud4SOA, www.cloud4soa.eu] * * * 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. */ /* * Copyright 2009-2012 the original author or authors. * * 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.cloudfoundry.client.lib.rest; import org.cloudfoundry.client.lib.CloudCredentials; import org.cloudfoundry.client.lib.HttpProxyConfiguration; import org.cloudfoundry.client.lib.domain.CloudInfo; import org.cloudfoundry.client.lib.oauth2.OauthClient; import org.cloudfoundry.client.lib.util.CloudUtil; import org.cloudfoundry.client.lib.util.JsonUtil; import org.cloudfoundry.client.lib.util.RestUtil; import org.cloudfoundry.client.lib.util.StringHttpMessageConverterWithoutMediaType; import org.cloudfoundry.client.lib.util.UploadApplicationPayloadHttpMessageConverter; import org.cloudfoundry.client.lib.UploadStatusCallback; import org.cloudfoundry.client.lib.archive.ApplicationArchive; import org.cloudfoundry.client.lib.archive.DirectoryApplicationArchive; import org.cloudfoundry.client.lib.archive.ZipApplicationArchive; import org.cloudfoundry.client.lib.domain.ApplicationStats; import org.cloudfoundry.client.lib.domain.CloudApplication; import org.cloudfoundry.client.lib.domain.CloudResources; import org.cloudfoundry.client.lib.domain.CloudService; import org.cloudfoundry.client.lib.domain.CrashesInfo; import org.cloudfoundry.client.lib.domain.InstancesInfo; import org.cloudfoundry.client.lib.domain.ServiceConfiguration; import org.cloudfoundry.client.lib.domain.Staging; import org.cloudfoundry.client.lib.domain.UploadApplicationPayload; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipFile; /** * Implementation for cloud controller v1 REST API * * @author Ramnivas Laddad * @author A.B.Srinivasan * @author Jennifer Hickey * @author Dave Syer * @author Thomas Risberg */ public class CloudControllerClientV1 extends AbstractCloudControllerClient { private OauthClient oauthClient; public CloudControllerClientV1(URL cloudControllerUrl, RestUtil restUtil, CloudCredentials cloudCredentials, URL authorizationEndpoint, HttpProxyConfiguration httpProxyConfiguration) { super(cloudControllerUrl, restUtil, cloudCredentials, authorizationEndpoint, httpProxyConfiguration); initializeOauthClient(httpProxyConfiguration); } public CloudInfo getInfo() { String resp = getRestTemplate().getForObject(getCloudControllerUrl() + "/info", String.class); Map<String, Object> infoMap = JsonUtil.convertJsonToMap(resp); return new CloudInfo(infoMap); } public boolean supportsSpaces() { return false; } public String login() { if (cloudCredentials.getEmail() == null) { Assert.hasLength(cloudCredentials.getToken(), "No authentication details provided"); token = cloudCredentials.getToken(); return token; } Assert.hasLength(cloudCredentials.getEmail(), "Email cannot be null or empty"); Assert.hasLength(cloudCredentials.getPassword(), "Password cannot be null or empty"); if (oauthClient != null) { OAuth2AccessToken token = oauthClient.getToken(cloudCredentials.getEmail(), cloudCredentials.getPassword()); this.token = token.getTokenType() + " " + token.getValue(); return this.token; } else { Map<String, String> payload = new HashMap<String, String>(); payload.put("password", cloudCredentials.getPassword()); Map<String, String> response = getRestTemplate().postForObject( getUrl("/users/{id}/tokens"), payload, Map.class, cloudCredentials.getEmail()); token = response.get("token"); return token; } } public void logout() { token = null; } public void register(String email, String password) { Map<String, String> payload = new HashMap<String, String>(); payload.put("email", email); payload.put("password", password); getRestTemplate().postForLocation(getUrl("/users"), payload); } public void updatePassword(CloudCredentials credentials, String newPassword) { if (oauthClient != null) { oauthClient.changePassword(token, credentials.getPassword(), newPassword); } else { Map<String, String> userInfo = getRestTemplate().getForObject(getUrl("/users/{id}"), Map.class, credentials.getEmail()); userInfo.put("password", newPassword); getRestTemplate().put(getUrl("/users/{id}"), userInfo, credentials.getEmail()); } CloudCredentials newCloudCredentials = new CloudCredentials(credentials.getEmail(), newPassword); if (cloudCredentials.getProxyUser() != null) { cloudCredentials = newCloudCredentials.proxyForUser(cloudCredentials.getProxyUser()); } else { cloudCredentials = newCloudCredentials; } } public void unregister() { getRestTemplate().delete(getUrl("/users/{email}"), cloudCredentials.getEmail()); token = null; } public List<CloudService> getServices() { @SuppressWarnings("unchecked") List<Map<String, Object>> servicesAsMap = getRestTemplate().getForObject(getUrl("/services"), List.class); List<CloudService> services = new ArrayList<CloudService>(); for (Map<String, Object> serviceAsMap : servicesAsMap) { services.add(new CloudService(serviceAsMap)); } return services; } public void createService(CloudService service) { getRestTemplate().postForLocation(getUrl("/services"), service); } public CloudService getService(String service) { @SuppressWarnings("unchecked") Map<String, Object> serviceAsMap = getRestTemplate().getForObject( getUrl("/services/{service}"), Map.class, service); return new CloudService(serviceAsMap); } public void deleteService(String service) { getRestTemplate().delete(getUrl("/services/{service}"), service); } public void deleteAllServices() { List<CloudService> services = getServices(); for (CloudService service : services) { deleteService(service.getName()); } } public List<ServiceConfiguration> getServiceConfigurations() { Map<String, Object> configurationAsMap = getRestTemplate().getForObject( getUrl("/info/services"), Map.class); if (configurationAsMap == null) { return Collections.emptyList(); } List<ServiceConfiguration> configurations = new ArrayList<ServiceConfiguration>(); for (Map.Entry<String, Object> typeEntry : configurationAsMap.entrySet()) { Map<String, Object> vendorMap = CloudUtil.parse(Map.class, typeEntry.getValue()); if (vendorMap == null) { continue; } for (Map.Entry<String, Object> vendorEntry : vendorMap.entrySet()) { Map<String, Object> versionMap = CloudUtil.parse(Map.class, vendorEntry.getValue()); if (versionMap == null) { continue; } for (Map.Entry<String, Object> serviceEntry : versionMap.entrySet()) { Map<String, Object> attributes = CloudUtil.parse(Map.class, serviceEntry.getValue()); if (attributes != null) { configurations.add(new ServiceConfiguration(attributes)); } } } } return configurations; } public List<CloudApplication> getApplications() { List<Map<String, Object>> appsAsMap = getRestTemplate().getForObject(getUrl("/apps"), List.class); List<CloudApplication> apps = new ArrayList<CloudApplication>(); for (Map<String, Object> appAsMap : appsAsMap) { apps.add(new CloudApplication(appAsMap)); } return apps; } public CloudApplication getApplication(String appName) { Map<String, Object> appAsMap = getRestTemplate().getForObject(getUrl("/apps/{appName}"), Map.class, appName); return new CloudApplication(appAsMap); } public ApplicationStats getApplicationStats(String appName) { @SuppressWarnings("unchecked") Map<String, Object> statsAsMap = getRestTemplate().getForObject(getUrl("/apps/{appName}/stats"), Map.class, appName); return new ApplicationStats(statsAsMap); } public int[] getApplicationMemoryChoices() { // TODO: Get it from cloudcontroller's 'info/resources' end point int[] generalChoices = new int[] {64, 128, 256, 512, 1024, 2048}; int maxMemory = getInfo().getLimits().getMaxTotalMemory(); int length = 0; for (int generalChoice : generalChoices) { if (generalChoice <= maxMemory) { length++; } } int[] result = new int[length]; System.arraycopy(generalChoices, 0, result, 0, length); return result; } public void createApplication(String appName, Staging staging, int memory, List<String> uris, List<String> serviceNames, boolean checkExists) { if (checkExists) { try { getApplication(appName); return; } catch (HttpClientErrorException e) { if (e.getStatusCode() != HttpStatus.NOT_FOUND) { throw e; } } } if (serviceNames == null) { serviceNames = new ArrayList<String>(); } CloudApplication payload = new CloudApplication(appName, staging.getRuntime(), staging.getFramework(), memory, 1, uris, serviceNames, CloudApplication.AppState.STOPPED); payload.setCommand(staging.getCommand()); Map<String, Object> appMap = createAppMap(payload); getRestTemplate().postForLocation(getUrl("/apps"), appMap); CloudApplication postedApp = getApplication(appName); if (serviceNames != null && serviceNames.size() != 0) { postedApp.setServices(serviceNames); updateApplication(postedApp); } } public void uploadApplication(String appName, File file, UploadStatusCallback callback) throws IOException { Assert.notNull(file, "File must not be null"); if (file.isDirectory()) { doUploadApplicationFolder(appName, file, callback); } else { doUploadApplicationZipFile(appName, file, callback); } } public void uploadApplication(String appName, ApplicationArchive archive, UploadStatusCallback callback) throws IOException { Assert.notNull(appName, "AppName must not be null"); Assert.notNull(archive, "Archive must not be null"); if (callback == null) { callback = UploadStatusCallback.NONE; } CloudResources knownRemoteResources = getKnownRemoteResources(archive); callback.onCheckResources(); callback.onMatchedFileNames(knownRemoteResources.getFilenames()); UploadApplicationPayload payload = new UploadApplicationPayload(archive, knownRemoteResources); callback.onProcessMatchedResources(payload.getTotalUncompressedSize()); HttpEntity<?> entity = generatePartialResourceRequest(payload, knownRemoteResources, HttpMethod.POST); String url = getUrl("apps/{appName}/application"); try { getRestTemplate().postForLocation(url, entity, appName); } catch (HttpServerErrorException hsee) { if (HttpStatus.INTERNAL_SERVER_ERROR.equals(hsee.getStatusCode())) { // this is for supporting legacy Micro Cloud Foundry 1.1 and older uploadAppUsingLegacyApi(url, payload, knownRemoteResources, appName); } else { throw hsee; } } catch (HttpClientErrorException hcee) { if (HttpStatus.NOT_FOUND.equals(hcee.getStatusCode())) { // this is for supporting legacy Micro Cloud Foundry 1.2 HttpEntity<?> entity12 = generatePartialResourceRequest(payload, knownRemoteResources, HttpMethod.PUT); getRestTemplate().put(url, entity12, appName); } else { throw hcee; } } } public void startApplication(String appName) { CloudApplication app = getApplication(appName); app.setState(CloudApplication.AppState.STARTED); app.setDebug(null); doUpdateApplication(app); } public void debugApplication(String appName, CloudApplication.DebugMode mode) { CloudApplication app = getApplication(appName); app.setState(CloudApplication.AppState.STARTED); app.setDebug(mode); doUpdateApplication(app); } public void stopApplication(String appName) { CloudApplication app = getApplication(appName); if (app != null) { app.setState(CloudApplication.AppState.STOPPED); doUpdateApplication(app); } } public void restartApplication(String appName) { stopApplication(appName); startApplication(appName); } public void deleteApplication(String appName) { getRestTemplate().delete(getUrl("/apps/{appName}"), appName); } public void deleteAllApplications() { List<CloudApplication> apps = getApplications(); for (CloudApplication app : apps) { deleteApplication(app.getName()); } } public void updateApplicationMemory(String appName, int memory) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setMemory(memory); doUpdateApplication(app); } public void updateApplicationInstances(String appName, int instances) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setInstances(instances); doUpdateApplication(app); } public void updateApplicationServices(String appName, List<String> services) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setServices(services); doUpdateApplication(app); } public void updateApplicationStaging(String appName, Staging staging) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setStaging(staging); doUpdateApplication(app); } public void updateApplicationUris(String appName, List<String> uris) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setUris(uris); doUpdateApplication(app); } public void updateApplicationEnv(String appName, Map<String, String> env) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setEnv(env); doUpdateApplication(app); } public void updateApplicationEnv(String appName, List<String> env) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setEnv(env); doUpdateApplication(app); } public String getFile(String appName, int instanceIndex, String filePath, int startPosition, int endPosition) { String urlPath = "/apps/{app}/instances/{instanceIndex}/files/{filePath}"; return doGetFile(urlPath, appName, instanceIndex, filePath, startPosition, endPosition); } public void bindService(String appName, String serviceName) { CloudApplication application = getApplication(appName); if (application.getServices() == null) { application.setServices(Collections.singletonList(serviceName)); } else { application.getServices().add(serviceName); } doUpdateApplication(application); } public void unbindService(String appName, String serviceName) { CloudApplication application = getApplication(appName); if (application.getServices() != null) { application.getServices().remove(serviceName); doUpdateApplication(application); } } public InstancesInfo getApplicationInstances(String appName) { @SuppressWarnings("unchecked") Map<String, Object> map = getRestTemplate().getForObject(getUrl("/apps/{appName}/instances"), Map.class, appName); @SuppressWarnings("unchecked") List<Map<String, Object>> instanceData = (List<Map<String, Object>>)map.get("instances"); return new InstancesInfo(instanceData); } public CrashesInfo getCrashes(String appName) { @SuppressWarnings("unchecked") Map<String, Object> map = getRestTemplate().getForObject(getUrl("/apps/{appName}/crashes"), Map.class, appName); @SuppressWarnings("unchecked") List<Map<String, Object>> crashData = (List<Map<String, Object>>)map.get("crashes"); return new CrashesInfo(crashData); } public void rename(String appName, String newName) { CloudApplication app = getApplication(appName); if (app == null) { throw new IllegalArgumentException("Application " + appName + " does not exist"); } app.setName(newName); getRestTemplate().put(getUrl("/apps/{appName}"), app, appName); } private void initializeOauthClient(HttpProxyConfiguration httpProxyConfiguration) { if (authorizationEndpoint != null) { this.oauthClient = restUtil.createOauthClient(authorizationEndpoint, httpProxyConfiguration); } } private Map<String, Object> createAppMap(CloudApplication payload) { Map<String, Object> appMap = JsonUtil.convertJsonToMap(JsonUtil.convertToJson(payload)); Map<String, String> stagingMap = (Map<String, String>) appMap.get("staging"); if (stagingMap.containsKey("runtime")) { stagingMap.put("stack", stagingMap.remove("runtime")); } if (stagingMap.containsKey("framework")) { stagingMap.put("model", stagingMap.remove("framework")); } return appMap; } /** * Update application. * * @param app the appplication info */ private void updateApplication(CloudApplication app) { getRestTemplate().put(getUrl("/apps/{appName}"), app, app.getName()); } private void doUploadApplicationFolder(String appName, File file, UploadStatusCallback callback) throws IOException { ApplicationArchive archive = new DirectoryApplicationArchive(file); uploadApplication(appName, archive, callback); } private void doUploadApplicationZipFile(String appName, File file, UploadStatusCallback callback) throws IOException { ZipFile zipFile = new ZipFile(file); try { ApplicationArchive archive = new ZipApplicationArchive(zipFile); uploadApplication(appName, archive, callback); } finally { zipFile.close(); } } /** * Upload an app using the legacy API used for older vcap versions like Micro Cloud Foundry 1.1 and older * As of Micro Cloud Foundry 1.2 and for any recent CloudFoundry.com deployment the current method of setting * the content type as JSON works fine. * * @param path app path * @param payload the application payload * @param knownRemoteResources known remote resources * @param appName name of app * @throws HttpServerErrorException */ private void uploadAppUsingLegacyApi(String path, UploadApplicationPayload payload, CloudResources knownRemoteResources, String appName) throws HttpServerErrorException, IOException { RestTemplate legacyRestTemplate = new RestTemplate(); legacyRestTemplate.setRequestFactory(this.getRestTemplate().getRequestFactory()); legacyRestTemplate.setErrorHandler(new ErrorHandler()); legacyRestTemplate.setMessageConverters(getLegacyMessageConverters()); HttpEntity<?> entity = generatePartialResourceRequest(payload, knownRemoteResources, HttpMethod.PUT); legacyRestTemplate.put(path, entity, appName); } /** * Get message converters to use for supporting legacy Micro Cloud Foundry 1.1 and older * * @return List of message converters */ private List<HttpMessageConverter<?>> getLegacyMessageConverters() { List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>(); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new UploadApplicationPayloadHttpMessageConverter()); FormHttpMessageConverter formPartsMessageConverter = new CloudFoundryFormHttpMessageConverter(); List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>(); StringHttpMessageConverter stringConverter = new StringHttpMessageConverterWithoutMediaType(); stringConverter.setWriteAcceptCharset(false); partConverters.add(stringConverter); partConverters.add(new ResourceHttpMessageConverter()); partConverters.add(new UploadApplicationPayloadHttpMessageConverter()); formPartsMessageConverter.setPartConverters(partConverters); messageConverters.add(formPartsMessageConverter); messageConverters.add(new MappingJacksonHttpMessageConverter()); return messageConverters; } private CloudResources getKnownRemoteResources(ApplicationArchive archive) throws IOException { CloudResources archiveResources = new CloudResources(archive); return getRestTemplate().postForObject(getUrl("/resources"), archiveResources, CloudResources.class); } private HttpEntity<MultiValueMap<String, ?>> generatePartialResourceRequest(UploadApplicationPayload application, CloudResources knownRemoteResources, HttpMethod method) throws IOException { MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>(2); if (method == HttpMethod.POST) { body.add("_method", "put"); } if (application.getNumEntries() > 0) { //If the entire app contents are cached, send nothing body.add("application", application); } ObjectMapper mapper = new ObjectMapper(); String knownRemoteResourcesPayload = mapper.writeValueAsString(knownRemoteResources); body.add("resources", knownRemoteResourcesPayload); HttpHeaders headers = new HttpHeaders(); headers.add("Accept", "text/html, application/xml"); headers.setContentType(MediaType.MULTIPART_FORM_DATA); return new HttpEntity<MultiValueMap<String, ?>>(body, headers); } /** * Update application. * * @param app the appplication info */ private void doUpdateApplication(CloudApplication app) { Map<String, Object> appMap = createAppMap(app); getRestTemplate().put(getUrl("/apps/{appName}"), appMap, app.getName()); } }