2 * Copyright © 2019 iconectiv
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.openecomp.core.externaltesting.impl;
20 import com.fasterxml.jackson.databind.ObjectMapper;
21 import com.google.gson.GsonBuilder;
22 import com.google.gson.JsonObject;
23 import com.google.gson.JsonParseException;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
32 import java.util.Optional;
33 import java.util.UUID;
34 import java.util.stream.Collectors;
35 import java.util.stream.Stream;
36 import javax.annotation.PostConstruct;
37 import lombok.EqualsAndHashCode;
38 import org.apache.commons.lang3.ArrayUtils;
39 import org.apache.commons.lang3.StringUtils;
40 import org.apache.commons.lang3.tuple.Pair;
41 import org.onap.sdc.tosca.services.YamlUtil;
42 import org.openecomp.core.externaltesting.api.ClientConfiguration;
43 import org.openecomp.core.externaltesting.api.ExternalTestingManager;
44 import org.openecomp.core.externaltesting.api.RemoteTestingEndpointDefinition;
45 import org.openecomp.core.externaltesting.api.TestTreeNode;
46 import org.openecomp.core.externaltesting.api.VtpNameDescriptionPair;
47 import org.openecomp.core.externaltesting.api.VtpTestCase;
48 import org.openecomp.core.externaltesting.api.VtpTestExecutionOutput;
49 import org.openecomp.core.externaltesting.api.VtpTestExecutionRequest;
50 import org.openecomp.core.externaltesting.api.VtpTestExecutionResponse;
51 import org.openecomp.core.externaltesting.errors.ExternalTestingException;
52 import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManager;
53 import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManagerFactory;
54 import org.openecomp.sdc.vendorsoftwareproduct.VendorSoftwareProductManager;
55 import org.openecomp.sdc.vendorsoftwareproduct.VspManagerFactory;
56 import org.openecomp.sdc.versioning.VersioningManager;
57 import org.openecomp.sdc.versioning.VersioningManagerFactory;
58 import org.openecomp.sdc.versioning.dao.types.Version;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.core.ParameterizedTypeReference;
62 import org.springframework.core.io.ByteArrayResource;
63 import org.springframework.http.HttpEntity;
64 import org.springframework.http.HttpHeaders;
65 import org.springframework.http.HttpMethod;
66 import org.springframework.http.MediaType;
67 import org.springframework.http.ResponseEntity;
68 import org.springframework.http.client.SimpleClientHttpRequestFactory;
69 import org.springframework.util.LinkedMultiValueMap;
70 import org.springframework.util.MultiValueMap;
71 import org.springframework.web.client.HttpStatusCodeException;
72 import org.springframework.web.client.ResourceAccessException;
73 import org.springframework.web.client.RestTemplate;
74 import org.springframework.web.util.UriComponentsBuilder;
76 public class ExternalTestingManagerImpl implements ExternalTestingManager {
78 static final String VSP_ID = "vspId";
79 static final String VSP_VERSION = "vspVersion";
80 private static final String FILE_URL_PREFIX = "file://";
81 private static final String HTTP_STATUS = "httpStatus";
82 private static final String CODE = "code";
83 private static final String ERROR = "error";
84 private static final String MESSAGE = "message";
85 private static final String DETAIL = "detail";
86 private static final String PATH = "path";
87 private static final String VTP_SCENARIOS_URI = "%s/v1/vtp/scenarios";
88 private static final String VTP_TESTSUITE_URI = "%s/v1/vtp/scenarios/%s/testsuites";
89 private static final String VTP_TESTCASES_URI = "%s/v1/vtp/scenarios/%s/testcases";
90 private static final String VTP_TESTCASE_URI = "%s/v1/vtp/scenarios/%s/testsuites/%s/testcases/%s";
91 private static final String VTP_EXECUTIONS_URI = "%s/v1/vtp/executions";
92 private static final String VTP_EXECUTION_URI = "%s/v1/vtp/executions/%s";
93 private static final String VTP_EXECUTION_ID_URL = "%s/v1/vtp/executions?requestId=%s";
94 private static final String INVALIDATE_STATE_ERROR_CODE = "SDC-TEST-001";
95 private static final String NO_ACCESS_CONFIGURATION_DEFINED = "No access configuration defined";
96 private static final String NO_SUCH_ENDPOINT_ERROR_CODE = "SDC-TEST-002";
97 private static final String ENDPOINT_ERROR_CODE = "SDC-TEST-003";
98 private static final String TESTING_HTTP_ERROR_CODE = "SDC-TEST-004";
99 private static final String SDC_RESOLVER_ERR = "SDC-TEST-005";
100 private static final String VSP_CSAR = "vsp";
101 private static final String VSP_HEAT = "vsp-zip";
102 private Logger logger = LoggerFactory.getLogger(ExternalTestingManagerImpl.class);
103 private VersioningManager versioningManager;
104 private VendorSoftwareProductManager vendorSoftwareProductManager;
105 private OrchestrationTemplateCandidateManager candidateManager;
107 private TestingAccessConfig accessConfig;
108 private List<RemoteTestingEndpointDefinition> endpoints;
110 private RestTemplate restTemplate;
112 public ExternalTestingManagerImpl() {
113 restTemplate = new RestTemplate();
116 ExternalTestingManagerImpl(VersioningManager versioningManager,
117 VendorSoftwareProductManager vendorSoftwareProductManager,
118 OrchestrationTemplateCandidateManager candidateManager) {
120 this.versioningManager = versioningManager;
121 this.vendorSoftwareProductManager = vendorSoftwareProductManager;
122 this.candidateManager = candidateManager;
126 * Read the configuration from the yaml file for this bean. If we get an exception during load, don't force an error starting SDC but log a
127 * warning. Do no warm...
132 if (versioningManager == null) {
133 versioningManager = VersioningManagerFactory.getInstance().createInterface();
135 if (vendorSoftwareProductManager == null) {
136 vendorSoftwareProductManager = VspManagerFactory.getInstance().createInterface();
138 if (candidateManager == null) {
139 candidateManager = OrchestrationTemplateCandidateManagerFactory.getInstance().createInterface();
145 private Stream<RemoteTestingEndpointDefinition> mapEndpointString(String ep) {
146 RemoteTestingEndpointDefinition rv = new RemoteTestingEndpointDefinition();
147 String[] cfg = ep.split(",");
148 if (cfg.length < 4) {
149 logger.error("invalid endpoint definition {}", ep);
150 return Stream.empty();
154 rv.setEnabled("true".equals(cfg[2]));
156 if (cfg.length > 4) {
157 rv.setScenarioFilter(cfg[4]);
159 if (cfg.length > 5) {
160 rv.setApiKey(cfg[5]);
162 return Stream.of(rv);
167 * Load the configuration for this component. When the SDC onboarding backend runs, it gets a system property called config.location. We can use
168 * that to locate the config-externaltesting.yaml file.
170 private void loadConfig() {
171 String loc = System.getProperty("config.location");
172 File file = new File(loc, "externaltesting-configuration.yaml");
173 try (InputStream fileInput = new FileInputStream(file)) {
174 YamlUtil yamlUtil = new YamlUtil();
175 accessConfig = yamlUtil.yamlToObject(fileInput, TestingAccessConfig.class);
177 if (logger.isInfoEnabled()) {
178 String s = new ObjectMapper().writeValueAsString(accessConfig);
179 logger.info("loaded external testing config {}", s);
183 accessConfig.getEndpoints().stream().flatMap(this::mapEndpointString).collect(Collectors.toList());
185 if (logger.isInfoEnabled()) {
186 String s = new ObjectMapper().writeValueAsString(endpoints);
187 logger.info("processed external testing config {}", s);
189 } catch (IOException ex) {
190 logger.error("failed to read external testing config. Disabling the feature", ex);
191 accessConfig = new TestingAccessConfig();
192 accessConfig.setEndpoints(new ArrayList<>());
193 accessConfig.setClient(new ClientConfiguration());
194 accessConfig.getClient().setEnabled(false);
195 endpoints = new ArrayList<>();
200 * Return the configuration of this feature that we want to expose to the client. Treated as a JSON blob for flexibility.
203 public ClientConfiguration getConfig() {
204 ClientConfiguration cc = null;
205 if (accessConfig != null) {
206 cc = accessConfig.getClient();
209 cc = new ClientConfiguration();
210 cc.setEnabled(false);
216 * To allow for functional testing, we let a caller invoke a setConfig request to enable/disable the client. This new value is not persisted.
218 * @return new client configuration
221 public ClientConfiguration setConfig(ClientConfiguration cc) {
222 if (accessConfig == null) {
223 accessConfig = new TestingAccessConfig();
225 accessConfig.setClient(cc);
230 * To allow for functional testing, we let a caller invoke a setEndpoints request to configure where the BE makes request to.
232 * @return new endpoint definitions.
235 public List<RemoteTestingEndpointDefinition> setEndpoints(List<RemoteTestingEndpointDefinition> endpoints) {
236 this.endpoints = endpoints;
237 return this.getEndpoints();
242 public TestTreeNode getTestCasesAsTree() {
243 TestTreeNode root = new TestTreeNode("root", "root");
245 // quick out in case of non-configured SDC
246 if (endpoints == null) {
250 for (RemoteTestingEndpointDefinition ep : endpoints) {
251 if (ep.isEnabled()) {
252 buildTreeFromEndpoint(ep, root);
258 private void buildTreeFromEndpoint(RemoteTestingEndpointDefinition ep, TestTreeNode root) {
260 logger.debug("process endpoint {}", ep.getId());
261 getScenarios(ep.getId()).stream()
262 .filter(s -> ((ep.getScenarioFilter() == null) || ep.getScenarioFilterPattern().matcher(s.getName())
263 .matches())).forEach(s -> {
264 addScenarioToTree(root, s);
265 getTestSuites(ep.getId(), s.getName()).forEach(suite -> addSuiteToTree(root, s, suite));
266 getTestCases(ep.getId(), s.getName()).forEach(tc -> {
268 VtpTestCase details =
269 getTestCase(ep.getId(), s.getName(), tc.getTestSuiteName(), tc.getTestCaseName());
270 addTestCaseToTree(root, ep.getId(), s.getName(), tc.getTestSuiteName(), details);
271 } catch (@SuppressWarnings("squid:S1166") ExternalTestingException ex) {
272 // Not logging stack trace on purpose. VTP was throwing exceptions for certain test cases.
273 logger.warn("failed to load test case {}", tc.getTestCaseName());
277 } catch (ExternalTestingException ex) {
278 logger.error("unable to contact testing endpoint {}", ep.getId(), ex);
282 private Optional<TestTreeNode> findNamedChild(TestTreeNode root, String name) {
283 if (root.getChildren() == null) {
284 return Optional.empty();
286 return root.getChildren().stream().filter(n -> n.getName().equals(name)).findFirst();
290 * Find the place in the tree to add the test case.
292 * @param root root of the tree.
293 * @param endpointName name of the endpoint to assign to the test case.
294 * @param scenarioName scenario to add this case to
295 * @param testSuiteName suite in the scenario to add this case to
296 * @param tc test case to add.
298 private void addTestCaseToTree(TestTreeNode root, String endpointName, String scenarioName, String testSuiteName,
304 findNamedChild(root, scenarioName)
305 .ifPresent(scenarioNode -> findNamedChild(scenarioNode, testSuiteName).ifPresent(suiteNode -> {
306 massageTestCaseForUI(tc, endpointName, scenarioName);
307 if (suiteNode.getTests() == null) {
308 suiteNode.setTests(new ArrayList<>());
310 suiteNode.getTests().add(tc);
314 private void massageTestCaseForUI(VtpTestCase testcase, String endpoint, String scenario) {
315 testcase.setEndpoint(endpoint);
317 if (testcase.getScenario() == null) {
318 testcase.setScenario(scenario);
323 * Add the test suite to the tree at the appropriate place if it does not already exist in the tree.
325 * @param root root of the tree.
326 * @param scenario scenario under which this suite should be placed
327 * @param suite test suite to add.
329 private void addSuiteToTree(final TestTreeNode root, final VtpNameDescriptionPair scenario,
330 final VtpNameDescriptionPair suite) {
331 findNamedChild(root, scenario.getName()).ifPresent(parent -> {
332 if (parent.getChildren() == null) {
333 parent.setChildren(new ArrayList<>());
335 if (parent.getChildren().stream().noneMatch(n -> StringUtils.equals(n.getName(), suite.getName()))) {
336 parent.getChildren().add(new TestTreeNode(suite.getName(), suite.getDescription()));
342 * Add the scenario to the tree if it does not already exist.
344 * @param root root of the tree.
345 * @param s scenario to add.
347 private void addScenarioToTree(TestTreeNode root, VtpNameDescriptionPair s) {
348 logger.debug("addScenario {} to {} with {}", s.getName(), root.getName(), root.getChildren());
349 if (root.getChildren() == null) {
350 root.setChildren(new ArrayList<>());
352 if (root.getChildren().stream().noneMatch(n -> StringUtils.equals(n.getName(), s.getName()))) {
353 logger.debug("createScenario {} in {}", s.getName(), root.getName());
354 root.getChildren().add(new TestTreeNode(s.getName(), s.getDescription()));
359 * Get the list of endpoints defined to the testing manager.
361 * @return list of endpoints or empty list if the manager is not configured.
363 public List<RemoteTestingEndpointDefinition> getEndpoints() {
364 if (endpoints != null) {
365 return endpoints.stream().filter(RemoteTestingEndpointDefinition::isEnabled).collect(Collectors.toList());
367 return new ArrayList<>();
372 * Code shared by getScenarios and getTestSuites.
374 private List<VtpNameDescriptionPair> returnNameDescriptionPairFromUrl(String url) {
375 ParameterizedTypeReference<List<VtpNameDescriptionPair>> t =
376 new ParameterizedTypeReference<List<VtpNameDescriptionPair>>() {
378 List<VtpNameDescriptionPair> rv = proxyGetRequestToExternalTestingSite(url, t);
380 rv = new ArrayList<>();
386 * Get the list of scenarios at a given endpoint.
388 public List<VtpNameDescriptionPair> getScenarios(final String endpoint) {
389 String url = buildEndpointUrl(VTP_SCENARIOS_URI, endpoint, ArrayUtils.EMPTY_STRING_ARRAY);
390 return returnNameDescriptionPairFromUrl(url);
394 * Get the list of test suites for an endpoint for the given scenario.
396 public List<VtpNameDescriptionPair> getTestSuites(final String endpoint, final String scenario) {
397 String url = buildEndpointUrl(VTP_TESTSUITE_URI, endpoint, new String[]{scenario});
398 return returnNameDescriptionPairFromUrl(url);
402 * Get the list of test cases under a scenario. This is the VTP API. It would seem better to get the list of cases under a test suite but that
406 public List<VtpTestCase> getTestCases(String endpoint, String scenario) {
407 String url = buildEndpointUrl(VTP_TESTCASES_URI, endpoint, new String[]{scenario});
408 ParameterizedTypeReference<List<VtpTestCase>> t = new ParameterizedTypeReference<List<VtpTestCase>>() {
410 List<VtpTestCase> rv = proxyGetRequestToExternalTestingSite(url, t);
412 rv = new ArrayList<>();
418 * Get a test case definition.
421 public VtpTestCase getTestCase(String endpoint, String scenario, String testSuite, String testCaseName) {
422 String url = buildEndpointUrl(VTP_TESTCASE_URI, endpoint, new String[]{scenario, testSuite, testCaseName});
423 ParameterizedTypeReference<VtpTestCase> t = new ParameterizedTypeReference<VtpTestCase>() {
425 return proxyGetRequestToExternalTestingSite(url, t);
429 * Return the results of a previous test execution.
431 * @param endpoint endpoint to query
432 * @param executionId execution to query.
433 * @return execution response from testing endpoint.
436 public VtpTestExecutionResponse getExecution(String endpoint, String executionId) {
437 String url = buildEndpointUrl(VTP_EXECUTION_URI, endpoint, new String[]{executionId});
438 ParameterizedTypeReference<VtpTestExecutionResponse> t =
439 new ParameterizedTypeReference<VtpTestExecutionResponse>() {
441 return proxyGetRequestToExternalTestingSite(url, t);
446 * Execute tests splitting them across endpoints and collecting the results.
448 * @param testsToRun list of tests to be executed.
449 * @return collection of result objects.
453 public List<VtpTestExecutionResponse> execute(final List<VtpTestExecutionRequest> testsToRun, String vspId,
454 String vspVersionId, String requestId, Map<String, byte[]> fileMap) {
455 if (endpoints == null) {
456 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
459 // partition the requests by endpoint.
460 Map<String, List<VtpTestExecutionRequest>> partitions =
461 testsToRun.stream().collect(Collectors.groupingBy(VtpTestExecutionRequest::getEndpoint));
463 // process each group and collect the results.
464 return partitions.entrySet().stream().flatMap(
465 e -> doExecute(e.getKey(), e.getValue(), vspId, vspVersionId, requestId, fileMap).stream())
466 .collect(Collectors.toList());
470 * Get the list of Execution by requestId.
473 public List<VtpTestExecutionOutput> getExecutionIds(String endpoint, String requestId) {
474 String url = buildEndpointUrl(VTP_EXECUTION_ID_URL, endpoint, new String[]{requestId});
475 ParameterizedTypeReference<List<VtpTestExecutionOutput>> t =
476 new ParameterizedTypeReference<List<VtpTestExecutionOutput>>() {
478 List<VtpTestExecutionOutput> rv = proxyGetRequestToExternalTestingSite(url, t);
480 rv = new ArrayList<>();
486 * Execute a set of tests at a given endpoint.
488 * @param endpointName name of the endpoint
489 * @param testsToRun set of tests to run
490 * @return list of execution responses.
492 private List<VtpTestExecutionResponse> doExecute(final String endpointName,
493 final List<VtpTestExecutionRequest> testsToRun, String vspId, String vspVersionId,
495 Map<String, byte[]> fileMap) {
496 if (endpoints == null) {
497 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
500 RemoteTestingEndpointDefinition endpoint =
501 endpoints.stream().filter(e -> StringUtils.equals(endpointName, e.getId())).findFirst().orElseThrow(
502 () -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 400,
503 "No endpoint named " + endpointName + " is defined"));
505 // if the endpoint requires an API key, specify it in the headers.
506 HttpHeaders headers = new HttpHeaders();
507 if (endpoint.getApiKey() != null) {
508 headers.add("X-API-Key", endpoint.getApiKey());
510 headers.setContentType(MediaType.MULTIPART_FORM_DATA);
513 MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
515 for (VtpTestExecutionRequest test : testsToRun) {
516 // it will true only noramal validation not for certification
517 if ((test.getParameters() != null) && (test.getParameters().containsKey(VSP_CSAR) || test.getParameters()
520 attachArchiveContent(test, body, vspId, vspVersionId);
525 attachFileContentInTest(body, fileMap);
527 // remove the endpoint from the test request since that is a FE/BE attribute
528 testsToRun.forEach(t -> t.setEndpoint(null));
529 String strExecution = new ObjectMapper().writeValueAsString(testsToRun);
530 body.add("executions", strExecution);
532 } catch (IOException ex) {
533 logger.error("exception converting tests to string", ex);
534 VtpTestExecutionResponse err = new VtpTestExecutionResponse();
535 err.setHttpStatus(500);
536 err.setCode(TESTING_HTTP_ERROR_CODE);
537 err.setMessage("Execution failed due to " + ex.getMessage());
538 return Collections.singletonList(err);
541 // form and send request.
542 HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
543 String url = buildEndpointUrl(VTP_EXECUTIONS_URI, endpointName, ArrayUtils.EMPTY_STRING_ARRAY);
544 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
545 if (requestId != null) {
546 builder = builder.queryParam("requestId", requestId);
548 ParameterizedTypeReference<List<VtpTestExecutionResponse>> t =
549 new ParameterizedTypeReference<List<VtpTestExecutionResponse>>() {
552 return proxyRequestToExternalTestingSite(builder.toUriString(), requestEntity, t);
553 } catch (ExternalTestingException ex) {
554 logger.info("exception caught invoking endpoint {}", endpointName, ex);
555 if (ex.getHttpStatus() == 504) {
556 return Collections.singletonList(new VtpTestExecutionResponse());
558 VtpTestExecutionResponse err = new VtpTestExecutionResponse();
559 err.setHttpStatus(ex.getHttpStatus());
560 err.setCode(TESTING_HTTP_ERROR_CODE);
561 err.setMessage(ex.getMessageCode() + ": " + ex.getDetail());
562 return Collections.singletonList(err);
567 * Return URL with endpoint url as prefix.
569 * @param format format string.
570 * @param endpointName endpoint to address
571 * @param args args for format.
572 * @return qualified url.
574 private String buildEndpointUrl(String format, String endpointName, String[] args) {
575 if (endpoints != null) {
576 RemoteTestingEndpointDefinition ep =
577 endpoints.stream().filter(e -> e.isEnabled() && e.getId().equals(endpointName)).findFirst()
578 .orElseThrow(() -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 500,
579 "No endpoint named " + endpointName + " is defined"));
581 Object[] newArgs = ArrayUtils.add(args, 0, ep.getUrl());
582 return String.format(format, newArgs);
584 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
588 * Proxy a get request to a testing endpoint.
590 * @param url URL to invoke.
591 * @param responseType type of response expected.
592 * @param <T> type of response expected
593 * @return instance of <T> parsed from the JSON response from endpoint.
595 private <T> T proxyGetRequestToExternalTestingSite(String url, ParameterizedTypeReference<T> responseType) {
596 return proxyRequestToExternalTestingSite(url, null, responseType);
600 * Make the actual HTTP post (using Spring RestTemplate) to an endpoint.
602 * @param url URL to the endpoint
603 * @param request optional request body to send
604 * @param responseType expected type
605 * @param <T> extended type
606 * @return instance of expected type
608 private <R, T> T proxyRequestToExternalTestingSite(String url, HttpEntity<R> request,
609 ParameterizedTypeReference<T> responseType) {
610 if (request != null) {
611 logger.debug("POST request to {} with {} for {}", url, request, responseType.getType().getTypeName());
613 logger.debug("GET request to {} for {}", url, responseType.getType().getTypeName());
616 SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
617 ResponseEntity<T> re;
620 rf.setReadTimeout(10000);
621 rf.setConnectTimeout(10000);
625 if (request != null) {
626 re = restTemplate.exchange(url, HttpMethod.POST, request, responseType);
628 re = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
630 } catch (HttpStatusCodeException ex) {
631 // make my own exception out of this.
632 HttpHeaders httpHeaders = ex.getResponseHeaders();
633 logger.warn("Unexpected HTTP Status from endpoint {}", ex.getRawStatusCode());
635 if (httpHeaders == null) {
636 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, "Failed to get HTTP Response Headers", ex);
639 MediaType responseHeadersContentType = httpHeaders.getContentType();
641 if (responseHeadersContentType == null || (!responseHeadersContentType.isCompatibleWith(MediaType.APPLICATION_JSON)
642 && !responseHeadersContentType.isCompatibleWith(MediaType.parseMediaType("application/problem+json")))) {
643 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(), ex.getResponseBodyAsString(), ex);
646 String s = ex.getResponseBodyAsString();
647 logger.warn("endpoint body content is {}", s);
650 JsonObject o = new GsonBuilder().create().fromJson(s, JsonObject.class);
651 throw buildTestingException(ex.getRawStatusCode(), o);
652 } catch (JsonParseException e) {
653 logger.warn("unexpected JSON response", e);
654 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(),
655 ex.getResponseBodyAsString(), ex);
657 } catch (ResourceAccessException ex) {
658 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, ex.getMessage(), ex);
659 } catch (Exception ex) {
660 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, "Generic Exception " + ex.getMessage(), ex);
664 logger.debug("http status of {} from external testing entity {}", re.getStatusCodeValue(), url);
667 logger.error("null response from endpoint");
672 private void attachFileContentInTest(MultiValueMap<String, Object> body, Map<String, byte[]> fileMap) {
673 if (fileMap != null) {
674 fileMap.forEach((name, inputStream) -> body.add("file", new NamedByteArrayResource(inputStream, name)));
680 * Errors from the endpoint could conform to the expected ETSI body or not. Here we try to handle various response body elements.
682 * @param statusCode http status code in response.
683 * @param o JSON object parsed from the http response body
684 * @return Testing error body that should be returned to the caller
686 private ExternalTestingException buildTestingException(int statusCode, JsonObject o) {
688 String message = null;
691 code = o.get(CODE).getAsString();
692 } else if (o.has(ERROR)) {
693 code = o.get(ERROR).getAsString();
695 if (o.has(HTTP_STATUS)) {
696 code = o.get(HTTP_STATUS).getAsJsonPrimitive().getAsString();
699 if (o.has(MESSAGE)) {
700 if (!o.get(MESSAGE).isJsonNull()) {
701 message = o.get(MESSAGE).getAsString();
703 } else if (o.has(DETAIL)) {
704 message = o.get(DETAIL).getAsString();
707 if (message == null) {
708 message = o.get(PATH).getAsString();
710 message = message + " " + o.get(PATH).getAsString();
713 return new ExternalTestingException(code, statusCode, message);
716 void attachArchiveContent(VtpTestExecutionRequest test, MultiValueMap<String, Object> body, String vspId,
717 String vspVersionId) {
719 extractMetadata(test, body, vspId, vspVersionId);
720 } catch (IOException ex) {
721 logger.error("metadata extraction failed", ex);
726 * Extract the metadata from the VSP CSAR file.
728 * @param requestItem item to add metadata to for processing
729 * @param vspId VSP identifier
730 * @param version VSP version
732 private void extractMetadata(VtpTestExecutionRequest requestItem, MultiValueMap<String, Object> body, String vspId,
733 String version) throws IOException {
735 Version ver = new Version(version);
736 logger.debug("attempt to retrieve archive for VSP {} version {}", vspId, ver.getId());
738 Optional<Pair<String, byte[]>> ozip = candidateManager.get(vspId, ver);
739 if (!ozip.isPresent()) {
740 ozip = vendorSoftwareProductManager.get(vspId, ver);
743 if (!ozip.isPresent()) {
744 List<Version> versions = versioningManager.list(vspId);
745 String knownVersions = versions.stream()
746 .map(v -> String.format("%d.%d: %s (%s)", v.getMajor(), v.getMinor(),
747 v.getStatus(), v.getId())).collect(Collectors.joining("\n"));
749 String detail = String.format(
750 "Archive processing failed. Unable to find archive for VSP ID %s and Version %s. Known versions are:\n%s",
751 vspId, version, knownVersions);
753 throw new ExternalTestingException(SDC_RESOLVER_ERR, 500, detail);
756 // safe here to do get.
757 Pair<String, byte[]> zip = ozip.get();
758 processArchive(requestItem, body, zip.getRight());
761 private void processArchive(final VtpTestExecutionRequest test, final MultiValueMap<String, Object> body,
764 // VTP does not support concurrent executions of the same test with the same associated file name.
765 // It writes files to /tmp and if we were to send two requests with the same file, the results
766 // are unpredictable.
767 String key = UUID.randomUUID().toString();
768 key = key.substring(0, key.indexOf('-'));
770 if (test.getParameters().containsKey(VSP_HEAT)) {
771 body.add("file", new NamedByteArrayResource(zip, key + ".heat.zip"));
772 test.getParameters().put(VSP_HEAT, FILE_URL_PREFIX + key + ".heat.zip");
774 body.add("file", new NamedByteArrayResource(zip, key + ".csar"));
775 test.getParameters().put(VSP_CSAR, FILE_URL_PREFIX + key + ".csar");
780 * We need to name the byte array we add to the multipart request sent to the VTP.
782 @EqualsAndHashCode(callSuper = false)
783 protected class NamedByteArrayResource extends ByteArrayResource {
785 private String filename;
787 NamedByteArrayResource(byte[] bytes, String filename) {
788 super(bytes, filename);
789 this.filename = filename;
793 public String getFilename() {
794 return this.filename;