Added supervision of producers 97/4797/1
authorPatrikBuhr <patrik.buhr@est.tech>
Thu, 1 Oct 2020 07:40:04 +0000 (09:40 +0200)
committerPatrikBuhr <patrik.buhr@est.tech>
Thu, 1 Oct 2020 07:41:13 +0000 (09:41 +0200)
Change-Id: I69471f80d4951a076458b58542c653ff8d0e58a4
Issue-ID: NONRTRIC-173
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
14 files changed:
enrichment-coordinator-service/docs/api.yaml
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/VoidResponse.java [new file with mode: 0644]
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.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/ProducerRegistrationInfo.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerStatusInfo.java [new file with mode: 0644]
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/EiType.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 [new file with mode: 0644]
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java

index 570caee..b643e8e 100644 (file)
@@ -3,7 +3,7 @@ info:
   description: This page lists all the rest apis for the service.
   version: '1.0'
   title: Enrichment Data service
-host: 'localhost:8081'
+host: 'localhost:8082'
 basePath: /
 tags:
   - name: A1-E Enrichment Data Consumer API
@@ -53,7 +53,7 @@ paths:
         '200':
           description: EI type
           schema:
-            $ref: '#/definitions/ei_type_info'
+            $ref: '#/definitions/EiType'
         '401':
           description: Unauthorized
         '403':
@@ -122,7 +122,7 @@ paths:
         '200':
           description: EI Job
           schema:
-            $ref: '#/definitions/ei_job_info'
+            $ref: '#/definitions/EiJob'
         '401':
           description: Unauthorized
         '403':
@@ -152,7 +152,7 @@ paths:
           description: eiJobInfo
           required: true
           schema:
-            $ref: '#/definitions/ei_job_info'
+            $ref: '#/definitions/EiJob'
         - name: eiTypeId
           in: path
           description: eiTypeId
@@ -161,12 +161,8 @@ paths:
       responses:
         '200':
           description: Job updated
-          schema:
-            type: object
         '201':
           description: Job created
-          schema:
-            type: object
         '401':
           description: Unauthorized
         '403':
@@ -197,12 +193,8 @@ paths:
       responses:
         '200':
           description: Not used
-          schema:
-            type: object
         '204':
           description: Job deleted
-          schema:
-            type: object
         '401':
           description: Unauthorized
         '403':
@@ -235,7 +227,7 @@ paths:
         '200':
           description: EI Job status
           schema:
-            $ref: '#/definitions/ei_job_status'
+            $ref: '#/definitions/EiJobStatus'
         '401':
           description: Unauthorized
         '403':
@@ -319,12 +311,8 @@ paths:
       responses:
         '200':
           description: Producer updated
-          schema:
-            type: object
         '201':
           description: Producer created
-          schema:
-            type: object
         '401':
           description: Unauthorized
         '403':
@@ -348,12 +336,8 @@ paths:
       responses:
         '200':
           description: Not used
-          schema:
-            type: object
         '204':
           description: Producer deleted
-          schema:
-            type: object
         '401':
           description: Unauthorized
         '403':
@@ -394,6 +378,34 @@ paths:
           schema:
             $ref: '#/definitions/error_information'
       deprecated: false
+  '/ei-producer/v1/eiproducers/{eiProducerId}/status':
+    get:
+      tags:
+        - Enrichment Data Producer API
+      summary: EI producer status
+      operationId: getEiProducerStatusUsingGET
+      produces:
+        - application/json
+      parameters:
+        - name: eiProducerId
+          in: path
+          description: eiProducerId
+          required: true
+          type: string
+      responses:
+        '200':
+          description: EI jobs
+          schema:
+            $ref: '#/definitions/producer_status'
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Enrichment Information producer is not found
+          schema:
+            $ref: '#/definitions/error_information'
+      deprecated: false
   /ei-producer/v1/eitypes:
     get:
       tags:
@@ -464,8 +476,35 @@ paths:
       responses:
         '200':
           description: OK
+        '201':
+          description: Created
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Not Found
+      deprecated: false
+  /producer_simulator/job_created_error:
+    post:
+      tags:
+        - Producer Simulator
+      summary: 'Callback for EI job creation, returns error'
+      operationId: jobCreatedCallbackReturnErrorUsingPOST
+      consumes:
+        - application/json
+      produces:
+        - application/json
+      parameters:
+        - in: body
+          name: request
+          description: request
+          required: true
           schema:
-            type: object
+            $ref: '#/definitions/producer_ei_job_request'
+      responses:
+        '200':
+          description: OK
         '201':
           description: Created
         '401':
@@ -495,8 +534,35 @@ paths:
       responses:
         '200':
           description: OK
