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 private Logger logger = LoggerFactory.getLogger(ExternalTestingManagerImpl.class);
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";
88 private static final String VTP_SCENARIOS_URI = "%s/v1/vtp/scenarios";
89 private static final String VTP_TESTSUITE_URI = "%s/v1/vtp/scenarios/%s/testsuites";
90 private static final String VTP_TESTCASES_URI = "%s/v1/vtp/scenarios/%s/testcases";
91 private static final String VTP_TESTCASE_URI = "%s/v1/vtp/scenarios/%s/testsuites/%s/testcases/%s";
92 private static final String VTP_EXECUTIONS_URI = "%s/v1/vtp/executions";
93 private static final String VTP_EXECUTION_URI = "%s/v1/vtp/executions/%s";
94 private static final String VTP_EXECUTION_ID_URL = "%s/v1/vtp/executions?requestId=%s";
97 private static final String INVALIDATE_STATE_ERROR_CODE = "SDC-TEST-001";
98 private static final String NO_ACCESS_CONFIGURATION_DEFINED = "No access configuration defined";
100 private static final String NO_SUCH_ENDPOINT_ERROR_CODE = "SDC-TEST-002";
101 private static final String ENDPOINT_ERROR_CODE = "SDC-TEST-003";
102 private static final String TESTING_HTTP_ERROR_CODE = "SDC-TEST-004";
103 private static final String SDC_RESOLVER_ERR = "SDC-TEST-005";
105 static final String VSP_ID = "vspId";
106 static final String VSP_VERSION = "vspVersion";
108 private static final String VSP_CSAR = "vsp";
109 private static final String VSP_HEAT = "vsp-zip";
112 private VersioningManager versioningManager;
113 private VendorSoftwareProductManager vendorSoftwareProductManager;
114 private OrchestrationTemplateCandidateManager candidateManager;
116 private TestingAccessConfig accessConfig;
117 private List<RemoteTestingEndpointDefinition> endpoints;
119 private RestTemplate restTemplate;
121 public ExternalTestingManagerImpl() {
122 restTemplate = new RestTemplate();
125 ExternalTestingManagerImpl(VersioningManager versioningManager,
126 VendorSoftwareProductManager vendorSoftwareProductManager,
127 OrchestrationTemplateCandidateManager candidateManager) {
129 this.versioningManager = versioningManager;
130 this.vendorSoftwareProductManager = vendorSoftwareProductManager;
131 this.candidateManager = candidateManager;
135 * Read the configuration from the yaml file for this bean. If we get an exception during load,
136 * don't force an error starting SDC but log a warning. Do no warm...
141 if (versioningManager == null) {
142 versioningManager = VersioningManagerFactory.getInstance().createInterface();
144 if (vendorSoftwareProductManager == null) {
145 vendorSoftwareProductManager = VspManagerFactory.getInstance().createInterface();
147 if (candidateManager == null) {
148 candidateManager = OrchestrationTemplateCandidateManagerFactory.getInstance().createInterface();
154 private Stream<RemoteTestingEndpointDefinition> mapEndpointString(String ep) {
155 RemoteTestingEndpointDefinition rv = new RemoteTestingEndpointDefinition();
156 String[] cfg = ep.split(",");
157 if (cfg.length < 4) {
158 logger.error("invalid endpoint definition {}", ep);
159 return Stream.empty();
163 rv.setEnabled("true".equals(cfg[2]));
165 if (cfg.length > 4) {
166 rv.setScenarioFilter(cfg[4]);
168 if (cfg.length > 5) {
169 rv.setApiKey(cfg[5]);
171 return Stream.of(rv);
176 * Load the configuration for this component. When the SDC onboarding backend
177 * runs, it gets a system property called config.location. We can use that
178 * to locate the config-externaltesting.yaml file.
180 private void loadConfig() {
181 String loc = System.getProperty("config.location");
182 File file = new File(loc, "externaltesting-configuration.yaml");
183 try (InputStream fileInput = new FileInputStream(file)) {
184 YamlUtil yamlUtil = new YamlUtil();
185 accessConfig = yamlUtil.yamlToObject(fileInput, TestingAccessConfig.class);
187 if (logger.isInfoEnabled()) {
188 String s = new ObjectMapper().writeValueAsString(accessConfig);
189 logger.info("loaded external testing config {}", s);
193 accessConfig.getEndpoints().stream().flatMap(this::mapEndpointString).collect(Collectors.toList());
195 if (logger.isInfoEnabled()) {
196 String s = new ObjectMapper().writeValueAsString(endpoints);
197 logger.info("processed external testing config {}", s);
199 } catch (IOException ex) {
200 logger.error("failed to read external testing config. Disabling the feature", ex);
201 accessConfig = new TestingAccessConfig();
202 accessConfig.setEndpoints(new ArrayList<>());
203 accessConfig.setClient(new ClientConfiguration());
204 accessConfig.getClient().setEnabled(false);
205 endpoints = new ArrayList<>();
210 * Return the configuration of this feature that we want to
211 * expose to the client. Treated as a JSON blob for flexibility.
214 public ClientConfiguration getConfig() {
215 ClientConfiguration cc = null;
216 if (accessConfig != null) {
217 cc = accessConfig.getClient();
220 cc = new ClientConfiguration();
221 cc.setEnabled(false);
227 * To allow for functional testing, we let a caller invoke
228 * a setConfig request to enable/disable the client. This
229 * new value is not persisted.
231 * @return new client configuration
234 public ClientConfiguration setConfig(ClientConfiguration cc) {
235 if (accessConfig == null) {
236 accessConfig = new TestingAccessConfig();
238 accessConfig.setClient(cc);
243 * To allow for functional testing, we let a caller invoke
244 * a setEndpoints request to configure where the BE makes request to.
246 * @return new endpoint definitions.
249 public List<RemoteTestingEndpointDefinition> setEndpoints(List<RemoteTestingEndpointDefinition> endpoints) {
250 this.endpoints = endpoints;
251 return this.getEndpoints();
256 public TestTreeNode getTestCasesAsTree() {
257 TestTreeNode root = new TestTreeNode("root", "root");
259 // quick out in case of non-configured SDC
260 if (endpoints == null) {
264 for (RemoteTestingEndpointDefinition ep : endpoints) {
265 if (ep.isEnabled()) {
266 buildTreeFromEndpoint(ep, root);
272 private void buildTreeFromEndpoint(RemoteTestingEndpointDefinition ep, TestTreeNode root) {
274 logger.debug("process endpoint {}", ep.getId());
275 getScenarios(ep.getId()).stream()
276 .filter(s -> ((ep.getScenarioFilter() == null) || ep.getScenarioFilterPattern().matcher(s.getName())
277 .matches())).forEach(s -> {
278 addScenarioToTree(root, s);
279 getTestSuites(ep.getId(), s.getName()).forEach(suite -> addSuiteToTree(root, s, suite));
280 getTestCases(ep.getId(), s.getName()).forEach(tc -> {
282 VtpTestCase details =
283 getTestCase(ep.getId(), s.getName(), tc.getTestSuiteName(), tc.getTestCaseName());
284 addTestCaseToTree(root, ep.getId(), s.getName(), tc.getTestSuiteName(), details);
285 } catch (@SuppressWarnings("squid:S1166") ExternalTestingException ex) {
286 // Not logging stack trace on purpose. VTP was throwing exceptions for certain test cases.
287 logger.warn("failed to load test case {}", tc.getTestCaseName());
291 } catch (ExternalTestingException ex) {
292 logger.error("unable to contact testing endpoint {}", ep.getId(), ex);
296 private Optional<TestTreeNode> findNamedChild(TestTreeNode root, String name) {
297 if (root.getChildren() == null) {
298 return Optional.empty();
300 return root.getChildren().stream().filter(n -> n.getName().equals(name)).findFirst();
304 * Find the place in the tree to add the test case.
306 * @param root root of the tree.
307 * @param endpointName name of the endpoint to assign to the test case.
308 * @param scenarioName scenario to add this case to
309 * @param testSuiteName suite in the scenario to add this case to
310 * @param tc test case to add.
312 private void addTestCaseToTree(TestTreeNode root, String endpointName, String scenarioName, String testSuiteName,
318 findNamedChild(root, scenarioName)
319 .ifPresent(scenarioNode -> findNamedChild(scenarioNode, testSuiteName).ifPresent(suiteNode -> {
320 massageTestCaseForUI(tc, endpointName, scenarioName);
321 if (suiteNode.getTests() == null) {
322 suiteNode.setTests(new ArrayList<>());
324 suiteNode.getTests().add(tc);
328 private void massageTestCaseForUI(VtpTestCase testcase, String endpoint, String scenario) {
329 testcase.setEndpoint(endpoint);
331 if (testcase.getScenario() == null) {
332 testcase.setScenario(scenario);
337 * Add the test suite to the tree at the appropriate place if it does not already exist in the tree.
339 * @param root root of the tree.
340 * @param scenario scenario under which this suite should be placed
341 * @param suite test suite to add.
343 private void addSuiteToTree(final TestTreeNode root, final VtpNameDescriptionPair scenario,
344 final VtpNameDescriptionPair suite) {
345 findNamedChild(root, scenario.getName()).ifPresent(parent -> {
346 if (parent.getChildren() == null) {
347 parent.setChildren(new ArrayList<>());
349 if (parent.getChildren().stream().noneMatch(n -> StringUtils.equals(n.getName(), suite.getName()))) {
350 parent.getChildren().add(new TestTreeNode(suite.getName(), suite.getDescription()));
356 * Add the scenario to the tree if it does not already exist.
358 * @param root root of the tree.
359 * @param s scenario to add.
361 private void addScenarioToTree(TestTreeNode root, VtpNameDescriptionPair s) {
362 logger.debug("addScenario {} to {} with {}", s.getName(), root.getName(), root.getChildren());
363 if (root.getChildren() == null) {
364 root.setChildren(new ArrayList<>());
366 if (root.getChildren().stream().noneMatch(n -> StringUtils.equals(n.getName(), s.getName()))) {
367 logger.debug("createScenario {} in {}", s.getName(), root.getName());
368 root.getChildren().add(new TestTreeNode(s.getName(), s.getDescription()));
373 * Get the list of endpoints defined to the testing manager.
375 * @return list of endpoints or empty list if the manager is not configured.
377 public List<RemoteTestingEndpointDefinition> getEndpoints() {
378 if (endpoints != null) {
379 return endpoints.stream().filter(RemoteTestingEndpointDefinition::isEnabled).collect(Collectors.toList());
381 return new ArrayList<>();
386 * Code shared by getScenarios and getTestSuites.
388 private List<VtpNameDescriptionPair> returnNameDescriptionPairFromUrl(String url) {
389 ParameterizedTypeReference<List<VtpNameDescriptionPair>> t =
390 new ParameterizedTypeReference<List<VtpNameDescriptionPair>>() { };
391 List<VtpNameDescriptionPair> rv = proxyGetRequestToExternalTestingSite(url, t);
393 rv = new ArrayList<>();
399 * Get the list of scenarios at a given endpoint.
401 public List<VtpNameDescriptionPair> getScenarios(final String endpoint) {
402 String url = buildEndpointUrl(VTP_SCENARIOS_URI, endpoint, ArrayUtils.EMPTY_STRING_ARRAY);
403 return returnNameDescriptionPairFromUrl(url);
407 * Get the list of test suites for an endpoint for the given scenario.
409 public List<VtpNameDescriptionPair> getTestSuites(final String endpoint, final String scenario) {
410 String url = buildEndpointUrl(VTP_TESTSUITE_URI, endpoint, new String[] {scenario});
411 return returnNameDescriptionPairFromUrl(url);
415 * Get the list of test cases under a scenario. This is the VTP API. It would
416 * seem better to get the list of cases under a test suite but that is not supported.
419 public List<VtpTestCase> getTestCases(String endpoint, String scenario) {
420 String url = buildEndpointUrl(VTP_TESTCASES_URI, endpoint, new String[] {scenario});
421 ParameterizedTypeReference<List<VtpTestCase>> t = new ParameterizedTypeReference<List<VtpTestCase>>() { };
422 List<VtpTestCase> rv = proxyGetRequestToExternalTestingSite(url, t);
424 rv = new ArrayList<>();
430 * Get a test case definition.
433 public VtpTestCase getTestCase(String endpoint, String scenario, String testSuite, String testCaseName) {
434 String url = buildEndpointUrl(VTP_TESTCASE_URI, endpoint, new String[] {scenario, testSuite, testCaseName});
435 ParameterizedTypeReference<VtpTestCase> t = new ParameterizedTypeReference<VtpTestCase>() { };
436 return proxyGetRequestToExternalTestingSite(url, t);
440 * Return the results of a previous test execution.
442 * @param endpoint endpoint to query
443 * @param executionId execution to query.
444 * @return execution response from testing endpoint.
447 public VtpTestExecutionResponse getExecution(String endpoint, String executionId) {
448 String url = buildEndpointUrl(VTP_EXECUTION_URI, endpoint, new String[] {executionId});
449 ParameterizedTypeReference<VtpTestExecutionResponse> t =
450 new ParameterizedTypeReference<VtpTestExecutionResponse>() { };
451 return proxyGetRequestToExternalTestingSite(url, t);
456 * Execute tests splitting them across endpoints and collecting the results.
458 * @param testsToRun list of tests to be executed.
459 * @return collection of result objects.
463 public List<VtpTestExecutionResponse> execute(final List<VtpTestExecutionRequest> testsToRun, String vspId,
464 String vspVersionId, String requestId, Map<String, byte[]> fileMap) {
465 if (endpoints == null) {
466 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
469 // partition the requests by endpoint.
470 Map<String, List<VtpTestExecutionRequest>> partitions =
471 testsToRun.stream().collect(Collectors.groupingBy(VtpTestExecutionRequest::getEndpoint));
473 // process each group and collect the results.
474 return partitions.entrySet().stream().flatMap(
475 e -> doExecute(e.getKey(), e.getValue(), vspId, vspVersionId, requestId, fileMap).stream())
476 .collect(Collectors.toList());
480 * Get the list of Execution by requestId.
483 public List<VtpTestExecutionOutput> getExecutionIds(String endpoint, String requestId) {
484 String url = buildEndpointUrl(VTP_EXECUTION_ID_URL, endpoint, new String[] {requestId});
485 ParameterizedTypeReference<List<VtpTestExecutionOutput>> t =
486 new ParameterizedTypeReference<List<VtpTestExecutionOutput>>() { };
487 List<VtpTestExecutionOutput> rv = proxyGetRequestToExternalTestingSite(url, t);
489 rv = new ArrayList<>();
495 * Execute a set of tests at a given endpoint.
497 * @param endpointName name of the endpoint
498 * @param testsToRun set of tests to run
499 * @return list of execution responses.
501 private List<VtpTestExecutionResponse> doExecute(final String endpointName,
502 final List<VtpTestExecutionRequest> testsToRun, String vspId, String vspVersionId, String requestId,
503 Map<String, byte[]> fileMap) {
504 if (endpoints == null) {
505 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
508 RemoteTestingEndpointDefinition endpoint =
509 endpoints.stream().filter(e -> StringUtils.equals(endpointName, e.getId())).findFirst().orElseThrow(
510 () -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 400,
511 "No endpoint named " + endpointName + " is defined"));
513 // if the endpoint requires an API key, specify it in the headers.
514 HttpHeaders headers = new HttpHeaders();
515 if (endpoint.getApiKey() != null) {
516 headers.add("X-API-Key", endpoint.getApiKey());
518 headers.setContentType(MediaType.MULTIPART_FORM_DATA);
521 MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
524 for (VtpTestExecutionRequest test : testsToRun) {
525 // it will true only noramal validation not for certification
526 if ((test.getParameters() != null) && (test.getParameters().containsKey(VSP_CSAR) || test.getParameters()
529 attachArchiveContent(test, body, vspId, vspVersionId);
534 attachFileContentInTest(body, fileMap);
536 // remove the endpoint from the test request since that is a FE/BE attribute
537 testsToRun.forEach(t -> t.setEndpoint(null));
538 String strExecution = new ObjectMapper().writeValueAsString(testsToRun);
539 body.add("executions", strExecution);
541 } catch (IOException ex) {
542 logger.error("exception converting tests to string", ex);
543 VtpTestExecutionResponse err = new VtpTestExecutionResponse();
544 err.setHttpStatus(500);
545 err.setCode(TESTING_HTTP_ERROR_CODE);
546 err.setMessage("Execution failed due to " + ex.getMessage());
547 return Collections.singletonList(err);
550 // form and send request.
551 HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
552 String url = buildEndpointUrl(VTP_EXECUTIONS_URI, endpointName, ArrayUtils.EMPTY_STRING_ARRAY);
553 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
554 if (requestId != null) {
555 builder = builder.queryParam("requestId", requestId);
557 ParameterizedTypeReference<List<VtpTestExecutionResponse>> t =
558 new ParameterizedTypeReference<List<VtpTestExecutionResponse>>() { };
560 return proxyRequestToExternalTestingSite(builder.toUriString(), requestEntity, t);
561 } catch (ExternalTestingException ex) {
562 logger.info("exception caught invoking endpoint {}", endpointName, ex);
563 if (ex.getHttpStatus() == 504) {
564 return Collections.singletonList(new VtpTestExecutionResponse());
566 VtpTestExecutionResponse err = new VtpTestExecutionResponse();
567 err.setHttpStatus(ex.getHttpStatus());
568 err.setCode(TESTING_HTTP_ERROR_CODE);
569 err.setMessage(ex.getMessageCode() + ": " + ex.getDetail());
570 return Collections.singletonList(err);
575 * Return URL with endpoint url as prefix.
577 * @param format format string.
578 * @param endpointName endpoint to address
579 * @param args args for format.
580 * @return qualified url.
582 private String buildEndpointUrl(String format, String endpointName, String[] args) {
583 if (endpoints != null) {
584 RemoteTestingEndpointDefinition ep =
585 endpoints.stream().filter(e -> e.isEnabled() && e.getId().equals(endpointName)).findFirst()
586 .orElseThrow(() -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 500,
587 "No endpoint named " + endpointName + " is defined"));
589 Object[] newArgs = ArrayUtils.add(args, 0, ep.getUrl());
590 return String.format(format, newArgs);
592 throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
596 * Proxy a get request to a testing endpoint.
598 * @param url URL to invoke.
599 * @param responseType type of response expected.
600 * @param <T> type of response expected
601 * @return instance of <T> parsed from the JSON response from endpoint.
603 private <T> T proxyGetRequestToExternalTestingSite(String url, ParameterizedTypeReference<T> responseType) {
604 return proxyRequestToExternalTestingSite(url, null, responseType);
608 * Make the actual HTTP post (using Spring RestTemplate) to an endpoint.
610 * @param url URL to the endpoint
611 * @param request optional request body to send
612 * @param responseType expected type
613 * @param <T> extended type
614 * @return instance of expected type
616 private <R, T> T proxyRequestToExternalTestingSite(String url, HttpEntity<R> request,
617 ParameterizedTypeReference<T> responseType) {
618 if (request != null) {
619 logger.debug("POST request to {} with {} for {}", url, request, responseType.getType().getTypeName());
621 logger.debug("GET request to {} for {}", url, responseType.getType().getTypeName());
623 SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
625 rf.setReadTimeout(10000);
626 rf.setConnectTimeout(10000);
628 ResponseEntity<T> re;
630 if (request != null) {
631 re = restTemplate.exchange(url, HttpMethod.POST, request, responseType);
633 re = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
635 } catch (HttpStatusCodeException ex) {
636 // make my own exception out of this.
637 logger.warn("Unexpected HTTP Status from endpoint {}", ex.getRawStatusCode());
638 if ((ex.getResponseHeaders().getContentType() != null) && (
639 (ex.getResponseHeaders().getContentType().isCompatibleWith(MediaType.APPLICATION_JSON))
640 || (ex.getResponseHeaders().getContentType()
641 .isCompatibleWith(MediaType.parseMediaType("application/problem+json"))))) {
642 String s = ex.getResponseBodyAsString();
643 logger.warn("endpoint body content is {}", s);
645 JsonObject o = new GsonBuilder().create().fromJson(s, JsonObject.class);
646 throw buildTestingException(ex.getRawStatusCode(), o);
647 } catch (JsonParseException e) {
648 logger.warn("unexpected JSON response", e);
649 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(),
650 ex.getResponseBodyAsString(), ex);
653 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(),
654 ex.getResponseBodyAsString(), ex);
656 } catch (ResourceAccessException ex) {
657 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, ex.getMessage(), ex);
658 } catch (Exception ex) {
659 throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, "Generic Exception " + ex.getMessage(), ex);
662 logger.debug("http status of {} from external testing entity {}", re.getStatusCodeValue(), url);
665 logger.error("null response from endpoint");
670 private void attachFileContentInTest(MultiValueMap<String, Object> body, Map<String, byte[]> fileMap) {
671 if (fileMap != null) {
672 fileMap.forEach((name, inputStream) -> body.add("file", new NamedByteArrayResource(inputStream, name)));
678 * Errors from the endpoint could conform to the expected ETSI body or not.
679 * Here we try to handle various response body elements.
681 * @param statusCode http status code in response.
682 * @param o JSON object parsed from the http response body
683 * @return Testing error body that should be returned to the caller
685 private ExternalTestingException buildTestingException(int statusCode, JsonObject o) {
687 String message = null;
690 code = o.get(CODE).getAsString();
691 } else if (o.has(ERROR)) {
692 code = o.get(ERROR).getAsString();
694 if (o.has(HTTP_STATUS)) {
695 code = o.get(HTTP_STATUS).getAsJsonPrimitive().getAsString();
698 if (o.has(MESSAGE)) {
699 if (!o.get(MESSAGE).isJsonNull()) {
700 message = o.get(MESSAGE).getAsString();
702 } else if (o.has(DETAIL)) {
703 message = o.get(DETAIL).getAsString();
706 if (message == null) {
707 message = o.get(PATH).getAsString();
709 message = message + " " + o.get(PATH).getAsString();
712 return new ExternalTestingException(code, statusCode, message);
715 void attachArchiveContent(VtpTestExecutionRequest test, MultiValueMap<String, Object> body, String vspId,
716 String vspVersionId) {
718 extractMetadata(test, body, vspId, vspVersionId);
719 } catch (IOException ex) {
720 logger.error("metadata extraction failed", ex);
725 * Extract the metadata from the VSP CSAR file.
727 * @param requestItem item to add metadata to for processing
728 * @param vspId VSP identifier
729 * @param version VSP version
731 private void extractMetadata(VtpTestExecutionRequest requestItem, MultiValueMap<String, Object> body, String vspId,
732 String version) throws IOException {
734 Version ver = new Version(version);
735 logger.debug("attempt to retrieve archive for VSP {} version {}", vspId, ver.getId());
737 Optional<Pair<String, byte[]>> ozip = candidateManager.get(vspId, ver);
738 if (!ozip.isPresent()) {
739 ozip = vendorSoftwareProductManager.get(vspId, ver);
742 if (!ozip.isPresent()) {
743 List<Version> versions = versioningManager.list(vspId);
744 String knownVersions = versions.stream()
745 .map(v -> String.format("%d.%d: %s (%s)", v.getMajor(), v.getMinor(),
746 v.getStatus(), v.getId())).collect(Collectors.joining("\n"));
748 String detail = String.format(
749 "Archive processing failed. Unable to find archive for VSP ID %s and Version %s. Known versions are:\n%s",
750 vspId, version, knownVersions);
752 throw new ExternalTestingException(SDC_RESOLVER_ERR, 500, detail);
755 // safe here to do get.
756 Pair<String, byte[]> zip = ozip.get();
757 processArchive(requestItem, body, zip.getRight());
760 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;