diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java index 351ff7ce524..3e8e0285bfe 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java @@ -41,7 +41,9 @@ import javax.net.ssl.X509TrustManager; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; @@ -71,9 +73,42 @@ public class NiciraNvpApi { private String _adminpass; private HttpClient _client; + + /* This factory method is protected so we can extend this + * in the unittests. + */ + protected HttpClient createHttpClient() { + return new HttpClient(s_httpClientManager); + } + + protected HttpMethod createMethod(String type, String uri) throws NiciraNvpApiException { + String url; + try { + url = new URL(_protocol, _host, "/ws.v1/login").toString(); + } catch (MalformedURLException e) { + s_logger.error("Unable to build Nicira API URL", e); + throw new NiciraNvpApiException("Unable to build Nicira API URL", e); + } + + if ("post".equalsIgnoreCase(type)) { + return new PostMethod(url); + } + else if ("get".equalsIgnoreCase(type)) { + return new GetMethod(url); + } + else if ("delete".equalsIgnoreCase(type)) { + return new DeleteMethod(url); + } + else if ("put".equalsIgnoreCase(type)) { + return new PutMethod(url); + } + else { + throw new NiciraNvpApiException("Requesting unknown method type"); + } + } public NiciraNvpApi() { - _client = new HttpClient(s_httpClientManager); + _client = createHttpClient(); _client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); try { @@ -100,9 +135,15 @@ public class NiciraNvpApi { * The method returns false if the login failed or the connection could not be made. * */ - private void login() throws NiciraNvpApiException { + protected void login() throws NiciraNvpApiException { String url; + if (_host == null || _host.isEmpty() || + _adminuser == null || _adminuser.isEmpty() || + _adminpass == null || _adminpass.isEmpty()) { + throw new NiciraNvpApiException("Hostname/credentials are null or empty"); + } + try { url = new URL(_protocol, _host, "/ws.v1/login").toString(); } catch (MalformedURLException e) { @@ -294,18 +335,16 @@ public class NiciraNvpApi { return executeRetrieveObject(new TypeToken>(){}.getType(), uri, params); } - private void executeUpdateObject(T newObject, String uri, Map parameters) throws NiciraNvpApiException { - String url; - try { - url = new URL(_protocol, _host, uri).toString(); - } catch (MalformedURLException e) { - s_logger.error("Unable to build Nicira API URL", e); - throw new NiciraNvpApiException("Connection to NVP Failed"); + protected void executeUpdateObject(T newObject, String uri, Map parameters) throws NiciraNvpApiException { + if (_host == null || _host.isEmpty() || + _adminuser == null || _adminuser.isEmpty() || + _adminpass == null || _adminpass.isEmpty()) { + throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } Gson gson = new Gson(); - PutMethod pm = new PutMethod(url); + PutMethod pm = (PutMethod) createMethod("put", uri); pm.setRequestHeader("Content-Type", "application/json"); try { pm.setRequestEntity(new StringRequestEntity( @@ -325,18 +364,16 @@ public class NiciraNvpApi { pm.releaseConnection(); } - private T executeCreateObject(T newObject, Type returnObjectType, String uri, Map parameters) throws NiciraNvpApiException { - String url; - try { - url = new URL(_protocol, _host, uri).toString(); - } catch (MalformedURLException e) { - s_logger.error("Unable to build Nicira API URL", e); - throw new NiciraNvpApiException("Unable to build Nicira API URL", e); + protected T executeCreateObject(T newObject, Type returnObjectType, String uri, Map parameters) throws NiciraNvpApiException { + if (_host == null || _host.isEmpty() || + _adminuser == null || _adminuser.isEmpty() || + _adminpass == null || _adminpass.isEmpty()) { + throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } Gson gson = new Gson(); - PostMethod pm = new PostMethod(url); + PostMethod pm = (PostMethod) createMethod("post", uri); pm.setRequestHeader("Content-Type", "application/json"); try { pm.setRequestEntity(new StringRequestEntity( @@ -366,16 +403,14 @@ public class NiciraNvpApi { return result; } - private void executeDeleteObject(String uri) throws NiciraNvpApiException { - String url; - try { - url = new URL(_protocol, _host, uri).toString(); - } catch (MalformedURLException e) { - s_logger.error("Unable to build Nicira API URL", e); - throw new NiciraNvpApiException("Unable to build Nicira API URL", e); + protected void executeDeleteObject(String uri) throws NiciraNvpApiException { + if (_host == null || _host.isEmpty() || + _adminuser == null || _adminuser.isEmpty() || + _adminpass == null || _adminpass.isEmpty()) { + throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } - - DeleteMethod dm = new DeleteMethod(url); + + DeleteMethod dm = (DeleteMethod) createMethod("delete", uri); dm.setRequestHeader("Content-Type", "application/json"); executeMethod(dm); @@ -389,16 +424,14 @@ public class NiciraNvpApi { dm.releaseConnection(); } - private T executeRetrieveObject(Type returnObjectType, String uri, Map parameters) throws NiciraNvpApiException { - String url; - try { - url = new URL(_protocol, _host, uri).toString(); - } catch (MalformedURLException e) { - s_logger.error("Unable to build Nicira API URL", e); - throw new NiciraNvpApiException("Unable to build Nicira API URL", e); + protected T executeRetrieveObject(Type returnObjectType, String uri, Map parameters) throws NiciraNvpApiException { + if (_host == null || _host.isEmpty() || + _adminuser == null || _adminuser.isEmpty() || + _adminpass == null || _adminpass.isEmpty()) { + throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } - GetMethod gm = new GetMethod(url); + GetMethod gm = (GetMethod) createMethod("get", uri); gm.setRequestHeader("Content-Type", "application/json"); if (parameters != null && !parameters.isEmpty()) { List nameValuePairs = new ArrayList(parameters.size()); @@ -430,7 +463,7 @@ public class NiciraNvpApi { return returnValue; } - private void executeMethod(HttpMethodBase method) throws NiciraNvpApiException { + protected void executeMethod(HttpMethodBase method) throws NiciraNvpApiException { try { _client.executeMethod(method); if (method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { @@ -441,9 +474,11 @@ public class NiciraNvpApi { } } catch (HttpException e) { s_logger.error("HttpException caught while trying to connect to the Nicira NVP Controller", e); + method.releaseConnection(); throw new NiciraNvpApiException("API call to Nicira NVP Controller Failed", e); } catch (IOException e) { s_logger.error("IOException caught while trying to connect to the Nicira NVP Controller", e); + method.releaseConnection(); throw new NiciraNvpApiException("API call to Nicira NVP Controller Failed", e); } } diff --git a/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NiciraNvpApiTest.java b/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NiciraNvpApiTest.java new file mode 100644 index 00000000000..51d961864a0 --- /dev/null +++ b/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NiciraNvpApiTest.java @@ -0,0 +1,302 @@ +// 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 com.cloud.network.nicira; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.util.Collections; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; + +public class NiciraNvpApiTest { + NiciraNvpApi _api; + HttpClient _client = mock(HttpClient.class); + HttpMethod _method; + + @Before + public void setUp() { + HttpClientParams hmp = mock(HttpClientParams.class); + when (_client.getParams()).thenReturn(hmp); + _api = new NiciraNvpApi() { + @Override + protected HttpClient createHttpClient() { + return _client; + } + + @Override + protected HttpMethod createMethod(String type, String uri) { + return _method; + } + }; + _api.setAdminCredentials("admin", "adminpass"); + _api.setControllerAddress("localhost"); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteLoginWithoutHostname() throws NiciraNvpApiException { + _api.setControllerAddress(null); + _api.login(); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteLoginWithoutCredentials() throws NiciraNvpApiException { + _api.setAdminCredentials(null, null); + _api.login(); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteUpdateObjectWithoutHostname() throws NiciraNvpApiException { + _api.setControllerAddress(null); + _api.executeUpdateObject(new String(), "/", Collections. emptyMap()); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteUpdateObjectWithoutCredentials() throws NiciraNvpApiException { + _api.setAdminCredentials(null, null); + _api.executeUpdateObject(new String(), "/", Collections. emptyMap()); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteCreateObjectWithoutHostname() throws NiciraNvpApiException { + _api.setControllerAddress(null); + _api.executeCreateObject(new String(), String.class, "/", Collections. emptyMap()); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteCreateObjectWithoutCredentials() throws NiciraNvpApiException { + _api.setAdminCredentials(null, null); + _api.executeCreateObject(new String(), String.class, "/", Collections. emptyMap()); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteDeleteObjectWithoutHostname() throws NiciraNvpApiException { + _api.setControllerAddress(null); + _api.executeDeleteObject("/"); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteDeleteObjectWithoutCredentials() throws NiciraNvpApiException { + _api.setAdminCredentials(null, null); + _api.executeDeleteObject("/"); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteRetrieveObjectWithoutHostname() throws NiciraNvpApiException { + _api.setControllerAddress(null); + _api.executeRetrieveObject(String.class, "/", Collections. emptyMap()); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteRetrieveObjectWithoutCredentials() throws NiciraNvpApiException { + _api.setAdminCredentials(null, null); + _api.executeDeleteObject("/"); + } + + @Test + public void executeMethodTest() throws NiciraNvpApiException { + GetMethod gm = mock(GetMethod.class); + + when(gm.getStatusCode()).thenReturn(HttpStatus.SC_OK); + _api.executeMethod(gm); + verify(gm, times(1)).getStatusCode(); + } + + /* Bit of a roundabout way to ensure that login is called after an un authorized result + * It not possible to properly mock login() + */ + @Test (expected=NiciraNvpApiException.class) + public void executeMethodTestWithLogin() throws NiciraNvpApiException, HttpException, IOException { + GetMethod gm = mock(GetMethod.class); + when(_client.executeMethod((HttpMethod)any())).thenThrow(new HttpException()); + when(gm.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED).thenReturn(HttpStatus.SC_UNAUTHORIZED); + _api.executeMethod(gm); + verify(gm, times(1)).getStatusCode(); + } + + @Test + public void testExecuteCreateObject() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + _method = mock(PostMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_CREATED); + when(_method.getResponseBodyAsString()).thenReturn("{ \"uuid\" : \"aaaa\" }"); + ls = _api.executeCreateObject(ls, LogicalSwitch.class, "/", Collections. emptyMap()); + assertTrue("aaaa".equals(ls.getUuid())); + verify(_method, times(1)).releaseConnection(); + + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteCreateObjectFailure() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + _method = mock(PostMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); + Header header = mock(Header.class); + when(header.getValue()).thenReturn("text/html"); + when(_method.getResponseHeader("Content-Type")).thenReturn(header); + when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later."); + try { + ls = _api.executeCreateObject(ls, LogicalSwitch.class, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteCreateObjectException() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + when(_client.executeMethod((HttpMethod) any())).thenThrow(new HttpException()); + _method = mock(PostMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); + Header header = mock(Header.class); + when(header.getValue()).thenReturn("text/html"); + when(_method.getResponseHeader("Content-Type")).thenReturn(header); + when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later."); + try { + ls = _api.executeCreateObject(ls, LogicalSwitch.class, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test + public void testExecuteUpdateObject() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + _method = mock(PutMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK); + _api.executeUpdateObject(ls, "/", Collections. emptyMap()); + verify(_method, times(1)).releaseConnection(); + verify(_client, times(1)).executeMethod(_method); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteUpdateObjectFailure() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + _method = mock(PutMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); + Header header = mock(Header.class); + when(header.getValue()).thenReturn("text/html"); + when(_method.getResponseHeader("Content-Type")).thenReturn(header); + when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later."); + try { + _api.executeUpdateObject(ls, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteUpdateObjectException() throws NiciraNvpApiException, IOException { + LogicalSwitch ls = new LogicalSwitch(); + _method = mock(PutMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(_client.executeMethod((HttpMethod) any())).thenThrow(new IOException()); + try { + _api.executeUpdateObject(ls, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test + public void testExecuteDeleteObject() throws NiciraNvpApiException, IOException { + _method = mock(DeleteMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT); + _api.executeDeleteObject("/"); + verify(_method, times(1)).releaseConnection(); + verify(_client, times(1)).executeMethod(_method); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteDeleteObjectFailure() throws NiciraNvpApiException, IOException { + _method = mock(DeleteMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); + Header header = mock(Header.class); + when(header.getValue()).thenReturn("text/html"); + when(_method.getResponseHeader("Content-Type")).thenReturn(header); + when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later."); + try { + _api.executeDeleteObject("/"); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteDeleteObjectException() throws NiciraNvpApiException, IOException { + _method = mock(DeleteMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT); + when(_client.executeMethod((HttpMethod) any())).thenThrow(new HttpException()); + try { + _api.executeDeleteObject("/"); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test + public void testExecuteRetrieveObject() throws NiciraNvpApiException, IOException { + _method = mock(GetMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(_method.getResponseBodyAsString()).thenReturn("{ \"uuid\" : \"aaaa\" }"); + _api.executeRetrieveObject(LogicalSwitch.class, "/", Collections. emptyMap()); + verify(_method, times(1)).releaseConnection(); + verify(_client, times(1)).executeMethod(_method); + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteRetrieveObjectFailure() throws NiciraNvpApiException, IOException { + _method = mock(GetMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); + when(_method.getResponseBodyAsString()).thenReturn("{ \"uuid\" : \"aaaa\" }"); + Header header = mock(Header.class); + when(header.getValue()).thenReturn("text/html"); + when(_method.getResponseHeader("Content-Type")).thenReturn(header); + when(_method.getResponseBodyAsString()).thenReturn("Off to timbuktu, won't be back later."); + try { + _api.executeRetrieveObject(LogicalSwitch.class, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + + @Test (expected=NiciraNvpApiException.class) + public void testExecuteRetrieveObjectException() throws NiciraNvpApiException, IOException { + _method = mock(GetMethod.class); + when(_method.getStatusCode()).thenReturn(HttpStatus.SC_OK); + when(_method.getResponseBodyAsString()).thenReturn("{ \"uuid\" : \"aaaa\" }"); + when(_client.executeMethod((HttpMethod) any())).thenThrow(new HttpException()); + try { + _api.executeRetrieveObject(LogicalSwitch.class, "/", Collections. emptyMap()); + } finally { + verify(_method, times(1)).releaseConnection(); + } + } + +}