Update of EI Data Producer API 02/5402/5
authorPatrikBuhr <patrik.buhr@est.tech>
Mon, 11 Jan 2021 14:03:54 +0000 (15:03 +0100)
committerPatrikBuhr <patrik.buhr@est.tech>
Wed, 13 Jan 2021 19:08:28 +0000 (20:08 +0100)
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 <patrik.buhr@est.tech>
Issue-ID: NONRTRIC-378

17 files changed:
enrichment-coordinator-service/api/ecs-api.json
enrichment-coordinator-service/api/ecs-api.yaml
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerConsts.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerEiTypeInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java

index 79ab5d3..6d2f0fa 100644 (file)
@@ -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",
                 "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",
                 "type": "string",
                 "required": true
             }],
-            "tags": ["Producer Callbacks"]
+            "tags": ["Data Producer Job Control (example producer)"]
         }},
         "/ei-producer/v1/eiproducers": {"get": {
             "summary": "EI producer identifiers",
                 "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,
                     "type": "string",
                     "required": true
                 }],
-                "tags": ["Enrichment Data Producer API"]
+                "tags": ["Data Producer Registry API"]
             },
             "delete": {
                 "summary": "Individual EI producer",
                     "type": "string",
                     "required": true
                 }],
-                "tags": ["Enrichment Data Producer API"]
+                "tags": ["Data Producer Registry API"]
             },
             "put": {
                 "summary": "Individual EI producer",
                         "required": true
                     }
                 ],
-                "tags": ["Enrichment Data Producer API"],
+                "tags": ["Data Producer Registry API"],
                 "consumes": ["application/json"]
             }
         },
                 "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",
                 "type": "string",
                 "required": true
             }],
-            "tags": ["Enrichment Data Producer API"]
+            "tags": ["Data Producer Registry API"]
         }},
         "/A1-EI/v1/eijobs": {"get": {
             "summary": "EI job identifiers",
             "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",
             "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",
             ],
             "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",
             "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",
index ac49e5a..3dd53b6 100644 (file)
@@ -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
index 785ddfc..f5aca7e 100644 (file)
@@ -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() {
index eb85d37..7ccf61d 100644 (file)
@@ -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<String> notifyJobStatus(Collection<EiType> 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<String> 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);
-        }
     }
 
 }
index 6e9438d..8267b6a 100644 (file)
@@ -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<EiProducer> 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<EiJob> 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<EiJob> validatePutEiJob(String eiJobId, ConsumerEiJobInfo eiJobInfo) {
index 45b4475..6d74b49 100644 (file)
@@ -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<String> 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<String> 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<EiProducer> getProducersForJob(EiJob eiJob, EiProducers eiProducers) {
index 9722594..7c46117 100644 (file)
@@ -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() {
     }
index e773117..349e5d5 100644 (file)
@@ -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<Object> 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<Object> 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<Object> 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<String> 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<Object> 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<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
+        Collection<String> 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<String> 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<EiProducers.EiTypeRegistrationInfo> 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<EiType> 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) //
index f13f981..3522081 100644 (file)
@@ -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<String> producerIds;
-
-    public ProducerEiTypeInfo(Object jobDataSchema, Collection<String> producers) {
+    public ProducerEiTypeInfo(Object jobDataSchema) {
         this.jobDataSchema = jobDataSchema;
-        this.producerIds = producers;
     }
 
     public ProducerEiTypeInfo() {
index 3f78580..94ae4a6 100644 (file)
@@ -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<ProducerEiTypeRegistrationInfo> types;
+    public Collection<String> 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<ProducerEiTypeRegistrationInfo> types, String jobCallbackUrl,
+    public ProducerRegistrationInfo(Collection<String> types, String jobCallbackUrl,
         String producerSupervisionCallbackUrl) {
-        this.types = types;
+        this.supportedTypeIds = types;
         this.jobCallbackUrl = jobCallbackUrl;
         this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl;
     }
index 63e5d4c..d5423ae 100644 (file)
@@ -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<String> enabledJobs = new HashSet<>();
+
     private int unresponsiveCounter = 0;
 
     public EiProducer(String id, Collection<EiType> 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());
+    }
+
 }
index fcc9156..a0d30e8 100644 (file)
@@ -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<EiTypeRegistrationInfo> supportedTypes();
+        Collection<EiType> 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<EiType> 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<EiType> 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<EiType> 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<EiProducer> 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<EiProducer> 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;
+    }
+
 }
index 265a8e4..363a65e 100644 (file)
@@ -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;
     }
index d8ee970..17c77b3 100644 (file)
@@ -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);
         }
     }
 
index 9f3dd49..d082835 100644 (file)
@@ -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<String> 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<String> 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<String> 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<ProducerEiTypeRegistrationInfo> 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<ProducerEiTypeRegistrationInfo> 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<String> 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));
 
index 562f286..e8bb659 100644 (file)
@@ -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());
index 2a3688c..726cef7 100644 (file)
@@ -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)}//