+ public List<VtpTestCase> getTestCases(String endpoint, String scenario) {
+ String url = buildEndpointUrl(VTP_TESTCASES_URI, endpoint, new String[] {scenario});
+ ParameterizedTypeReference<List<VtpTestCase>> t = new ParameterizedTypeReference<List<VtpTestCase>>() { };
+ List<VtpTestCase> rv = proxyGetRequestToExternalTestingSite(url, t);
+ if (rv == null) {
+ rv = new ArrayList<>();
+ }
+ return rv;
+ }
+
+ /**
+ * Get a test case definition.
+ */
+ @Override
+ public VtpTestCase getTestCase(String endpoint, String scenario, String testSuite, String testCaseName) {
+ String url = buildEndpointUrl(VTP_TESTCASE_URI, endpoint, new String[] {scenario, testSuite, testCaseName});
+ ParameterizedTypeReference<VtpTestCase> t = new ParameterizedTypeReference<VtpTestCase>() { };
+ return proxyGetRequestToExternalTestingSite(url, t);
+ }
+
+ /**
+ * Return the results of a previous test execution.
+ *
+ * @param endpoint endpoint to query
+ * @param executionId execution to query.
+ * @return execution response from testing endpoint.
+ */
+ @Override
+ public VtpTestExecutionResponse getExecution(String endpoint, String executionId) {
+ String url = buildEndpointUrl(VTP_EXECUTION_URI, endpoint, new String[] {executionId});
+ ParameterizedTypeReference<VtpTestExecutionResponse> t =
+ new ParameterizedTypeReference<VtpTestExecutionResponse>() { };
+ return proxyGetRequestToExternalTestingSite(url, t);
+ }
+
+
+ /**
+ * Execute tests splitting them across endpoints and collecting the results.
+ *
+ * @param testsToRun list of tests to be executed.
+ * @return collection of result objects.
+ */
+
+ @Override
+ public List<VtpTestExecutionResponse> execute(final List<VtpTestExecutionRequest> testsToRun, String vspId,
+ String vspVersionId, String requestId, Map<String, byte[]> fileMap) {
+ if (endpoints == null) {
+ throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
+ }
+
+ // partition the requests by endpoint.
+ Map<String, List<VtpTestExecutionRequest>> partitions =
+ testsToRun.stream().collect(Collectors.groupingBy(VtpTestExecutionRequest::getEndpoint));
+
+ // process each group and collect the results.
+ return partitions.entrySet().stream().flatMap(
+ e -> doExecute(e.getKey(), e.getValue(), vspId, vspVersionId, requestId, fileMap).stream())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get the list of Execution by requestId.
+ */
+ @Override
+ public List<VtpTestExecutionOutput> getExecutionIds(String endpoint, String requestId) {
+ String url = buildEndpointUrl(VTP_EXECUTION_ID_URL, endpoint, new String[] {requestId});
+ ParameterizedTypeReference<List<VtpTestExecutionOutput>> t =
+ new ParameterizedTypeReference<List<VtpTestExecutionOutput>>() { };
+ List<VtpTestExecutionOutput> rv = proxyGetRequestToExternalTestingSite(url, t);
+ if (rv == null) {
+ rv = new ArrayList<>();
+ }
+ return rv;
+ }
+
+ /**
+ * Execute a set of tests at a given endpoint.
+ *
+ * @param endpointName name of the endpoint
+ * @param testsToRun set of tests to run
+ * @return list of execution responses.
+ */
+ private List<VtpTestExecutionResponse> doExecute(final String endpointName,
+ final List<VtpTestExecutionRequest> testsToRun, String vspId, String vspVersionId, String requestId,
+ Map<String, byte[]> fileMap) {
+ if (endpoints == null) {
+ throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
+ }
+
+ RemoteTestingEndpointDefinition endpoint =
+ endpoints.stream().filter(e -> StringUtils.equals(endpointName, e.getId())).findFirst().orElseThrow(
+ () -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 400,
+ "No endpoint named " + endpointName + " is defined"));
+
+ // if the endpoint requires an API key, specify it in the headers.
+ HttpHeaders headers = new HttpHeaders();
+ if (endpoint.getApiKey() != null) {
+ headers.add("X-API-Key", endpoint.getApiKey());
+ }
+ headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+ // build the body.
+ MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+
+
+ for (VtpTestExecutionRequest test : testsToRun) {
+ // it will true only noramal validation not for certification
+ if ((test.getParameters() != null) && (test.getParameters().containsKey(VSP_CSAR) || test.getParameters()
+ .containsKey(
+ VSP_HEAT))) {
+ attachArchiveContent(test, body, vspId, vspVersionId);
+ }
+
+ }
+
+ attachFileContentInTest(body, fileMap);
+ try {
+ // remove the endpoint from the test request since that is a FE/BE attribute
+ testsToRun.forEach(t -> t.setEndpoint(null));
+ String strExecution = new ObjectMapper().writeValueAsString(testsToRun);
+ body.add("executions", strExecution);
+
+ } catch (IOException ex) {
+ logger.error("exception converting tests to string", ex);
+ VtpTestExecutionResponse err = new VtpTestExecutionResponse();
+ err.setHttpStatus(500);
+ err.setCode(TESTING_HTTP_ERROR_CODE);
+ err.setMessage("Execution failed due to " + ex.getMessage());
+ return Collections.singletonList(err);
+ }
+
+ // form and send request.
+ HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
+ String url = buildEndpointUrl(VTP_EXECUTIONS_URI, endpointName, ArrayUtils.EMPTY_STRING_ARRAY);
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
+ if (requestId != null) {
+ builder = builder.queryParam("requestId", requestId);
+ }
+ ParameterizedTypeReference<List<VtpTestExecutionResponse>> t =
+ new ParameterizedTypeReference<List<VtpTestExecutionResponse>>() { };
+ try {
+ return proxyRequestToExternalTestingSite(builder.toUriString(), requestEntity, t);
+ } catch (ExternalTestingException ex) {
+ logger.info("exception caught invoking endpoint {}", endpointName, ex);
+ if (ex.getHttpStatus() == 504) {
+ return Collections.singletonList(new VtpTestExecutionResponse());
+ }
+ VtpTestExecutionResponse err = new VtpTestExecutionResponse();
+ err.setHttpStatus(ex.getHttpStatus());
+ err.setCode(TESTING_HTTP_ERROR_CODE);
+ err.setMessage(ex.getMessageCode() + ": " + ex.getDetail());
+ return Collections.singletonList(err);
+ }
+ }
+
+ /**
+ * Return URL with endpoint url as prefix.
+ *
+ * @param format format string.
+ * @param endpointName endpoint to address
+ * @param args args for format.
+ * @return qualified url.
+ */
+ private String buildEndpointUrl(String format, String endpointName, String[] args) {
+ if (endpoints != null) {
+ RemoteTestingEndpointDefinition ep =
+ endpoints.stream().filter(e -> e.isEnabled() && e.getId().equals(endpointName)).findFirst()
+ .orElseThrow(() -> new ExternalTestingException(NO_SUCH_ENDPOINT_ERROR_CODE, 500,
+ "No endpoint named " + endpointName + " is defined"));
+
+ Object[] newArgs = ArrayUtils.add(args, 0, ep.getUrl());
+ return String.format(format, newArgs);
+ }
+ throw new ExternalTestingException(INVALIDATE_STATE_ERROR_CODE, 500, NO_ACCESS_CONFIGURATION_DEFINED);
+ }
+
+ /**
+ * Proxy a get request to a testing endpoint.
+ *
+ * @param url URL to invoke.
+ * @param responseType type of response expected.
+ * @param <T> type of response expected
+ * @return instance of <T> parsed from the JSON response from endpoint.
+ */
+ private <T> T proxyGetRequestToExternalTestingSite(String url, ParameterizedTypeReference<T> responseType) {
+ return proxyRequestToExternalTestingSite(url, null, responseType);
+ }
+
+ /**
+ * Make the actual HTTP post (using Spring RestTemplate) to an endpoint.
+ *
+ * @param url URL to the endpoint
+ * @param request optional request body to send
+ * @param responseType expected type
+ * @param <T> extended type
+ * @return instance of expected type
+ */
+ private <R, T> T proxyRequestToExternalTestingSite(String url, HttpEntity<R> request,
+ ParameterizedTypeReference<T> responseType) {
+ if (request != null) {
+ logger.debug("POST request to {} with {} for {}", url, request, responseType.getType().getTypeName());
+ } else {
+ logger.debug("GET request to {} for {}", url, responseType.getType().getTypeName());
+ }
+ SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
+ if (rf != null) {
+ rf.setReadTimeout(10000);
+ rf.setConnectTimeout(10000);
+ }
+ ResponseEntity<T> re;
+ try {
+ if (request != null) {
+ re = restTemplate.exchange(url, HttpMethod.POST, request, responseType);
+ } else {
+ re = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
+ }
+ } catch (HttpStatusCodeException ex) {
+ // make my own exception out of this.
+ logger.warn("Unexpected HTTP Status from endpoint {}", ex.getRawStatusCode());
+ if ((ex.getResponseHeaders().getContentType() != null) && (
+ (ex.getResponseHeaders().getContentType().isCompatibleWith(MediaType.APPLICATION_JSON))
+ || (ex.getResponseHeaders().getContentType()
+ .isCompatibleWith(MediaType.parseMediaType("application/problem+json"))))) {
+ String s = ex.getResponseBodyAsString();
+ logger.warn("endpoint body content is {}", s);
+ try {
+ JsonObject o = new GsonBuilder().create().fromJson(s, JsonObject.class);
+ throw buildTestingException(ex.getRawStatusCode(), o);
+ } catch (JsonParseException e) {
+ logger.warn("unexpected JSON response", e);
+ throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(),
+ ex.getResponseBodyAsString(), ex);
+ }
+ } else {
+ throw new ExternalTestingException(ENDPOINT_ERROR_CODE, ex.getStatusCode().value(),
+ ex.getResponseBodyAsString(), ex);
+ }
+ } catch (ResourceAccessException ex) {
+ throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, ex.getMessage(), ex);
+ } catch (Exception ex) {
+ throw new ExternalTestingException(ENDPOINT_ERROR_CODE, 500, "Generic Exception " + ex.getMessage(), ex);
+ }
+ if (re != null) {
+ logger.debug("http status of {} from external testing entity {}", re.getStatusCodeValue(), url);
+ return re.getBody();
+ } else {
+ logger.error("null response from endpoint");
+ return null;
+ }
+ }
+
+ private void attachFileContentInTest(MultiValueMap<String, Object> body, Map<String, byte[]> fileMap) {
+ if (fileMap != null) {
+ fileMap.forEach((name, inputStream) -> body.add("file", new NamedByteArrayResource(inputStream, name)));
+ }
+
+ }
+
+ /**
+ * Errors from the endpoint could conform to the expected ETSI body or not.
+ * Here we try to handle various response body elements.
+ *
+ * @param statusCode http status code in response.
+ * @param o JSON object parsed from the http response body
+ * @return Testing error body that should be returned to the caller
+ */
+ private ExternalTestingException buildTestingException(int statusCode, JsonObject o) {
+ String code = null;
+ String message = null;
+
+ if (o.has(CODE)) {
+ code = o.get(CODE).getAsString();
+ } else if (o.has(ERROR)) {
+ code = o.get(ERROR).getAsString();
+ } else {
+ if (o.has(HTTP_STATUS)) {
+ code = o.get(HTTP_STATUS).getAsJsonPrimitive().getAsString();
+ }
+ }
+ if (o.has(MESSAGE)) {
+ if (!o.get(MESSAGE).isJsonNull()) {
+ message = o.get(MESSAGE).getAsString();
+ }
+ } else if (o.has(DETAIL)) {
+ message = o.get(DETAIL).getAsString();
+ }
+ if (o.has(PATH)) {
+ if (message == null) {
+ message = o.get(PATH).getAsString();
+ } else {
+ message = message + " " + o.get(PATH).getAsString();
+ }
+ }
+ return new ExternalTestingException(code, statusCode, message);
+ }
+
+ void attachArchiveContent(VtpTestExecutionRequest test, MultiValueMap<String, Object> body, String vspId,
+ String vspVersionId) {
+ try {
+ extractMetadata(test, body, vspId, vspVersionId);
+ } catch (IOException ex) {
+ logger.error("metadata extraction failed", ex);
+ }
+ }
+
+ /**
+ * Extract the metadata from the VSP CSAR file.
+ *
+ * @param requestItem item to add metadata to for processing
+ * @param vspId VSP identifier
+ * @param version VSP version
+ */
+ private void extractMetadata(VtpTestExecutionRequest requestItem, MultiValueMap<String, Object> body, String vspId,
+ String version) throws IOException {
+
+ Version ver = new Version(version);
+ logger.debug("attempt to retrieve archive for VSP {} version {}", vspId, ver.getId());
+
+ Optional<Pair<String, byte[]>> ozip = candidateManager.get(vspId, ver);
+ if (!ozip.isPresent()) {
+ ozip = vendorSoftwareProductManager.get(vspId, ver);
+ }
+
+ if (!ozip.isPresent()) {
+ List<Version> versions = versioningManager.list(vspId);
+ String knownVersions = versions.stream()
+ .map(v -> String.format("%d.%d: %s (%s)", v.getMajor(), v.getMinor(),
+ v.getStatus(), v.getId())).collect(Collectors.joining("\n"));
+
+ String detail = String.format(
+ "Archive processing failed. Unable to find archive for VSP ID %s and Version %s. Known versions are:\n%s",
+ vspId, version, knownVersions);
+
+ throw new ExternalTestingException(SDC_RESOLVER_ERR, 500, detail);
+ }
+
+ // safe here to do get.
+ Pair<String, byte[]> zip = ozip.get();
+ processArchive(requestItem, body, zip.getRight());
+ }
+
+ private void processArchive(final VtpTestExecutionRequest test, final MultiValueMap<String, Object> body,
+ final byte[] zip) {
+
+
+ // VTP does not support concurrent executions of the same test with the same associated file name.
+ // It writes files to /tmp and if we were to send two requests with the same file, the results
+ // are unpredictable.
+ String key = UUID.randomUUID().toString();
+ key = key.substring(0, key.indexOf('-'));
+
+ if (test.getParameters().containsKey(VSP_HEAT)) {
+ body.add("file", new NamedByteArrayResource(zip, key + ".heat.zip"));
+ test.getParameters().put(VSP_HEAT, FILE_URL_PREFIX + key + ".heat.zip");
+ } else {
+ body.add("file", new NamedByteArrayResource(zip, key + ".csar"));
+ test.getParameters().put(VSP_CSAR, FILE_URL_PREFIX + key + ".csar");
+ }
+ }
+
+ /**
+ * We need to name the byte array we add to the multipart request sent to the VTP.
+ */
+ @EqualsAndHashCode(callSuper = false)
+ protected class NamedByteArrayResource extends ByteArrayResource {
+
+ private String filename;
+
+ NamedByteArrayResource(byte[] bytes, String filename) {
+ super(bytes, filename);
+ this.filename = filename;
+ }
+
+ @Override
+ public String getFilename() {
+ return this.filename;
+ }