Add HTTP Client library and refactor Northbound IntegrationTest 08/1608/4
authorRyan Moats <rmoats@us.ibm.com>
Wed, 2 Oct 2013 15:11:17 +0000 (10:11 -0500)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 3 Oct 2013 20:17:51 +0000 (20:17 +0000)
Create a new commons.httpclient library for bundles that need
to function as an HTTP client.  Refactor the existing NB
IntegrationTest code to use this new library.

Ammended - library is now a profile of apache httpclient classes
to ensure things like timeouts are set on default and resources
are correctly released when finished.

Change-Id: I76f513b77700213752f4183e917fe239a9c324ff
Signed-off-by: Ryan Moats <rmoats@us.ibm.com>
opendaylight/commons/httpclient/pom.xml [new file with mode: 0644]
opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPClient.java [new file with mode: 0644]
opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPRequest.java [new file with mode: 0644]
opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPResponse.java [new file with mode: 0644]
opendaylight/distribution/opendaylight/pom.xml
opendaylight/northbound/integrationtest/pom.xml
opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java

diff --git a/opendaylight/commons/httpclient/pom.xml b/opendaylight/commons/httpclient/pom.xml
new file mode 100644 (file)
index 0000000..9f46206
--- /dev/null
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">\r
+  <modelVersion>4.0.0</modelVersion>\r
+  <parent>\r
+    <groupId>org.opendaylight.controller</groupId>\r
+    <artifactId>commons.opendaylight</artifactId>\r
+    <version>1.4.1-SNAPSHOT</version>\r
+    <relativePath>../../commons/opendaylight</relativePath>\r
+  </parent>\r
+  <scm>\r
+    <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>\r
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>\r
+    <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:Main</url>\r
+    <tag>HEAD</tag>\r
+  </scm>\r
+\r
+  <artifactId>commons.httpclient</artifactId>\r
+  <version>0.1.1-SNAPSHOT</version>\r
+  <packaging>bundle</packaging>\r
+\r
+  <build>\r
+    <plugins>\r
+      <plugin>\r
+        <groupId>org.apache.felix</groupId>\r
+        <artifactId>maven-bundle-plugin</artifactId>\r
+        <version>${bundle.plugin.version}</version>\r
+        <extensions>true</extensions>\r
+        <configuration>\r
+          <instructions>\r
+            <Import-Package>\r
+            javax.crypto,\r
+            javax.crypto.spec,\r
+            javax.net,\r
+            javax.net.ssl,\r
+            javax.security.auth.x500,\r
+            javax.servlet,\r
+            org.apache.log4j,\r
+            org.ietf.jgss,\r
+            !org.apache.commons.codec.*,\r
+            !org.apache.log,\r
+            !org.apache.avalon.framework.*\r
+            </Import-Package>\r
+            <Export-Package>\r
+              org.opendaylight.controller.commons.httpclient\r
+            </Export-Package>\r
+            <Embed-Dependency>\r
+              httpclient,httpcore,commons-logging\r
+            </Embed-Dependency>\r
+            <Embed-Transitive>false</Embed-Transitive>\r
+          </instructions>\r
+          <manifestLocation>${project.basedir}/META-INF</manifestLocation>\r
+        </configuration>\r
+      </plugin>\r
+      <plugin>\r
+        <groupId>org.apache.maven.plugins</groupId>\r
+        <artifactId>maven-checkstyle-plugin</artifactId>\r
+        <version>${checkstyle.version}</version>\r
+        <dependencies>\r
+          <dependency>\r
+            <groupId>org.opendaylight.controller</groupId>\r
+            <artifactId>checkstyle</artifactId>\r
+            <version>0.0.2-SNAPSHOT</version>\r
+          </dependency>\r
+        </dependencies>\r
+        <configuration>\r
+          <failsOnError>true</failsOnError>\r
+          <configLocation>controller/space_and_tabs_checks.xml</configLocation>\r
+        </configuration>\r
+      </plugin>\r
+    </plugins>\r
+  </build>\r
+  <dependencies>\r
+    <dependency>\r
+      <groupId>junit</groupId>\r
+      <artifactId>junit</artifactId>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <artifactId>httpclient</artifactId>\r
+      <version>4.3</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.apache.httpcomponents</groupId>\r
+      <artifactId>httpcore</artifactId>\r
+    <version>4.3</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>commons-logging</groupId>\r
+      <artifactId>commons-logging</artifactId>\r
+      <version>1.1.3</version>\r
+    </dependency>\r
+  </dependencies>\r
+</project>\r
diff --git a/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPClient.java b/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPClient.java
new file mode 100644 (file)
index 0000000..ca6d683
--- /dev/null
@@ -0,0 +1,108 @@
+package org.opendaylight.controller.commons.httpclient;\r
+\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import org.apache.http.Header;\r
+import org.apache.http.HeaderIterator;\r
+import org.apache.http.HttpEntity;\r
+import org.apache.http.client.ClientProtocolException;\r
+import org.apache.http.client.config.RequestConfig;\r
+import org.apache.http.client.methods.CloseableHttpResponse;\r
+import org.apache.http.client.methods.HttpDelete;\r
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;\r
+import org.apache.http.client.methods.HttpGet;\r
+import org.apache.http.client.methods.HttpPost;\r
+import org.apache.http.client.methods.HttpPut;\r
+import org.apache.http.client.methods.HttpRequestBase;\r
+import org.apache.http.entity.StringEntity;\r
+import org.apache.http.impl.client.CloseableHttpClient;\r
+import org.apache.http.impl.client.HttpClients;\r
+import org.apache.http.util.EntityUtils;\r
+\r
+public class HTTPClient {\r
+    static public HTTPResponse sendRequest(HTTPRequest request) throws Exception {\r
+\r
+        CloseableHttpClient httpclient = HttpClients.createDefault();\r
+        if (httpclient == null)\r
+            throw new ClientProtocolException("Couldn't create an HTTP client");\r
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(request.getTimeout())\r
+            .setConnectTimeout(request.getTimeout()).build();\r
+        HttpRequestBase httprequest;\r
+        String method = request.getMethod();\r
+\r
+        if (method.equalsIgnoreCase("GET")) {\r
+            httprequest = new HttpGet(request.getUri());\r
+        } else if (method.equalsIgnoreCase("POST")) {\r
+            httprequest = new HttpPost(request.getUri());\r
+            if (request.getEntity() != null) {\r
+                StringEntity sentEntity = new StringEntity(request.getEntity());\r
+                sentEntity.setContentType(request.getContentType());\r
+                ((HttpEntityEnclosingRequestBase) httprequest).setEntity(sentEntity);\r
+            }\r
+        } else if (method.equalsIgnoreCase("PUT")) {\r
+            httprequest = new HttpPut(request.getUri());\r
+            if (request.getEntity() != null) {\r
+                StringEntity sentEntity = new StringEntity(request.getEntity());\r
+                sentEntity.setContentType(request.getContentType());\r
+                ((HttpEntityEnclosingRequestBase) httprequest).setEntity(sentEntity);\r
+            }\r
+        } else if (method.equalsIgnoreCase("DELETE")) {\r
+            httprequest = new HttpDelete(request.getUri());\r
+        } else {\r
+            httpclient.close();\r
+            throw new IllegalArgumentException("This profile class only supports GET, POST, PUT, and DELETE methods");\r
+        }\r
+        httprequest.setConfig(requestConfig);\r
+\r
+        // add request headers\r
+        Iterator<String> headerIterator = request.getHeaders().keySet().iterator();\r
+        while (headerIterator.hasNext()) {\r
+            String header = headerIterator.next();\r
+            Iterator<String> valueIterator = request.getHeaders().get(header).iterator();\r
+            while (valueIterator.hasNext()) {\r
+                httprequest.addHeader(header, valueIterator.next());\r
+            }\r
+        }\r
+\r
+        CloseableHttpResponse response = httpclient.execute(httprequest);\r
+        try {\r
+            HttpEntity receivedEntity = response.getEntity();\r
+            int httpResponseCode = response.getStatusLine().getStatusCode();\r
+            HTTPResponse ans = new HTTPResponse();\r
+            HashMap<String, List<String>> headerMap = new HashMap<String, List<String>>();\r
+\r
+            // copy response headers\r
+            HeaderIterator it = response.headerIterator();\r
+            while (it.hasNext()) {\r
+                Header h = it.nextHeader();\r
+                String name = h.getName();\r
+                String value = h.getValue();\r
+                if (headerMap.containsKey(name))\r
+                    headerMap.get(name).add(value);\r
+                else {\r
+                    List<String> list = new ArrayList<String>();\r
+                    list.add(value);\r
+                    headerMap.put(name, list);\r
+                }\r
+            }\r
+            ans.setHeaders(headerMap);\r
+\r
+            if (httpResponseCode > 299) {\r
+                ans.setStatus(httpResponseCode);\r
+                ans.setEntity(response.getStatusLine().getReasonPhrase());\r
+                return ans;\r
+            }\r
+            ans.setStatus(response.getStatusLine().getStatusCode());\r
+            if (receivedEntity != null)\r
+                ans.setEntity(EntityUtils.toString(receivedEntity));\r
+            else\r
+                ans.setEntity(null);\r
+            return ans;\r
+        } finally {\r
+            response.close();\r
+        }\r
+    }\r
+}\r
diff --git a/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPRequest.java b/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPRequest.java
new file mode 100644 (file)
index 0000000..7e39a78
--- /dev/null
@@ -0,0 +1,86 @@
+/*\r
+ * Copyright IBM Corporation, 2013.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.commons.httpclient;\r
+\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+\r
+public class HTTPRequest {\r
+    // the HTTP method to use: currently GET, POST, PUT, and DELETE are supported\r
+    String method;\r
+\r
+    // the full URI to send to (including protocol)\r
+    String uri;\r
+\r
+    // the entity body to send\r
+    String entity;\r
+\r
+    // additional headers (separate from content-type) to include in the request\r
+    Map<String, List<String>> headers;\r
+\r
+    // timeout in milliseconds.  Defaults to 3 seconds\r
+    int timeout;\r
+\r
+    // content type to set.  Defaults to application/json\r
+    String contentType;\r
+\r
+    public HTTPRequest() {\r
+        timeout = 3000;\r
+        contentType = "application/json";\r
+    }\r
+\r
+    public String getMethod() {\r
+        return method;\r
+    }\r
+\r
+    public void setMethod(String method) {\r
+        this.method = method;\r
+    }\r
+\r
+    public String getUri() {\r
+        return uri;\r
+    }\r
+\r
+    public void setUri(String uri) {\r
+        this.uri = uri;\r
+    }\r
+\r
+    public String getEntity() {\r
+        return entity;\r
+    }\r
+\r
+    public void setEntity(String entity) {\r
+        this.entity = entity;\r
+    }\r
+\r
+    public Map<String, List<String>> getHeaders() {\r
+        return headers;\r
+    }\r
+\r
+    public void setHeaders(Map<String, List<String>> headers) {\r
+        this.headers = headers;\r
+    }\r
+\r
+    public int getTimeout() {\r
+        return timeout;\r
+    }\r
+\r
+    public void setTimeout(int timeout) {\r
+        this.timeout = timeout;\r
+    }\r
+\r
+    public String getContentType() {\r
+        return contentType;\r
+    }\r
+\r
+    public void setContentType(String contentType) {\r
+        this.contentType = contentType;\r
+    }\r
+}\r
diff --git a/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPResponse.java b/opendaylight/commons/httpclient/src/main/java/org/opendaylight/controller/commons/httpclient/HTTPResponse.java
new file mode 100644 (file)
index 0000000..096fce2
--- /dev/null
@@ -0,0 +1,45 @@
+/*\r
+ * Copyright IBM Corporation, 2013.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.commons.httpclient;\r
+\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+public class HTTPResponse {\r
+    Integer status;        // response status\r
+    String  entity;        // response entity\r
+    Map<String, List<String>> headers;  // http header values\r
+\r
+    public HTTPResponse() {\r
+    }\r
+\r
+    public Integer getStatus() {\r
+        return status;\r
+    }\r
+\r
+    public void setStatus(Integer status) {\r
+        this.status = status;\r
+    }\r
+\r
+    public String getEntity() {\r
+        return entity;\r
+    }\r
+\r
+    public void setEntity(String entity) {\r
+        this.entity = entity;\r
+    }\r
+\r
+    public Map<String, List<String>> getHeaders() {\r
+        return headers;\r
+    }\r
+\r
+    public void setHeaders(Map<String, List<String>> map) {\r
+        this.headers = map;\r
+    }\r
+}\r
index eac5a70..cdba77a 100644 (file)
 
     <!-- Parents -->
     <module>../../commons/concepts</module>
+    <module>../../commons/httpclient</module>
     <module>../../commons/integrationtest</module>
     <module>../../commons/checkstyle</module>
     <module>../../commons/opendaylight</module>
index 87ccb68..aaa50e4 100644 (file)
     </pluginRepository>
   </pluginRepositories>
   <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>commons.httpclient</artifactId>
+      <version>0.1.1-SNAPSHOT</version>
+    </dependency>
     <dependency>
       <groupId>org.opendaylight.controller</groupId>
       <artifactId>connectionmanager</artifactId>
index e457cbf..3f3aec0 100644 (file)
@@ -8,17 +8,12 @@ import static org.ops4j.pax.exam.CoreOptions.options;
 import static org.ops4j.pax.exam.CoreOptions.systemPackages;
 import static org.ops4j.pax.exam.CoreOptions.systemProperty;
 
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -32,6 +27,9 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.opendaylight.controller.commons.httpclient.HTTPClient;
+import org.opendaylight.controller.commons.httpclient.HTTPRequest;
+import org.opendaylight.controller.commons.httpclient.HTTPResponse;
 import org.opendaylight.controller.hosttracker.IfIptoHost;
 import org.opendaylight.controller.sal.core.Bandwidth;
 import org.opendaylight.controller.sal.core.ConstructionException;
@@ -149,53 +147,48 @@ public class NorthboundIT {
         }
 
         try {
-            URL url = new URL(restUrl);
             this.userManager.getAuthorizationList();
             this.userManager.authenticate("admin", "admin");
+            HTTPRequest request = new HTTPRequest();
+
+            request.setUri(restUrl);
+            request.setMethod(method);
+            request.setTimeout(5000);  // HostTracker doesn't respond within 3 seconds
+
+            Map<String, List<String>> headers = new HashMap<String, List<String>>();
             String authString = "admin:admin";
             byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
             String authStringEnc = new String(authEncBytes);
-
-            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-            connection.setRequestMethod(method);
-            connection.setRequestProperty("Authorization", "Basic " + authStringEnc);
-            connection.setRequestProperty("Content-Type", "application/json");
-            connection.setRequestProperty("Accept", "application/json");
-
+            List<String> header = new ArrayList<String>();
+            header.add("Basic "+authStringEnc);
+            headers.put("Authorization", header);
+            header = new ArrayList<String>();
+            header.add("application/json");
+            headers.put("Accept", header);
+            request.setHeaders(headers);
+            request.setContentType("application/json");
             if (body != null) {
-                connection.setDoOutput(true);
-                OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
-                wr.write(body);
-                wr.flush();
+                request.setEntity(body);
             }
-            connection.connect();
-            connection.getContentType();
+
+            HTTPResponse response = HTTPClient.sendRequest(request);
 
             // Response code for success should be 2xx
-            httpResponseCode = connection.getResponseCode();
+            httpResponseCode = response.getStatus();
             if (httpResponseCode > 299) {
                 return httpResponseCode.toString();
             }
 
             if (debugMsg) {
-                System.out.println("HTTP response code: " + connection.getResponseCode());
-                System.out.println("HTTP response message: " + connection.getResponseMessage());
+                System.out.println("HTTP response code: " + response.getStatus());
+                System.out.println("HTTP response message: " + response.getEntity());
             }
 
-            InputStream is = connection.getInputStream();
-            BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
-            StringBuilder sb = new StringBuilder();
-            int cp;
-            while ((cp = rd.read()) != -1) {
-                sb.append((char) cp);
-            }
-            is.close();
-            connection.disconnect();
+            return response.getEntity();
+        } catch (Exception e) {
             if (debugMsg) {
-                System.out.println("Response : "+sb.toString());
+                e.printStackTrace();
             }
-            return sb.toString();
-        } catch (Exception e) {
             return null;
         }
     }
@@ -1381,6 +1374,7 @@ public class NorthboundIT {
                 mavenBundle("org.opendaylight.controller", "forwarding.staticrouting").versionAsInProject(),
                 mavenBundle("org.opendaylight.controller", "bundlescanner").versionAsInProject(),
                 mavenBundle("org.opendaylight.controller", "bundlescanner.implementation").versionAsInProject(),
+                mavenBundle("org.opendaylight.controller", "commons.httpclient").versionAsInProject(),
 
                 // Northbound bundles
                 mavenBundle("org.opendaylight.controller", "commons.northbound").versionAsInProject(),