From ebf3211ddd6e634ca9c0a2fec56abd1f12c7625d Mon Sep 17 00:00:00 2001 From: PatrikBuhr Date: Wed, 16 Sep 2020 13:17:29 +0200 Subject: [PATCH] Enrichment Service Added producer API Added producer simulator for tests Added a mock Change-Id: Ic627e9e608cd55cb193f72bf1194f5ae1c5a56e2 Issue-ID: NONRTRIC-173 Signed-off-by: PatrikBuhr --- .../config/application.yaml | 2 +- enrichment-coordinator-service/docs/api.yaml | 341 ++++++++++++++++++++- enrichment-coordinator-service/pom.xml | 16 +- .../java/org/oransc/enrichment/BeanFactory.java | 12 + .../java/org/oransc/enrichment/SwaggerConfig.java | 6 + .../enrichment/clients/ProducerCallbacks.java | 94 ++++++ .../oransc/enrichment/clients/ProducerJobInfo.java | 65 ++++ .../enrichment/controllers/ErrorResponse.java | 17 +- .../controllers/consumer/ConsumerConsts.java | 2 +- .../controllers/consumer/ConsumerController.java | 56 +++- .../controllers/consumer/ConsumerEiJobInfo.java | 8 +- .../controllers/consumer/ConsumerEiJobStatus.java | 4 +- .../controllers/producer/ProducerConsts.java | 31 ++ .../controllers/producer/ProducerController.java | 306 ++++++++++++++++++ .../controllers/producer/ProducerEiTypeInfo.java | 55 ++++ .../producer/ProducerRegistrationInfo.java | 85 +++++ .../org/oransc/enrichment/repository/EiJob.java | 2 +- .../org/oransc/enrichment/repository/EiJobs.java | 37 +-- .../oransc/enrichment/repository/EiProducer.java | 39 +++ .../oransc/enrichment/repository/EiProducers.java | 72 +++++ .../org/oransc/enrichment/repository/EiType.java | 41 ++- .../org/oransc/enrichment/repository/EiTypes.java | 8 +- .../org/oransc/enrichment/repository/MultiMap.java | 63 ++++ .../org/oransc/enrichment/ApplicationTest.java | 190 ++++++++++-- .../oransc/enrichment/MockEnrichmentService.java | 62 ++++ .../controller/ProducerSimulatorController.java | 112 +++++++ 26 files changed, 1615 insertions(+), 111 deletions(-) create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerConsts.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java create mode 100644 enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/MultiMap.java create mode 100644 enrichment-coordinator-service/src/test/java/org/oransc/enrichment/MockEnrichmentService.java create mode 100644 enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java diff --git a/enrichment-coordinator-service/config/application.yaml b/enrichment-coordinator-service/config/application.yaml index 6279a3e2..db7d3af2 100644 --- a/enrichment-coordinator-service/config/application.yaml +++ b/enrichment-coordinator-service/config/application.yaml @@ -17,7 +17,7 @@ logging: org.springframework: ERROR org.springframework.data: ERROR org.springframework.web.reactive.function.client.ExchangeFunctions: ERROR - org.oransc.policyagent: INFO + org.oransc.enrichment: INFO file: /var/log/policy-agent/application.log server: port : 8433 diff --git a/enrichment-coordinator-service/docs/api.yaml b/enrichment-coordinator-service/docs/api.yaml index a5c07e7d..bb34e2bc 100644 --- a/enrichment-coordinator-service/docs/api.yaml +++ b/enrichment-coordinator-service/docs/api.yaml @@ -8,6 +8,10 @@ basePath: / tags: - name: A1-E Enrichment Data Consumer API description: Consumer Controller + - name: Enrichment Data Producer API + description: Producer Controller + - name: Producer Simulator + description: Producer Simulator Controller paths: /A1-EI/v1/eitypes: get: @@ -246,9 +250,279 @@ paths: schema: $ref: '#/definitions/error_information' deprecated: false + /ei-producer/v1/eiproducers: + get: + tags: + - Enrichment Data Producer API + summary: Query EI producer identifiers + description: DETAILS TBD + operationId: getEiProducerIdentifiersUsingGET + produces: + - application/json + responses: + '200': + description: EI producer identifiers + schema: + type: array + items: + type: string + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + '/ei-producer/v1/eiproducers/{eiProducerId}': + get: + tags: + - Enrichment Data Producer API + summary: Job definition for an individual EI producer + description: Query EI jobs + operationId: getEiProducerUsingGET + produces: + - application/json + parameters: + - name: eiProducerId + in: path + description: eiProducerId + required: true + type: string + responses: + '200': + description: EI Jobs + schema: + $ref: '#/definitions/producer_ei_type_info' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Enrichment Information producer is not found + schema: + $ref: '#/definitions/error_information' + deprecated: false + put: + tags: + - Enrichment Data Producer API + summary: Definitions for an individual EI producer + description: Put EI producer + operationId: putEiProducerUsingPUT + consumes: + - application/json + produces: + - application/json + parameters: + - name: eiProducerId + in: path + description: eiProducerId + required: true + type: string + - in: body + name: registrationInfo + description: registrationInfo + required: true + schema: + $ref: '#/definitions/producer_registration_info' + responses: + '200': + description: Producer updated + schema: + type: object + '201': + description: Producer created + schema: + type: object + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + delete: + tags: + - Enrichment Data Producer API + summary: Individual EI Producer + description: Delete an EI Producer + operationId: deleteEiProducerUsingDELETE + produces: + - application/json + parameters: + - name: eiProducerId + in: path + description: eiProducerId + required: true + type: string + responses: + '200': + description: Not used + schema: + type: object + '204': + description: Producer deleted + schema: + type: object + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Producer is not found + schema: + $ref: '#/definitions/error_information' + deprecated: false + '/ei-producer/v1/eiproducers/{eiProducerId}/eijobs': + get: + tags: + - Enrichment Data Producer API + summary: Job definition for an individual EI producer + description: Query EI producer jobs + operationId: getEiProducerJobsUsingGET + produces: + - application/json + parameters: + - name: eiProducerId + in: path + description: eiProducerId + required: true + type: string + responses: + '200': + description: EI jobs + schema: + type: array + items: + $ref: '#/definitions/producer_ei_job_request' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Enrichment Information producer is not found + schema: + $ref: '#/definitions/error_information' + deprecated: false + /ei-producer/v1/eitypes: + get: + tags: + - Enrichment Data Producer API + summary: Query EI type identifiers + description: DETAILS TBD + operationId: getEiTypeIdentifiersUsingGET_1 + produces: + - application/json + responses: + '200': + description: EI type identifiers + schema: + type: array + items: + type: string + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + '/ei-producer/v1/eitypes/{eiTypeId}': + get: + tags: + - Enrichment Data Producer API + summary: Definitions for an individual EI Type + description: Query EI type + operationId: getEiTypeUsingGET_1 + produces: + - application/json + parameters: + - name: eiTypeId + in: path + description: eiTypeId + required: true + type: string + responses: + '200': + description: EI type + schema: + $ref: '#/definitions/producer_ei_type_info' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Enrichment Information type is not found + schema: + $ref: '#/definitions/error_information' + deprecated: false + /producer_simulator/job_created: + post: + tags: + - Producer Simulator + summary: Callback for job creation + operationId: jobCreatedCallbackUsingPOST + consumes: + - application/json + produces: + - application/json + parameters: + - in: body + name: request + description: request + required: true + schema: + $ref: '#/definitions/producer_ei_job_request' + responses: + '200': + description: OK + schema: + type: object + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /producer_simulator/job_deleted: + post: + tags: + - Producer Simulator + summary: Callback for job deletion + operationId: jobDeletedCallbackUsingPOST + consumes: + - application/json + produces: + - application/json + parameters: + - in: body + name: request + description: request + required: true + schema: + $ref: '#/definitions/producer_ei_job_request' + responses: + '200': + description: OK + schema: + type: object + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false definitions: ei_job_info: type: object + required: + - job_data + - owner properties: job_data: type: object @@ -256,13 +530,12 @@ definitions: owner: type: string description: Identity of the owner of the job - result_target: - type: string - description: the deliver information for the EI. This is typically a URL. title: ei_job_info description: Information for a Enrichment Information Job ei_job_status: type: object + required: + - operational_state properties: operational_state: type: string @@ -297,4 +570,66 @@ definitions: description: 'The HTTP status code generated by the origin server for this occurrence of the problem. ' title: error_information description: 'Problem as defined in https://tools.ietf.org/html/rfc7807' + producer_ei_job_request: + type: object + required: + - identity + properties: + identity: + type: string + description: Json for the job data + job_data: + type: object + description: Json for the job data + type_identity: + type: string + description: Type idenitity for the job + title: producer_ei_job_request + description: Information EI job start + producer_ei_type_info: + type: object + properties: + job_data_schema: + type: object + description: Json schema for the job data + producer_ids: + type: array + description: Registered producers + items: + type: string + title: producer_ei_type_info + description: Information for an EI type + producer_ei_type_registration_info: + type: object + required: + - ei_type_identity + properties: + ei_type_identity: + type: string + description: EI type identity + job_data_schema: + type: object + description: Json schema for the job data + title: producer_ei_type_registration_info + description: Information for an EI type + producer_registration_info: + type: object + required: + - job_creation_callback_url + - job_deletion_callback_url + - supported_ei_types + properties: + job_creation_callback_url: + type: string + description: callback for job creation + job_deletion_callback_url: + type: string + description: callback for job deletion + supported_ei_types: + type: array + description: Supported EI types + items: + $ref: '#/definitions/producer_ei_type_registration_info' + title: producer_registration_info + description: Information for an EI Producer diff --git a/enrichment-coordinator-service/pom.xml b/enrichment-coordinator-service/pom.xml index e1884fc0..3f156a7a 100644 --- a/enrichment-coordinator-service/pom.xml +++ b/enrichment-coordinator-service/pom.xml @@ -30,8 +30,8 @@ org.o-ran-sc.nonrtric - policy-agent - 2.1.0-SNAPSHOT + enrichment-coordinator-service + 0.0.0-SNAPSHOT The Apache Software License, Version 2.0 @@ -54,8 +54,8 @@ 20190722 3.6 3.8.0 - 2.8.1 - 1.18.0 + 2.12.2 + 1.24.3 0.30.0 1.1.11 2.1.1 @@ -293,7 +293,7 @@ false - generate-policy-agent-image + generate-enrichment-coordinator-service-image package build @@ -302,7 +302,7 @@ ${env.CONTAINER_PULL_REGISTRY} - o-ran-sc/nonrtric-policy-agent:${project.version} + o-ran-sc/nonrtric-enrichment-coordinator-service:${project.version} try ${basedir} @@ -319,7 +319,7 @@ - push-policy-agent-image + push-enrichment-coordinator-service-image build push @@ -329,7 +329,7 @@ ${env.CONTAINER_PUSH_REGISTRY} - o-ran-sc/nonrtric-policy-agent:${project.version} + o-ran-sc/nonrtric-enrichment-coordinator-service:${project.version} ${basedir} Dockerfile diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java index 7b868f52..9e2142e6 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java @@ -23,8 +23,10 @@ package org.oransc.enrichment; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.catalina.connector.Connector; +import org.oransc.enrichment.clients.ProducerCallbacks; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducers; import org.oransc.enrichment.repository.EiTypes; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; @@ -64,11 +66,21 @@ class BeanFactory { return new EiTypes(); } + @Bean + public EiProducers eiProducers() { + return new EiProducers(); + } + @Bean public ApplicationConfig getApplicationConfig() { return this.applicationConfig; } + @Bean + public ProducerCallbacks getProducerCallbacks() { + return new ProducerCallbacks(); + } + private static Connector getHttpConnector(int httpPort) { Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setScheme("http"); diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java index 07dbefdf..ee80183b 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java @@ -23,7 +23,10 @@ package org.oransc.enrichment; import com.fasterxml.classmate.TypeResolver; import com.google.common.base.Predicates; +import org.oransc.enrichment.clients.ProducerJobInfo; import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo; +import org.oransc.enrichment.controllers.producer.ProducerEiTypeInfo; +import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; @@ -65,6 +68,9 @@ public class SwaggerConfig extends WebMvcConfigurationSupport { return new Docket(DocumentationType.SWAGGER_2) // .apiInfo(apiInfo()) // .additionalModels(resolver.resolve(ConsumerEiJobInfo.class)) // + .additionalModels(resolver.resolve(ProducerRegistrationInfo.class)) // + .additionalModels(resolver.resolve(ProducerEiTypeInfo.class)) // + .additionalModels(resolver.resolve(ProducerJobInfo.class)) // .select() // .apis(RequestHandlerSelectors.any()) // .paths(PathSelectors.any()) // diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java new file mode 100644 index 00000000..a77b7727 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java @@ -0,0 +1,94 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2020 Nordix Foundation + * %% + * 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.oransc.enrichment.clients; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.lang.invoke.MethodHandles; + +import org.oransc.enrichment.configuration.ApplicationConfig; +import org.oransc.enrichment.configuration.ImmutableWebClientConfig; +import org.oransc.enrichment.configuration.WebClientConfig; +import org.oransc.enrichment.repository.EiJob; +import org.oransc.enrichment.repository.EiProducer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Callbacks to the EiProducer + */ +@SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. +public class ProducerCallbacks { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static Gson gson = new GsonBuilder() // + .serializeNulls() // + .create(); // + + @Autowired + ApplicationConfig applicationConfig; + + public void notifyProducersJobCreated(EiJob eiJob) { + for (EiProducer producer : eiJob.type().getProducers()) { + notifyProducerJobStarted(producer, eiJob); + } + } + + public void notifyProducersJobDeleted(EiJob eiJob) { + AsyncRestClient restClient = restClient(false); + ProducerJobInfo request = new ProducerJobInfo(eiJob.jobData(), eiJob, eiJob.type()); + String body = gson.toJson(request); + for (EiProducer producer : eiJob.type().getProducers()) { + restClient.post(producer.jobDeletionCallbackUrl(), body) // + .subscribe(notUsed -> logger.debug("Job subscription started OK {}", producer.id()), // + throwable -> logger.warn("Job subscription failed {}", producer.id(), throwable.toString()), null); + } + } + + public void notifyProducerJobStarted(EiProducer producer, EiJob eiJob) { + AsyncRestClient restClient = restClient(false); + ProducerJobInfo request = new ProducerJobInfo(eiJob.jobData(), eiJob, eiJob.type()); + String body = gson.toJson(request); + + restClient.post(producer.jobCreationCallbackUrl(), body) // + .subscribe(notUsed -> logger.debug("Job subscription started OK {}", producer.id()), // + throwable -> logger.warn("Job subscription failed {}", producer.id(), throwable.toString()), null); + + } + + private AsyncRestClient restClient(boolean useTrustValidation) { + WebClientConfig config = this.applicationConfig.getWebClientConfig(); + config = ImmutableWebClientConfig.builder() // + .keyStoreType(config.keyStoreType()) // + .keyStorePassword(config.keyStorePassword()) // + .keyStore(config.keyStore()) // + .keyPassword(config.keyPassword()) // + .isTrustStoreUsed(useTrustValidation) // + .trustStore(config.trustStore()) // + .trustStorePassword(config.trustStorePassword()) // + .build(); + + return new AsyncRestClient("", config); + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java new file mode 100644 index 00000000..60762b91 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java @@ -0,0 +1,65 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2020 Nordix Foundation. 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.oransc.enrichment.clients; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import org.immutables.gson.Gson; +import org.oransc.enrichment.repository.EiJob; +import org.oransc.enrichment.repository.EiType; + +@Gson.TypeAdapters +@ApiModel(value = "producer_ei_job_request", description = "Information EI job start") +public class ProducerJobInfo { + + @ApiModelProperty(value = "Json for the job data", required = true) + @SerializedName("identity") + @JsonProperty("identity") + public String id; + + @ApiModelProperty(value = "Type idenitity for the job") + @SerializedName("type_identity") + @JsonProperty("type_identity") + public String typeId; + + @ApiModelProperty(value = "Json for the job data") + @SerializedName("job_data") + @JsonProperty("job_data") + public Object jobData; + + public ProducerJobInfo(Object jobData, String id, String typeId) { + this.id = id; + this.jobData = jobData; + this.typeId = typeId; + } + + public ProducerJobInfo(Object jobData, EiJob job, EiType type) { + this(jobData, job.id(), type.getId()); + } + + public ProducerJobInfo() { + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java index 1df2df73..edaa3bb4 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/ErrorResponse.java @@ -83,24 +83,19 @@ public class ErrorResponse { this.message = message; } - public static Mono> createMono(String text, HttpStatus code) { - return Mono.just(create(text, code)); - } - public static Mono> createMono(Exception e, HttpStatus code) { - return createMono(e.toString(), code); + return Mono.just(create(e, code)); } - public static ResponseEntity create(String text, HttpStatus code) { - ErrorInfo p = new ErrorInfo(text, code.value()); + public static ResponseEntity create(Exception e, HttpStatus code) { + if (e instanceof RuntimeException) { + code = HttpStatus.INTERNAL_SERVER_ERROR; + } + ErrorInfo p = new ErrorInfo(e.toString(), code.value()); String json = gson.toJson(p); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PROBLEM_JSON); return new ResponseEntity<>(json, headers, code); } - public static ResponseEntity create(Exception e, HttpStatus code) { - return create(e.toString(), code); - } - } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java index d38d6dca..dcddef38 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerConsts.java @@ -22,7 +22,7 @@ package org.oransc.enrichment.controllers.consumer; public class ConsumerConsts { - public static final String A1E_API_ROOT = "/A1-EI/v1"; + public static final String API_ROOT = "/A1-EI/v1"; public static final String CONSUMER_API_NAME = "A1-E Enrichment Data Consumer API"; public static final String OWNER_PARAM = "owner"; public static final String OWNER_PARAM_DESCRIPTION = "identifies the owner of the job"; diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java index 7dfaecae..c43a4957 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java @@ -22,6 +22,7 @@ package org.oransc.enrichment.controllers.consumer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -29,15 +30,20 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; +import org.oransc.enrichment.clients.ProducerCallbacks; +import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.controllers.ErrorResponse; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; import org.oransc.enrichment.repository.EiType; import org.oransc.enrichment.repository.EiTypes; import org.oransc.enrichment.repository.ImmutableEiJob; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -49,21 +55,30 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +@SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. @RestController("ConsumerController") @Api(tags = {ConsumerConsts.CONSUMER_API_NAME}) public class ConsumerController { + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @Autowired + ApplicationConfig applicationConfig; + @Autowired private EiJobs eiJobs; @Autowired private EiTypes eiTypes; + @Autowired + ProducerCallbacks producerCallbacks; + private static Gson gson = new GsonBuilder() // .serializeNulls() // .create(); // - @GetMapping(path = ConsumerConsts.A1E_API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD") @ApiResponses( value = { // @@ -77,13 +92,13 @@ public class ConsumerController { ) { List result = new ArrayList<>(); for (EiType eiType : this.eiTypes.getAllEiTypes()) { - result.add(eiType.id()); + result.add(eiType.getId()); } return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK); } - @GetMapping(path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Definitions for an individual EI Type", notes = "Query EI type") @ApiResponses( value = { // @@ -104,7 +119,7 @@ public class ConsumerController { } @GetMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs", + path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Query EI job identifiers", notes = "Returns the EI Job identifiers for an EI Type") @ApiResponses( @@ -138,7 +153,7 @@ public class ConsumerController { } @GetMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", + path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Individual EI Job", notes = "") @ApiResponses( @@ -161,7 +176,7 @@ public class ConsumerController { } @GetMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}/status", + path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}/status", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "EI Job status", notes = "") @ApiResponses( @@ -184,11 +199,12 @@ public class ConsumerController { } private ConsumerEiJobStatus toEiJobStatus(EiJob job) { + // TODO return new ConsumerEiJobStatus(ConsumerEiJobStatus.OperationalState.ENABLED); } @DeleteMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", + path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Individual EI Job", notes = "Delete EI job") @ApiResponses( @@ -203,7 +219,9 @@ public class ConsumerController { @PathVariable("eiTypeId") String eiTypeId, // @PathVariable("eiJobId") String eiJobId) { try { - this.eiJobs.remove(eiJobId); + EiJob job = this.eiJobs.getJob(eiJobId); + this.eiJobs.remove(job); + this.producerCallbacks.notifyProducersJobDeleted(job); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); @@ -211,7 +229,7 @@ public class ConsumerController { } @PutMapping( - path = ConsumerConsts.A1E_API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", // + path = ConsumerConsts.API_ROOT + "/eitypes/{eiTypeId}/eijobs/{eiJobId}", // produces = MediaType.APPLICATION_JSON_VALUE, // consumes = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Individual EI Job", notes = "Create or update an EI Job") @@ -228,28 +246,38 @@ public class ConsumerController { @PathVariable("eiJobId") String eiJobId, // @RequestBody ConsumerEiJobInfo eiJobInfo) { try { - this.eiTypes.getType(eiTypeId); // Just to check that the type exists + EiType eiType = this.eiTypes.getType(eiTypeId); + validateJobData(eiType.getJobDataSchema(), eiJobInfo.jobData); final boolean newJob = this.eiJobs.get(eiJobId) == null; - this.eiJobs.put(toEiJob(eiJobInfo, eiJobId, eiTypeId)); + EiJob eiJob = toEiJob(eiJobInfo, eiJobId, eiType); + this.eiJobs.put(eiJob); + this.producerCallbacks.notifyProducersJobCreated(eiJob); return new ResponseEntity<>(newJob ? HttpStatus.CREATED : HttpStatus.OK); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); } } + private void validateJobData(Object schemaObj, Object json) { + if (schemaObj instanceof JsonObject) { + JsonObject schema = (JsonObject) schemaObj; + logger.debug("schema {} json {}", schema, json); + } + } + // Status TBD - private EiJob toEiJob(ConsumerEiJobInfo info, String id, String typeId) { + private EiJob toEiJob(ConsumerEiJobInfo info, String id, EiType type) { return ImmutableEiJob.builder() // .id(id) // - .typeId(typeId) // + .type(type) // .owner(info.owner) // .jobData(info.jobData) // .build(); } private ConsumerEiTypeInfo toEiTypeInfo(EiType t) { - return new ConsumerEiTypeInfo(t.jobDataSchema()); + return new ConsumerEiTypeInfo(t.getJobDataSchema()); } private ConsumerEiJobInfo toEiJobInfo(EiJob s) { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java index 08d3caea..515c6169 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java @@ -32,14 +32,14 @@ import org.immutables.gson.Gson; @ApiModel(value = "ei_job_info", description = "Information for a Enrichment Information Job") public class ConsumerEiJobInfo { - @ApiModelProperty(value = "Identity of the owner of the job") + @ApiModelProperty(value = "Identity of the owner of the job", required = true) @SerializedName("owner") - @JsonProperty("owner") + @JsonProperty(value = "owner", required = true) public String owner; - @ApiModelProperty(value = "EI Type specific job data") + @ApiModelProperty(value = "EI Type specific job data", required = true) @SerializedName("job_data") - @JsonProperty("job_data") + @JsonProperty(value = "job_data", required = true) public Object jobData; public ConsumerEiJobInfo() { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java index dbdd1a34..282f44da 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java @@ -42,9 +42,9 @@ public class ConsumerEiJobStatus { + "ENABLED: TBD\n" // + "DISABLED: TBD."; - @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "operational_state") + @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "operational_state", required = true) @SerializedName("operational_state") - @JsonProperty("operational_state") + @JsonProperty(value = "operational_state", required = true) public final OperationalState state; public ConsumerEiJobStatus(OperationalState state) { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerConsts.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerConsts.java new file mode 100644 index 00000000..9e56197f --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerConsts.java @@ -0,0 +1,31 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2020 Nordix Foundation. 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.oransc.enrichment.controllers.producer; + +public class ProducerConsts { + + public static final String API_ROOT = "/ei-producer/v1"; + public static final String PRODUCER_API_NAME = "Enrichment Data Producer API"; + + private ProducerConsts() { + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java new file mode 100644 index 00000000..b88e047f --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java @@ -0,0 +1,306 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2019-2020 Nordix Foundation. 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.oransc.enrichment.controllers.producer; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.oransc.enrichment.clients.ProducerCallbacks; +import org.oransc.enrichment.clients.ProducerJobInfo; +import org.oransc.enrichment.controllers.ErrorResponse; +import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; +import org.oransc.enrichment.repository.EiJob; +import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducer; +import org.oransc.enrichment.repository.EiProducers; +import org.oransc.enrichment.repository.EiType; +import org.oransc.enrichment.repository.EiTypes; +import org.oransc.enrichment.repository.ImmutableEiProducer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally +@RestController("ProducerController") +@Api(tags = {ProducerConsts.PRODUCER_API_NAME}) +public class ProducerController { + + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static Gson gson = new GsonBuilder() // + .serializeNulls() // + .create(); // + + @Autowired + private EiJobs eiJobs; + + @Autowired + private EiTypes eiTypes; + + @Autowired + private EiProducers eiProducers; + + @Autowired + ProducerCallbacks producerCallbacks; + + @GetMapping(path = ProducerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Query EI type identifiers", notes = "DETAILS TBD") + @ApiResponses( + value = { // + @ApiResponse( + code = 200, + message = "EI type identifiers", + response = String.class, + responseContainer = "List"), // + }) + public ResponseEntity getEiTypeIdentifiers( // + ) { + List result = new ArrayList<>(); + for (EiType eiType : this.eiTypes.getAllEiTypes()) { + result.add(eiType.getId()); + } + + return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK); + } + + @GetMapping(path = ProducerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Definitions for an individual EI Type", notes = "Query EI type") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "EI type", response = ProducerEiTypeInfo.class), // + @ApiResponse( + code = 404, + message = "Enrichment Information type is not found", + response = ErrorResponse.ErrorInfo.class)}) + public ResponseEntity getEiType( // + @PathVariable("eiTypeId") String eiTypeId) { + try { + EiType t = this.eiTypes.getType(eiTypeId); + ProducerEiTypeInfo info = toEiTypeInfo(t); + return new ResponseEntity<>(gson.toJson(info), HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @GetMapping(path = ProducerConsts.API_ROOT + "/eiproducers", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Query EI producer identifiers", notes = "DETAILS TBD") + @ApiResponses( + value = { // + @ApiResponse( + code = 200, + message = "EI producer identifiers", + response = String.class, + responseContainer = "List"), // + }) + public ResponseEntity getEiProducerIdentifiers( // + ) { + List result = new ArrayList<>(); + for (EiProducer eiProducer : this.eiProducers.getAllProducers()) { + result.add(eiProducer.id()); + } + + return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK); + } + + @GetMapping( + path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}", + produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Definition for an individual EI producer", notes = "Query EI jobs") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "EI Jobs", response = ProducerEiTypeInfo.class), // + @ApiResponse( + code = 404, + message = "Enrichment Information producer is not found", + response = ErrorResponse.ErrorInfo.class)}) + public ResponseEntity getEiProducer( // + @PathVariable("eiProducerId") String eiProducerId) { + try { + EiProducer p = this.eiProducers.getProducer(eiProducerId); + ProducerRegistrationInfo info = toEiProducerRegistrationInfo(p); + return new ResponseEntity<>(gson.toJson(info), HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @GetMapping( + path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}/eijobs", + produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Job definitions for an individual EI producer", notes = "Query EI producer jobs") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "EI jobs", response = ProducerJobInfo.class, responseContainer = "List"), // + @ApiResponse( + code = 404, + message = "Enrichment Information producer is not found", + response = ErrorResponse.ErrorInfo.class)}) + public ResponseEntity getEiProducerJobs( // + @PathVariable("eiProducerId") String eiProducerId) { + try { + EiProducer producer = this.eiProducers.getProducer(eiProducerId); + Collection producerJobs = new ArrayList<>(); + for (EiType type : producer.eiTypes()) { + for (EiJob eiJob : this.eiJobs.getJobsForType(type)) { + ProducerJobInfo request = new ProducerJobInfo(eiJob.jobData(), eiJob, eiJob.type()); + producerJobs.add(request); + } + } + + return new ResponseEntity<>(gson.toJson(producerJobs), HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @PutMapping( + path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}", + produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Individual EI producer", notes = "Put EI producer") + @ApiResponses( + value = { // + @ApiResponse(code = 201, message = "Producer created", response = void.class), // + @ApiResponse(code = 200, message = "Producer updated", response = void.class)}// + ) + public ResponseEntity putEiProducer( // + @PathVariable("eiProducerId") String eiProducerId, // + @RequestBody ProducerRegistrationInfo registrationInfo) { + try { + final EiProducer previousDefinition = this.eiProducers.get(eiProducerId); + if (previousDefinition != null) { + deregisterProducer(previousDefinition, false); + } + + registerProducer(eiProducerId, registrationInfo); + + return new ResponseEntity<>(previousDefinition == null ? HttpStatus.CREATED : HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @DeleteMapping( + path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}", + produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Individual EI Producer", notes = "Delete an EI Producer") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "Not used", response = void.class), + @ApiResponse(code = 204, message = "Producer deleted", response = void.class), + @ApiResponse(code = 404, message = "Producer is not found", response = ErrorResponse.ErrorInfo.class)}) + public ResponseEntity deleteEiProducer(@PathVariable("eiProducerId") String eiProducerId) { + try { + final EiProducer producer = this.eiProducers.getProducer(eiProducerId); + deregisterProducer(producer, true); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + private EiType registerType(ProducerEiTypeRegistrationInfo typeInfo) { + EiType type = this.eiTypes.get(typeInfo.eiTypeId); + if (type == null) { + type = new EiType(typeInfo.eiTypeId, typeInfo.jobDataSchema); + this.eiTypes.put(type); + } + return type; + + } + + EiProducer createProducer(Collection types, String producerId, ProducerRegistrationInfo registrationInfo) { + return ImmutableEiProducer.builder() // + .id(producerId) // + .eiTypes(types) // + .jobCreationCallbackUrl(registrationInfo.jobCreationCallbackUrl) // + .jobDeletionCallbackUrl(registrationInfo.jobDeletionCallbackUrl) // + .build(); + } + + private void registerProducer(String producerId, ProducerRegistrationInfo registrationInfo) { + ArrayList types = new ArrayList<>(); + for (ProducerEiTypeRegistrationInfo typeInfo : registrationInfo.types) { + types.add(registerType(typeInfo)); + } + EiProducer producer = createProducer(types, producerId, registrationInfo); + this.eiProducers.put(producer); + + for (EiType type : types) { + for (EiJob job : this.eiJobs.getJobsForType(type)) { + this.producerCallbacks.notifyProducerJobStarted(producer, job); + } + type.addProducer(producer); + } + } + + private void deregisterProducer(EiProducer producer, boolean deleteJobs) { + this.eiProducers.remove(producer); + for (EiType type : producer.eiTypes()) { + boolean removed = type.removeProducer(producer) != null; + if (!removed) { + this.logger.error("Bug, no producer found"); + } + if (type.getProducerIds().isEmpty() && deleteJobs) { + this.eiTypes.remove(type); + for (EiJob job : this.eiJobs.getJobsForType(type.getId())) { + this.eiJobs.remove(job); + this.logger.warn("Deleted job {} because no producers left", job.id()); + } + } + } + } + + ProducerRegistrationInfo toEiProducerRegistrationInfo(EiProducer p) { + Collection types = new ArrayList<>(); + for (EiType type : p.eiTypes()) { + types.add(toEiTypeRegistrationInfo(type)); + } + return new ProducerRegistrationInfo(types, p.jobCreationCallbackUrl(), p.jobDeletionCallbackUrl()); + } + + private ProducerEiTypeRegistrationInfo toEiTypeRegistrationInfo(EiType type) { + return new ProducerEiTypeRegistrationInfo(type.getJobDataSchema(), type.getId()); + } + + private ProducerEiTypeInfo toEiTypeInfo(EiType t) { + return new ProducerEiTypeInfo(t.getJobDataSchema(), t.getProducerIds()); + } +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java new file mode 100644 index 00000000..1c9167bf --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java @@ -0,0 +1,55 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2020 Nordix Foundation. 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.oransc.enrichment.controllers.producer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.Collection; + +import org.immutables.gson.Gson; + +@Gson.TypeAdapters +@ApiModel(value = "producer_ei_type_info", description = "Information for an EI type") +public class ProducerEiTypeInfo { + + @ApiModelProperty(value = "Json schema for the job data") + @SerializedName("job_data_schema") + @JsonProperty("job_data_schema") + public Object jobDataSchema; + + @ApiModelProperty(value = "Registered producers") + @SerializedName("producer_ids") + @JsonProperty(value = "producer_ids", required = true) + public Collection producerIds; + + public ProducerEiTypeInfo(Object jobDataSchema, Collection producers) { + this.jobDataSchema = jobDataSchema; + this.producerIds = producers; + } + + public ProducerEiTypeInfo() { + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java new file mode 100644 index 00000000..c48c716f --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java @@ -0,0 +1,85 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2020 Nordix Foundation. 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.oransc.enrichment.controllers.producer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.Collection; + +import org.immutables.gson.Gson; + +@Gson.TypeAdapters +@ApiModel(value = "producer_registration_info", description = "Information for an EI Producer") +public class ProducerRegistrationInfo { + + @Gson.TypeAdapters + @ApiModel(value = "producer_ei_type_registration_info", description = "Information for an EI type") + public static class ProducerEiTypeRegistrationInfo { + + @ApiModelProperty(value = "EI type identity", required = true) + @SerializedName("ei_type_identity") + @JsonProperty(value = "ei_type_identity", required = true) + public String eiTypeId; + + @ApiModelProperty(value = "Json schema for the job data") + @SerializedName("job_data_schema") + @JsonProperty("job_data_schema") + public Object jobDataSchema; + + public ProducerEiTypeRegistrationInfo(Object jobDataSchema, String eiTypeId) { + this.jobDataSchema = jobDataSchema; + this.eiTypeId = eiTypeId; + } + + public ProducerEiTypeRegistrationInfo() { + } + } + + @ApiModelProperty(value = "Supported EI types", required = true) + @SerializedName("supported_ei_types") + @JsonProperty(value = "supported_ei_types", required = true) + public Collection types; + + @ApiModelProperty(value = "callback for job creation", required = true) + @SerializedName("job_creation_callback_url") + @JsonProperty(value = "job_creation_callback_url", required = true) + public String jobCreationCallbackUrl; + + @ApiModelProperty(value = "callback for job deletion", required = true) + @SerializedName("job_deletion_callback_url") + @JsonProperty(value = "job_deletion_callback_url", required = true) + public String jobDeletionCallbackUrl; + + public ProducerRegistrationInfo(Collection types, String jobCreationCallbackUrl, + String jobDeletionCallbackUrl) { + this.types = types; + this.jobCreationCallbackUrl = jobCreationCallbackUrl; + this.jobDeletionCallbackUrl = jobDeletionCallbackUrl; + } + + public ProducerRegistrationInfo() { + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java index 79f62f86..7df4a50e 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java @@ -32,7 +32,7 @@ public interface EiJob { String id(); - String typeId(); + EiType type(); String owner(); diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java index bb2e40fd..9326195f 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java @@ -21,7 +21,6 @@ package org.oransc.enrichment.repository; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Vector; @@ -33,11 +32,12 @@ import org.oransc.enrichment.exceptions.ServiceException; */ public class EiJobs { private Map allEiJobs = new HashMap<>(); - private Map> jobsByType = new HashMap<>(); + + private MultiMap jobsByType = new MultiMap<>(); public synchronized void put(EiJob job) { allEiJobs.put(job.id(), job); - multiMapPut(this.jobsByType, job.typeId(), job); + jobsByType.put(job.type().getId(), job.id(), job); } public synchronized Collection getJobs() { @@ -53,7 +53,11 @@ public class EiJobs { } public synchronized Collection getJobsForType(String typeId) { - return multiMapGet(this.jobsByType, typeId); + return jobsByType.get(typeId); + } + + public synchronized Collection getJobsForType(EiType type) { + return jobsByType.get(type.getId()); } public synchronized EiJob get(String id) { @@ -70,7 +74,7 @@ public class EiJobs { public synchronized void remove(EiJob job) { this.allEiJobs.remove(job.id()); - multiMapRemove(this.jobsByType, job.typeId(), job); + jobsByType.remove(job.type().getId(), job.id()); } public synchronized int size() { @@ -79,28 +83,7 @@ public class EiJobs { public synchronized void clear() { this.allEiJobs.clear(); - } - - private void multiMapPut(Map> multiMap, String key, EiJob value) { - multiMap.computeIfAbsent(key, k -> new HashMap<>()).put(value.id(), value); - } - - private void multiMapRemove(Map> multiMap, String key, EiJob value) { - Map map = multiMap.get(key); - if (map != null) { - map.remove(value.id()); - if (map.isEmpty()) { - multiMap.remove(key); - } - } - } - - private Collection multiMapGet(Map> multiMap, String key) { - Map map = multiMap.get(key); - if (map == null) { - return Collections.emptyList(); - } - return new Vector<>(map.values()); + this.jobsByType.clear(); } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java new file mode 100644 index 00000000..30e62de3 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java @@ -0,0 +1,39 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 Nordix Foundation + * %% + * 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.oransc.enrichment.repository; + +import java.util.Collection; + +import org.immutables.gson.Gson; +import org.immutables.value.Value; + +@Value.Immutable +@Gson.TypeAdapters +public interface EiProducer { + public String id(); + + public Collection eiTypes(); + + public String jobCreationCallbackUrl(); + + public String jobDeletionCallbackUrl(); + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java new file mode 100644 index 00000000..483850d9 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java @@ -0,0 +1,72 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 Nordix Foundation + * %% + * 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.oransc.enrichment.repository; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import org.oransc.enrichment.exceptions.ServiceException; + +/** + * Dynamic representation of all Rics in the system. + */ +public class EiProducers { + private Map allEiProducers = new HashMap<>(); + + public synchronized void put(EiProducer producer) { + allEiProducers.put(producer.id(), producer); + + } + + public synchronized Collection getAllProducers() { + return new Vector<>(allEiProducers.values()); + } + + public synchronized EiProducer getProducer(String id) throws ServiceException { + EiProducer p = allEiProducers.get(id); + if (p == null) { + throw new ServiceException("Could not find EI producer: " + id); + } + return p; + } + + public synchronized EiProducer get(String id) { + return allEiProducers.get(id); + } + + public synchronized void remove(String id) { + this.allEiProducers.remove(id); + } + + public synchronized void remove(EiProducer producer) { + this.allEiProducers.remove(producer.id()); + } + + public synchronized int size() { + return allEiProducers.size(); + } + + public synchronized void clear() { + this.allEiProducers.clear(); + } +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java index 997484d8..803fcba7 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiType.java @@ -20,13 +20,40 @@ package org.oransc.enrichment.repository; -import org.immutables.gson.Gson; -import org.immutables.value.Value; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; -@Value.Immutable -@Gson.TypeAdapters -public interface EiType { - public String id(); +import lombok.Getter; - public Object jobDataSchema(); +public class EiType { + @Getter + private final String id; + + @Getter + private final Object jobDataSchema; + + private final Map producers = new HashMap<>(); + + public EiType(String id, Object jobDataSchema) { + this.id = id; + this.jobDataSchema = jobDataSchema; + } + + public synchronized Collection getProducers() { + return Collections.unmodifiableCollection(producers.values()); + } + + public synchronized Collection getProducerIds() { + return Collections.unmodifiableCollection(producers.keySet()); + } + + public synchronized void addProducer(EiProducer producer) { + this.producers.put(producer.id(), producer); + } + + public synchronized EiProducer removeProducer(EiProducer producer) { + return this.producers.remove(producer.id()); + } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java index 7668ff16..5454e8a7 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java @@ -34,7 +34,7 @@ public class EiTypes { Map allEiTypes = new HashMap<>(); public synchronized void put(EiType type) { - allEiTypes.put(type.id(), type); + allEiTypes.put(type.getId(), type); } public synchronized Collection getAllEiTypes() { @@ -44,7 +44,7 @@ public class EiTypes { public synchronized EiType getType(String id) throws ServiceException { EiType type = allEiTypes.get(id); if (type == null) { - throw new ServiceException("Could not find EI Job: " + id); + throw new ServiceException("Could not find EI type: " + id); } return type; } @@ -57,6 +57,10 @@ public class EiTypes { allEiTypes.remove(id); } + public synchronized void remove(EiType type) { + this.remove(type.getId()); + } + public synchronized int size() { return allEiTypes.size(); } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/MultiMap.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/MultiMap.java new file mode 100644 index 00000000..e64ea223 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/MultiMap.java @@ -0,0 +1,63 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 Nordix Foundation + * %% + * 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.oransc.enrichment.repository; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +/** + * Dynamic representation of all Rics in the system. + */ + +public class MultiMap { + + private final Map> map = new HashMap<>(); + + public void put(String key, String id, T value) { + this.map.computeIfAbsent(key, k -> new HashMap<>()).put(id, value); + } + + public void remove(String key, String id) { + Map innerMap = this.map.get(key); + if (innerMap != null) { + innerMap.remove(id); + if (innerMap.isEmpty()) { + this.map.remove(key); + } + } + } + + public Collection get(String key) { + Map innerMap = this.map.get(key); + if (innerMap == null) { + return Collections.emptyList(); + } + return new Vector<>(innerMap.values()); + } + + public void clear() { + this.map.clear(); + } + +} diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java index d8714277..cf340764 100644 --- a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java @@ -21,6 +21,7 @@ package org.oransc.enrichment; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.gson.Gson; @@ -29,29 +30,44 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.oransc.enrichment.clients.AsyncRestClient; +import org.oransc.enrichment.clients.ProducerJobInfo; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.configuration.ImmutableWebClientConfig; import org.oransc.enrichment.configuration.WebClientConfig; +import org.oransc.enrichment.controller.ProducerSimulatorController; import org.oransc.enrichment.controllers.consumer.ConsumerConsts; import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo; +import org.oransc.enrichment.controllers.producer.ProducerConsts; +import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo; +import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducer; +import org.oransc.enrichment.repository.EiProducers; import org.oransc.enrichment.repository.EiType; import org.oransc.enrichment.repository.EiTypes; import org.oransc.enrichment.repository.ImmutableEiJob; -import org.oransc.enrichment.repository.ImmutableEiType; +import org.oransc.enrichment.repository.ImmutableEiProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -80,9 +96,15 @@ class ApplicationTest { @Autowired EiTypes eiTypes; + @Autowired + EiProducers eiProducers; + @Autowired ApplicationConfig applicationConfig; + @Autowired + ProducerSimulatorController producerSimulator; + private static Gson gson = new GsonBuilder() // .serializeNulls() // .create(); // @@ -92,7 +114,10 @@ class ApplicationTest { */ @TestConfiguration static class TestBeanFactory { - + @Bean + public ServletWebServerFactory servletContainer() { + return new TomcatServletWebServerFactory(); + } } @LocalServerPort @@ -102,12 +127,19 @@ class ApplicationTest { void reset() { this.eiJobs.clear(); this.eiTypes.clear(); + this.eiProducers.clear(); + this.producerSimulator.getTestResults().reset(); + } + + @AfterEach + void check() { + assertThat(this.producerSimulator.getTestResults().errorFound).isFalse(); } @Test void getEiTypes() throws Exception { addEiType("test"); - String url = "/eitypes"; + String url = ConsumerConsts.API_ROOT + "/eitypes"; String rsp = restClient().get(url).block(); assertThat(rsp).isEqualTo("[\"test\"]"); } @@ -115,35 +147,35 @@ class ApplicationTest { @Test void getEiType() throws Exception { addEiType("test"); - String url = "/eitypes/test"; + String url = ConsumerConsts.API_ROOT + "/eitypes/test"; String rsp = restClient().get(url).block(); assertThat(rsp).contains("job_data_schema"); } @Test void getEiTypeNotFound() throws Exception { - String url = "/eitypes/junk"; - testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI Job: junk"); + String url = ConsumerConsts.API_ROOT + "/eitypes/junk"; + testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk"); } @Test void getEiJobsIds() throws Exception { addEiJob("typeId", "jobId"); - String url = "/eitypes/typeId/eijobs"; + String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs"; String rsp = restClient().get(url).block(); assertThat(rsp).isEqualTo("[\"jobId\"]"); } @Test void getEiJobTypeNotFound() throws Exception { - String url = "/eitypes/junk/eijobs"; - testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI Job: junk"); + String url = ConsumerConsts.API_ROOT + "/eitypes/junk/eijobs"; + testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk"); } @Test void getEiJob() throws Exception { addEiJob("typeId", "jobId"); - String url = "/eitypes/typeId/eijobs/jobId"; + String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId"; String rsp = restClient().get(url).block(); assertThat(rsp).contains("job_data"); } @@ -151,7 +183,7 @@ class ApplicationTest { @Test void getEiJobStatus() throws Exception { addEiJob("typeId", "jobId"); - String url = "/eitypes/typeId/eijobs/jobId/status"; + String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId/status"; String rsp = restClient().get(url).block(); assertThat(rsp).contains("ENABLED"); } @@ -162,38 +194,131 @@ class ApplicationTest { void deleteEiJob() throws Exception { addEiJob("typeId", "jobId"); assertThat(this.eiJobs.size()).isEqualTo(1); - String url = "/eitypes/typeId/eijobs/jobId"; + String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId"; restClient().delete(url).block(); assertThat(this.eiJobs.size()).isEqualTo(0); + + ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1)); + assertThat(simulatorResults.jobsStopped.get(0).id).isEqualTo("jobId"); } @Test void putEiJob() throws Exception { addEiType("typeId"); - String url = "/eitypes/typeId/eijobs/jobId"; + String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId"; String body = gson.toJson(eiJobInfo()); ResponseEntity resp = restClient().putForEntity(url, body).block(); assertThat(this.eiJobs.size()).isEqualTo(1); assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED); + ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1)); + ProducerJobInfo request = simulatorResults.jobsStarted.get(0); + assertThat(request.id).isEqualTo("jobId"); + resp = restClient().putForEntity(url, body).block(); assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); EiJob job = this.eiJobs.getJob("jobId"); assertThat(job.owner()).isEqualTo("owner"); } - ConsumerEiJobInfo eiJobInfo() { - return new ConsumerEiJobInfo(jsonObject(), "owner"); + @Test + void getEiProducerTypes() throws Exception { + this.addEiJob("typeId", "jobId"); + String url = ProducerConsts.API_ROOT + "/eitypes"; + + ResponseEntity resp = restClient().getForEntity(url).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); } - // @Test - @SuppressWarnings("squid:S2699") - void runMock() throws Exception { - logger.info("Keeping server alive! " + this.port); - synchronized (this) { - this.wait(); - } + @Test + void putEiProducer() throws Exception { + String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; + String body = gson.toJson(producerEiRegistratioInfo()); + + ResponseEntity resp = restClient().putForEntity(url, body).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + assertThat(this.eiTypes.size()).isEqualTo(1); + EiType type = this.eiTypes.getType("typeId"); + assertThat(type.getProducerIds().contains("eiProducerId")).isTrue(); + assertThat(this.eiProducers.size()).isEqualTo(1); + assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId().equals("typeId")).isTrue(); + + resp = restClient().putForEntity(url, body).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + void putEiProducerExistingJob() throws Exception { + this.addEiJob("typeId", "jobId"); + String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; + String body = gson.toJson(producerEiRegistratioInfo()); + + ResponseEntity resp = restClient().putForEntity(url, body).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED); + + ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1)); + ProducerJobInfo request = simulatorResults.jobsStarted.get(0); + assertThat(request.id).isEqualTo("jobId"); + } + + @Test + void getEiJobsForProducer() { + this.addEiJob("typeId", "jobId1"); + this.addEiJob("typeId", "jobId2"); + + // PUT a consumer + String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; + String body = gson.toJson(producerEiRegistratioInfo()); + restClient().putForEntity(url, body).block(); + + url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs"; + ResponseEntity resp = restClient().getForEntity(url).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); + + ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class); + assertThat(parsedResp[0].typeId).isEqualTo("typeId"); + assertThat(parsedResp[1].typeId).isEqualTo("typeId"); + } + + @Test + void deleteEiProducer() throws Exception { + String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; + String url2 = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId2"; + String body = gson.toJson(producerEiRegistratioInfo()); + restClient().putForEntity(url, body).block(); + restClient().putForEntity(url2, body).block(); + assertThat(this.eiProducers.size()).isEqualTo(2); + EiType type = this.eiTypes.getType("typeId"); + assertThat(type.getProducerIds().contains("eiProducerId")).isTrue(); + assertThat(type.getProducerIds().contains("eiProducerId2")).isTrue(); + + restClient().deleteForEntity(url).block(); + assertThat(this.eiProducers.size()).isEqualTo(1); + assertThat(this.eiTypes.getType("typeId").getProducerIds().contains("eiProducerId")).isFalse(); + + restClient().deleteForEntity(url2).block(); + assertThat(this.eiProducers.size()).isEqualTo(0); + assertThat(this.eiTypes.size()).isEqualTo(0); + } + + ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo() { + return new ProducerEiTypeRegistrationInfo(jsonObject(), "typeId"); + } + + ProducerRegistrationInfo producerEiRegistratioInfo() { + Collection types = new ArrayList<>(); + types.add(producerEiTypeRegistrationInfo()); + return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL, + baseUrl() + ProducerSimulatorController.JOB_DELETED_URL); + } + + ConsumerEiJobInfo eiJobInfo() { + return new ConsumerEiJobInfo(jsonObject(), "owner"); } JsonObject jsonObject() { @@ -204,10 +329,10 @@ class ApplicationTest { } private EiJob addEiJob(String typeId, String jobId) { - addEiType(typeId); + EiType type = addEiType(typeId); EiJob job = ImmutableEiJob.builder() // .id(jobId) // - .typeId(typeId) // + .type(type) // .owner("owner") // .jobData(jsonObject()) // .build(); @@ -216,16 +341,21 @@ class ApplicationTest { } private EiType addEiType(String typeId) { - EiType t = ImmutableEiType.builder() // - .id(typeId) // - .jobDataSchema(jsonObject()) // + EiType eiType = new EiType(typeId, jsonObject()); + this.eiTypes.put(eiType); // + EiProducer producer = ImmutableEiProducer.builder() // + .id("producerId") // + .eiTypes(Arrays.asList(eiType)) // + .jobCreationCallbackUrl(baseUrl() + ProducerSimulatorController.JOB_CREATED_URL) // + .jobDeletionCallbackUrl(baseUrl() + ProducerSimulatorController.JOB_DELETED_URL) // .build(); - this.eiTypes.put(t); - return t; + this.eiProducers.put(producer); + eiType.addProducer(producer); + return eiType; } private String baseUrl() { - return "https://localhost:" + this.port + ConsumerConsts.A1E_API_ROOT; + return "https://localhost:" + this.port; } private AsyncRestClient restClient(boolean useTrustValidation) { diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/MockEnrichmentService.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/MockEnrichmentService.java new file mode 100644 index 00000000..eaaf543e --- /dev/null +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/MockEnrichmentService.java @@ -0,0 +1,62 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2019-2020 Nordix Foundation. 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.oransc.enrichment; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) +@TestPropertySource( + properties = { // + "server.ssl.key-store=./config/keystore.jks", // + "app.webclient.trust-store=./config/truststore.jks"}) +class MockEnrichmentService { + private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class); + + /** + * Overrides the BeanFactory. + */ + @TestConfiguration + static class TestBeanFactory { + + } + + @LocalServerPort + private int port; + + @Test + @SuppressWarnings("squid:S2699") + void runMock() throws Exception { + logger.warn("**************** Keeping server alive! " + this.port); + synchronized (this) { + this.wait(); + } + } +} diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java new file mode 100644 index 00000000..307d4091 --- /dev/null +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java @@ -0,0 +1,112 @@ +/*- + * ========================LICENSE_START================================= + * ONAP : ccsdk oran + * ====================================================================== + * Copyright (C) 2019-2020 Nordix Foundation. 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.oransc.enrichment.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.Getter; + +import org.oransc.enrichment.clients.ProducerJobInfo; +import org.oransc.enrichment.controllers.ErrorResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController("ProducerSimulatorController") +@Api(tags = {"Producer Simulator"}) +public class ProducerSimulatorController { + + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static final String JOB_CREATED_URL = "/producer_simulator/job_created"; + public static final String JOB_DELETED_URL = "/producer_simulator/job_deleted"; + + public static class TestResults { + + public List jobsStarted = Collections.synchronizedList(new ArrayList()); + public List jobsStopped = Collections.synchronizedList(new ArrayList()); + public boolean errorFound = false; + + public TestResults() { + } + + public void reset() { + jobsStarted.clear(); + jobsStopped.clear(); + this.errorFound = false; + } + } + + @Getter + private TestResults testResults = new TestResults(); + + @PostMapping(path = JOB_CREATED_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Callback for job creation", notes = "") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "OK", response = void.class)}// + ) + public ResponseEntity jobCreatedCallback( // + @RequestBody ProducerJobInfo request) { + try { + this.testResults.jobsStarted.add(request); + logger.info("Job started callback {}", request.id); + if (request.id == null) { + throw new NullPointerException("Illegal argument"); + } + return new ResponseEntity<>(HttpStatus.OK); + } catch (Exception e) { + this.testResults.errorFound = true; + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @PostMapping(path = JOB_DELETED_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Callback for job deletion", notes = "") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "OK", response = void.class)}// + ) + public ResponseEntity jobDeletedCallback( // + @RequestBody ProducerJobInfo request) { + try { + logger.info("Job deleted callback {}", request.id); + this.testResults.jobsStopped.add(request); + return new ResponseEntity<>(HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + +} -- 2.16.6