Implementing rest utilities to invoke Docker.
Project: http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/commit/9f0eb33c Tree: http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/tree/9f0eb33c Diff: http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/diff/9f0eb33c Branch: refs/heads/docker Commit: 9f0eb33cd40e56f4294afb9d6e832f44dd0a7f97 Parents: fbc9e2a Author: Nadeesh Dilanga <[email protected]> Authored: Wed Jun 15 03:42:37 2016 -0400 Committer: Nadeesh Dilanga <[email protected]> Committed: Wed Jun 15 03:42:37 2016 -0400 ---------------------------------------------------------------------- .../activities/docker/DockerActivity.java | 22 ++- .../taverna/activities/docker/DockerConfig.java | 77 +++++++++ .../activities/docker/DockerHttpResponse.java | 49 ++++++ .../taverna/activities/docker/RESTUtil.java | 157 +++++++++++++++++++ .../docker/test/TestCreateContainer.java | 43 +++++ 5 files changed, 343 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/9f0eb33c/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerActivity.java ---------------------------------------------------------------------- diff --git a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerActivity.java b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerActivity.java index 2760546..c81e9de 100644 --- a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerActivity.java +++ b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerActivity.java @@ -19,6 +19,8 @@ package org.apache.taverna.activities.docker; import com.fasterxml.jackson.databind.JsonNode; +import org.apache.taverna.invocation.InvocationContext; +import org.apache.taverna.reference.ReferenceService; import org.apache.taverna.reference.T2Reference; import org.apache.taverna.workflowmodel.processor.activity.AbstractAsynchronousActivity; import org.apache.taverna.workflowmodel.processor.activity.ActivityConfigurationException; @@ -31,18 +33,28 @@ import java.util.Map; */ public class DockerActivity extends AbstractAsynchronousActivity<JsonNode> { - @Override - public void configure(JsonNode jsonNode) throws ActivityConfigurationException { + private JsonNode activityConfig; + @Override + public void configure(JsonNode activityConfig) throws ActivityConfigurationException { + this.activityConfig = activityConfig; } @Override public JsonNode getConfiguration() { - return null; + return activityConfig; } @Override - public void executeAsynch(Map<String, T2Reference> map, AsynchronousActivityCallback asynchronousActivityCallback) { - + public void executeAsynch(Map<String, T2Reference> map, final AsynchronousActivityCallback callback) { + callback.requestRun(new Runnable() { + @Override + public void run() { + InvocationContext context = callback.getContext(); + ReferenceService referenceService = context.getReferenceService(); + //TODO invoke container remote api and set final response result to callback.receiveResult(); + + } + }); } } http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/9f0eb33c/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerConfig.java ---------------------------------------------------------------------- diff --git a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerConfig.java b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerConfig.java new file mode 100644 index 0000000..30d12c5 --- /dev/null +++ b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerConfig.java @@ -0,0 +1,77 @@ +/* +* 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.taverna.activities.docker; + +import com.fasterxml.jackson.databind.JsonNode; + +public class DockerConfig { + + /** + * Hold the hostname of the docker container. + */ + private String containerHost; + + /** + * Remote REST API port exposed by Docker + */ + private int remoteAPIPort = 443; + + /** + * JSON payload to invoke create container REST API. + */ + private JsonNode createContainerPayload; + + /** + * Complete HTTP URL for create container + */ + private final String createContainerURL; + + /** + * Docker remote REST resource path for creating a container + */ + public static final String CREATE_CONTAINER_RESOURCE_PATH = "/containers/create"; + + /** + * Transport protocol + */ + public static final String PROTOCOL = "https"; + + public DockerConfig(String containerHost, int remoteAPIPort, JsonNode createContainerPayload) { + this.containerHost = containerHost; + this.remoteAPIPort = remoteAPIPort; + this.createContainerPayload = createContainerPayload; + this.createContainerURL = PROTOCOL + "://" + containerHost + ":" + remoteAPIPort + CREATE_CONTAINER_RESOURCE_PATH ; + } + + public String getContainerHost() { + return containerHost; + } + + public int getRemoteAPIPort() { + return remoteAPIPort; + } + + public JsonNode getCreateContainerPayload() { + return createContainerPayload; + } + + public String getCreateContainerURL() { + return createContainerURL; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/9f0eb33c/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerHttpResponse.java ---------------------------------------------------------------------- diff --git a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerHttpResponse.java b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerHttpResponse.java new file mode 100644 index 0000000..336bca9 --- /dev/null +++ b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/DockerHttpResponse.java @@ -0,0 +1,49 @@ +/* +* 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.taverna.activities.docker; + +import org.apache.http.Header; + +public class DockerHttpResponse { + + private Header[] headers; + + private int statusCode; + + private String body; + + public DockerHttpResponse(Header[] headers, int statusCode, String body) { + this.headers = headers; + this.statusCode = statusCode; + this.body = body; + } + + public Header[] getHeaders() { + return headers; + } + + public int getStatusCode() { + return statusCode; + } + + public String getBody() { + return body; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/9f0eb33c/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/RESTUtil.java ---------------------------------------------------------------------- diff --git a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/RESTUtil.java b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/RESTUtil.java index 460f275..99fff51 100644 --- a/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/RESTUtil.java +++ b/taverna-docker-activity/src/main/java/org/apache/taverna/activities/docker/RESTUtil.java @@ -19,5 +19,162 @@ package org.apache.taverna.activities.docker; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.message.BasicHeader; +import org.apache.http.ssl.SSLContexts; +import org.apache.log4j.Logger; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.security.cert.CertificateException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + public class RESTUtil { + + /** + * Http header name for content type + */ + private static final String CONTENT_TYPE = "Content-Type"; + + /** + * Http content type value for JSON messages. + */ + private static final String JSON_CONTENT_TYPE = "application/json"; + + /** + * Logger + */ + private static Logger LOG = Logger.getLogger(RESTUtil.class); + + + public static boolean createContainer(DockerConfig dockerConfig) { + try { + URL url = new URL(dockerConfig.getCreateContainerURL()); + org.apache.http.conn.ssl.SSLSocketFactory factory = new org.apache.http.conn.ssl.SSLSocketFactory(SSLContext.getDefault()); + Scheme https = new Scheme(DockerConfig.PROTOCOL,factory , url.getPort()); + SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(https); + ClientConnectionManager connectionManager = new SingleClientConnManager(null, schemeRegistry); + Map<String,String> headers = new HashMap<String,String>(); + headers.put(CONTENT_TYPE, JSON_CONTENT_TYPE); + DockerHttpResponse response = doPost(connectionManager,dockerConfig.getCreateContainerURL(), headers, dockerConfig.getCreateContainerPayload()); + if(response.getStatusCode() == 201){ + JsonNode node = getJson(response.getBody()); + LOG.info(String.format("Successfully created Docker container id: %s ", getDockerId(node))); + return true; + } + + } catch (MalformedURLException e1) { + LOG.error(String.format("Malformed URL encountered. This can be due to invalid URL parts. " + + "Docker Host=%s, Port=%d and Resource Path=%s", + dockerConfig.getContainerHost(), + dockerConfig.getRemoteAPIPort(), + DockerConfig.CREATE_CONTAINER_RESOURCE_PATH), e1); + } catch (NoSuchAlgorithmException e2) { + LOG.error("Failed to create SSLContext for invoking the REST service over https.", e2); + } catch (IOException e3) { + LOG.error("Error occurred while reading the docker http response", e3); + } + return false; + } + + private static DockerHttpResponse doPost(ClientConnectionManager connectionManager, String url, Map<String, String> headers, JsonNode payload) { + DefaultHttpClient httpClient = null; + CloseableHttpResponse response = null; + DockerHttpResponse dockerResponse = null; + HttpPost httpPost = null; + try { + httpPost = new HttpPost(url); + HttpEntity entity = new StringEntity(payload.toString()); + httpPost.setEntity(entity); + for (Map.Entry<String, String> entry : headers.entrySet()) { + httpPost.setHeader(new BasicHeader(entry.getKey(), entry.getValue())); + } + httpClient = new DefaultHttpClient(connectionManager, null); + response = httpClient.execute(httpPost); + if (response != null) { + dockerResponse = new DockerHttpResponse(response.getAllHeaders(), response.getStatusLine().getStatusCode(),readBody(response.getEntity()).toString()); + } + } catch (IOException e) { + e.printStackTrace(); + LOG.error("Failed to complete Http POST invocation", e); + dockerResponse = new DockerHttpResponse(new Header[]{new BasicHeader( + CONTENT_TYPE, JSON_CONTENT_TYPE)}, + 500, + "{\"error\":\"internal server error\", \"message\":\""+ e.getMessage() +"\"}"); + } finally { + if(httpPost != null){ + httpPost.releaseConnection(); + } + if (httpClient != null) { + httpClient.close(); + } + if (response != null) { + try { + response.close(); + } catch (IOException ignore) {} + } + } + return dockerResponse; + } + + private static StringBuilder readBody(HttpEntity entity) throws IOException { + String charset = null; + String contentType = entity.getContentType().getValue().toLowerCase(); + String[] contentTypeParts = contentType.split(";"); + for (String contentTypePart : contentTypeParts) { + contentTypePart = contentTypePart.trim(); + if (contentTypePart.startsWith("charset=")) { + charset = contentTypePart.substring("charset=".length()); + } + } + BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), charset != null ? charset : "UTF-8")); + String str; + StringBuilder responseBuilder = new StringBuilder(); + while ((str = reader.readLine()) != null) { + responseBuilder.append(str + "\n"); + } + return responseBuilder; + } + + private static JsonNode getJson(String s) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(s); + } + + private static String getDockerId(JsonNode node){ + String dockerId = null; + Iterator<JsonNode> itr = node.elements(); + while(itr.hasNext()){ + JsonNode child = itr.next(); + if("id".equalsIgnoreCase(child.textValue())){ + dockerId = child.textValue(); + break; + } + } + return dockerId; + } + } http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/9f0eb33c/taverna-docker-activity/src/test/java/org/apache/taverna/activities/docker/test/TestCreateContainer.java ---------------------------------------------------------------------- diff --git a/taverna-docker-activity/src/test/java/org/apache/taverna/activities/docker/test/TestCreateContainer.java b/taverna-docker-activity/src/test/java/org/apache/taverna/activities/docker/test/TestCreateContainer.java new file mode 100644 index 0000000..bd68abb --- /dev/null +++ b/taverna-docker-activity/src/test/java/org/apache/taverna/activities/docker/test/TestCreateContainer.java @@ -0,0 +1,43 @@ +/* +* 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.taverna.activities.docker.test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.taverna.activities.docker.DockerConfig; +import org.apache.taverna.activities.docker.RESTUtil; +import org.junit.Test; + +import java.io.IOException; + +public class TestCreateContainer{ + +// @Test +// public void testCreateContainer(){ +// try { +// String payload = "{\"Hostname\":\"foo.com\", \"User\":\"foo\", \"Memory\":0, \"MemorySwap\":0,\"AttachStdin\":false, \"AttachStdout\":true,\"Attachstderr\":true,\"PortSpecs\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null, \"Cmd\":[\"date\"], \"Image\":\"ubuntu\",\"Tag\":\"latest\",\"Volumes\":{\"/tmp\":{} },\"WorkingDir\":\"\",\"DisableNetwork\":false, \"ExposedPorts\":{\"22/tcp\": {} }}"; +// DockerConfig config = new DockerConfig("192.168.99.100",2376, new ObjectMapper().readTree(payload)); +// boolean res = RESTUtil.createContainer(config); +// System.out.println(">>>" + res); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// } +}