+        '201':
+          description: Created
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Not Found
+      deprecated: false
+  /producer_simulator/job_deleted_error:
+    post:
+      tags:
+        - Producer Simulator
+      summary: 'Callback for EI job creation, returns error'
+      operationId: jobDeletedCallbackReturnErrorUsingPOST
+      consumes:
+        - application/json
+      produces:
+        - application/json
+      parameters:
+        - in: body
+          name: request
+          description: request
+          required: true
           schema:
-            type: object
+            $ref: '#/definitions/producer_ei_job_request'
+      responses:
+        '200':
+          description: OK
         '201':
           description: Created
         '401':
@@ -506,31 +572,71 @@ paths:
         '404':
           description: Not Found
       deprecated: false
+  /producer_simulator/supervision:
+    get:
+      tags:
+        - Producer Simulator
+      summary: Producer supervision
+      operationId: producerSupervisionUsingGET
+      produces:
+        - application/json
+      responses:
+        '200':
+          description: OK
+          schema:
+            type: string
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Not Found
+      deprecated: false
+  /producer_simulator/supervision_error:
+    get:
+      tags:
+        - Producer Simulator
+      summary: Producer supervision error
+      operationId: producerSupervisionErrorUsingGET
+      produces:
+        - application/json
+      responses:
+        '200':
+          description: OK
+          schema:
+            type: string
+        '401':
+          description: Unauthorized
+        '403':
+          description: Forbidden
+        '404':
+          description: Not Found
+      deprecated: false
 definitions:
-  ei_job_info:
+  EiJob:
     type: object
     required:
-      - job_data
-      - job_owner
-      - target_uri
+      - jobOwner
+      - jobParameters
+      - targetUri
     properties:
-      job_data:
-        type: object
-        description: EI Type specific job data
-      job_owner:
+      jobOwner:
         type: string
         description: Identity of the owner of the job
-      target_uri:
+      jobParameters:
+        type: object
+        description: EI Type specific job data
+      targetUri:
         type: string
         description: The target of the EI data
-    title: ei_job_info
-    description: Information for a Enrichment Information Job
-  ei_job_status:
+    title: EiJob
+    description: Information for an Enrichment Information Job
+  EiJobStatus:
     type: object
     required:
-      - operational_state
+      - operationalState
     properties:
-      operational_state:
+      operationalState:
         type: string
         description: |-
           Operational state, values:
@@ -539,16 +645,19 @@ definitions:
         enum:
           - ENABLED
           - DISABLED
-    title: ei_job_status
+    title: EiJobStatus
     description: Status for an EI Job
-  ei_type_info:
+  EiType:
     type: object
     properties:
-      job_data_schema:
+      eiJobParametersSchema:
         type: object
         description: Json schema for the job data
-    title: ei_type_info
+    title: EiType
     description: Information for an EI type
+  Mono«ResponseEntity«object»»:
+    type: object
+    title: Mono«ResponseEntity«object»»
   error_information:
     type: object
     properties:
@@ -613,6 +722,7 @@ definitions:
     required:
       - ei_job_creation_callback_url
       - ei_job_deletion_callback_url
+      - ei_producer_supervision_callback_url
       - supported_ei_types
     properties:
       ei_job_creation_callback_url:
@@ -621,6 +731,9 @@ definitions:
       ei_job_deletion_callback_url:
         type: string
         description: callback for job deletion
+      ei_producer_supervision_callback_url:
+        type: string
+        description: callback for producer supervision
       supported_ei_types:
         type: array
         description: Supported EI types
@@ -628,4 +741,24 @@ definitions:
           $ref: '#/definitions/producer_ei_type_registration_info'
     title: producer_registration_info
     description: Information for an EI producer
+  producer_status:
+    type: object
+    required:
+      - operational_state
+    properties:
+      operational_state:
+        type: string
+        description: |-
+          Operational state, values:
+          ENABLED: TBD
+          DISABLED: TBD.
+        enum:
+          - ENABLED
+          - DISABLED
+    title: producer_status
+    description: Status for an EI Producer
+  void:
+    type: object
+    title: void
+    description: Void/empty
 
index f42c6e3..7f28a12 100644 (file)
@@ -26,8 +26,6 @@ import com.google.gson.GsonBuilder;
 import java.lang.invoke.MethodHandles;
 
 import org.oransc.enrichment.configuration.ApplicationConfig;
-import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
-import org.oransc.enrichment.configuration.WebClientConfig;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiProducer;
 import org.slf4j.Logger;
