From: PatrikBuhr Date: Wed, 18 Aug 2021 12:11:41 +0000 (+0200) Subject: ECS, support for notification of available information types X-Git-Tag: 1.2.0~113^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=366bc97828bf62e39a41318c1407a2c7c8cb5b74;p=nonrtric.git ECS, support for notification of available information types This service operation can be used for a data conumer to subscribe to notifications for changes in the availability of data types. Change-Id: Iecc8866685ee0029463f0c5a032ee37acf3a7c55 Signed-off-by: PatrikBuhr Issue-ID: NONRTRIC-570 --- diff --git a/enrichment-coordinator-service/api/ecs-api.json b/enrichment-coordinator-service/api/ecs-api.json index c89d6138..b2d800bd 100644 --- a/enrichment-coordinator-service/api/ecs-api.json +++ b/enrichment-coordinator-service/api/ecs-api.json @@ -3,17 +3,36 @@ "consumer_information_type": { "description": "Information for an Information type", "type": "object", - "required": ["job_data_schema"], - "properties": {"job_data_schema": { - "description": "Json schema for the job data", - "type": "object" - }} + "required": [ + "job_data_schema", + "no_of_producers", + "type_status" + ], + "properties": { + "no_of_producers": { + "format": "int32", + "description": "The number of registered producers for the type", + "type": "integer" + }, + "type_status": { + "description": "Allowed values:
ENABLED: one or several producers for the information type are available
DISABLED: no producers for the information type are available", + "type": "string", + "enum": [ + "ENABLED", + "DISABLED" + ] + }, + "job_data_schema": { + "description": "Json schema for the job data", + "type": "object" + } + } }, "EiTypeObject": { "description": "Information for an EI type", "type": "object" }, - "status_info": { + "service_status_info": { "type": "object", "required": [ "no_of_jobs", @@ -70,6 +89,33 @@ } } }, + "consumer_type_registration_info": { + "description": "Information for an Information type", + "type": "object", + "required": [ + "info_type_id", + "job_data_schema", + "status" + ], + "properties": { + "info_type_id": { + "description": "Information type identifier", + "type": "string" + }, + "job_data_schema": { + "description": "Json schema for the job data", + "type": "object" + }, + "status": { + "description": "Allowed values:
REGISTERED: the information type has been registered
DEREGISTERED: the information type has been removed", + "type": "string", + "enum": [ + "REGISTERED", + "DEREGISTERED" + ] + } + } + }, "ProblemDetails": { "description": "A problem detail to carry details in a HTTP response according to RFC 7807", "type": "object", @@ -92,7 +138,7 @@ "type": "object", "required": ["eiJobStatus"], "properties": {"eiJobStatus": { - "description": "Allowed values for EI job status", + "description": "Allowed values for EI job status:
ENABLED: the A1-EI producer is able to deliver EI result for the EI job
DISABLED: the A1-EI producer is unable to deliver EI result for the EI job", "type": "string", "enum": [ "ENABLED", @@ -109,7 +155,7 @@ ], "properties": { "info_job_status": { - "description": "Allowed values for Information Job status", + "description": "Allowed values:
ENABLED: the A1-Information producer is able to deliver result for the Information Job
DISABLED: the A1-Information producer is unable to deliver result for the Information Job", "type": "string", "enum": [ "ENABLED", @@ -117,10 +163,10 @@ ] }, "producers": { - "description": "An array of all registerred Information Producer Identifiers.", + "description": "An array of all registered Information Producer Identifiers.", "type": "array", "items": { - "description": "An array of all registerred Information Producer Identifiers.", + "description": "An array of all registered Information Producer Identifiers.", "type": "string" } } @@ -199,7 +245,7 @@ } }, "consumer_job": { - "description": "Information for an Enrichment Information Job", + "description": "Information for an Enrichment Information Job", "type": "object", "required": [ "info_type_id", @@ -246,10 +292,44 @@ "Void": { "description": "Void/empty ", "type": "object" + }, + "consumer_type_subscription_info": { + "description": "Information for an information type subscription", + "type": "object", + "required": [ + "owner", + "status_result_uri" + ], + "properties": { + "owner": { + "description": "Identity of the owner of the subscription", + "type": "string" + }, + "status_result_uri": { + "description": "The target URI of the subscribed information", + "type": "string" + } + } } }}, "openapi": "3.0.1", "paths": { + "/example_dataproducer/info_job/{infoJobId}": {"delete": { + "summary": "Callback for Information Job deletion", + "description": "The call is invoked to terminate a data subscription. The endpoint is provided by the Information Producer.", + "operationId": "jobDeletedCallback", + "responses": {"200": { + "description": "OK", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }}, + "parameters": [{ + "schema": {"type": "string"}, + "in": "path", + "name": "infoJobId", + "required": true + }], + "tags": ["Data producer (callbacks)"] + }}, "/data-producer/v1/info-types": {"get": { "summary": "Info Type identifiers", "operationId": "getInfoTypdentifiers", @@ -283,6 +363,20 @@ }], "tags": ["A1-EI (registration)"] }}, + "/example_dataproducer/info_job": {"post": { + "summary": "Callback for Information Job creation/modification", + "requestBody": { + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/producer_info_job_request"}}}, + "required": true + }, + "description": "The call is invoked to activate or to modify a data subscription. The endpoint is provided by the Information Producer.", + "operationId": "jobCreatedCallback", + "responses": {"200": { + "description": "OK", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }}, + "tags": ["Data producer (callbacks)"] + }}, "/data-producer/v1/info-types/{infoTypeId}": { "get": { "summary": "Individual Information Type", @@ -364,6 +458,90 @@ "tags": ["Data producer (registration)"] } }, + "/data-consumer/v1/info-type-subscription/{subscriptionId}": { + "get": { + "summary": "Individual subscription for information types (registration/deregistration)", + "operationId": "getIndividualTypeSubscription", + "responses": { + "200": { + "description": "Type subscription", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/consumer_type_subscription_info"}}} + }, + "404": { + "description": "Subscription is not found", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProblemDetails"}}} + } + }, + "parameters": [{ + "schema": {"type": "string"}, + "in": "path", + "name": "subscriptionId", + "required": true + }], + "tags": ["Data consumer"] + }, + "delete": { + "summary": "Individual subscription for information types (registration/deregistration)", + "operationId": "deleteIndividualTypeSubscription", + "responses": { + "200": { + "description": "Not used", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }, + "204": { + "description": "Subscription deleted", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }, + "404": { + "description": "Subscription is not found", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProblemDetails"}}} + } + }, + "parameters": [{ + "schema": {"type": "string"}, + "in": "path", + "name": "subscriptionId", + "required": true + }], + "tags": ["Data consumer"] + }, + "put": { + "summary": "Individual subscription for information types (registration/deregistration)", + "requestBody": { + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/consumer_type_subscription_info"}}}, + "required": true + }, + "description": "This service operation is used to subscribe to notifications for changes in the availability of data types.", + "operationId": "putIndividualTypeSubscription", + "responses": { + "200": { + "description": "Subscription updated", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }, + "201": { + "description": "Subscription created", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + } + }, + "parameters": [{ + "schema": {"type": "string"}, + "in": "path", + "name": "subscriptionId", + "required": true + }], + "tags": ["Data consumer"] + } + }, + "/example_dataproducer/health_check": {"get": { + "summary": "Producer supervision", + "description": "The endpoint is provided by the Information Producer and is used for supervision of the producer.", + "operationId": "producerSupervision", + "responses": {"200": { + "description": "The producer is OK", + "content": {"application/json": {"schema": {"type": "string"}}} + }}, + "tags": ["Data producer (callbacks)"] + }}, "/A1-EI/v1/eitypes": {"get": { "summary": "EI type identifiers", "operationId": "getEiTypeIdentifiers", @@ -465,31 +643,35 @@ "tags": ["Data producer (registration)"] } }, - "/producer_simulator/info_job/{infoJobId}": {"delete": { - "summary": "Callback for Information Job deletion", - "description": "The call is invoked to terminate a data subscription. The endpoint is provided by the Information Producer.", - "operationId": "jobDeletedCallback", - "responses": {"200": { - "description": "OK", - "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} - }}, - "parameters": [{ - "schema": {"type": "string"}, - "in": "path", - "name": "infoJobId", - "required": true - }], - "tags": ["Data producer (callbacks)"] - }}, "/status": {"get": { "summary": "Returns status and statistics of this service", "operationId": "getStatus", "responses": {"200": { "description": "Service is living", - "content": {"application/json": {"schema": {"$ref": "#/components/schemas/status_info"}}} + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/service_status_info"}}} }}, "tags": ["Service status"] }}, + "/data-consumer/v1/info-type-subscription": {"get": { + "summary": "Information type subscription identifiers", + "description": "query for information type subscription identifiers", + "operationId": "getInfoTypeSubscriptions", + "responses": {"200": { + "description": "Information type subscription identifiers", + "content": {"application/json": {"schema": { + "type": "array", + "items": {"type": "string"} + }}} + }}, + "parameters": [{ + "schema": {"type": "string"}, + "in": "query", + "name": "owner", + "description": "selects result for one owner", + "required": false + }], + "tags": ["Data consumer"] + }}, "/A1-EI/v1/eijobs/{eiJobId}": { "get": { "summary": "Individual EI job", @@ -567,16 +749,6 @@ "tags": ["A1-EI (registration)"] } }, - "/producer_simulator/health_check": {"get": { - "summary": "Producer supervision", - "description": "The endpoint is provided by the Information Producer and is used for supervision of the producer.", - "operationId": "producerSupervision", - "responses": {"200": { - "description": "The producer is OK", - "content": {"application/json": {"schema": {"type": "string"}}} - }}, - "tags": ["Data producer (callbacks)"] - }}, "/data-consumer/v1/info-jobs": {"get": { "summary": "Information Job identifiers", "description": "query for information job identifiers", @@ -606,7 +778,7 @@ "schema": {"type": "string"}, "in": "query", "name": "owner", - "description": "selects subscription jobs for one job owner", + "description": "selects result for one owner", "required": false } ], @@ -742,20 +914,6 @@ }], "tags": ["Data consumer"] }}, - "/producer_simulator/info_job": {"post": { - "summary": "Callback for Information Job creation/modification", - "requestBody": { - "content": {"application/json": {"schema": {"$ref": "#/components/schemas/producer_info_job_request"}}}, - "required": true - }, - "description": "The call is invoked to activate or to modify a data subscription. The endpoint is provided by the Information Producer.", - "operationId": "jobCreatedCallback", - "responses": {"200": { - "description": "OK", - "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} - }}, - "tags": ["Data producer (callbacks)"] - }}, "/example_dataconsumer/info_jobs/{infoJobId}/status": {"post": { "summary": "Callback for changed Information Job status", "requestBody": { @@ -898,6 +1056,20 @@ "required": true }], "tags": ["Data consumer"] + }}, + "/example_dataconsumer/info_type_status": {"post": { + "summary": "Callback for changed Information type registration status", + "requestBody": { + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/consumer_type_registration_info"}}}, + "required": true + }, + "description": "The primitive is implemented by the data consumer and is invoked when a Information type status has been changed.
Subscription are managed by primitives in 'Data consumer'", + "operationId": "typeStatusCallback", + "responses": {"200": { + "description": "OK", + "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Void"}}} + }}, + "tags": ["Data consumer (callbacks)"] }} }, "info": { @@ -905,7 +1077,7 @@ "name": "Copyright (C) 2020 Nordix Foundation. Licensed under the Apache License.", "url": "http://www.apache.org/licenses/LICENSE-2.0" }, - "description": "

API documentation<\/h1>

General<\/h2>

The service is mainly a broker between data producers and data consumers. A data producer has the ability to produce one or several types of data (Information Type). One type of data can be produced by zero to many producers.

A data consumer can have several active data subscriptions (Information Job). One Information Job consists of the type of data to produce and additional parameters for filtering of the data. These parameters are different for different data types.<\/p>

APIs provided by the service<\/h2>

A1-EI<\/h4>

This API is between Near-RT RIC and the Non-RT RIC. The Near-RT RIC is a data consumer, which creates Information Jobs to subscribe for data. In this context, the information is referred to as 'Enrichment Information', EI.<\/p>

Data producer API<\/h4>

This API is provided by the Non-RT RIC platform and is intended to be part of the O-RAN R1 interface. The API is for use by different kinds of data producers and provides support for:

  • Registry of supported information types and which parameters needed to setup a subscription.<\/li>
  • Registry of existing data producers.<\/li>
  • Callback API provided by producers to setup subscriptions.<\/li><\/ul><\/p>

    Data consumer API<\/h4>

    This API is provided by the Non-RT RIC platform and is intended to be part of the O-RAN R1 interface. The API is for use by different kinds of data consumers and provides support for:

    • Querying of available types of data to consume.<\/li>
    • Management of data subscription jobs<\/li><\/ul><\/p>

      Service status<\/h4>

      This API provides a means to monitor the health of this service.<\/p>", + "description": "

      API documentation<\/h1>

      General<\/h2>

      The service is mainly a broker between data producers and data consumers. A data producer has the ability to produce one or several types of data (Information Type). One type of data can be produced by zero to many producers.

      A data consumer can have several active data subscriptions (Information Job). One Information Job consists of the type of data to produce and additional parameters for filtering of the data. These parameters are different for different data types.<\/p>

      APIs provided by the service<\/h2>

      A1-EI<\/h4>

      This API is between Near-RT RIC and the Non-RT RIC. The Near-RT RIC is a data consumer, which creates Information Jobs to subscribe for data. In this context, the information is referred to as 'Enrichment Information', EI.<\/p>

      Data producer API<\/h4>

      This API is provided by the Non-RT RIC platform and is intended to be part of the O-RAN R1 interface. The API is for use by different kinds of data producers and provides support for:

      • Registry of supported information types and which parameters needed to setup a subscription.<\/li>
      • Registry of existing data producers.<\/li>
      • Callback API provided by producers to setup subscriptions.<\/li><\/ul><\/p>

        Data consumer API<\/h4>

        This API is provided by the Non-RT RIC platform and is intended to be part of the O-RAN R1 interface. The API is for use by different kinds of data consumers and provides support for:

        • Querying of available types of data to consume.<\/li>
        • Management of data subscription jobs<\/li>
        • Optional callback API provided by consumers to get notification on added and removed information types.<\/li><\/ul><\/p>

          Service status<\/h4>

          This API provides a means to monitor the health of this service.<\/p>", "title": "Data management and exposure", "version": "1.0" }, diff --git a/enrichment-coordinator-service/api/ecs-api.yaml b/enrichment-coordinator-service/api/ecs-api.yaml index 6318ee16..3f2a1cd8 100644 --- a/enrichment-coordinator-service/api/ecs-api.yaml +++ b/enrichment-coordinator-service/api/ecs-api.yaml @@ -20,8 +20,9 @@ info: by the Non-RT RIC platform and is intended to be part of the O-RAN R1 interface. The API is for use by different kinds of data consumers and provides support for:

          • Querying of available types of data to consume.
          • Management of data subscription - jobs

          Service status

          This API provides a means to monitor - the health of this service.

          + jobs
        • Optional callback API provided by consumers to get notification on + added and removed information types.

        Service status

        This + API provides a means to monitor the health of this service.

        license: name: Copyright (C) 2020 Nordix Foundation. Licensed under the Apache License. url: http://www.apache.org/licenses/LICENSE-2.0 @@ -42,6 +43,29 @@ tags: - name: Data consumer description: API for data consumers paths: + /example_dataproducer/info_job/{infoJobId}: + delete: + tags: + - Data producer (callbacks) + summary: Callback for Information Job deletion + description: The call is invoked to terminate a data subscription. The endpoint + is provided by the Information Producer. + operationId: jobDeletedCallback + parameters: + - name: infoJobId + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Void' /data-producer/v1/info-types: get: tags: @@ -84,6 +108,27 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' + /example_dataproducer/info_job: + post: + tags: + - Data producer (callbacks) + summary: Callback for Information Job creation/modification + description: The call is invoked to activate or to modify a data subscription. + The endpoint is provided by the Information Producer. + operationId: jobCreatedCallback + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/producer_info_job_request' + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Void' /data-producer/v1/info-types/{infoTypeId}: get: tags: @@ -187,6 +232,114 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' + /data-consumer/v1/info-type-subscription/{subscriptionId}: + get: + tags: + - Data consumer + summary: Individual subscription for information types (registration/deregistration) + operationId: getIndividualTypeSubscription + parameters: + - name: subscriptionId + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Type subscription + content: + application/json: + schema: + $ref: '#/components/schemas/consumer_type_subscription_info' + 404: + description: Subscription is not found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + put: + tags: + - Data consumer + summary: Individual subscription for information types (registration/deregistration) + description: This service operation is used to subscribe to notifications for + changes in the availability of data types. + operationId: putIndividualTypeSubscription + parameters: + - name: subscriptionId + in: path + required: true + style: simple + explode: false + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/consumer_type_subscription_info' + required: true + responses: + 200: + description: Subscription updated + content: + application/json: + schema: + $ref: '#/components/schemas/Void' + 201: + description: Subscription created + content: + application/json: + schema: + $ref: '#/components/schemas/Void' + delete: + tags: + - Data consumer + summary: Individual subscription for information types (registration/deregistration) + operationId: deleteIndividualTypeSubscription + parameters: + - name: subscriptionId + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Not used + content: + application/json: + schema: + $ref: '#/components/schemas/Void' + 204: + description: Subscription deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Void' + 404: + description: Subscription is not found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetails' + /example_dataproducer/health_check: + get: + tags: + - Data producer (callbacks) + summary: Producer supervision + description: The endpoint is provided by the Information Producer and is used + for supervision of the producer. + operationId: producerSupervision + responses: + 200: + description: The producer is OK + content: + application/json: + schema: + type: string /A1-EI/v1/eitypes: get: tags: @@ -314,42 +467,44 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' - /producer_simulator/info_job/{infoJobId}: - delete: + /status: + get: tags: - - Data producer (callbacks) - summary: Callback for Information Job deletion - description: The call is invoked to terminate a data subscription. The endpoint - is provided by the Information Producer. - operationId: jobDeletedCallback - parameters: - - name: infoJobId - in: path - required: true - style: simple - explode: false - schema: - type: string + - Service status + summary: Returns status and statistics of this service + operationId: getStatus responses: 200: - description: OK + description: Service is living content: application/json: schema: - $ref: '#/components/schemas/Void' - /status: + $ref: '#/components/schemas/service_status_info' + /data-consumer/v1/info-type-subscription: get: tags: - - Service status - summary: Returns status and statistics of this service - operationId: getStatus + - Data consumer + summary: Information type subscription identifiers + description: query for information type subscription identifiers + operationId: getInfoTypeSubscriptions + parameters: + - name: owner + in: query + description: selects result for one owner + required: false + style: form + explode: true + schema: + type: string responses: 200: - description: Service is living + description: Information type subscription identifiers content: application/json: schema: - $ref: '#/components/schemas/status_info' + type: array + items: + type: string /A1-EI/v1/eijobs/{eiJobId}: get: tags: @@ -447,21 +602,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' - /producer_simulator/health_check: - get: - tags: - - Data producer (callbacks) - summary: Producer supervision - description: The endpoint is provided by the Information Producer and is used - for supervision of the producer. - operationId: producerSupervision - responses: - 200: - description: The producer is OK - content: - application/json: - schema: - type: string /data-consumer/v1/info-jobs: get: tags: @@ -480,7 +620,7 @@ paths: type: string - name: owner in: query - description: selects subscription jobs for one job owner + description: selects result for one owner required: false style: form explode: true @@ -660,27 +800,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' - /producer_simulator/info_job: - post: - tags: - - Data producer (callbacks) - summary: Callback for Information Job creation/modification - description: The call is invoked to activate or to modify a data subscription. - The endpoint is provided by the Information Producer. - operationId: jobCreatedCallback - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/producer_info_job_request' - required: true - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Void' /example_dataconsumer/info_jobs/{infoJobId}/status: post: tags: @@ -860,13 +979,49 @@ paths: application/json: schema: $ref: '#/components/schemas/ProblemDetails' + /example_dataconsumer/info_type_status: + post: + tags: + - Data consumer (callbacks) + summary: Callback for changed Information type registration status + description: The primitive is implemented by the data consumer and is invoked + when a Information type status has been changed.
        Subscription are managed + by primitives in 'Data consumer' + operationId: typeStatusCallback + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/consumer_type_registration_info' + required: true + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Void' components: schemas: consumer_information_type: required: - job_data_schema + - no_of_producers + - type_status type: object properties: + no_of_producers: + type: integer + description: The number of registered producers for the type + format: int32 + type_status: + type: string + description: 'Allowed values:
        ENABLED: one or several producers for + the information type are available
        DISABLED: no producers for the + information type are available' + enum: + - ENABLED + - DISABLED job_data_schema: type: object description: Json schema for the job data @@ -874,7 +1029,7 @@ components: EiTypeObject: type: object description: Information for an EI type - status_info: + service_status_info: required: - no_of_jobs - no_of_producers @@ -917,6 +1072,27 @@ components: type: string description: callback for Information Job description: Information for an Information Producer + consumer_type_registration_info: + required: + - info_type_id + - job_data_schema + - status + type: object + properties: + info_type_id: + type: string + description: Information type identifier + job_data_schema: + type: object + description: Json schema for the job data + status: + type: string + description: 'Allowed values:
        REGISTERED: the information type has + been registered
        DEREGISTERED: the information type has been removed' + enum: + - REGISTERED + - DEREGISTERED + description: Information for an Information type ProblemDetails: type: object properties: @@ -940,7 +1116,9 @@ components: properties: eiJobStatus: type: string - description: Allowed values for EI job status + description: 'Allowed values for EI job status:
        ENABLED: the A1-EI + producer is able to deliver EI result for the EI job
        DISABLED: the + A1-EI producer is unable to deliver EI result for the EI job' enum: - ENABLED - DISABLED @@ -953,16 +1131,18 @@ components: properties: info_job_status: type: string - description: Allowed values for Information Job status + description: 'Allowed values:
        ENABLED: the A1-Information producer + is able to deliver result for the Information Job
        DISABLED: the A1-Information + producer is unable to deliver result for the Information Job' enum: - ENABLED - DISABLED producers: type: array - description: An array of all registerred Information Producer Identifiers. + description: An array of all registered Information Producer Identifiers. items: type: string - description: An array of all registerred Information Producer Identifiers. + description: An array of all registered Information Producer Identifiers. description: Status for an Information Job EiJobObject: required: @@ -1045,7 +1225,7 @@ components: status_notification_uri: type: string description: The target of Information subscription job status notifications - description: Information for an Enrichment Information Job + description: Information for an Enrichment Information Job producer_status: required: - operational_state @@ -1061,3 +1241,16 @@ components: Void: type: object description: 'Void/empty ' + consumer_type_subscription_info: + required: + - owner + - status_result_uri + type: object + properties: + owner: + type: string + description: Identity of the owner of the subscription + status_result_uri: + type: string + description: The target URI of the subscribed information + description: Information for an information type subscription diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java index 6b5edc03..5f61e312 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/SwaggerConfig.java @@ -89,6 +89,7 @@ public class SwaggerConfig { + "
          " // + "
        • Querying of available types of data to consume.
        • " // + "
        • Management of data subscription jobs
        • " // + + "
        • Optional callback API provided by consumers to get notification on added and removed information types.
        • " // + "
        " // + "

        " // + "

        Service status

        " // diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/StatusController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/StatusController.java index 68b654e4..9e215486 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/StatusController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/StatusController.java @@ -59,7 +59,7 @@ public class StatusController { private InfoProducers infoProducers; @Gson.TypeAdapters - @Schema(name = "status_info") + @Schema(name = "service_status_info") public static class StatusInfo { @Schema(name = "status", description = "status text") @SerializedName("status") diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/a1e/A1eEiJobStatus.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/a1e/A1eEiJobStatus.java index cc558658..dff0bf3a 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/a1e/A1eEiJobStatus.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/a1e/A1eEiJobStatus.java @@ -32,13 +32,13 @@ import org.immutables.gson.Gson; public class A1eEiJobStatus { @Gson.TypeAdapters - @Schema(name = "EiJobStatusValues", description = "Allowed values for EI job status") + @Schema(name = "EiJobStatusValues", description = OPERATIONAL_STATE_DESCRIPTION) public enum EiJobStatusValues { ENABLED, DISABLED } - private static final String OPERATIONAL_STATE_DESCRIPTION = "values:\n" // - + "ENABLED: the A1-EI producer is able to deliver EI result for the EI job\n" // + private static final String OPERATIONAL_STATE_DESCRIPTION = "Allowed values for EI job status:
        " // + + "ENABLED: the A1-EI producer is able to deliver EI result for the EI job
        " // + "DISABLED: the A1-EI producer is unable to deliver EI result for the EI job"; @Schema(name = "eiJobStatus", description = OPERATIONAL_STATE_DESCRIPTION, required = true) diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerCallbacks.java new file mode 100644 index 00000000..97a829c4 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerCallbacks.java @@ -0,0 +1,81 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 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.r1consumer; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.lang.invoke.MethodHandles; + +import org.oransc.enrichment.clients.AsyncRestClient; +import org.oransc.enrichment.clients.AsyncRestClientFactory; +import org.oransc.enrichment.configuration.ApplicationConfig; +import org.oransc.enrichment.repository.InfoType; +import org.oransc.enrichment.repository.InfoTypeSubscriptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Callbacks to the Consumer. Notifies consumer according to the API (which this + * class adapts to) + */ +@SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. +@Component +public class ConsumerCallbacks implements InfoTypeSubscriptions.Callbacks { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static Gson gson = new GsonBuilder().create(); + + private final AsyncRestClient restClient; + + public ConsumerCallbacks(@Autowired ApplicationConfig config) { + AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config.getWebClientConfig()); + this.restClient = restClientFactory.createRestClientNoHttpProxy(""); + } + + @Override + public void notifyTypeRegistered(InfoType type, InfoTypeSubscriptions.SubscriptionInfo subscriptionInfo) { + ConsumerTypeRegistrationInfo info = new ConsumerTypeRegistrationInfo(type.getJobDataSchema(), + ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.REGISTERED, type.getId()); + String body = gson.toJson(info); + + post(subscriptionInfo.getCallbackUrl(), body); + + } + + @Override + public void notifyTypeRemoved(InfoType type, InfoTypeSubscriptions.SubscriptionInfo subscriptionInfo) { + ConsumerTypeRegistrationInfo info = new ConsumerTypeRegistrationInfo(type.getJobDataSchema(), + ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.DEREGISTERED, type.getId()); + String body = gson.toJson(info); + post(subscriptionInfo.getCallbackUrl(), body); + + } + + private void post(String url, String body) { + restClient.post(url, body) // + .subscribe(response -> logger.debug("Post OK {}", url), // + throwable -> logger.warn("Post failed for consumer callback {} {}", url, body), null); + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerConsts.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerConsts.java index 6ddd5834..2fa00e1f 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerConsts.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerConsts.java @@ -25,10 +25,11 @@ public class ConsumerConsts { public static final String API_ROOT = "/data-consumer/v1"; public static final String CONSUMER_API_NAME = "Data consumer"; + public static final String CONSUMER_API_CALLBACKS_NAME = "Data consumer (callbacks)"; public static final String CONSUMER_API_DESCRIPTION = "API for data consumers"; public static final String OWNER_PARAM = "owner"; - public static final String OWNER_PARAM_DESCRIPTION = "selects subscription jobs for one job owner"; + public static final String OWNER_PARAM_DESCRIPTION = "selects result for one owner"; public static final String INDIVIDUAL_JOB = "Individual data subscription job"; @@ -42,6 +43,12 @@ public class ConsumerConsts { public static final String PERFORM_TYPE_CHECK_PARAM_DESCRIPTION = "when true, a validation of that the type exists and that the job matches the type schema."; + public static final String INDIVIDUAL_TYPE_SUBSCRIPTION = + "Individual subscription for information types (registration/deregistration)"; + + public static final String TYPE_SUBSCRIPTION_DESCRIPTION = + "This service operation is used to subscribe to notifications for changes in the availability of data types."; + private ConsumerConsts() { } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerController.java index 9f16728b..fd1901e3 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerController.java @@ -48,8 +48,10 @@ import org.oransc.enrichment.controllers.r1producer.ProducerCallbacks; import org.oransc.enrichment.exceptions.ServiceException; import org.oransc.enrichment.repository.InfoJob; import org.oransc.enrichment.repository.InfoJobs; +import org.oransc.enrichment.repository.InfoProducer; import org.oransc.enrichment.repository.InfoProducers; import org.oransc.enrichment.repository.InfoType; +import org.oransc.enrichment.repository.InfoTypeSubscriptions; import org.oransc.enrichment.repository.InfoTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +90,13 @@ public class ConsumerController { private InfoProducers infoProducers; @Autowired - ProducerCallbacks producerCallbacks; + private ConsumerCallbacks consumerCallbacks; + + @Autowired + private ProducerCallbacks producerCallbacks; + + @Autowired + private InfoTypeSubscriptions infoTypeSubscriptions; private static Gson gson = new GsonBuilder().create(); @@ -305,6 +313,132 @@ public class ConsumerController { .onErrorResume(throwable -> Mono.just(ErrorResponse.create(throwable, HttpStatus.NOT_FOUND))); } + @GetMapping(path = "/info-type-subscription", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = "Information type subscription identifiers", + description = "query for information type subscription identifiers") + @ApiResponses( + value = { // + @ApiResponse( + responseCode = "200", + description = "Information type subscription identifiers", // + content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))),}) + public ResponseEntity getInfoTypeSubscriptions( // + + @Parameter( + name = ConsumerConsts.OWNER_PARAM, + required = false, // + description = ConsumerConsts.OWNER_PARAM_DESCRIPTION) // + @RequestParam(name = ConsumerConsts.OWNER_PARAM, required = false) String owner) { + try { + List result = new ArrayList<>(); + if (owner != null) { + this.infoTypeSubscriptions.getSubscriptionsForOwner(owner) + .forEach(subscription -> result.add(subscription.getId())); + } else { + this.infoTypeSubscriptions.getAllSubscriptions() + .forEach(subscription -> result.add(subscription.getId())); + } + return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @GetMapping(path = "/info-type-subscription/{subscriptionId}", produces = MediaType.APPLICATION_JSON_VALUE) // + @Operation(summary = ConsumerConsts.INDIVIDUAL_TYPE_SUBSCRIPTION, description = "") // + @ApiResponses( + value = { // + @ApiResponse( + responseCode = "200", + description = "Type subscription", // + content = @Content(schema = @Schema(implementation = ConsumerTypeSubscriptionInfo.class))), // + @ApiResponse( + responseCode = "404", + description = "Subscription is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) + public ResponseEntity getIndividualTypeSubscription( // + @PathVariable("subscriptionId") String subscriptionId) { + try { + InfoTypeSubscriptions.SubscriptionInfo subscription = + this.infoTypeSubscriptions.getSubscription(subscriptionId); + return new ResponseEntity<>(gson.toJson(toTypeSuscriptionInfo(subscription)), HttpStatus.OK); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + @PutMapping( + path = "/info-type-subscription/{subscriptionId}", // + produces = MediaType.APPLICATION_JSON_VALUE, // + consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = ConsumerConsts.INDIVIDUAL_TYPE_SUBSCRIPTION, + description = ConsumerConsts.TYPE_SUBSCRIPTION_DESCRIPTION) + @ApiResponses( + value = { // + @ApiResponse( + responseCode = "201", + description = "Subscription created", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), // + @ApiResponse( + responseCode = "200", + description = "Subscription updated", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))) // + }) + public Mono> putIndividualTypeSubscription( // + @PathVariable("subscriptionId") String subscriptionId, // + @RequestBody ConsumerTypeSubscriptionInfo subscription) { + + final boolean isNewSubscription = this.infoTypeSubscriptions.get(subscriptionId) == null; + this.infoTypeSubscriptions.put(toTypeSuscriptionInfo(subscription, subscriptionId)); + return Mono.just(new ResponseEntity<>(isNewSubscription ? HttpStatus.CREATED : HttpStatus.OK)); + } + + @DeleteMapping(path = "/info-type-subscription/{subscriptionId}", produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = ConsumerConsts.INDIVIDUAL_TYPE_SUBSCRIPTION, description = "") + @ApiResponses( + value = { // + @ApiResponse( + responseCode = "200", + description = "Not used", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), + @ApiResponse( + responseCode = "204", + description = "Subscription deleted", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Subscription is not found", // + content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) // + }) + public ResponseEntity deleteIndividualTypeSubscription( // + @PathVariable("subscriptionId") String subscriptionId) { + try { + InfoTypeSubscriptions.SubscriptionInfo subscription = + this.infoTypeSubscriptions.getSubscription(subscriptionId); + this.infoTypeSubscriptions.remove(subscription); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } catch (Exception e) { + return ErrorResponse.create(e, HttpStatus.NOT_FOUND); + } + } + + private ConsumerTypeSubscriptionInfo toTypeSuscriptionInfo(InfoTypeSubscriptions.SubscriptionInfo s) { + return new ConsumerTypeSubscriptionInfo(s.getCallbackUrl(), s.getOwner()); + } + + private InfoTypeSubscriptions.SubscriptionInfo toTypeSuscriptionInfo(ConsumerTypeSubscriptionInfo s, + String subscriptionId) { + return InfoTypeSubscriptions.SubscriptionInfo.builder() // + .callback(this.consumerCallbacks) // + .owner(s.owner) // + .id(subscriptionId) // + .callbackUrl(s.statusResultUri).build(); + + } + private Mono startInfoSubscriptionJob(InfoJob newInfoJob) { return this.producerCallbacks.startInfoSubscriptionJob(newInfoJob, infoProducers) // .doOnNext(noOfAcceptingProducers -> this.logger.debug("Started job {}, number of activated producers: {}", @@ -370,7 +504,17 @@ public class ConsumerController { } private ConsumerInfoTypeInfo toInfoTypeInfo(InfoType type) { - return new ConsumerInfoTypeInfo(type.getJobDataSchema()); + return new ConsumerInfoTypeInfo(type.getJobDataSchema(), typeStatus(type), + this.infoProducers.getProducerIdsForType(type.getId()).size()); + } + + private ConsumerInfoTypeInfo.ConsumerTypeStatusValues typeStatus(InfoType type) { + for (InfoProducer producer : this.infoProducers.getProducersForType(type)) { + if (producer.isAvailable()) { + return ConsumerInfoTypeInfo.ConsumerTypeStatusValues.ENABLED; + } + } + return ConsumerInfoTypeInfo.ConsumerTypeStatusValues.DISABLED; } private ConsumerJobInfo toInfoJobInfo(InfoJob s) { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerInfoTypeInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerInfoTypeInfo.java index c227a203..92ee19bd 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerInfoTypeInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerInfoTypeInfo.java @@ -2,7 +2,7 @@ * ========================LICENSE_START================================= * O-RAN-SC * %% - * Copyright (C) 2020 Nordix Foundation + * Copyright (C) 2021 Nordix Foundation * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,30 @@ public class ConsumerInfoTypeInfo { @JsonProperty(value = "job_data_schema", required = true) public Object jobDataSchema; - public ConsumerInfoTypeInfo(Object jobDataSchema) { + @Gson.TypeAdapters + @Schema(name = "consumer_type_status_values", description = STATUS_DESCRIPTION) + public enum ConsumerTypeStatusValues { + ENABLED, DISABLED + } + + private static final String STATUS_DESCRIPTION = "Allowed values:
        " // + + "ENABLED: one or several producers for the information type are available
        " // + + "DISABLED: no producers for the information type are available"; + + @Schema(name = "type_status", description = STATUS_DESCRIPTION, required = true) + @SerializedName("type_status") + @JsonProperty(value = "type_status", required = true) + public ConsumerTypeStatusValues state; + + @Schema(name = "no_of_producers", description = "The number of registered producers for the type", required = true) + @SerializedName("no_of_producers") + @JsonProperty(value = "no_of_producers", required = true) + public int noOfProducers; + + public ConsumerInfoTypeInfo(Object jobDataSchema, ConsumerTypeStatusValues state, int noOfProducers) { this.jobDataSchema = jobDataSchema; + this.state = state; + this.noOfProducers = noOfProducers; } public ConsumerInfoTypeInfo() { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobInfo.java index bc376280..4f91d640 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobInfo.java @@ -28,7 +28,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import org.immutables.gson.Gson; @Gson.TypeAdapters -@Schema(name = "consumer_job", description = "Information for an Enrichment Information Job") +@Schema(name = "consumer_job", description = "Information for an Enrichment Information Job") public class ConsumerJobInfo { @Schema( diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobStatus.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobStatus.java index 84f605b9..3e8bb884 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobStatus.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerJobStatus.java @@ -34,16 +34,16 @@ import org.immutables.gson.Gson; public class ConsumerJobStatus { @Gson.TypeAdapters - @Schema(name = "info_job_status_values", description = "Allowed values for Information Job status") + @Schema(name = "info_job_status_values", description = OPERATIONAL_STATE_DESCRIPTION) public enum InfoJobStatusValues { ENABLED, DISABLED } - private static final String OPERATIONAL_STATE_DESCRIPTION = "values:\n" // - + "ENABLED: the A1-Information producer is able to deliver result for the Information Job\n" // + private static final String OPERATIONAL_STATE_DESCRIPTION = "Allowed values:
        " // + + "ENABLED: the A1-Information producer is able to deliver result for the Information Job
        " // + "DISABLED: the A1-Information producer is unable to deliver result for the Information Job"; - private static final String PRODUCERS_DESCRIPTION = "An array of all registerred Information Producer Identifiers."; + private static final String PRODUCERS_DESCRIPTION = "An array of all registered Information Producer Identifiers."; @Schema(name = "info_job_status", description = OPERATIONAL_STATE_DESCRIPTION, required = true) @SerializedName("info_job_status") diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeRegistrationInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeRegistrationInfo.java new file mode 100644 index 00000000..3d1533fc --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeRegistrationInfo.java @@ -0,0 +1,68 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 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.r1consumer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.immutables.gson.Gson; + +@Gson.TypeAdapters +@Schema(name = "consumer_type_registration_info", description = "Information for an Information type") +public class ConsumerTypeRegistrationInfo { + + @Schema(name = "info_type_id", description = "Information type identifier", required = true) + @SerializedName("info_type_id") + @JsonProperty(value = "info_type_id", required = true) + public String infoTypeId; + + @Schema(name = "job_data_schema", description = "Json schema for the job data", required = true) + @SerializedName("job_data_schema") + @JsonProperty(value = "job_data_schema", required = true) + public Object jobDataSchema; + + @Gson.TypeAdapters + @Schema(name = "consumer_type_registration_values", description = REGISTRATION_DESCRIPTION) + public enum ConsumerTypeStatusValues { + REGISTERED, DEREGISTERED + } + + private static final String REGISTRATION_DESCRIPTION = "Allowed values:
        " // + + "REGISTERED: the information type has been registered
        " // + + "DEREGISTERED: the information type has been removed"; + + @Schema(name = "status", description = REGISTRATION_DESCRIPTION, required = true) + @SerializedName("status") + @JsonProperty(value = "status", required = true) + public ConsumerTypeStatusValues state; + + public ConsumerTypeRegistrationInfo(Object jobDataSchema, ConsumerTypeStatusValues state, String infoTypeId) { + this.jobDataSchema = jobDataSchema; + this.state = state; + this.infoTypeId = infoTypeId; + } + + public ConsumerTypeRegistrationInfo() { + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeSubscriptionInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeSubscriptionInfo.java new file mode 100644 index 00000000..a0c4722d --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1consumer/ConsumerTypeSubscriptionInfo.java @@ -0,0 +1,53 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 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.r1consumer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; + +import org.immutables.gson.Gson; + +@EqualsAndHashCode +@Gson.TypeAdapters +@Schema(name = "consumer_type_subscription_info", description = "Information for an information type subscription") +public class ConsumerTypeSubscriptionInfo { + + @Schema(name = "status_result_uri", description = "The target URI of the subscribed information", required = true) + @SerializedName("status_result_uri") + @JsonProperty(value = "status_result_uri", required = true) + public String statusResultUri = ""; + + @Schema(name = "owner", description = "Identity of the owner of the subscription", required = true) + @SerializedName("owner") + @JsonProperty(value = "owner", required = true) + public String owner = ""; + + public ConsumerTypeSubscriptionInfo() { + } + + public ConsumerTypeSubscriptionInfo(String statusResultUri, String owner) { + this.statusResultUri = statusResultUri; + this.owner = owner; + } +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1producer/ProducerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1producer/ProducerController.java index 15bd56dd..29426ab0 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1producer/ProducerController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/r1producer/ProducerController.java @@ -46,6 +46,7 @@ import org.oransc.enrichment.repository.InfoJobs; import org.oransc.enrichment.repository.InfoProducer; import org.oransc.enrichment.repository.InfoProducers; import org.oransc.enrichment.repository.InfoType; +import org.oransc.enrichment.repository.InfoTypeSubscriptions; import org.oransc.enrichment.repository.InfoTypes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -75,6 +76,9 @@ public class ProducerController { @Autowired private InfoProducers infoProducers; + @Autowired + private InfoTypeSubscriptions typeSubscriptions; + @GetMapping(path = ProducerConsts.API_ROOT + "/info-types", produces = MediaType.APPLICATION_JSON_VALUE) // @Operation(summary = "Info Type identifiers", description = "") // @ApiResponses( @@ -145,7 +149,9 @@ public class ProducerController { if (registrationInfo.jobDataSchema == null) { return ErrorResponse.create("No schema provided", HttpStatus.BAD_REQUEST); } - this.infoTypes.put(new InfoType(infoTypeId, registrationInfo.jobDataSchema)); + InfoType newDefinition = new InfoType(infoTypeId, registrationInfo.jobDataSchema); + this.infoTypes.put(newDefinition); + this.typeSubscriptions.notifyTypeRegistered(newDefinition); return new ResponseEntity<>(previousDefinition == null ? HttpStatus.CREATED : HttpStatus.OK); } @@ -184,6 +190,7 @@ public class ProducerController { return ErrorResponse.create("The type has active producers: " + firstProducerId, HttpStatus.NOT_ACCEPTABLE); } this.infoTypes.remove(type); + this.typeSubscriptions.notifyTypeRemoved(type); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/InfoTypeSubscriptions.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/InfoTypeSubscriptions.java new file mode 100644 index 00000000..f54a8498 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/InfoTypeSubscriptions.java @@ -0,0 +1,137 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 Nordix Foundation + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================LICENSE_END=================================== + */ + +package org.oransc.enrichment.repository; + +import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import lombok.Builder; +import lombok.Getter; + +import org.oransc.enrichment.exceptions.ServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Subscriptions of callbacks for type registrations + */ +@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally +@Component +public class InfoTypeSubscriptions { + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final Map allSubscriptions = new HashMap<>(); + private final MultiMap subscriptionsByOwner = new MultiMap<>(); + + public interface Callbacks { + void notifyTypeRegistered(InfoType type, SubscriptionInfo subscriptionInfo); + + void notifyTypeRemoved(InfoType type, SubscriptionInfo subscriptionInfo); + } + + @Builder + @Getter + public static class SubscriptionInfo { + private String id; + + private String callbackUrl; + + private String owner; + + private Callbacks callback; + } + + public synchronized void put(SubscriptionInfo subscription) { + allSubscriptions.put(subscription.getId(), subscription); + subscriptionsByOwner.put(subscription.owner, subscription.id, subscription); + logger.debug("Added type status subscription {}", subscription.id); + } + + public synchronized Collection getAllSubscriptions() { + return new Vector<>(allSubscriptions.values()); + } + + /** + * Get a subscription and throw if not fond. + * + * @param id the ID of the subscription to get. + * @return SubscriptionInfo + * @throws ServiceException if not found + */ + public synchronized SubscriptionInfo getSubscription(String id) throws ServiceException { + SubscriptionInfo p = allSubscriptions.get(id); + if (p == null) { + throw new ServiceException("Could not find Information subscription: " + id); + } + return p; + } + + /** + * Get a subscription or return null if not found. Equivalent to get in all java + * collections. + * + * @param id the ID of the subscription to get. + * @return SubscriptionInfo + */ + public synchronized SubscriptionInfo get(String id) { + return allSubscriptions.get(id); + } + + public synchronized int size() { + return allSubscriptions.size(); + } + + public synchronized void clear() { + this.allSubscriptions.clear(); + this.subscriptionsByOwner.clear(); + } + + public void remove(SubscriptionInfo subscription) { + allSubscriptions.remove(subscription.getId()); + this.subscriptionsByOwner.remove(subscription.owner, subscription.id); + logger.debug("Removed type status subscription {}", subscription.id); + } + + /** + * returns all subscriptions for an owner. The colllection can contain 0..n + * subscriptions. + * + * @param owner + * @return + */ + public synchronized Collection getSubscriptionsForOwner(String owner) { + return this.subscriptionsByOwner.get(owner); + } + + public synchronized void notifyTypeRegistered(InfoType type) { + this.allSubscriptions + .forEach((id, subscription) -> subscription.callback.notifyTypeRegistered(type, subscription)); + } + + public synchronized void notifyTypeRemoved(InfoType type) { + this.allSubscriptions + .forEach((id, subscription) -> subscription.callback.notifyTypeRemoved(type, subscription)); + } + +} diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java index dceed3ca..df938adf 100644 --- a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java @@ -58,6 +58,8 @@ import org.oransc.enrichment.controllers.r1consumer.ConsumerConsts; import org.oransc.enrichment.controllers.r1consumer.ConsumerInfoTypeInfo; import org.oransc.enrichment.controllers.r1consumer.ConsumerJobInfo; import org.oransc.enrichment.controllers.r1consumer.ConsumerJobStatus; +import org.oransc.enrichment.controllers.r1consumer.ConsumerTypeRegistrationInfo; +import org.oransc.enrichment.controllers.r1consumer.ConsumerTypeSubscriptionInfo; import org.oransc.enrichment.controllers.r1producer.ProducerCallbacks; import org.oransc.enrichment.controllers.r1producer.ProducerConsts; import org.oransc.enrichment.controllers.r1producer.ProducerInfoTypeInfo; @@ -70,6 +72,7 @@ import org.oransc.enrichment.repository.InfoJobs; import org.oransc.enrichment.repository.InfoProducer; import org.oransc.enrichment.repository.InfoProducers; import org.oransc.enrichment.repository.InfoType; +import org.oransc.enrichment.repository.InfoTypeSubscriptions; import org.oransc.enrichment.repository.InfoTypes; import org.oransc.enrichment.tasks.ProducerSupervision; import org.slf4j.Logger; @@ -135,6 +138,9 @@ class ApplicationTest { @Autowired ProducerCallbacks producerCallbacks; + @Autowired + InfoTypeSubscriptions infoTypeSubscriptions; + private static Gson gson = new GsonBuilder().create(); /** @@ -227,6 +233,8 @@ class ApplicationTest { ConsumerInfoTypeInfo info = gson.fromJson(rsp, ConsumerInfoTypeInfo.class); assertThat(info).isNotNull(); assertThat(info.jobDataSchema).isNotNull(); + assertThat(info.state).isEqualTo(ConsumerInfoTypeInfo.ConsumerTypeStatusValues.ENABLED); + assertThat(info.noOfProducers).isEqualTo(1); } @Test @@ -352,8 +360,9 @@ class ApplicationTest { String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId/status"; String rsp = restClient().get(url).block(); - assertThat(rsp).contains("ENABLED"); - assertThat(rsp).contains(PRODUCER_ID); + assertThat(rsp) // + .contains("ENABLED") // + .contains(PRODUCER_ID); ConsumerJobStatus status = gson.fromJson(rsp, ConsumerJobStatus.class); assertThat(status.producers).contains(PRODUCER_ID); @@ -546,11 +555,12 @@ class ApplicationTest { @Test void producerDeleteEiType() throws Exception { putInfoType(TYPE_ID); - String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID; - restClient().delete(url).block(); + deleteInfoType(TYPE_ID); + assertThat(this.infoTypes.size()).isZero(); - testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "Information type not found"); + testErrorCode(restClient().delete(deleteInfoTypeUrl(TYPE_ID)), HttpStatus.NOT_FOUND, + "Information type not found"); } @Test @@ -714,12 +724,13 @@ class ApplicationTest { deleteEiProducer("infoProducerId"); assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains assertThat(this.infoJobs.size()).isEqualTo(1); // The job remains - await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1)); - assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); + await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1)); + assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state) + .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); putInfoProducerWithOneType("infoProducerId", TYPE_ID); - await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2)); - assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); + await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2)); + assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); } @Test @@ -734,13 +745,14 @@ class ApplicationTest { putInfoProducerWithOneType(PRODUCER_ID, "junk"); verifyJobStatus(EI_JOB_ID, "DISABLED"); ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults(); - await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1)); - assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); + await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1)); + assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state) + .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID); verifyJobStatus(EI_JOB_ID, "ENABLED"); - await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2)); - assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); + await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2)); + assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); } @Test @@ -781,8 +793,9 @@ class ApplicationTest { verifyJobStatus(EI_JOB_ID, "ENABLED"); deleteEiProducer(PRODUCER_ID); // A Job disabled status notification shall now be received - await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); - assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); + await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1)); + assertThat(consumerResults.eiJobStatusCallbacks.get(0).state) + .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED); verifyJobStatus(EI_JOB_ID, "DISABLED"); } @@ -797,7 +810,7 @@ class ApplicationTest { assertThat(this.infoProducers.size()).isEqualTo(1); assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED); - // After 3 failed checks, the producer shall be deregisterred + // After 3 failed checks, the producer shall be deregistered this.producerSupervision.createTask().blockLast(); assertThat(this.infoProducers.size()).isZero(); // The producer is removed assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains @@ -805,8 +818,9 @@ class ApplicationTest { // Now we have one disabled job, and no producer. // PUT a producer, then a Job ENABLED status notification shall be received putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID); - await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2)); - assertThat(consumerResults.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); + await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(2)); + assertThat(consumerResults.eiJobStatusCallbacks.get(1).state) + .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); verifyJobStatus(EI_JOB_ID, "ENABLED"); } @@ -829,8 +843,9 @@ class ApplicationTest { // Run the supervision and wait for the job to get started in the producer this.producerSupervision.createTask().blockLast(); ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults(); - await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); - assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); + await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1)); + assertThat(consumerResults.eiJobStatusCallbacks.get(0).state) + .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED); verifyJobStatus(EI_JOB_ID, "ENABLED"); } @@ -904,6 +919,81 @@ class ApplicationTest { assertThat(this.infoJobs.size()).isZero(); } + @Test + void testConsumerTypeSubscription() throws Exception { + + final String callbackUrl = baseUrl() + ConsumerSimulatorController.getTypeStatusCallbackUrl(); + final ConsumerTypeSubscriptionInfo info = new ConsumerTypeSubscriptionInfo(callbackUrl, "owner"); + + { + // PUT a subscription + String body = gson.toJson(info); + ResponseEntity resp = + restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block(); + assertThat(this.infoTypeSubscriptions.size()).isEqualTo(1); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED); + resp = restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); + } + { + // GET IDs + ResponseEntity resp = restClient().getForEntity(typeSubscriptionUrl()).block(); + assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]"); + resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=owner").block(); + assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]"); + resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=junk").block(); + assertThat(resp.getBody()).isEqualTo("[]"); + } + + { + // GET the individual subscription + ResponseEntity resp = restClient().getForEntity(typeSubscriptionUrl() + "/subscriptionId").block(); + ConsumerTypeSubscriptionInfo respInfo = gson.fromJson(resp.getBody(), ConsumerTypeSubscriptionInfo.class); + assertThat(respInfo).isEqualTo(info); + } + + { + // Test the callbacks + final ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults(); + + // Test callback for PUT type + this.putInfoType(TYPE_ID); + await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(1)); + assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(0).state) + .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.REGISTERED); + + // Test callback for DELETE type + this.deleteInfoType(TYPE_ID); + await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(2)); + assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(1).state) + .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.DEREGISTERED); + } + + { + // DELETE the subscription + ResponseEntity resp = + restClient().deleteForEntity(typeSubscriptionUrl() + "/subscriptionId").block(); + assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(this.infoTypeSubscriptions.size()).isZero(); + resp = restClient().getForEntity(typeSubscriptionUrl()).block(); + assertThat(resp.getBody()).isEqualTo("[]"); + } + } + + @Test + void testTypeSubscriptionErrorCodes() throws Exception { + + testErrorCode(restClient().get(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND, + "Could not find Information subscription: junk"); + + testErrorCode(restClient().delete(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND, + "Could not find Information subscription: junk"); + } + + private String typeSubscriptionUrl() { + return ConsumerConsts.API_ROOT + "/info-type-subscription"; + } + private void deleteEiProducer(String infoProducerId) { String url = ProducerConsts.API_ROOT + "/info-producers/" + infoProducerId; restClient().deleteForEntity(url).block(); @@ -1007,7 +1097,14 @@ class ApplicationTest { ResponseEntity resp = restClient().putForEntity(url, body).block(); this.infoTypes.getType(infoTypeId); return resp.getStatusCode(); + } + + private String deleteInfoTypeUrl(String typeId) { + return ProducerConsts.API_ROOT + "/info-types/" + typeId; + } + private void deleteInfoType(String typeId) { + restClient().delete(deleteInfoTypeUrl(typeId)).block(); } private InfoType putEiProducerWithOneTypeRejecting(String producerId, String infoTypeId) diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java index a9702b29..27160f13 100644 --- a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java @@ -37,6 +37,8 @@ import lombok.Getter; import org.oransc.enrichment.controllers.VoidResponse; import org.oransc.enrichment.controllers.a1e.A1eConsts; import org.oransc.enrichment.controllers.a1e.A1eEiJobStatus; +import org.oransc.enrichment.controllers.r1consumer.ConsumerConsts; +import org.oransc.enrichment.controllers.r1consumer.ConsumerTypeRegistrationInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -48,17 +50,20 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController("ConsumerSimulatorController") -@Tag(name = A1eConsts.CONSUMER_API_CALLBACKS_NAME) public class ConsumerSimulatorController { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public static class TestResults { - public List status = Collections.synchronizedList(new ArrayList()); + public List eiJobStatusCallbacks = + Collections.synchronizedList(new ArrayList()); + public List typeRegistrationInfoCallbacks = + Collections.synchronizedList(new ArrayList()); public void reset() { - status.clear(); + eiJobStatusCallbacks.clear(); + typeRegistrationInfoCallbacks.clear(); } } @@ -69,6 +74,7 @@ public class ConsumerSimulatorController { return "/example_dataconsumer/info_jobs/" + infoJobId + "/status"; } + @Tag(name = A1eConsts.CONSUMER_API_CALLBACKS_NAME) @PostMapping( path = "/example_dataconsumer/info_jobs/{infoJobId}/status", produces = MediaType.APPLICATION_JSON_VALUE) @@ -86,7 +92,33 @@ public class ConsumerSimulatorController { @PathVariable("infoJobId") String infoJobId, // @RequestBody A1eEiJobStatus status) { logger.info("Job status callback status: {} infoJobId: {}", status.state, infoJobId); - this.testResults.status.add(status); + this.testResults.eiJobStatusCallbacks.add(status); + return new ResponseEntity<>(HttpStatus.OK); + } + + private static final String TYPE_STATUS_CALLBACK_URL = "/example_dataconsumer/info_type_status"; + + public static String getTypeStatusCallbackUrl() { + return TYPE_STATUS_CALLBACK_URL; + } + + @Tag(name = ConsumerConsts.CONSUMER_API_CALLBACKS_NAME) + @PostMapping(path = TYPE_STATUS_CALLBACK_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = "Callback for changed Information type registration status", + description = "The primitive is implemented by the data consumer and is invoked when a Information type status has been changed.
        " + + "Subscription are managed by primitives in '" + ConsumerConsts.CONSUMER_API_NAME + "'") + @ApiResponses( + value = { // + @ApiResponse( + responseCode = "200", + description = "OK", // + content = @Content(schema = @Schema(implementation = VoidResponse.class))) // + }) + public ResponseEntity typeStatusCallback( // + @RequestBody ConsumerTypeRegistrationInfo status) { + logger.info("Job type registration status callback status: {}", status); + this.testResults.typeRegistrationInfoCallbacks.add(status); return new ResponseEntity<>(HttpStatus.OK); } diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java index 1c0767b0..66af67cb 100644 --- a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ProducerSimulatorController.java @@ -56,11 +56,11 @@ public class ProducerSimulatorController { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - public static final String JOB_URL = "/producer_simulator/info_job"; - public static final String JOB_ERROR_URL = "/producer_simulator/info_job_error"; + public static final String JOB_URL = "/example_dataproducer/info_job"; + public static final String JOB_ERROR_URL = "/example_dataproducer/info_job_error"; - public static final String SUPERVISION_URL = "/producer_simulator/health_check"; - public static final String SUPERVISION_ERROR_URL = "/producer_simulator/health_check_error"; + public static final String SUPERVISION_URL = "/example_dataproducer/health_check"; + public static final String SUPERVISION_ERROR_URL = "/example_dataproducer/health_check_error"; public static class TestResults { @@ -111,7 +111,7 @@ public class ProducerSimulatorController { } } - @DeleteMapping(path = "/producer_simulator/info_job/{infoJobId}", produces = MediaType.APPLICATION_JSON_VALUE) + @DeleteMapping(path = JOB_URL + "/{infoJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @Operation( summary = "Callback for Information Job deletion", description = "The call is invoked to terminate a data subscription. The endpoint is provided by the Information Producer.")