diff options
author | Michal Kabaj <michal.kabaj@nokia.com> | 2018-02-14 12:36:17 +0100 |
---|---|---|
committer | Michal Kabaj <michal.kabaj@nokia.com> | 2018-02-19 10:55:30 +0100 |
commit | c5281dd38d7f1989bf8750c6072192cdbc46031d (patch) | |
tree | a4882409621c6506b5d1b39789a803fcb9d88035 /appc-adapters/appc-chef-adapter | |
parent | 39b6b136b115e4ee15a599546b23cd9a68f7b673 (diff) |
ChefApiClient JUnits
Add(fix) JUnits for ChefApiClient + refactor.
Junits work on mocked HttpClient instead of attempting to make real calls.
All ChefApiClient methods are now tested.
Improve readability and refactored the ChefApiClient flow (simplified).
Heavily improved SRP in both ChefApiClient and Api request creation.
Change-Id: I997490bf269e7d78b741baf7f8d5d7bf64f6b99a
Issue-ID: APPC-437
Signed-off-by: Michal Kabaj <michal.kabaj@nokia.com>
Diffstat (limited to 'appc-adapters/appc-chef-adapter')
11 files changed, 747 insertions, 221 deletions
diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClient.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClient.java index d7080fc12..33d463bde 100644 --- a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClient.java +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClient.java @@ -9,79 +9,92 @@ * 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. - * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. * ============LICENSE_END========================================================= */ package org.onap.appc.adapter.chef.chefclient; -import org.apache.http.client.methods.*; -import org.onap.appc.adapter.chef.chefapi.*; + +import java.io.IOException; +import java.net.URISyntaxException; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.util.EntityUtils; +import org.onap.appc.adapter.chef.chefclient.ChefRequestBuilder.OngoingRequestBuilder; public class ChefApiClient { + + private final HttpClient httpClient; + private final ChefApiHeaderFactory chefApiHeaderFactory; private String endpoint; private String userId; private String pemPath; private String organizations; - /** - * - * @param userId user name correspond to the pem key - * @param pemPath path of the auth key - * @param endpoint chef api server address - */ - public ChefApiClient(String userId, String pemPath, String endpoint,String organizations){ + ChefApiClient(HttpClient httpClient, ChefApiHeaderFactory chefApiHeaderFactory, + String endpoint, String organizations, String userId, String pemPath) { + this.httpClient = httpClient; + this.chefApiHeaderFactory = chefApiHeaderFactory; + this.endpoint = endpoint; + this.organizations = organizations; this.userId = userId; this.pemPath = pemPath; - this.endpoint = endpoint; - this.organizations=organizations; } - /** - * - * @param path in the endpoint. e.g /clients - * @return - */ - public Get get(String path){ - Get get = new Get(new HttpGet(endpoint+path)); - get.setPemPath(pemPath); - get.setUserId(userId); - get.setOrganizations(organizations); - get.setChefPath(path); - return get; + public ChefResponse get(String path) { + OngoingRequestBuilder requestBuilder = ChefRequestBuilder.newRequestTo(endpoint) + .httpGet() + .withPath(path) + .withHeaders(chefApiHeaderFactory.create("GET", path, "", userId, organizations, pemPath)); + return execute(requestBuilder); + } + + public ChefResponse delete(String path) { + OngoingRequestBuilder requestBuilder = ChefRequestBuilder.newRequestTo(endpoint) + .httpDelete() + .withPath(path) + .withHeaders(chefApiHeaderFactory.create("DELETE", path, "", userId, organizations, pemPath)); + return execute(requestBuilder); + } + + public ChefResponse post(String path, String body) { + OngoingRequestBuilder requestBuilder = ChefRequestBuilder.newRequestTo(endpoint) + .httpPost(body) + .withPath(path) + .withHeaders(chefApiHeaderFactory.create("POST", path, body, userId, organizations, pemPath)); + return execute(requestBuilder); } - public Put put(String path){ - Put put = new Put(new HttpPut(endpoint+path)); - put.setPemPath(pemPath); - put.setUserId(userId); - put.setOrganizations(organizations); - put.setChefPath(path); - return put; - } - public Post post(String path){ - Post post = new Post(new HttpPost(endpoint+path)); - post.setPemPath(pemPath); - post.setUserId(userId); - post.setOrganizations(organizations); - post.setChefPath(path); - return post; + public ChefResponse put(String path, String body) { + OngoingRequestBuilder requestBuilder = ChefRequestBuilder.newRequestTo(endpoint) + .httpPut(body) + .withPath(path) + .withHeaders(chefApiHeaderFactory.create("PUT", path, body, userId, organizations, pemPath)); + return execute(requestBuilder); } - public Delete delete(String path){ - Delete del = new Delete(new HttpDelete(endpoint+path)); - del.setPemPath(pemPath); - del.setUserId(userId); - del.setOrganizations(organizations); - del.setChefPath(path); - return del; + private ChefResponse execute(OngoingRequestBuilder chefRequest) { + try { + HttpResponse response = httpClient.execute(chefRequest.build()); + int statusCode = response.getStatusLine().getStatusCode(); + HttpEntity httpEntity = response.getEntity(); + String responseBody = EntityUtils.toString(httpEntity); + return ChefResponse.create(statusCode, responseBody); + } catch (IOException ex) { + return ChefResponse.create(HttpStatus.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); + } catch (URISyntaxException ex) { + return ChefResponse.create(HttpStatus.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); + } } } diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientFactory.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientFactory.java new file mode 100644 index 000000000..b3c4272c1 --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientFactory.java @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClients; + +public final class ChefApiClientFactory { + + private HttpClient httpClient = HttpClients.createDefault(); + private ChefApiHeaderFactory chefApiHeaderFactory = new ChefApiHeaderFactory(); + + public ChefApiClient create(String endPoint, String organizations, String userId, String pemPath) { + return new ChefApiClient(httpClient, + chefApiHeaderFactory, + endPoint, + organizations, + userId, + pemPath); + } +} diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactory.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactory.java new file mode 100644 index 000000000..94abd06dd --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactory.java @@ -0,0 +1,71 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import java.util.Date; + +class ChefApiHeaderFactory { + + private FormattedTimestamp formattedTimestamp = new FormattedTimestamp(); + + static { + System.setProperty("javax.net.ssl.trustStore", "/opt/onap/appc/chef/chefServerSSL.jks"); + System.setProperty("javax.net.ssl.trustStorePassword", "adminadmin"); + } + + ImmutableMap<String, String> create(String methodName, String path, String body, String userId, + String organizations, String pemPath) { + + String hashedBody = Utils.sha1AndBase64(body); + String timeStamp = formattedTimestamp.format(new Date()); + + Builder<String, String> builder = ImmutableMap.builder(); + builder + .put("Content-type", "application/json") + .put("Accept", "application/json") + .put("X-Ops-Timestamp", timeStamp) + .put("X-Ops-UserId", userId) + .put("X-Chef-Version", "12.4.1") + .put("X-Ops-Content-Hash", hashedBody) + .put("X-Ops-Sign", "version=1.0") + .build(); + + String hashedPath = Utils.sha1AndBase64("/organizations/" + organizations + path); + + StringBuilder sb = new StringBuilder(); + sb.append("Method:").append(methodName).append("\n"); + sb.append("Hashed Path:").append(hashedPath).append("\n"); + sb.append("X-Ops-Content-Hash:").append(hashedBody).append("\n"); + sb.append("X-Ops-Timestamp:").append(timeStamp).append("\n"); + sb.append("X-Ops-UserId:").append(userId); + + String auth_String = Utils.signWithRSA(sb.toString(), pemPath); + String[] auth_headers = Utils.splitAs60(auth_String); + + for (int i = 0; i < auth_headers.length; i++) { + builder.put("X-Ops-Authorization-" + (i + 1), auth_headers[i]); + } + + return builder.build(); + } + +} diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefRequestBuilder.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefRequestBuilder.java new file mode 100644 index 000000000..acf0a489d --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefRequestBuilder.java @@ -0,0 +1,112 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map.Entry; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; + +public final class ChefRequestBuilder { + + private ChefRequestBuilder() { + } + + public static OngoingRequestBuilder newRequestTo(String endPoint) { + return new OngoingRequestBuilder(endPoint); + } + + public static class OngoingRequestBuilder { + + private HttpRequestBase httpRequestBase; + private String endPoint; + private String path; + private ImmutableMap<String, String> headers; + + private OngoingRequestBuilder(String endPoint) { + this.endPoint = endPoint; + } + + public OngoingRequestBuilder withPath(String path) { + this.path = path; + return this; + } + + public OngoingRequestBuilder httpGet() { + httpRequestBase = new HttpGet(); + return this; + } + + public OngoingRequestBuilder httpDelete() { + httpRequestBase = new HttpDelete(); + return this; + } + + public OngoingRequestBuilder httpPost(String body) { + HttpPost httpPost = new HttpPost(); + toEntity(body); + httpRequestBase = httpPost; + return this; + } + + public OngoingRequestBuilder httpPut(String body) { + HttpPut httpPut = new HttpPut(); + toEntity(body); + httpRequestBase = httpPut; + return this; + } + + private void toEntity(String body) { + StringEntity stringEntity = new StringEntity(body, "UTF-8"); + stringEntity.setContentType("application/json"); + } + + public OngoingRequestBuilder withHeaders(ImmutableMap<String, String> headers) { + this.headers = headers; + return this; + } + + public HttpRequestBase build() throws URISyntaxException { + setRequestUri(); + setRequestHeaders(); + + return httpRequestBase; + } + + private void setRequestUri() throws URISyntaxException { + URI fullPath = new URIBuilder(endPoint) + .setPath(path).build(); + httpRequestBase.setURI(fullPath); + } + + private void setRequestHeaders() { + for (Entry<String, String> entry : headers.entrySet()) { + httpRequestBase.addHeader(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefResponse.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefResponse.java new file mode 100644 index 000000000..e83711a44 --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/ChefResponse.java @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +public final class ChefResponse { + + private final String body; + private final int status; + + public static ChefResponse create(int status, String body) { + return new ChefResponse(status, body); + } + + private ChefResponse(int status, String body) { + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestamp.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestamp.java new file mode 100644 index 000000000..15ad87389 --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestamp.java @@ -0,0 +1,36 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +class FormattedTimestamp { + + String format(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String timeStamp = sdf.format(date); + timeStamp = timeStamp.replace(" ", "T"); + timeStamp = timeStamp + "Z"; + return timeStamp; + } +}
\ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/impl/ChefAdapterImpl.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/impl/ChefAdapterImpl.java index 24df42f35..65e64f839 100644 --- a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/impl/ChefAdapterImpl.java +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/main/java/org/onap/appc/adapter/chef/impl/ChefAdapterImpl.java @@ -40,6 +40,8 @@ import org.json.JSONObject; import org.onap.appc.adapter.chef.ChefAdapter; import org.onap.appc.adapter.chef.chefapi.ApiMethod; import org.onap.appc.adapter.chef.chefclient.ChefApiClient; +import org.onap.appc.adapter.chef.chefclient.ChefApiClientFactory; +import org.onap.appc.adapter.chef.chefclient.ChefResponse; import org.onap.ccsdk.sli.core.sli.SvcLogicContext; import org.onap.ccsdk.sli.core.sli.SvcLogicException; import com.att.eelf.configuration.EELFLogger; @@ -108,6 +110,7 @@ public class ChefAdapterImpl implements ChefAdapter { private static final String CHEF_SERVER_RESULT_MSG_STR = "chefServerResult.message"; private static final String CHEF_ACTION_STR = "chefAction"; private static final String NODE_LIST_STR = "NodeList"; + private ChefApiClientFactory chefApiClientFactory = new ChefApiClientFactory(); /** * This default constructor is used as a work around because the activator wasnt getting called @@ -157,18 +160,16 @@ public class ChefAdapterImpl implements ChefAdapter { String message; if (privateKeyCheck()) { // update the details of an environment on the Chef server. - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); - ApiMethod am = cac.put("/environments/" + envName).body(env); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefApiClient chefApiClient = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); + ChefResponse chefResponse = chefApiClient.put("/environments/" + envName, env); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); if (code == 404) { // need create a new environment - am = cac.post("/environments").body(env); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); - logger.info("requestbody" + am.getReqBody()); + chefResponse = chefApiClient.post("/environments", env); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); + logger.info("requestbody {}", chefResponse.getBody()); } } else { @@ -209,7 +210,7 @@ public class ChefAdapterImpl implements ChefAdapter { code = 200; String message = null; if (privateKeyCheck()) { - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); + ChefApiClient cac = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); for (String nodeName: nodes) { JSONObject nodeJ = new JSONObject(nodeS); @@ -217,10 +218,9 @@ public class ChefAdapterImpl implements ChefAdapter { nodeJ.put("name", nodeName); String nodeObject = nodeJ.toString(); logger.info(nodeObject); - ApiMethod am = cac.put("/nodes/" + nodeName).body(nodeObject); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = cac.put("/nodes/" + nodeName, nodeObject); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); if (code != 200) { break; } @@ -270,13 +270,12 @@ public class ChefAdapterImpl implements ChefAdapter { rc.isAlive(); SvcLogicContext svcLogic = rc.getSvcLogicContext(); - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); - ApiMethod am = cac.post(chefAction).body(pushRequest); - am.execute(); - code = am.getReturnCode(); + ChefApiClient cac = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); + ChefResponse chefResponse = cac.post(chefAction, pushRequest); + code = chefResponse.getStatusCode(); logger.info("pushRequest:" + pushRequest); - logger.info("requestbody:" + am.getReqBody()); - String message = am.getResponseBodyAsString(); + logger.info("requestbody: {}", chefResponse.getBody()); + String message = chefResponse.getBody(); if (code == 201) { int startIndex = message.indexOf("jobs") + 5; int endIndex = message.length() - 2; @@ -319,9 +318,9 @@ public class ChefAdapterImpl implements ChefAdapter { String chefAction = "/nodes/" + node; String message; if (privateKeyCheck()) { - ApiMethod am = getApiMethod(chefAction); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = getApiMethod(chefAction); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); } else { code = 500; message = CANNOT_FIND_PRIVATE_KEY_STR + clientPrivatekey; @@ -375,10 +374,9 @@ public class ChefAdapterImpl implements ChefAdapter { } } - private ApiMethod getApiMethod(String chefAction) { - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); - - return cac.get(chefAction).execute(); + private ChefResponse getApiMethod(String chefAction) { + ChefApiClient cac = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); + return cac.get(chefAction); } /** @@ -492,9 +490,9 @@ public class ChefAdapterImpl implements ChefAdapter { String message; if (privateKeyCheck()) { - ApiMethod am = getApiMethod(chefAction); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = getApiMethod(chefAction); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); } else { code = 500; message = CANNOT_FIND_PRIVATE_KEY_STR + clientPrivatekey; @@ -517,12 +515,11 @@ public class ChefAdapterImpl implements ChefAdapter { int code; String message; if (privateKeyCheck()) { - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); + ChefApiClient chefApiClient = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); - ApiMethod am = cac.put(chefAction).body(chefNodeStr); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = chefApiClient.put(chefAction, chefNodeStr); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); } else { code = 500; message = CANNOT_FIND_PRIVATE_KEY_STR + clientPrivatekey; @@ -548,14 +545,13 @@ public class ChefAdapterImpl implements ChefAdapter { String message; // should load pem from somewhere else if (privateKeyCheck()) { - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); + ChefApiClient chefApiClient = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); // need pass path into it // "/nodes/testnode" - ApiMethod am = cac.post(chefAction).body(chefNodeStr); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = chefApiClient.post(chefAction, chefNodeStr); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); } else { code = 500; message = CANNOT_FIND_PRIVATE_KEY_STR + clientPrivatekey; @@ -577,11 +573,10 @@ public class ChefAdapterImpl implements ChefAdapter { int code; String message; if (privateKeyCheck()) { - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); - ApiMethod am = cac.delete(chefAction); - am.execute(); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefApiClient chefApiClient = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); + ChefResponse chefResponse = chefApiClient.delete(chefAction); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); } else { code = 500; message = CANNOT_FIND_PRIVATE_KEY_STR + clientPrivatekey; @@ -637,9 +632,9 @@ public class ChefAdapterImpl implements ChefAdapter { String status = StringUtils.EMPTY; for (int i = 0; i < retryTimes; i++) { sleepFor(retryInterval); - ApiMethod am = getApiMethod(chefAction); - code = am.getReturnCode(); - message = am.getResponseBodyAsString(); + ChefResponse chefResponse = getApiMethod(chefAction); + code = chefResponse.getStatusCode(); + message = chefResponse.getBody(); JSONObject obj = new JSONObject(message); status = obj.getString("status"); if (!"running".equals(status)) { @@ -692,12 +687,11 @@ public class ChefAdapterImpl implements ChefAdapter { RequestContext rc = new RequestContext(ctx); rc.isAlive(); SvcLogicContext svcLogic = rc.getSvcLogicContext(); - ChefApiClient cac = new ChefApiClient(username, clientPrivatekey, chefserver, organizations); - ApiMethod am = cac.post(chefAction).body(pushRequest); + ChefApiClient chefApiClient = chefApiClientFactory.create(chefserver, organizations, username, clientPrivatekey); + ChefResponse chefResponse = chefApiClient.post(chefAction, pushRequest); - am.execute(); - code = am.getReturnCode(); - String message = am.getResponseBodyAsString(); + code = chefResponse.getStatusCode(); + String message = chefResponse.getBody(); if (code == 201) { int startIndex = message.indexOf("jobs") + 6; int endIndex = message.length() - 2; diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java new file mode 100644 index 000000000..58b55854a --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java @@ -0,0 +1,215 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; + +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.function.Supplier; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChefApiClientTest { + + private static final String END_POINT = "https://chefServer"; + private static final String ORGANIZATIONS_PATH = "onap"; + private static final String USER_ID = "testUser"; + private static final String REQUEST_PATH = "/test/path"; + private static final String BODY = "SOME BODY STRING"; + private static final ImmutableMap<String, String> HEADERS = ImmutableMap.<String, String>builder() + .put("Content-type", "application/json") + .put("Accept", "application/json") + .put("X-Ops-Timestamp", "1970-01-15T06:56:07Z") + .put("X-Ops-UserId", USER_ID) + .put("X-Chef-Version", "12.4.1") + .put("X-Ops-Content-Hash", BODY) + .put("X-Ops-Sign", "version=1.0").build(); + + @Mock + private HttpClient httpClient; + @Mock + private ChefApiHeaderFactory chefHttpHeaderFactory; + + @InjectMocks + private ChefApiClientFactory chefApiClientFactory; + private static final String PEM_FILEPATH = "path/to/pemFile"; + private ChefApiClient chefApiClient; + + @Before + public void setUp() { + chefApiClient = chefApiClientFactory.create( + END_POINT, + ORGANIZATIONS_PATH, + USER_ID, + PEM_FILEPATH); + } + + @Test + public void execute_HttpGet_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "GET"; + String body = ""; + Supplier<ChefResponse> chefClientApiCall = () -> chefApiClient.get(REQUEST_PATH); + + // WHEN //THEN + assertChefApiClientCall(methodName, body, chefClientApiCall); + } + + @Test + public void execute_HttpDelete_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "DELETE"; + String body = ""; + Supplier<ChefResponse> chefClientApiCall = () -> chefApiClient.delete(REQUEST_PATH); + + // WHEN //THEN + assertChefApiClientCall(methodName, body, chefClientApiCall); + } + + @Test + public void execute_HttpPost_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "POST"; + Supplier<ChefResponse> chefClientApiCall = () -> chefApiClient.post(REQUEST_PATH, BODY); + + // WHEN //THEN + assertChefApiClientCall(methodName, BODY, chefClientApiCall); + } + + @Test + public void execute_HttpPut_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "PUT"; + Supplier<ChefResponse> chefClientApiCall = () -> chefApiClient.put(REQUEST_PATH, BODY); + + // WHEN //THEN + assertChefApiClientCall(methodName, BODY, chefClientApiCall); + } + + private void assertChefApiClientCall(String methodName, String body, Supplier<ChefResponse> httpMethod) + throws IOException { + // GIVEN + given(chefHttpHeaderFactory.create(methodName, REQUEST_PATH, body, USER_ID, ORGANIZATIONS_PATH, PEM_FILEPATH)) + .willReturn(HEADERS); + + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(HttpStatus.SC_OK); + HttpResponse httpResponse = mock(HttpResponse.class); + given(httpResponse.getStatusLine()).willReturn(statusLine); + given(httpResponse.getEntity()).willReturn(new StringEntity("Successful Response String")); + given(httpClient.execute(argThat(new HttpRequestBaseMatcher(methodName)))) + .willReturn(httpResponse); + + // WHEN + ChefResponse chefResponse = httpMethod.get(); + + // THEN + assertEquals("Successful Response String", chefResponse.getBody()); + assertEquals(HttpStatus.SC_OK, chefResponse.getStatusCode()); + } + + @Test + public void execute_shouldHandleException_whenHttpClientExecutionFails() throws IOException { + + // GIVEN + given(chefHttpHeaderFactory.create("GET", REQUEST_PATH, "", USER_ID, ORGANIZATIONS_PATH, PEM_FILEPATH)) + .willReturn(HEADERS); + + String expectedErrorMsg = "HttpClient call failed"; + given(httpClient.execute(argThat(new HttpRequestBaseMatcher("GET")))) + .willThrow(new IOException(expectedErrorMsg)); + + // WHEN + ChefResponse chefResponse = chefApiClient.get(REQUEST_PATH); + + // THEN + assertEquals(expectedErrorMsg, chefResponse.getBody()); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, chefResponse.getStatusCode()); + } + + @Test + public void execute_shouldHandleException_whenEndpointURIisMalformed() { + // GIVEN + String expectedErrorMsg = "Malformed escape pair at index 1: /%#@/"; + + // WHEN + ChefApiClient chefApiClient = chefApiClientFactory.create( + "/%#@/", + ORGANIZATIONS_PATH, + USER_ID, + PEM_FILEPATH); + ChefResponse chefResponse = chefApiClient.get(REQUEST_PATH); + + // THEN + assertEquals(expectedErrorMsg, chefResponse.getBody()); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, chefResponse.getStatusCode()); + } + + private class HttpRequestBaseMatcher extends ArgumentMatcher<HttpRequestBase> { + + private final String methodName; + + public HttpRequestBaseMatcher(String methodName) { + this.methodName = methodName; + } + + @Override + public boolean matches(Object argument) { + HttpRequestBase httpRequestBase = (HttpRequestBase) argument; + + boolean headersMatch = checkIfHeadersMatch(httpRequestBase); + try { + return methodName.equals(httpRequestBase.getMethod()) + && new URI(END_POINT + REQUEST_PATH).equals(httpRequestBase.getURI()) + && headersMatch; + } catch (URISyntaxException e) { + e.printStackTrace(); + return false; + } + } + + private boolean checkIfHeadersMatch(HttpRequestBase httpRequestBase) { + Header[] generatedHeaders = httpRequestBase.getAllHeaders(); + return generatedHeaders.length > 0 + && generatedHeaders.length == HEADERS.size() + && HEADERS.entrySet().stream() + .allMatch(p -> httpRequestBase.getFirstHeader(p.getKey()).getValue().equals(p.getValue())); + } + } +}
\ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java new file mode 100644 index 000000000..cc309811d --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; + +import com.google.common.collect.ImmutableMap; +import java.util.Date; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChefApiHeaderFactoryTest { + + private static final String ORGANIZATIONS_PATH = "onap"; + private static final String USER_ID = "testUser"; + private static final String REQUEST_PATH = "/test/path"; + private static final String EXPECTED_TIMESTAMP = "1970-01-15T06:56:07Z"; + private static final String EMPTY_BODY = ""; + + @Mock + private FormattedTimestamp formattedTimestamp; + + @InjectMocks + private ChefApiHeaderFactory chefApiHeaderFactory; + + @Test + public void create_shouldCreateProperChefHeaders_withHashedAuthorizationString() { + // GIVEN + given(formattedTimestamp.format(any(Date.class))).willReturn(EXPECTED_TIMESTAMP); + String pemFilePath = getClass().getResource("/testclient.pem").getPath(); + + // WHEN + ImmutableMap<String, String> headers = chefApiHeaderFactory + .create("GET", REQUEST_PATH, "", USER_ID, ORGANIZATIONS_PATH, pemFilePath); + + // THEN + assertEquals(headers, createExpectedHeaders()); + } + + private ImmutableMap<String, String> createExpectedHeaders() { + String hashedBody = Utils.sha1AndBase64(EMPTY_BODY); + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + builder + .put("Content-type", "application/json") + .put("Accept", "application/json") + .put("X-Ops-Timestamp", EXPECTED_TIMESTAMP) + .put("X-Ops-UserId", USER_ID) + .put("X-Chef-Version", "12.4.1") + .put("X-Ops-Content-Hash", hashedBody) + .put("X-Ops-Sign", "version=1.0") + .put("X-Ops-Authorization-1", "i+HGCso703727yd2ZQWMZIIpGKgTzm41fA31LIExNxEf9mOUMcpesIHjH/Wr") + .put("X-Ops-Authorization-2", "QEvsX/Gy1ay9KsUtqhy9GA6PB8UfDeMNoVUisqR4HQW+S6IOfvqBjW+2afzE") + .put("X-Ops-Authorization-3", "RdRReB/TJIF3s6ZC8vNpbEdY9kHmwiDglhxmS8X2FS+ArSh/DK/i7MqBbjux") + .put("X-Ops-Authorization-4", "49iiOlRVG7aTr/FA115hlBYP9CYCIQWKIBUOK3JyV9fXNdVqc9R0r1XdjxUl") + .put("X-Ops-Authorization-5", "EDGw6tuE8YW8mH5wkgHCjKpXG3WjmWt2X6kUrdIu44qCBK2N3sZziSub2fJA") + .put("X-Ops-Authorization-6", "hPBuOhjiYDZuFUqC99lCryM0Hf5RMw1uTlkYsBEZmA=="); + + return builder.build(); + } +}
\ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java new file mode 100644 index 000000000..47d2f6c2c --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java @@ -0,0 +1,40 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static org.junit.Assert.assertEquals; + +import java.util.Date; +import org.junit.Test; + +public class FormattedTimestampTest { + + @Test + public void format_shouldFormatGivenDate_withCorrectTimezoneSet() { + // GIVEN + String expectedFormattedDate = "1970-01-15T06:56:07Z"; + + // WHEN + String formattedDateWithTimezone = new FormattedTimestamp().format(new Date(1234567890)); + + // THEN + assertEquals(expectedFormattedDate, formattedDateWithTimezone); + } +}
\ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java deleted file mode 100644 index 9e787b64d..000000000 --- a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java +++ /dev/null @@ -1,119 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * ONAP : APPC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. - * ================================================================================ - * Copyright (C) 2017 Amdocs - * ============================================================================= - * 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. - * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. - * ============LICENSE_END========================================================= - */ - -package org.onap.appc.adapter.chef.chefclient; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import java.io.InputStream; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.Properties; -import org.junit.Before; -import org.junit.Test; -import org.onap.appc.adapter.chef.chefapi.ApiMethod; -import org.onap.appc.adapter.chef.chefapi.Delete; -import org.onap.appc.adapter.chef.chefapi.Get; -import org.onap.appc.adapter.chef.chefapi.Post; -import org.onap.appc.adapter.chef.chefapi.Put; - -public class TestChefApiClient { - - private ChefApiClient client; - private Properties props; - private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - @Before - public void setup() throws IllegalArgumentException, IllegalAccessException { - props = new Properties(); - InputStream propStr = getClass().getResourceAsStream("/test.properties"); - if (propStr == null) { - fail("src/test/resources/test.properties missing"); - } - - try { - props.load(propStr); - propStr.close(); - } catch (Exception e) { - e.printStackTrace(); - fail("Could not initialize properties"); - } - client = new ChefApiClient(props.getProperty("org.onap.appc.adapter.chef.chefclient.userId"), - System.getProperty("user.dir") + props.getProperty("org.onap.appc.adapter.chef.chefclient.pemPath"), - props.getProperty("org.onap.appc.adapter.chef.chefclient.endPoint"), - props.getProperty("org.onap.appc.adapter.chef.chefclient.organizations")); - } - - @Test - public void testGet() { - Get get = client.get(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = get.execute(); - String[] response = method.test.split("\n"); - thenStringShouldMatch("GET", response); - } - - @Test - public void testPut() { - Put put = client.put(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = put.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("PUT", response); - } - - @Test - public void testPost() { - Post post = client.post(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = post.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("POST", response); - } - - @Test - public void testDelete() { - Delete delete = client.delete(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = delete.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("DELETE", response); - } - - private void thenStringShouldMatch(String method, String[] response) { - assertEquals("sb Method:" + method, response[0]); - assertEquals("Hashed Path:+JEk1y2gXwqZRweNjXYtx4ojxW8=", response[1]); - assertEquals("X-Ops-Content-Hash:2jmj7l5rSw0yVb/vlWAYkK/YBwk=", response[2]); - checkTimestamp(response[3], 30000); - assertEquals("X-Ops-UserId:test", response[4]); - } - - private void checkTimestamp(String timeStampHeader, long maxDeltaMs) { - assertTrue(timeStampHeader.startsWith("X-Ops-Timestamp:")); - LocalDateTime ld1 = LocalDateTime.parse(timeStampHeader.replace("X-Ops-Timestamp:", ""), dtf); - assertTrue(ChronoUnit.MILLIS.between(ld1, LocalDateTime.now(ZoneId.of("UTC"))) <= maxDeltaMs); - } -} |