From: PatrikBuhr Date: Mon, 11 Jan 2021 14:03:54 +0000 (+0100) Subject: Update of EI Data Producer API X-Git-Tag: 2.2.0~90^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=e912ee4367d6a305ac038c86dec816b5ce71191b;p=nonrtric.git Update of EI Data Producer API The EI data type lifecycle is decoupled from the producer. This means that: - A consumer can create EI jobs regardless if there is any data producer - An EI job will be in state ENABLED when one or more producer has accepted the job - types can be registerred and removed separately by own REST calls - a type cannot be removed when there is any dataproducer Added queryparameter "ei_type_id" to GET "/eiproducers" Change-Id: I088804718ee492f96e02288df63f4404a7ea862d Signed-off-by: PatrikBuhr Issue-ID: NONRTRIC-378 --- diff --git a/enrichment-coordinator-service/api/ecs-api.json b/enrichment-coordinator-service/api/ecs-api.json index 79ab5d35..6d2f0fa8 100644 --- a/enrichment-coordinator-service/api/ecs-api.json +++ b/enrichment-coordinator-service/api/ecs-api.json @@ -20,7 +20,7 @@ "description": "request", "required": true }], - "tags": ["Producer Callbacks"], + "tags": ["Data Producer Job Control (example producer)"], "consumes": ["application/json"] }}, "/A1-EI/v1/eitypes/{eiTypeId}": {"get": { @@ -77,7 +77,7 @@ "required": true } ], - "tags": ["Consumer Callbacks"], + "tags": ["A1-EI (enrichment information) callbacks"], "consumes": ["application/json"] }}, "/ei-producer/v1/eitypes": {"get": { @@ -97,7 +97,7 @@ "403": {"description": "Forbidden"}, "404": {"description": "Not Found"} }, - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }}, "/A1-EI/v1/eitypes": {"get": { "summary": "EI type identifiers", @@ -142,7 +142,7 @@ "type": "string", "required": true }], - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }}, "/producer_simulator/ei_job/{eiJobId}": {"delete": { "summary": "Callback for EI job deletion", @@ -162,7 +162,7 @@ "type": "string", "required": true }], - "tags": ["Producer Callbacks"] + "tags": ["Data Producer Job Control (example producer)"] }}, "/ei-producer/v1/eiproducers": {"get": { "summary": "EI producer identifiers", @@ -181,34 +181,106 @@ "403": {"description": "Forbidden"}, "404": {"description": "Not Found"} }, - "tags": ["Enrichment Data Producer API"] - }}, - "/ei-producer/v1/eitypes/{eiTypeId}": {"get": { - "summary": "Individual EI type", - "deprecated": false, - "produces": ["application/json"], - "operationId": "getEiTypeUsingGET_1", - "responses": { - "200": { - "schema": {"$ref": "#/definitions/producer_ei_type_info"}, - "description": "EI type" - }, - "401": {"description": "Unauthorized"}, - "403": {"description": "Forbidden"}, - "404": { - "schema": {"$ref": "#/definitions/ProblemDetails"}, - "description": "Enrichment Information type is not found" - } - }, "parameters": [{ - "in": "path", - "name": "eiTypeId", - "description": "eiTypeId", + "in": "query", + "allowEmptyValue": false, + "name": "ei_type_id", + "description": "If given, only the producers for the EI Data type is returned.", "type": "string", - "required": true + "required": false }], - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }}, + "/ei-producer/v1/eitypes/{eiTypeId}": { + "get": { + "summary": "Individual EI type", + "deprecated": false, + "produces": ["application/json"], + "operationId": "getEiTypeUsingGET_1", + "responses": { + "200": { + "schema": {"$ref": "#/definitions/producer_ei_type_info"}, + "description": "EI type" + }, + "401": {"description": "Unauthorized"}, + "403": {"description": "Forbidden"}, + "404": { + "schema": {"$ref": "#/definitions/ProblemDetails"}, + "description": "Enrichment Information type is not found" + } + }, + "parameters": [{ + "in": "path", + "name": "eiTypeId", + "description": "eiTypeId", + "type": "string", + "required": true + }], + "tags": ["Data Producer Registry API"] + }, + "delete": { + "summary": "Individual EI type", + "deprecated": false, + "produces": ["application/json"], + "operationId": "deleteEiTypeUsingDELETE", + "responses": { + "200": {"description": "Not used"}, + "401": {"description": "Unauthorized"}, + "204": {"description": "Producer deleted"}, + "403": {"description": "Forbidden"}, + "404": { + "schema": {"$ref": "#/definitions/ProblemDetails"}, + "description": "Enrichment Information type is not found" + }, + "406": { + "schema": {"$ref": "#/definitions/ProblemDetails"}, + "description": "The Enrichment Information type has one or several active producers" + } + }, + "parameters": [{ + "in": "path", + "name": "eiTypeId", + "description": "eiTypeId", + "type": "string", + "required": true + }], + "tags": ["Data Producer Registry API"] + }, + "put": { + "summary": "Individual EI type", + "deprecated": false, + "produces": ["application/json"], + "operationId": "putEiTypeUsingPUT", + "responses": { + "200": { + "schema": {"type": "object"}, + "description": "OK" + }, + "201": {"description": "Created"}, + "401": {"description": "Unauthorized"}, + "403": {"description": "Forbidden"}, + "404": {"description": "Not Found"} + }, + "parameters": [ + { + "in": "path", + "name": "eiTypeId", + "description": "eiTypeId", + "type": "string", + "required": true + }, + { + "schema": {"$ref": "#/definitions/producer_ei_type_info"}, + "in": "body", + "name": "registrationInfo", + "description": "registrationInfo", + "required": true + } + ], + "tags": ["Data Producer Registry API"], + "consumes": ["application/json"] + } + }, "/status": {"get": { "summary": "Returns status and statistics of this service", "deprecated": false, @@ -336,7 +408,7 @@ "type": "string", "required": true }], - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }, "delete": { "summary": "Individual EI producer", @@ -360,7 +432,7 @@ "type": "string", "required": true }], - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }, "put": { "summary": "Individual EI producer", @@ -390,7 +462,7 @@ "required": true } ], - "tags": ["Enrichment Data Producer API"], + "tags": ["Data Producer Registry API"], "consumes": ["application/json"] } }, @@ -408,7 +480,7 @@ "403": {"description": "Forbidden"}, "404": {"description": "Not Found"} }, - "tags": ["Producer Callbacks"] + "tags": ["Data Producer Job Control (example producer)"] }}, "/ei-producer/v1/eiproducers/{eiProducerId}/eijobs": {"get": { "summary": "EI job definitions", @@ -438,7 +510,7 @@ "type": "string", "required": true }], - "tags": ["Enrichment Data Producer API"] + "tags": ["Data Producer Registry API"] }}, "/A1-EI/v1/eijobs": {"get": { "summary": "EI job identifiers", @@ -542,22 +614,6 @@ "type": "object", "title": "EiTypeObject" }, - "producer_ei_type_registration_info": { - "description": "Information for an EI type", - "type": "object", - "title": "producer_ei_type_registration_info", - "required": ["ei_type_identity"], - "properties": { - "ei_type_identity": { - "description": "EI type identity", - "type": "string" - }, - "ei_job_data_schema": { - "description": "Json schema for the job data", - "type": "object" - } - } - }, "status_info": { "type": "object", "title": "status_info", @@ -591,17 +647,10 @@ "description": "Information for an EI type", "type": "object", "title": "producer_ei_type_info", - "properties": { - "ei_producer_ids": { - "description": "Registered producers", - "type": "array", - "items": {"type": "string"} - }, - "ei_job_data_schema": { - "description": "Json schema for the job data", - "type": "object" - } - } + "properties": {"ei_job_data_schema": { + "description": "Json schema for the job data", + "type": "object" + }} }, "producer_registration_info": { "description": "Information for an EI producer", @@ -614,9 +663,9 @@ ], "properties": { "supported_ei_types": { - "description": "Supported EI types", + "description": "Supported EI type IDs", "type": "array", - "items": {"$ref": "#/definitions/producer_ei_type_registration_info"} + "items": {"type": "string"} }, "ei_producer_supervision_callback_url": { "description": "callback for producer supervision", @@ -725,16 +774,16 @@ "description": "Consumer Controller" }, { - "name": "Consumer Callbacks", + "name": "A1-EI (enrichment information) callbacks", "description": "Consumer Simulator Controller" }, { - "name": "Enrichment Data Producer API", - "description": "Producer Controller" + "name": "Data Producer Job Control (example producer)", + "description": "Producer Simulator Controller" }, { - "name": "Producer Callbacks", - "description": "Producer Simulator Controller" + "name": "Data Producer Registry API", + "description": "Producer Controller" }, { "name": "Service status", diff --git a/enrichment-coordinator-service/api/ecs-api.yaml b/enrichment-coordinator-service/api/ecs-api.yaml index ac49e5a8..3dd53b6e 100644 --- a/enrichment-coordinator-service/api/ecs-api.yaml +++ b/enrichment-coordinator-service/api/ecs-api.yaml @@ -8,19 +8,19 @@ servers: tags: - name: A1-EI (enrichment information) description: Consumer Controller -- name: Consumer Callbacks +- name: A1-EI (enrichment information) callbacks description: Consumer Simulator Controller -- name: Enrichment Data Producer API - description: Producer Controller -- name: Producer Callbacks +- name: Data Producer Job Control (example producer) description: Producer Simulator Controller +- name: Data Producer Registry API + description: Producer Controller - name: Service status description: Status Controller paths: /producer_simulator/ei_job: post: tags: - - Producer Callbacks + - Data Producer Job Control (example producer) summary: Callback for EI job creation operationId: jobCreatedCallbackUsingPOST requestBody: @@ -83,7 +83,7 @@ paths: /consumer_simulator/eijobs/{eiJobId}/status: post: tags: - - Consumer Callbacks + - A1-EI (enrichment information) callbacks summary: Callback for EI job status operationId: jobStatusCallbackUsingPOST parameters: @@ -120,7 +120,7 @@ paths: /ei-producer/v1/eitypes: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: EI type identifiers operationId: getEiTypeIdentifiersUsingGET_1 responses: @@ -170,7 +170,7 @@ paths: /ei-producer/v1/eiproducers/{eiProducerId}/status: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: EI producer status operationId: getEiProducerStatusUsingGET parameters: @@ -203,7 +203,7 @@ paths: /producer_simulator/ei_job/{eiJobId}: delete: tags: - - Producer Callbacks + - Data Producer Job Control (example producer) summary: Callback for EI job deletion operationId: jobDeletedCallbackUsingDELETE parameters: @@ -230,9 +230,16 @@ paths: /ei-producer/v1/eiproducers: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: EI producer identifiers operationId: getEiProducerIdentifiersUsingGET + parameters: + - name: ei_type_id + in: query + description: If given, only the producers for the EI Data type is returned. + allowEmptyValue: false + schema: + type: string responses: 200: description: EI producer identifiers @@ -255,7 +262,7 @@ paths: /ei-producer/v1/eitypes/{eiTypeId}: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: Individual EI type operationId: getEiTypeUsingGET_1 parameters: @@ -285,6 +292,83 @@ paths: schema: $ref: '#/components/schemas/ProblemDetails' deprecated: false + put: + tags: + - Data Producer Registry API + summary: Individual EI type + operationId: putEiTypeUsingPUT + parameters: + - name: eiTypeId + in: path + description: eiTypeId + required: true + schema: + type: string + requestBody: + description: registrationInfo + content: + application/json: + schema: + $ref: '#/components/schemas/producer_ei_type_info' + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 201: + description: Created + content: {} + 401: + description: Unauthorized + content: {} + 403: + description: Forbidden + content: {} + 404: + description: Not Found + content: {} + deprecated: false + delete: + tags: + - Data Producer Registry API + summary: Individual EI type + operationId: deleteEiTypeUsingDELETE + parameters: + - name: eiTypeId + in: path + description: eiTypeId + required: true + schema: + type: string + responses: + 200: + description: Not used + content: {} + 204: + description: Producer deleted + content: {} + 401: + description: Unauthorized + content: {} + 403: + description: Forbidden + content: {} + 404: + description: Enrichment Information type is not found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + 406: + description: The Enrichment Information type has one or several active producers + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + deprecated: false /status: get: tags: @@ -415,7 +499,7 @@ paths: /ei-producer/v1/eiproducers/{eiProducerId}: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: Individual EI producer operationId: getEiProducerUsingGET parameters: @@ -447,7 +531,7 @@ paths: deprecated: false put: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: Individual EI producer operationId: putEiProducerUsingPUT parameters: @@ -483,7 +567,7 @@ paths: deprecated: false delete: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: Individual EI producer operationId: deleteEiProducerUsingDELETE parameters: @@ -516,7 +600,7 @@ paths: /producer_simulator/health_check: get: tags: - - Producer Callbacks + - Data Producer Job Control (example producer) summary: Producer supervision operationId: producerSupervisionUsingGET responses: @@ -539,7 +623,7 @@ paths: /ei-producer/v1/eiproducers/{eiProducerId}/eijobs: get: tags: - - Enrichment Data Producer API + - Data Producer Registry API summary: EI job definitions description: EI job definitions for one EI producer operationId: getEiProducerJobsUsingGET @@ -676,20 +760,6 @@ components: title: EiTypeObject type: object description: Information for an EI type - producer_ei_type_registration_info: - title: producer_ei_type_registration_info - required: - - ei_type_identity - type: object - properties: - ei_type_identity: - type: string - description: EI type identity - ei_job_data_schema: - type: object - properties: {} - description: Json schema for the job data - description: Information for an EI type status_info: title: status_info type: object @@ -716,11 +786,6 @@ components: title: producer_ei_type_info type: object properties: - ei_producer_ids: - type: array - description: Registered producers - items: - type: string ei_job_data_schema: type: object properties: {} @@ -736,9 +801,9 @@ components: properties: supported_ei_types: type: array - description: Supported EI types + description: Supported EI type IDs items: - $ref: '#/components/schemas/producer_ei_type_registration_info' + type: string ei_producer_supervision_callback_url: type: string description: callback for producer supervision 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 785ddfc7..f5aca7e6 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 @@ -28,7 +28,6 @@ import org.apache.catalina.connector.Connector; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.controllers.producer.ProducerCallbacks; import org.oransc.enrichment.repository.EiJobs; -import org.oransc.enrichment.repository.EiProducers; import org.oransc.enrichment.repository.EiTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +49,6 @@ class BeanFactory { private ProducerCallbacks producerCallbacks; private EiTypes eiTypes; private EiJobs eiJobs; - private EiProducers eiProducers; @Bean public ObjectMapper mapper() { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java index eb85d376..7ccf61d0 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java @@ -24,13 +24,13 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.lang.invoke.MethodHandles; +import java.util.Collection; import org.oransc.enrichment.clients.AsyncRestClient; import org.oransc.enrichment.clients.AsyncRestClientFactory; import org.oransc.enrichment.configuration.ApplicationConfig; 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.slf4j.Logger; @@ -38,6 +38,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + /** * Callbacks to the EiProducer */ @@ -60,44 +63,28 @@ public class ConsumerCallbacks { this.eiProducers = eiProducers; } - public void notifyConsumersProducerDeleted(EiProducer eiProducer) { - for (EiType type : eiProducer.getEiTypes()) { - if (this.eiProducers.getProducersForType(type).isEmpty()) { - // No producers left for the type - for (EiJob job : this.eiJobs.getJobsForType(type)) { - if (job.isLastStatusReportedEnabled()) { - noifyJobOwner(job, new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED)); - job.setLastReportedStatus(false); - } - } - } - } - } - - public void notifyConsumersProducerAdded(EiProducer eiProducer) { - for (EiType type : eiProducer.getEiTypes()) { - notifyConsumersTypeAdded(type); - } + public Flux notifyJobStatus(Collection eiTypes) { + return Flux.fromIterable(eiTypes) // + .flatMap(eiType -> Flux.fromIterable(this.eiJobs.getJobsForType(eiType))) // + .filter(eiJob -> !eiJob.getJobStatusUrl().isEmpty()) // + .filter(eiJob -> this.eiProducers.isJobEnabled(eiJob) != eiJob.isLastStatusReportedEnabled()) + .flatMap(this::noifyStatusToJobOwner); } - public void notifyConsumersTypeAdded(EiType eiType) { - for (EiJob job : this.eiJobs.getJobsForType(eiType)) { - if (!job.isLastStatusReportedEnabled()) { - noifyJobOwner(job, new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED)); - job.setLastReportedStatus(true); - } - } - } + private Mono noifyStatusToJobOwner(EiJob job) { + boolean isJobEnabled = this.eiProducers.isJobEnabled(job); + ConsumerEiJobStatus status = + isJobEnabled ? new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED) + : new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); + String body = gson.toJson(status); + return this.restClient.post(job.getJobStatusUrl(), body) // + .doOnNext(response -> logger.debug("Consumer notified OK {}", job.getId())) // + .doOnNext(response -> job.setLastReportedStatus(isJobEnabled)) // + .onErrorResume(throwable -> { + logger.warn("Consumer notify failed {} {}", job.getJobStatusUrl(), throwable.toString()); + return Mono.empty(); + }); - private void noifyJobOwner(EiJob job, ConsumerEiJobStatus status) { - if (!job.getJobStatusUrl().isEmpty()) { - String body = gson.toJson(status); - this.restClient.post(job.getJobStatusUrl(), body) // - .subscribe(notUsed -> logger.debug("Consumer notified OK {}", job.getId()), // - throwable -> logger.warn("Consumer notify failed {} {}", job.getJobStatusUrl(), - throwable.toString()), // - null); - } } } 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 6e9438d3..8267b6af 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 @@ -30,8 +30,8 @@ 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.Collection; import java.util.List; import org.everit.json.schema.Schema; @@ -44,10 +44,11 @@ import org.oransc.enrichment.controllers.producer.ProducerCallbacks; import org.oransc.enrichment.exceptions.ServiceException; 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.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -68,6 +69,8 @@ import reactor.core.publisher.Mono; @RequestMapping(path = ConsumerConsts.API_ROOT, produces = MediaType.APPLICATION_JSON_VALUE) public class ConsumerController { + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @Autowired ApplicationConfig applicationConfig; @@ -208,16 +211,11 @@ public class ConsumerController { } } - private Collection getProducers(EiJob eiJob) { - return this.eiProducers.getProducersForType(eiJob.getTypeId()); - } - private ConsumerEiJobStatus toEiJobStatus(EiJob job) { - if (getProducers(job).isEmpty()) { - return new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); - } else { - return new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED); - } + return this.eiProducers.isJobEnabled(job) + ? new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED) + : new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); + } @DeleteMapping(path = "/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -269,13 +267,9 @@ public class ConsumerController { private Mono startEiJob(EiJob newEiJob) { return this.producerCallbacks.startEiJob(newEiJob, eiProducers) // - .flatMap(noOfAcceptingProducers -> { - if (noOfAcceptingProducers.intValue() > 0) { - return Mono.just(newEiJob); - } else { - return Mono.error(new ServiceException("Job not accepted by any producers", HttpStatus.CONFLICT)); - } - }); + .doOnNext(noOfAcceptingProducers -> this.logger.debug( + "Started EI job {}, number of activated producers: {}", newEiJob.getId(), noOfAcceptingProducers)) // + .flatMap(noOfAcceptingProducers -> Mono.just(newEiJob)); } private Mono validatePutEiJob(String eiJobId, ConsumerEiJobInfo eiJobInfo) { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java index 45b44754..6d74b497 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java @@ -61,7 +61,7 @@ public class ProducerCallbacks { for (EiProducer producer : getProducersForJob(eiJob, eiProducers)) { String url = producer.getJobCallbackUrl() + "/" + eiJob.getId(); restClient.delete(url) // - .subscribe(notUsed -> logger.debug("Producer job deleted OK {}", producer.getId()), // + .subscribe(response -> logger.debug("Producer job deleted OK {}", producer.getId()), // throwable -> logger.warn("Producer job delete failed {} {}", producer.getId(), throwable.getMessage()), null); @@ -88,18 +88,18 @@ public class ProducerCallbacks { * @param producer * @param eiJobs */ - public void restartEiJobs(EiProducer producer, EiJobs eiJobs) { + public Flux restartEiJobs(EiProducer producer, EiJobs eiJobs) { final int maxNoOfParalellRequests = 10; Retry retrySpec = Retry.backoff(3, Duration.ofSeconds(1)); - Flux.fromIterable(producer.getEiTypes()) // + return Flux.fromIterable(producer.getEiTypes()) // .flatMap(type -> Flux.fromIterable(eiJobs.getJobsForType(type))) // .flatMap(job -> postStartEiJob(producer, job, retrySpec), maxNoOfParalellRequests) // .onErrorResume(t -> { logger.error("Could not restart EI Job for producer: {}, reason :{}", producer.getId(), t.getMessage()); return Flux.empty(); - }) // - .subscribe(); + }); // + } private Mono postStartEiJob(EiProducer producer, EiJob eiJob, Retry retrySpec) { @@ -109,10 +109,12 @@ public class ProducerCallbacks { return restClient.post(producer.getJobCallbackUrl(), body) // .retryWhen(retrySpec) // .doOnNext(resp -> logger.debug("Job subscription {} started OK {}", eiJob.getId(), producer.getId())) // + .doOnNext(resp -> producer.setJobDisabled(eiJob)) // .onErrorResume(throwable -> { logger.warn("Job subscription failed {}", producer.getId(), throwable.toString()); return Mono.empty(); - }); + }) // + .doOnNext(resp -> producer.setJobEnabled(eiJob)); } private Collection getProducersForJob(EiJob eiJob, EiProducers eiProducers) { 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 index 9722594f..7c46117e 100644 --- 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 @@ -23,7 +23,7 @@ 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"; + public static final String PRODUCER_API_NAME = "Data Producer Registry 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 index e773117a..349e5d56 100644 --- 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 @@ -25,17 +25,17 @@ import com.google.gson.GsonBuilder; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +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.Collection; import java.util.List; import org.oransc.enrichment.controllers.ErrorResponse; import org.oransc.enrichment.controllers.VoidResponse; -import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; +import org.oransc.enrichment.exceptions.ServiceException; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; import org.oransc.enrichment.repository.EiProducer; @@ -43,9 +43,6 @@ import org.oransc.enrichment.repository.EiProducers; import org.oransc.enrichment.repository.EiType; import org.oransc.enrichment.repository.EiTypes; import org.oransc.enrichment.repository.ImmutableEiProducerRegistrationInfo; -import org.oransc.enrichment.repository.ImmutableEiTypeRegistrationInfo; -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; @@ -55,6 +52,7 @@ 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally @@ -62,8 +60,6 @@ import org.springframework.web.bind.annotation.RestController; @Api(tags = {ProducerConsts.PRODUCER_API_NAME}) public class ProducerController { - private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private static Gson gson = new GsonBuilder().create(); @Autowired @@ -115,6 +111,45 @@ public class ProducerController { } } + @PutMapping(path = ProducerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Individual EI type", notes = "") + public ResponseEntity putEiType( // + @PathVariable("eiTypeId") String eiTypeId, @RequestBody ProducerEiTypeInfo registrationInfo) { + + EiType previousDefinition = this.eiTypes.get(eiTypeId); + this.eiTypes.put(new EiType(eiTypeId, registrationInfo.jobDataSchema)); + return new ResponseEntity<>(previousDefinition == null ? HttpStatus.CREATED : HttpStatus.OK); + } + + @DeleteMapping(path = ProducerConsts.API_ROOT + "/eitypes/{eiTypeId}", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Individual EI type", notes = "") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class), + @ApiResponse(code = 204, message = "Producer deleted", response = VoidResponse.class), + @ApiResponse( + code = 404, + message = "Enrichment Information type is not found", + response = ErrorResponse.ErrorInfo.class), + @ApiResponse( + code = 406, + message = "The Enrichment Information type has one or several active producers", + response = ErrorResponse.ErrorInfo.class)}) + public ResponseEntity deleteEiType( // + @PathVariable("eiTypeId") String eiTypeId) { + + EiType type = this.eiTypes.get(eiTypeId); + if (type == null) { + return ErrorResponse.create("EI type not found", HttpStatus.NOT_FOUND); + } + if (!this.eiProducers.getProducersForType(type).isEmpty()) { + String firstProducerId = this.eiProducers.getProducersForType(type).iterator().next().getId(); + return ErrorResponse.create("The type has active producers: " + firstProducerId, HttpStatus.NOT_ACCEPTABLE); + } + this.eiTypes.remove(type); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + @GetMapping(path = ProducerConsts.API_ROOT + "/eiproducers", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "EI producer identifiers", notes = "") @ApiResponses( @@ -126,9 +161,15 @@ public class ProducerController { responseContainer = "List"), // }) public ResponseEntity getEiProducerIdentifiers( // + @ApiParam( + name = "ei_type_id", + required = false, + value = "If given, only the producers for the EI Data type is returned.") // + @RequestParam(name = "ei_type_id", required = false) String typeId // ) { List result = new ArrayList<>(); - for (EiProducer eiProducer : this.eiProducers.getAllProducers()) { + for (EiProducer eiProducer : typeId == null ? this.eiProducers.getAllProducers() + : this.eiProducers.getProducersForType(typeId)) { result.add(eiProducer.getId()); } @@ -247,7 +288,7 @@ public class ProducerController { public ResponseEntity deleteEiProducer(@PathVariable("eiProducerId") String eiProducerId) { try { final EiProducer producer = this.eiProducers.getProducer(eiProducerId); - this.eiProducers.deregisterProducer(producer, this.eiTypes); + this.eiProducers.deregisterProducer(producer); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); @@ -255,32 +296,25 @@ public class ProducerController { } private ProducerRegistrationInfo toEiProducerRegistrationInfo(EiProducer p) { - Collection types = new ArrayList<>(); + Collection types = new ArrayList<>(); for (EiType type : p.getEiTypes()) { - types.add(toEiTypeRegistrationInfo(type)); + types.add(type.getId()); } return new ProducerRegistrationInfo(types, p.getJobCallbackUrl(), p.getProducerSupervisionCallbackUrl()); } - private ProducerEiTypeRegistrationInfo toEiTypeRegistrationInfo(EiType type) { - return new ProducerEiTypeRegistrationInfo(type.getJobDataSchema(), type.getId()); - } - private ProducerEiTypeInfo toEiTypeInfo(EiType t) { - Collection producerIds = this.eiProducers.getProducerIdsForType(t.getId()); - return new ProducerEiTypeInfo(t.getJobDataSchema(), producerIds); + return new ProducerEiTypeInfo(t.getJobDataSchema()); } private EiProducers.EiProducerRegistrationInfo toEiProducerRegistrationInfo(String eiProducerId, - ProducerRegistrationInfo info) { - Collection supportedTypes = new ArrayList<>(); - for (ProducerEiTypeRegistrationInfo typeInfo : info.types) { - EiProducers.EiTypeRegistrationInfo i = ImmutableEiTypeRegistrationInfo.builder() // - .id(typeInfo.eiTypeId) // - .jobDataSchema(typeInfo.jobDataSchema) // - .build(); - supportedTypes.add(i); + ProducerRegistrationInfo info) throws ServiceException { + Collection supportedTypes = new ArrayList<>(); + for (String typeId : info.supportedTypeIds) { + EiType type = this.eiTypes.getType(typeId); + supportedTypes.add(type); } + return ImmutableEiProducerRegistrationInfo.builder() // .id(eiProducerId) // .jobCallbackUrl(info.jobCallbackUrl) // 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 index f13f9813..35220810 100644 --- 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 @@ -26,8 +26,6 @@ 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 @@ -39,14 +37,8 @@ public class ProducerEiTypeInfo { @JsonProperty("ei_job_data_schema") public Object jobDataSchema; - @ApiModelProperty(value = "Registered producers") - @SerializedName("ei_producer_ids") - @JsonProperty(value = "ei_producer_ids", required = true) - public Collection producerIds; - - public ProducerEiTypeInfo(Object jobDataSchema, Collection producers) { + public ProducerEiTypeInfo(Object jobDataSchema) { 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 index 3f785803..94ae4a6a 100644 --- 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 @@ -57,10 +57,10 @@ public class ProducerRegistrationInfo { } } - @ApiModelProperty(value = "Supported EI types", required = true) + @ApiModelProperty(value = "Supported EI type IDs", required = true) @SerializedName("supported_ei_types") @JsonProperty(value = "supported_ei_types", required = true) - public Collection types; + public Collection supportedTypeIds; @ApiModelProperty(value = "callback for EI job", required = true) @SerializedName("ei_job_callback_url") @@ -72,9 +72,9 @@ public class ProducerRegistrationInfo { @JsonProperty(value = "ei_producer_supervision_callback_url", required = true) public String producerSupervisionCallbackUrl; - public ProducerRegistrationInfo(Collection types, String jobCallbackUrl, + public ProducerRegistrationInfo(Collection types, String jobCallbackUrl, String producerSupervisionCallbackUrl) { - this.types = types; + this.supportedTypeIds = types; this.jobCallbackUrl = jobCallbackUrl; this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl; } 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 index 63e5d4c9..d5423ae1 100644 --- 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 @@ -21,6 +21,8 @@ package org.oransc.enrichment.repository; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import lombok.Getter; @@ -37,6 +39,8 @@ public class EiProducer { @Getter private final String producerSupervisionCallbackUrl; + private final Set enabledJobs = new HashSet<>(); + private int unresponsiveCounter = 0; public EiProducer(String id, Collection eiTypes, String jobCallbackUrl, @@ -63,4 +67,16 @@ public class EiProducer { return this.unresponsiveCounter == 0; } + public synchronized void setJobEnabled(EiJob job) { + this.enabledJobs.add(job.getId()); + } + + public synchronized void setJobDisabled(EiJob job) { + this.enabledJobs.remove(job.getId()); + } + + synchronized boolean isJobEnabled(EiJob job) { + return this.enabledJobs.contains(job.getId()); + } + } 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 index fcc91568..a0d30e8b 100644 --- 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 @@ -52,29 +52,18 @@ public class EiProducers { @Autowired private ConsumerCallbacks consumerCallbacks; - @Autowired - private EiTypes eiTypes; - @Autowired private EiJobs eiJobs; - @Immutable - public interface EiTypeRegistrationInfo { - String id(); - - Object jobDataSchema(); - } - @Immutable public interface EiProducerRegistrationInfo { String id(); - Collection supportedTypes(); + Collection supportedTypes(); String jobCallbackUrl(); String producerSupervisionCallbackUrl(); - } public EiProducer registerProducer(EiProducerRegistrationInfo producerInfo) { @@ -93,45 +82,22 @@ public class EiProducers { producersByType.put(type.getId(), producer.getId(), producer); } - if (previousDefinition != null) { - purgeTypes(previousDefinition.getEiTypes()); - this.consumerCallbacks.notifyConsumersProducerDeleted(previousDefinition); - } + Collection previousTypes = + previousDefinition != null ? previousDefinition.getEiTypes() : new ArrayList<>(); - producerCallbacks.restartEiJobs(producer, this.eiJobs); - consumerCallbacks.notifyConsumersProducerAdded(producer); - return producer; - } + producerCallbacks.restartEiJobs(producer, this.eiJobs) // + .collectList() // + .flatMapMany(list -> consumerCallbacks.notifyJobStatus(producer.getEiTypes())) // + .collectList() // + .flatMapMany(list -> consumerCallbacks.notifyJobStatus(previousTypes)) // + .subscribe(); - private void purgeTypes(Collection types) { - for (EiType type : types) { - if (getProducersForType(type.getId()).isEmpty()) { - this.eiTypes.remove(type); - } - } - } - - private EiType getType(EiTypeRegistrationInfo typeInfo) { - EiType type = this.eiTypes.get(typeInfo.id()); - if (type == null) { - type = new EiType(typeInfo.id(), typeInfo.jobDataSchema()); - this.eiTypes.put(type); - this.consumerCallbacks.notifyConsumersTypeAdded(type); - } - return type; + return producer; } private EiProducer createProducer(EiProducerRegistrationInfo producerInfo) { - ArrayList types = new ArrayList<>(); - - EiProducer producer = new EiProducer(producerInfo.id(), types, producerInfo.jobCallbackUrl(), + return new EiProducer(producerInfo.id(), producerInfo.supportedTypes(), producerInfo.jobCallbackUrl(), producerInfo.producerSupervisionCallbackUrl()); - - for (EiTypeRegistrationInfo typeInfo : producerInfo.supportedTypes()) { - EiType type = getType(typeInfo); - types.add(type); - } - return producer; } public synchronized Collection getAllProducers() { @@ -159,17 +125,15 @@ public class EiProducers { this.producersByType.clear(); } - public void deregisterProducer(EiProducer producer, EiTypes eiTypes) { + public void deregisterProducer(EiProducer producer) { allEiProducers.remove(producer.getId()); for (EiType type : producer.getEiTypes()) { if (producersByType.remove(type.getId(), producer.getId()) == null) { this.logger.error("Bug, no producer found"); } - if (this.producersByType.get(type.getId()).isEmpty()) { - eiTypes.remove(type); - } } - this.consumerCallbacks.notifyConsumersProducerDeleted(producer); + this.consumerCallbacks.notifyJobStatus(producer.getEiTypes()) // + .subscribe(); } public synchronized Collection getProducersForType(EiType type) { @@ -188,4 +152,13 @@ public class EiProducers { return producerIds; } + public synchronized boolean isJobEnabled(EiJob job) { + for (EiProducer producer : this.producersByType.get(job.getTypeId())) { + if (producer.isJobEnabled(job)) { + return true; + } + } + return false; + } + } 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 265a8e41..363a65e2 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 @@ -84,7 +84,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 type: " + id); + throw new ServiceException("EI type not found: " + id); } return type; } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java index d8ee9707..17c77b36 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java @@ -23,11 +23,8 @@ package org.oransc.enrichment.tasks; import org.oransc.enrichment.clients.AsyncRestClient; import org.oransc.enrichment.clients.AsyncRestClientFactory; import org.oransc.enrichment.configuration.ApplicationConfig; -import org.oransc.enrichment.controllers.consumer.ConsumerCallbacks; -import org.oransc.enrichment.repository.EiJobs; import org.oransc.enrichment.repository.EiProducer; import org.oransc.enrichment.repository.EiProducers; -import org.oransc.enrichment.repository.EiTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,20 +45,13 @@ public class ProducerSupervision { private static final Logger logger = LoggerFactory.getLogger(ProducerSupervision.class); private final EiProducers eiProducers; - private final EiJobs eiJobs; - private final EiTypes eiTypes; private final AsyncRestClient restClient; - private final ConsumerCallbacks consumerCallbacks; @Autowired - public ProducerSupervision(ApplicationConfig applicationConfig, EiProducers eiProducers, EiJobs eiJobs, - EiTypes eiTypes, ConsumerCallbacks consumerCallbacks) { + public ProducerSupervision(ApplicationConfig applicationConfig, EiProducers eiProducers) { AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(applicationConfig.getWebClientConfig()); this.restClient = restClientFactory.createRestClientNoHttpProxy(""); - this.eiJobs = eiJobs; this.eiProducers = eiProducers; - this.eiTypes = eiTypes; - this.consumerCallbacks = consumerCallbacks; } @Scheduled(fixedRate = 1000 * 60 * 5) @@ -89,8 +79,7 @@ public class ProducerSupervision { logger.warn("Unresponsive producer: {} exception: {}", producer.getId(), throwable.getMessage()); producer.setAliveStatus(false); if (producer.isDead()) { - this.eiProducers.deregisterProducer(producer, this.eiTypes); - this.consumerCallbacks.notifyConsumersProducerDeleted(producer); + this.eiProducers.deregisterProducer(producer); } } 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 9f3dd496..d0828357 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 @@ -34,8 +34,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Arrays; import org.json.JSONObject; import org.junit.jupiter.api.AfterEach; @@ -57,6 +56,7 @@ import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus; import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo; import org.oransc.enrichment.controllers.producer.ProducerCallbacks; import org.oransc.enrichment.controllers.producer.ProducerConsts; +import org.oransc.enrichment.controllers.producer.ProducerEiTypeInfo; import org.oransc.enrichment.controllers.producer.ProducerJobInfo; import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo; import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; @@ -190,6 +190,12 @@ class ApplicationTest { assertThat(rsp).isEqualTo("[]"); } + @Test + void testPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException { + assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.CREATED); + assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.OK); + } + @Test void testGetEiType() throws Exception { putEiProducerWithOneType(EI_PRODUCER_ID, "test"); @@ -199,10 +205,29 @@ class ApplicationTest { assertThat(info).isNotNull(); } + @Test + void testDeleteEiType() throws Exception { + putEiType(EI_TYPE_ID); + String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID; + restClient().delete(url).block(); + assertThat(this.eiTypes.size()).isEqualTo(0); + + testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "EI type not found"); + } + + @Test + void testDeleteEiTypeExistingProducer() throws Exception { + putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); + String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID; + testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE, + "The type has active producers: " + EI_PRODUCER_ID); + assertThat(this.eiTypes.size()).isEqualTo(1); + } + @Test void testGetEiTypeNotFound() throws Exception { String url = ConsumerConsts.API_ROOT + "/eitypes/junk"; - testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk"); + testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "EI type not found: junk"); } @Test @@ -307,19 +332,24 @@ class ApplicationTest { assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); EiJob job = this.eiJobs.getJob("jobId"); assertThat(job.getOwner()).isEqualTo("owner"); + + verifyJobStatus(EI_JOB_ID, "ENABLED"); } @Test void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException { putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID); - String url = ConsumerConsts.API_ROOT + "/eijobs/jobId"; + String url = ConsumerConsts.API_ROOT + "/eijobs/" + EI_JOB_ID; String body = gson.toJson(eiJobInfo()); - testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Job not accepted by any producers"); + restClient().put(url, body).block(); ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults(); // There is one retry -> 2 calls await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2)); assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2); + + verifyJobStatus(EI_JOB_ID, "DISABLED"); + } @Test @@ -350,20 +380,6 @@ class ApplicationTest { assertThat(resp.getBody()).contains(EI_TYPE_ID_2); } - @Test - void testReplacingEiProducerTypes() throws Exception { - final String REPLACED_TYPE_ID = "replaced"; - putEiProducerWithOneType(EI_PRODUCER_ID, REPLACED_TYPE_ID); - putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); - - String url = ProducerConsts.API_ROOT + "/eitypes"; - - ResponseEntity resp = restClient().getForEntity(url).block(); - assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(resp.getBody()).contains(EI_TYPE_ID); - assertThat(resp.getBody()).doesNotContain(REPLACED_TYPE_ID); - } - @Test void testChangingEiTypeGetRejected() throws Exception { putEiProducerWithOneType("producer1", "typeId1"); @@ -378,6 +394,7 @@ class ApplicationTest { @Test void testPutEiProducer() throws Exception { + this.putEiType(EI_TYPE_ID); String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID)); @@ -385,7 +402,6 @@ class ApplicationTest { assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(this.eiTypes.size()).isEqualTo(1); - EiType type = this.eiTypes.getType(EI_TYPE_ID); assertThat(this.eiProducers.getProducersForType(EI_TYPE_ID).size()).isEqualTo(1); assertThat(this.eiProducers.size()).isEqualTo(1); assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID); @@ -414,6 +430,7 @@ class ApplicationTest { @Test void testPutProducerAndEiJob() throws Exception { + this.putEiType(EI_TYPE_ID); String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID)); restClient().putForEntity(url, body).block(); @@ -469,7 +486,7 @@ class ApplicationTest { deleteEiProducer("eiProducerId2"); assertThat(this.eiProducers.size()).isZero(); - assertThat(this.eiTypes.size()).isZero(); + assertThat(this.eiTypes.size()).isEqualTo(1); verifyJobStatus("jobId", "DISABLED"); } @@ -486,7 +503,7 @@ class ApplicationTest { deleteEiProducer("eiProducerId2"); assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left deleteEiProducer("eiProducerId"); - assertThat(this.eiTypes.size()).isZero(); // The type is gone + assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1)); assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); @@ -504,7 +521,7 @@ class ApplicationTest { putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); putEiJob(EI_TYPE_ID, EI_JOB_ID); - // change the type for the producer, the EI_TYPE_ID is deleted + // change the type for the producer, the job shall be disabled putEiProducerWithOneType(EI_PRODUCER_ID, "junk"); verifyJobStatus(EI_JOB_ID, "DISABLED"); ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults(); @@ -522,7 +539,8 @@ class ApplicationTest { putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID; ResponseEntity resp = restClient().getForEntity(url).block(); - assertThat(resp.getBody()).contains(EI_PRODUCER_ID); + ProducerEiTypeInfo info = gson.fromJson(resp.getBody(), ProducerEiTypeInfo.class); + assertThat(info.jobDataSchema).isNotNull(); } @Test @@ -531,6 +549,14 @@ class ApplicationTest { String url = ProducerConsts.API_ROOT + "/eiproducers"; ResponseEntity resp = restClient().getForEntity(url).block(); assertThat(resp.getBody()).contains(EI_PRODUCER_ID); + + url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=" + EI_TYPE_ID; + resp = restClient().getForEntity(url).block(); + assertThat(resp.getBody()).contains(EI_PRODUCER_ID); + + url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=junk"; + resp = restClient().getForEntity(url).block(); + assertThat(resp.getBody()).isEqualTo("[]"); } @Test @@ -541,9 +567,16 @@ class ApplicationTest { // Create a job putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); putEiJob(EI_TYPE_ID, EI_JOB_ID); + verifyJobStatus(EI_JOB_ID, "ENABLED"); deleteEiProducer(EI_PRODUCER_ID); + verifyJobStatus(EI_JOB_ID, "DISABLED"); } + // Job disabled status notification shall be received + ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); + assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); + assertThat(this.eiProducers.size()).isEqualTo(1); assertThat(this.eiTypes.size()).isEqualTo(1); assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED); @@ -551,22 +584,15 @@ class ApplicationTest { this.producerSupervision.createTask().blockLast(); this.producerSupervision.createTask().blockLast(); - // Now we have one producer that is disabled, but the job will be enabled until - // the producer/type is removed + // Now we have one producer that is disabled assertThat(this.eiProducers.size()).isEqualTo(1); assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED); - verifyJobStatus(EI_JOB_ID, "ENABLED"); // After 3 failed checks, the producer and the type shall be deregisterred this.producerSupervision.createTask().blockLast(); assertThat(this.eiProducers.size()).isEqualTo(0); // The producer is removed - assertThat(this.eiTypes.size()).isEqualTo(0); // The type is removed - verifyJobStatus(EI_JOB_ID, "DISABLED"); + assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains - // Job disabled status notification shall be received - ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults(); - await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); - assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); } @Test @@ -660,18 +686,14 @@ class ApplicationTest { ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId) throws JsonMappingException, JsonProcessingException { - Collection types = new ArrayList<>(); - types.add(producerEiTypeRegistrationInfo(typeId)); - return new ProducerRegistrationInfo(types, // + return new ProducerRegistrationInfo(Arrays.asList(typeId), // baseUrl() + ProducerSimulatorController.JOB_ERROR_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL); } ProducerRegistrationInfo producerEiRegistratioInfo(String typeId) throws JsonMappingException, JsonProcessingException { - Collection types = new ArrayList<>(); - types.add(producerEiTypeRegistrationInfo(typeId)); - return new ProducerRegistrationInfo(types, // + return new ProducerRegistrationInfo(Arrays.asList(typeId), // baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL); } @@ -723,17 +745,29 @@ class ApplicationTest { return this.eiJobs.getJob(jobId); } + private HttpStatus putEiType(String eiTypeId) + throws JsonMappingException, JsonProcessingException, ServiceException { + String url = ProducerConsts.API_ROOT + "/eitypes/" + eiTypeId; + String body = gson.toJson(producerEiTypeRegistrationInfo(eiTypeId)); + ResponseEntity resp = restClient().putForEntity(url, body).block(); + this.eiTypes.getType(eiTypeId); + return resp.getStatusCode(); + + } + private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId) throws JsonMappingException, JsonProcessingException, ServiceException { + this.putEiType(eiTypeId); String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId; String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId)); - restClient().putForEntity(url, body).block(); return this.eiTypes.getType(eiTypeId); } private EiType putEiProducerWithOneType(String producerId, String eiTypeId) throws JsonMappingException, JsonProcessingException, ServiceException { + this.putEiType(eiTypeId); + String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId; String body = gson.toJson(producerEiRegistratioInfo(eiTypeId)); diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java index 562f2869..e8bb659c 100644 --- a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java @@ -45,7 +45,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController("ConsumerSimulatorController") -@Api(tags = {"Consumer Callbacks"}) +@Api(tags = {"A1-EI (enrichment information) callbacks"}) public class ConsumerSimulatorController { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); 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 index 2a3688cf..726cef7e 100644 --- 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 @@ -48,7 +48,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController("ProducerSimulatorController") -@Api(tags = {"Producer Callbacks"}) +@Api(tags = {"Data Producer Job Control (example producer)"}) public class ProducerSimulatorController { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -134,7 +134,7 @@ public class ProducerSimulatorController { } @DeleteMapping(path = JOB_ERROR_URL + "/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "Callback for EI job creation, returns error", notes = "", hidden = true) + @ApiOperation(value = "Callback for EI job deletion, returns error", notes = "", hidden = true) @ApiResponses( value = { // @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//