@@ -52,16 +50,22 @@ public class ProducerCallbacks {
     ApplicationConfig applicationConfig;
 
     public void notifyProducersJobDeleted(EiJob eiJob) {
-        AsyncRestClient restClient = restClient(false);
+        AsyncRestClient restClient = restClient();
         ProducerJobInfo request = new ProducerJobInfo(eiJob);
         String body = gson.toJson(request);
         for (EiProducer producer : eiJob.type().getProducers()) {
-            restClient.post(producer.jobDeletionCallbackUrl(), body) //
-                .subscribe(notUsed -> logger.debug("Job subscription started OK {}", producer.id()), //
-                    throwable -> logger.warn("Job subscription failed {}", producer.id(), throwable.toString()), null);
+            restClient.post(producer.getJobDeletionCallbackUrl(), body) //
+                .subscribe(notUsed -> logger.debug("Job deleted OK {}", producer.getId()), //
+                    throwable -> logger.warn("Job delete failed {}", producer.getId(), throwable.toString()), null);
         }
     }
 
+    /**
+     * Calls all producers for an EiJob activation.
+     * 
+     * @param eiJob an EI job
+     * @return the number of producers that returned OK
+     */
     public Mono<Integer> notifyProducersJobStarted(EiJob eiJob) {
         return Flux.fromIterable(eiJob.type().getProducers()) //
             .flatMap(eiProducer -> notifyProducerJobStarted(eiProducer, eiJob)) //
@@ -69,32 +73,28 @@ public class ProducerCallbacks {
             .flatMap(okResponses -> Mono.just(Integer.valueOf(okResponses.size()))); //
     }
 
+    /**
+     * Calls one producer for an EiJob activation.
+     * 
+     * @param producer a producer
+     * @param eiJob an EI job
+     * @return the body of the response from the REST call
+     */
     public Mono<String> notifyProducerJobStarted(EiProducer producer, EiJob eiJob) {
-        AsyncRestClient restClient = restClient(false);
+        AsyncRestClient restClient = restClient();
         ProducerJobInfo request = new ProducerJobInfo(eiJob);
         String body = gson.toJson(request);
 
-        return restClient.post(producer.jobCreationCallbackUrl(), body)
-            .doOnNext(resp -> logger.debug("Job subscription started OK {}", producer.id()))
+        return restClient.post(producer.getJobCreationCallbackUrl(), body)
+            .doOnNext(resp -> logger.debug("Job subscription started OK {}", producer.getId()))
             .onErrorResume(throwable -> {
-                logger.warn("Job subscription failed {}", producer.id(), throwable.toString());
+                logger.warn("Job subscription failed {}", producer.getId(), throwable.toString());
                 return Mono.empty();
             });
     }
 
-    private AsyncRestClient restClient(boolean useTrustValidation) {
-        WebClientConfig config = this.applicationConfig.getWebClientConfig();
-        config = ImmutableWebClientConfig.builder() //
-            .keyStoreType(config.keyStoreType()) //
-            .keyStorePassword(config.keyStorePassword()) //
-            .keyStore(config.keyStore()) //
-            .keyPassword(config.keyPassword()) //
-            .isTrustStoreUsed(useTrustValidation) //
-            .trustStore(config.trustStore()) //
-            .trustStorePassword(config.trustStorePassword()) //
-            .build();
-
-        return new AsyncRestClient("", config);
+    private AsyncRestClient restClient() {
+        return new AsyncRestClient("", this.applicationConfig.getWebClientConfig());
     }
 
 }
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/VoidResponse.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/VoidResponse.java
new file mode 100644 (file)
index 0000000..1933c2b
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers;
+
+import io.swagger.annotations.ApiModel;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "void", description = "Void/empty")
+public class VoidResponse {
+    private VoidResponse() {
+    }
+}
index b120d5f..288421a 100644 (file)
@@ -30,7 +30,6 @@ import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 
-import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,14 +39,13 @@ import org.json.JSONObject;
 import org.oransc.enrichment.clients.ProducerCallbacks;
 import org.oransc.enrichment.configuration.ApplicationConfig;
 import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
 import org.oransc.enrichment.exceptions.ServiceException;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiJobs;
 import org.oransc.enrichment.repository.EiType;
 import org.oransc.enrichment.repository.EiTypes;
 import org.oransc.enrichment.repository.ImmutableEiJob;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -65,8 +63,6 @@ import reactor.core.publisher.Mono;
 @Api(tags = {ConsumerConsts.CONSUMER_API_NAME})
 public class ConsumerController {
 
-    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
     @Autowired
     ApplicationConfig applicationConfig;
 
@@ -214,8 +210,8 @@ public class ConsumerController {
     @ApiOperation(value = "Individual EI Job", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "Not used", response = void.class),
-            @ApiResponse(code = 204, message = "Job deleted", response = void.class),
+            @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
+            @ApiResponse(code = 204, message = "Job deleted", response = VoidResponse.class),
             @ApiResponse(
                 code = 404,
                 message = "Enrichment Information type or job is not found",
@@ -240,8 +236,8 @@ public class ConsumerController {
     @ApiOperation(value = "Individual EI Job", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 201, message = "Job created", response = void.class), //
-            @ApiResponse(code = 200, message = "Job updated", response = void.class), // ,
+            @ApiResponse(code = 201, message = "Job created", response = VoidResponse.class), //
+            @ApiResponse(code = 200, message = "Job updated", response = VoidResponse.class), // ,
             @ApiResponse(
                 code = 404,
                 message = "Enrichment Information type is not found",
index 7743bce..8adff41 100644 (file)
@@ -36,6 +36,7 @@ import java.util.List;
 import org.oransc.enrichment.clients.ProducerCallbacks;
 import org.oransc.enrichment.clients.ProducerJobInfo;
 import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiJobs;
@@ -43,7 +44,6 @@ import org.oransc.enrichment.repository.EiProducer;
 import org.oransc.enrichment.repository.EiProducers;
 import org.oransc.enrichment.repository.EiType;
 import org.oransc.enrichment.repository.EiTypes;
-import org.oransc.enrichment.repository.ImmutableEiProducer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -134,7 +134,7 @@ public class ProducerController {
     ) {
         List<String> result = new ArrayList<>();
         for (EiProducer eiProducer : this.eiProducers.getAllProducers()) {
-            result.add(eiProducer.id());
+            result.add(eiProducer.getId());
         }
 
         return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
@@ -178,7 +178,7 @@ public class ProducerController {
         try {
             EiProducer producer = this.eiProducers.getProducer(eiProducerId);
             Collection<ProducerJobInfo> producerJobs = new ArrayList<>();
-            for (EiType type : producer.eiTypes()) {
+            for (EiType type : producer.getEiTypes()) {
                 for (EiJob eiJob : this.eiJobs.getJobsForType(type)) {
                     ProducerJobInfo request = new ProducerJobInfo(eiJob);
                     producerJobs.add(request);
@@ -191,14 +191,42 @@ public class ProducerController {
         }
     }
 
+    @GetMapping(
+        path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}/status",
+        produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "EI producer status")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "EI jobs", response = ProducerStatusInfo.class), //
+            @ApiResponse(
+                code = 404,
+                message = "Enrichment Information producer is not found",
+                response = ErrorResponse.ErrorInfo.class)})
+    public ResponseEntity<Object> getEiProducerStatus( //
+        @PathVariable("eiProducerId") String eiProducerId) {
+        try {
+            EiProducer producer = this.eiProducers.getProducer(eiProducerId);
+            return new ResponseEntity<>(gson.toJson(producerStatusInfo(producer)), HttpStatus.OK);
+        } catch (Exception e) {
+            return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+        }
+    }
+
+    private ProducerStatusInfo producerStatusInfo(EiProducer producer) {
+        ProducerStatusInfo.OperationalState opState =
+            producer.isAvailable() ? ProducerStatusInfo.OperationalState.ENABLED
+                : ProducerStatusInfo.OperationalState.DISABLED;
+        return new ProducerStatusInfo(opState);
+    }
+
     @PutMapping(
         path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}",
         produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Individual EI producer", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 201, message = "Producer created", response = void.class), //
-            @ApiResponse(code = 200, message = "Producer updated", response = void.class)}//
+            @ApiResponse(code = 201, message = "Producer created", response = VoidResponse.class), //
+            @ApiResponse(code = 200, message = "Producer updated", response = VoidResponse.class)}//
     )
     public ResponseEntity<Object> putEiProducer( //
         @PathVariable("eiProducerId") String eiProducerId, //
@@ -206,14 +234,14 @@ public class ProducerController {
         try {
             EiProducer previousDefinition = this.eiProducers.get(eiProducerId);
             if (previousDefinition != null) {
-                for (EiType type : previousDefinition.eiTypes()) {
+                for (EiType type : previousDefinition.getEiTypes()) {
                     type.removeProducer(previousDefinition);
                 }
             }
 
             registerProducer(eiProducerId, registrationInfo);
             if (previousDefinition != null) {
-                purgeTypes(previousDefinition.eiTypes());
+                purgeTypes(previousDefinition.getEiTypes());
             }
 
             return new ResponseEntity<>(previousDefinition == null ? HttpStatus.CREATED : HttpStatus.OK);
@@ -225,7 +253,7 @@ public class ProducerController {
     private void purgeTypes(Collection<EiType> types) {
         for (EiType type : types) {
             if (type.getProducerIds().isEmpty()) {
-                this.deregisterType(type);
+                this.eiTypes.deregisterType(type, this.eiJobs);
             }
         }
     }
@@ -236,13 +264,13 @@ public class ProducerController {
     @ApiOperation(value = "Individual EI producer", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "Not used", response = void.class),
-            @ApiResponse(code = 204, message = "Producer deleted", response = void.class),
+            @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
+            @ApiResponse(code = 204, message = "Producer deleted", response = VoidResponse.class),
             @ApiResponse(code = 404, message = "Producer is not found", response = ErrorResponse.ErrorInfo.class)})
     public ResponseEntity<Object> deleteEiProducer(@PathVariable("eiProducerId") String eiProducerId) {
         try {
             final EiProducer producer = this.eiProducers.getProducer(eiProducerId);
-            deregisterProducer(producer);
+            this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs);
             return new ResponseEntity<>(HttpStatus.NO_CONTENT);
         } catch (Exception e) {
             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
@@ -256,16 +284,11 @@ public class ProducerController {
             this.eiTypes.put(type);
         }
         return type;
-
     }
 
     EiProducer createProducer(Collection<EiType> types, String producerId, ProducerRegistrationInfo registrationInfo) {
-        return ImmutableEiProducer.builder() //
-            .id(producerId) //
-            .eiTypes(types) //
-            .jobCreationCallbackUrl(registrationInfo.jobCreationCallbackUrl) //
-            .jobDeletionCallbackUrl(registrationInfo.jobDeletionCallbackUrl) //
-            .build();
+        return new EiProducer(producerId, types, registrationInfo.jobCreationCallbackUrl,
+            registrationInfo.jobDeletionCallbackUrl, registrationInfo.producerSupervisionCallbackUrl);
     }
 
     private EiProducer registerProducer(String producerId, ProducerRegistrationInfo registrationInfo) {
@@ -279,43 +302,20 @@ public class ProducerController {
         for (EiType type : types) {
             for (EiJob job : this.eiJobs.getJobsForType(type)) {
                 this.producerCallbacks.notifyProducerJobStarted(producer, job) //
-                    .subscribe(//
-                        response -> logger.debug("Producer notified OK"), //
-                        throwable -> logger.warn("Producer rejected job {}", throwable.getMessage()) //
-                    );
+                    .subscribe();
             }
             type.addProducer(producer);
         }
         return producer;
     }
 
-    private void deregisterType(EiType type) {
-        this.eiTypes.remove(type);
-        for (EiJob job : this.eiJobs.getJobsForType(type.getId())) {
-            this.eiJobs.remove(job);
-            this.logger.warn("Deleted job {} because no producers left", job.id());
-        }
-    }
-
-    private void deregisterProducer(EiProducer producer) {
-        this.eiProducers.remove(producer);
-        for (EiType type : producer.eiTypes()) {
-            boolean removed = type.removeProducer(producer) != null;
-            if (!removed) {
-                this.logger.error("Bug, no producer found");
-            }
-            if (type.getProducerIds().isEmpty()) {
-                deregisterType(type);
-            }
-        }
-    }
-
     ProducerRegistrationInfo toEiProducerRegistrationInfo(EiProducer p) {
         Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
-        for (EiType type : p.eiTypes()) {
+        for (EiType type : p.getEiTypes()) {
             types.add(toEiTypeRegistrationInfo(type));
         }
-        return new ProducerRegistrationInfo(types, p.jobCreationCallbackUrl(), p.jobDeletionCallbackUrl());
+        return new ProducerRegistrationInfo(types, p.getJobCreationCallbackUrl(), p.getJobDeletionCallbackUrl(),
+            p.getProducerSupervisionCallbackUrl());
     }
 
     private ProducerEiTypeRegistrationInfo toEiTypeRegistrationInfo(EiType type) {
index 1ef2ed4..19bb541 100644 (file)
@@ -72,11 +72,17 @@ public class ProducerRegistrationInfo {
     @JsonProperty(value = "ei_job_deletion_callback_url", required = true)
     public String jobDeletionCallbackUrl;
 
+    @ApiModelProperty(value = "callback for producer supervision", required = true)
+    @SerializedName("ei_producer_supervision_callback_url")
+    @JsonProperty(value = "ei_producer_supervision_callback_url", required = true)
+    public String producerSupervisionCallbackUrl;
+
     public ProducerRegistrationInfo(Collection<ProducerEiTypeRegistrationInfo> types, String jobCreationCallbackUrl,
-        String jobDeletionCallbackUrl) {
+        String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) {
         this.types = types;
         this.jobCreationCallbackUrl = jobCreationCallbackUrl;
         this.jobDeletionCallbackUrl = jobDeletionCallbackUrl;
+        this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl;
     }
 
     public ProducerRegistrationInfo() {
diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerStatusInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerStatusInfo.java
new file mode 100644 (file)
index 0000000..8f2e0d0
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers.producer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "producer_status", description = "Status for an EI Producer")
+public class ProducerStatusInfo {
+
+    @Gson.TypeAdapters
+    @ApiModel(value = "producer_operational_state", description = "Represents the operational states")
+    public enum OperationalState {
+        ENABLED, DISABLED
+    }
+
+    private static final String OPERATIONAL_STATE_DESCRIPTION = "Operational state, values:\n" //
+        + "ENABLED: TBD\n" //
+        + "DISABLED: TBD.";
+
+    @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "operational_state", required = true)
+    @SerializedName("operational_state")
+    @JsonProperty(value = "operational_state", required = true)
+    public final OperationalState opState;
+
+    public ProducerStatusInfo(OperationalState state) {
+        this.opState = state;
+    }
+
+}
index 30e62de..99932a7 100644 (file)
@@ -22,18 +22,49 @@ package org.oransc.enrichment.repository;
 
 import java.util.Collection;
 
-import org.immutables.gson.Gson;
-import org.immutables.value.Value;
+import lombok.Getter;
 
-@Value.Immutable
-@Gson.TypeAdapters
-public interface EiProducer {
-    public String id();
+public class EiProducer {
+    @Getter
+    private final String id;
 
-    public Collection<EiType> eiTypes();
+    @Getter
+    private final Collection<EiType> eiTypes;
 
-    public String jobCreationCallbackUrl();
+    @Getter
+    private final String jobCreationCallbackUrl;
 
-    public String jobDeletionCallbackUrl();
+    @Getter
+    private final String jobDeletionCallbackUrl;
+
+    @Getter
+    private final String producerSupervisionCallbackUrl;
+
+    private int unresponsiveCounter = 0;
+
+    public EiProducer(String id, Collection<EiType> eiTypes, String jobCreationCallbackUrl,
+        String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) {
+        this.id = id;
+        this.eiTypes = eiTypes;
+        this.jobCreationCallbackUrl = jobCreationCallbackUrl;
+        this.jobDeletionCallbackUrl = jobDeletionCallbackUrl;
+        this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl;
+    }
+
+    public synchronized void setAliveStatus(boolean isAlive) {
+        if (isAlive) {
+            unresponsiveCounter = 0;
+        } else {
+            unresponsiveCounter++;
+        }
+    }
+
+    public synchronized boolean isDead() {
+        return this.unresponsiveCounter >= 3;
+    }
+
+    public synchronized boolean isAvailable() {
+        return this.unresponsiveCounter == 0;
+    }
 
 }
index 483850d..b3cd895 100644 (file)
 
 package org.oransc.enrichment.repository;
 
+import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Vector;
 
 import org.oransc.enrichment.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * Dynamic representation of all Rics in the system.
+ * Dynamic representation of all EiProducers.
  */
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
 public class EiProducers {
+    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
     private Map<String, EiProducer> allEiProducers = new HashMap<>();
 
     public synchronized void put(EiProducer producer) {
-        allEiProducers.put(producer.id(), producer);
+        allEiProducers.put(producer.getId(), producer);
 
     }
 
@@ -58,10 +63,6 @@ public class EiProducers {
         this.allEiProducers.remove(id);
     }
 
-    public synchronized void remove(EiProducer producer) {
-        this.allEiProducers.remove(producer.id());
-    }
-
     public synchronized int size() {
         return allEiProducers.size();
     }
@@ -69,4 +70,22 @@ public class EiProducers {
     public synchronized void clear() {
         this.allEiProducers.clear();
     }
+
+    public void deregisterProducer(EiProducer producer, EiTypes eiTypes, EiJobs eiJobs) {
+        this.remove(producer);
+        for (EiType type : producer.getEiTypes()) {
+            boolean removed = type.removeProducer(producer) != null;
+            if (!removed) {
+                this.logger.error("Bug, no producer found");
+            }
+            if (type.getProducerIds().isEmpty()) {
+                eiTypes.deregisterType(type, eiJobs);
+            }
+        }
+    }
+
+    private synchronized void remove(EiProducer producer) {
+        this.allEiProducers.remove(producer.getId());
+    }
+
 }
index 803fcba..a354198 100644 (file)
@@ -50,10 +50,10 @@ public class EiType {
     }
 
     public synchronized void addProducer(EiProducer producer) {
-        this.producers.put(producer.id(), producer);
+        this.producers.put(producer.getId(), producer);
     }
 
     public synchronized EiProducer removeProducer(EiProducer producer) {
-        return this.producers.remove(producer.id());
+        return this.producers.remove(producer.getId());
     }
 }
index 5454e8a..9b7b640 100644 (file)
 
 package org.oransc.enrichment.repository;
 
+import java.lang.invoke.MethodHandles;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Vector;
 
 import org.oransc.enrichment.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Dynamic representation of all EI Types in the system.
  */
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
 public class EiTypes {
-    Map<String, EiType> allEiTypes = new HashMap<>();
+    private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+    private final Map<String, EiType> allEiTypes = new HashMap<>();
 
     public synchronized void put(EiType type) {
         allEiTypes.put(type.getId(), type);
@@ -69,4 +74,12 @@ public class EiTypes {
         this.allEiTypes.clear();
     }
 
+    public void deregisterType(EiType type, EiJobs eiJobs) {
+        this.remove(type);
+        for (EiJob job : eiJobs.getJobsForType(type.getId())) {
+            eiJobs.remove(job);
+            this.logger.warn("Deleted job {} because no producers left", job.id());
+        }
+    }
+
 }
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
new file mode 100644 (file)
index 0000000..3b62fa7
--- /dev/null
@@ -0,0 +1,98 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.tasks;
+
+import org.oransc.enrichment.clients.AsyncRestClient;
+import org.oransc.enrichment.configuration.ApplicationConfig;
+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;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Regularly checks the availability of the EI Producers
+ */
+@Component
+@EnableScheduling
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
+public class ProducerSupervision {
+    private static final Logger logger = LoggerFactory.getLogger(ProducerSupervision.class);
+
+    @Autowired
+    ApplicationConfig applicationConfig;
+
+    @Autowired
+    EiProducers eiProducers;
+
+    @Autowired
+    EiJobs eiJobs;
+
+    @Autowired
+    EiTypes eiTypes;
+
+    @Scheduled(fixedRate = 1000 * 60 * 5)
+    public void checkAllProducers() {
+        logger.debug("Checking producers starting");
+        createTask().subscribe(null, null, () -> logger.debug("Checking all Producers completed"));
+    }
+
+    public Flux<EiProducer> createTask() {
+        return Flux.fromIterable(eiProducers.getAllProducers()) //
+            .flatMap(this::checkOneProducer);
+    }
+
+    private Mono<EiProducer> checkOneProducer(EiProducer producer) {
+        return restClient().get(producer.getProducerSupervisionCallbackUrl()) //
+            .onErrorResume(throwable -> {
+                handleNonRespondingProducer(throwable, producer);
+                return Mono.empty();
+            })//
+            .doOnNext(response -> handleRespondingProducer(response, producer))
+            .flatMap(response -> Mono.just(producer));
+    }
+
+    private void handleNonRespondingProducer(Throwable throwable, EiProducer producer) {
+        logger.warn("Unresponsive producer: {} exception: {}", producer.getId(), throwable.getMessage());
+        producer.setAliveStatus(false);
+        if (producer.isDead()) {
+            this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs);
+        }
+    }
+
+    private void handleRespondingProducer(String response, EiProducer producer) {
+        logger.debug("{}", response);
+        producer.setAliveStatus(true);
+    }
+
+    private AsyncRestClient restClient() {
+        return new AsyncRestClient("", this.applicationConfig.getWebClientConfig());
+    }
+
+}
index 9a731e6..9cfe222 100644 (file)
@@ -49,12 +49,14 @@ import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo;
 import org.oransc.enrichment.controllers.producer.ProducerConsts;
 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
+import org.oransc.enrichment.controllers.producer.ProducerStatusInfo;
 import org.oransc.enrichment.exceptions.ServiceException;
 import org.oransc.enrichment.repository.EiJob;
 import org.oransc.enrichment.repository.EiJobs;
 import org.oransc.enrichment.repository.EiProducers;
 import org.oransc.enrichment.repository.EiType;
 import org.oransc.enrichment.repository.EiTypes;
+import org.oransc.enrichment.tasks.ProducerSupervision;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@@ -103,6 +105,9 @@ class ApplicationTest {
     @Autowired
     ProducerSimulatorController producerSimulator;
 
+    @Autowired
+    ProducerSupervision producerSupervision;
+
     private static Gson gson = new GsonBuilder() //
         .serializeNulls() //
         .create(); //
@@ -323,7 +328,7 @@ class ApplicationTest {
         EiType type = this.eiTypes.getType(EI_TYPE_ID);
         assertThat(type.getProducerIds()).contains("eiProducerId");
         assertThat(this.eiProducers.size()).isEqualTo(1);
-        assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
+        assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
 
         resp = restClient().putForEntity(url, body).block();
         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -426,6 +431,33 @@ class ApplicationTest {
         assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
     }
 
+    private void assertProducerOpState(String producerId,
+        ProducerStatusInfo.OperationalState expectedOperationalState) {
+        String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
+        ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
+        ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
+        assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
+    }
+
+    @Test
+    void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
+        putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
+
+        assertThat(this.eiProducers.size()).isEqualTo(1);
+        assertThat(this.eiTypes.size()).isEqualTo(1);
+        assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
+
+        this.producerSupervision.createTask().blockLast();
+        this.producerSupervision.createTask().blockLast();
+        assertThat(this.eiProducers.size()).isEqualTo(1);
+        assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
+
+        // After 3 failed checks, the producer shall be deregisterred
+        this.producerSupervision.createTask().blockLast();
+        assertThat(this.eiProducers.size()).isEqualTo(0);
+        assertThat(this.eiTypes.size()).isEqualTo(0);
+    }
+
     ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
         throws JsonMappingException, JsonProcessingException {
         return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
@@ -435,16 +467,20 @@ class ApplicationTest {
         throws JsonMappingException, JsonProcessingException {
         Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
         types.add(producerEiTypeRegistrationInfo(typeId));
-        return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_ERROR_URL,
-            baseUrl() + ProducerSimulatorController.JOB_DELETED_ERROR_URL);
+        return new ProducerRegistrationInfo(types, //
+            baseUrl() + ProducerSimulatorController.JOB_CREATED_ERROR_URL,
+            baseUrl() + ProducerSimulatorController.JOB_DELETED_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, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
-            baseUrl() + ProducerSimulatorController.JOB_DELETED_URL);
+        return new ProducerRegistrationInfo(types, //
+            baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
+            baseUrl() + ProducerSimulatorController.JOB_DELETED_URL,
+            baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
     }
 
     ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
index c44a9ee..479c1b2 100644 (file)
@@ -34,11 +34,13 @@ import lombok.Getter;
 
 import org.oransc.enrichment.clients.ProducerJobInfo;
 import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
@@ -54,6 +56,9 @@ public class ProducerSimulatorController {
     public static final String JOB_CREATED_ERROR_URL = "/producer_simulator/job_created_error";
     public static final String JOB_DELETED_ERROR_URL = "/producer_simulator/job_deleted_error";
 
+    public static final String SUPERVISION_URL = "/producer_simulator/supervision";
+    public static final String SUPERVISION_ERROR_URL = "/producer_simulator/supervision_error";
+
     public static class TestResults {
 
         public List<ProducerJobInfo> jobsStarted = Collections.synchronizedList(new ArrayList<ProducerJobInfo>());
@@ -81,7 +86,7 @@ public class ProducerSimulatorController {
     @ApiOperation(value = "Callback for EI job creation", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "OK", response = void.class)}//
+            @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
     )
     public ResponseEntity<Object> jobCreatedCallback( //
         @RequestBody ProducerJobInfo request) {
@@ -102,7 +107,7 @@ public class ProducerSimulatorController {
     @ApiOperation(value = "Callback for EI job deletion", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "OK", response = void.class)}//
+            @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
     )
     public ResponseEntity<Object> jobDeletedCallback( //
         @RequestBody ProducerJobInfo request) {
@@ -119,7 +124,7 @@ public class ProducerSimulatorController {
     @ApiOperation(value = "Callback for EI job creation, returns error", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "OK", response = void.class)}//
+            @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
     )
     public ResponseEntity<Object> jobCreatedCallbackReturnError( //
         @RequestBody ProducerJobInfo request) {
@@ -132,7 +137,7 @@ public class ProducerSimulatorController {
     @ApiOperation(value = "Callback for EI job creation, returns error", notes = "")
     @ApiResponses(
         value = { //
-            @ApiResponse(code = 200, message = "OK", response = void.class)}//
+            @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
     )
     public ResponseEntity<Object> jobDeletedCallbackReturnError( //
         @RequestBody ProducerJobInfo request) {
@@ -141,4 +146,26 @@ public class ProducerSimulatorController {
         return ErrorResponse.create("Producer returns error on delete job", HttpStatus.NOT_FOUND);
     }
 
+    @GetMapping(path = SUPERVISION_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Producer supervision", notes = "")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "OK", response = String.class)}//
+    )
+    public ResponseEntity<Object> producerSupervision() {
+        logger.info("Producer supervision");
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @GetMapping(path = SUPERVISION_ERROR_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Producer supervision error", notes = "")
+    @ApiResponses(
+        value = { //
+            @ApiResponse(code = 200, message = "OK", response = String.class)}//
+    )
+    public ResponseEntity<Object> producerSupervisionError() {
+        logger.info("Producer supervision error");
+        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+
 }