/* * Copyright 2013 Cloud4SOA, www.cloud4soa.eu * * 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. */ /* * 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.caldecott.client; import org.cloudfoundry.caldecott.TunnelException; import org.cloudfoundry.client.lib.domain.CloudApplication; import org.cloudfoundry.client.lib.CloudFoundryClient; import org.cloudfoundry.client.lib.domain.Staging; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.TypeFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * A utility class for accessing information regarding tunnels and data services. * * @author Thomas Risberg */ public class TunnelHelper { private static final String TUNNEL_APP_NAME = "caldecott"; private static final String[] TUNNEL_URI_SCHEMES = {"https:", "http:"}; private static final String TUNNEL_AUTH_KEY = "CALDECOTT_AUTH"; private static final Map<String, String> TUNNEL_URI_CACHE = new ConcurrentHashMap<String, String>(); private static final RestTemplate restTemplate = new RestTemplate(); private static ObjectMapper objectMapper = new ObjectMapper(); public static String getTunnelAppName() { return TUNNEL_APP_NAME; } public static CloudApplication getTunnelAppInfo(CloudFoundryClient client) { return client.getApplication(TunnelHelper.getTunnelAppName()); } public static void deployTunnelApp(CloudFoundryClient client) { ClassPathResource cpr = new ClassPathResource("extras/caldecott_helper.zip"); try { File temp = copyCaldecottZipFile(cpr); client.createApplication(TUNNEL_APP_NAME, new Staging("ruby19", "sinatra"), 64, Arrays.asList(new String[]{getRandomUrl(client, TUNNEL_APP_NAME)}), Arrays.asList(new String[] {}), false); client.uploadApplication(TUNNEL_APP_NAME, temp); client.updateApplicationEnv(TUNNEL_APP_NAME, Collections.singletonMap("CALDECOTT_AUTH", UUID.randomUUID().toString())); client.startApplication(TUNNEL_APP_NAME); temp.delete(); } catch (IOException e) { throw new TunnelException("Unable to deploy the Caldecott server application", e); } } public static void bindServiceToTunnelApp(CloudFoundryClient client, String serviceName) { if (getTunnelAppInfo(client).getServices().contains(serviceName)) { return; } client.stopApplication(getTunnelAppName()); System.out.println("binding "+serviceName); client.bindService(getTunnelAppName(), serviceName); System.out.println("binded"); client.startApplication(getTunnelAppName()); } public static String getTunnelUri(CloudFoundryClient client) { String uriAuthority = client.getApplication(TunnelHelper.getTunnelAppName()).getUris().get(0); if (TUNNEL_URI_CACHE.containsKey(uriAuthority)) { return TUNNEL_URI_CACHE.get(uriAuthority); } String uriScheme = testUriSchemes(client, TUNNEL_URI_SCHEMES, uriAuthority); String uri = uriScheme + "//" + uriAuthority; TUNNEL_URI_CACHE.put(uriAuthority, uri); return uri; } public static String getTunnelAuth(CloudFoundryClient client) { String auth = client.getApplication(TunnelHelper.getTunnelAppName()).getEnvAsMap().get(TUNNEL_AUTH_KEY); return auth; } public static Map<String, String> getTunnelServiceInfo(CloudFoundryClient client, String serviceName) { String urlToUse = getTunnelUri(client) + "/services/" + serviceName; HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.set("Auth-Token", getTunnelAuth(client)); HttpEntity<?> requestEntity = new HttpEntity(requestHeaders); HttpEntity<String> response = restTemplate.exchange(urlToUse, HttpMethod.GET, requestEntity, String.class); String json = response.getBody().trim(); Map<String, String> svcInfo = new HashMap<String, String>(); try { svcInfo = convertJsonToMap(json); } catch (IOException e) { return new HashMap<String, String>(); } if (svcInfo.containsKey("url")) { String svcUrl = svcInfo.get("url"); try { URI uri = new URI(svcUrl); String[] userInfo; if (uri.getUserInfo().contains(":")) { userInfo = uri.getUserInfo().split(":"); } else { userInfo = new String[2]; userInfo[0] = uri.getUserInfo(); userInfo[1] = ""; } svcInfo.put("user", userInfo[0]); svcInfo.put("username", userInfo[0]); svcInfo.put("password", userInfo[1]); svcInfo.put("host", uri.getHost()); svcInfo.put("hostname", uri.getHost()); svcInfo.put("port", ""+uri.getPort()); svcInfo.put("path", (uri.getPath().startsWith("/") ? uri.getPath().substring(1): uri.getPath())); svcInfo.put("vhost", svcInfo.get("path")); } catch (URISyntaxException e) {} } return svcInfo; } private static String testUriSchemes(CloudFoundryClient client, String[] uriSchemes, String uriAuthority) { int i = 0; int retries = 0; String scheme = null; while (i < uriSchemes.length) { scheme = uriSchemes[i]; String uriToUse = scheme + "//" + uriAuthority; try { getTunnelProtocolVersion(client, uriToUse); break; } catch (HttpClientErrorException e) { if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) { if (retries < 10) { retries++; } else { throw new TunnelException("Not able to locate tunnel server at: " + uriToUse, e); } } else { throw new TunnelException("Error accessing tunnel server at: " + uriToUse, e); } } catch (ResourceAccessException e) { if (e.getMessage().contains("refused") || e.getMessage().contains("unable") || e.getMessage().contains("Connection timed out")) { i++; } else { throw e; } } catch (RuntimeException e) { throw e; } } return scheme; } private static File copyCaldecottZipFile(ClassPathResource cpr) throws IOException { File temp = File.createTempFile("caldecott", "zip"); InputStream in = cpr.getInputStream(); OutputStream out = new FileOutputStream(temp); int read = 0; byte[] bytes = new byte[1024]; while ((read = in.read(bytes)) != -1) { out.write(bytes, 0, read); } in.close(); out.flush(); out.close(); return temp; } public static String getRandomUrl(CloudFoundryClient client, String appname) { int range = 0x01000000; int r = new Random().nextInt(range - 1); StringBuilder url = new StringBuilder(); url.append(appname); url.append("-"); url.append(Integer.toHexString((r))); String domain = client.getCloudControllerUrl().getHost(); if (domain.startsWith("api.")) { domain = domain.substring(3); } else { url.append("-"); } url.append(domain); return url.toString(); } public static String getTunnelProtocolVersion(CloudFoundryClient client, String uri) { String uriToUse = uri + "/info"; HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.set("Auth-Token", getTunnelAuth(client)); HttpEntity<?> requestEntity = new HttpEntity(requestHeaders); HttpEntity<String> response = restTemplate.exchange(uriToUse, HttpMethod.GET, requestEntity, String.class); return response.getBody().trim(); } public static Map<String, String> convertJsonToMap(String json) throws IOException { Map<String, String> svcInfo = objectMapper.readValue(json, TypeFactory.mapType(HashMap.class, String.class, String.class)); return svcInfo; } }