"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: <br/>ENABLED: one or several producers for the information type are available <br/>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",
}
}
},
+ "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: <br/>REGISTERED: the information type has been registered <br/>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",
"type": "object",
"required": ["eiJobStatus"],
"properties": {"eiJobStatus": {
- "description": "Allowed values for EI job status",
+ "description": "Allowed values for EI job status: <br/>ENABLED: the A1-EI producer is able to deliver EI result for the EI job <br/>DISABLED: the A1-EI producer is unable to deliver EI result for the EI job",
"type": "string",
"enum": [
"ENABLED",
],
"properties": {
"info_job_status": {
- "description": "Allowed values for Information Job status",
+ "description": "Allowed values: <br/>ENABLED: the A1-Information producer is able to deliver result for the Information Job <br/>DISABLED: the A1-Information producer is unable to deliver result for the Information Job",
"type": "string",
"enum": [
"ENABLED",
]
},
"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"
}
}
}
},
"consumer_job": {
- "description": "Information for an Enrichment Information Job",
+ "description": "Information for an Enrichment Information Job",
"type": "object",
"required": [
"info_type_id",
"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",
}],
"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",
"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",
"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",
"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",
"schema": {"type": "string"},
"in": "query",
"name": "owner",
- "description": "selects subscription jobs for one job owner",
+ "description": "selects result for one owner",
"required": false
}
],
}],
"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": {
"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. <br/>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": {
"name": "Copyright (C) 2020 Nordix Foundation. Licensed under the Apache License.",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
},
- "description": "<h1>API documentation<\/h1><h2>General<\/h2><p> 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. <br /><br />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><h2>APIs provided by the service<\/h2><h4>A1-EI<\/h4><p> 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><h4>Data producer API<\/h4><p> 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:<ul><li>Registry of supported information types and which parameters needed to setup a subscription.<\/li><li>Registry of existing data producers.<\/li><li>Callback API provided by producers to setup subscriptions.<\/li><\/ul><\/p><h4>Data consumer API<\/h4><p> 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:<ul><li>Querying of available types of data to consume.<\/li><li>Management of data subscription jobs<\/li><\/ul><\/p><h4>Service status<\/h4><p> This API provides a means to monitor the health of this service.<\/p>",
+ "description": "<h1>API documentation<\/h1><h2>General<\/h2><p> 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. <br /><br />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><h2>APIs provided by the service<\/h2><h4>A1-EI<\/h4><p> 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><h4>Data producer API<\/h4><p> 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:<ul><li>Registry of supported information types and which parameters needed to setup a subscription.<\/li><li>Registry of existing data producers.<\/li><li>Callback API provided by producers to setup subscriptions.<\/li><\/ul><\/p><h4>Data consumer API<\/h4><p> 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:<ul><li>Querying of available types of data to consume.<\/li><li>Management of data subscription jobs<\/li><li>Optional callback API provided by consumers to get notification on added and removed information types.<\/li><\/ul><\/p><h4>Service status<\/h4><p> This API provides a means to monitor the health of this service.<\/p>",
"title": "Data management and exposure",
"version": "1.0"
},
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:<ul><li>Querying
of available types of data to consume.</li><li>Management of data subscription
- jobs</li></ul></p><h4>Service status</h4><p> This API provides a means to monitor
- the health of this service.</p>
+ jobs</li><li>Optional callback API provided by consumers to get notification on
+ added and removed information types.</li></ul></p><h4>Service status</h4><p> This
+ API provides a means to monitor the health of this service.</p>
license:
name: Copyright (C) 2020 Nordix Foundation. Licensed under the Apache License.
url: http://www.apache.org/licenses/LICENSE-2.0
- 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:
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:
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:
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:
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:
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
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:
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. <br/>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: <br/>ENABLED: one or several producers for
+ the information type are available <br/>DISABLED: no producers for the
+ information type are available'
+ enum:
+ - ENABLED
+ - DISABLED
job_data_schema:
type: object
description: Json schema for the job data
EiTypeObject:
type: object
description: Information for an EI type
- status_info:
+ service_status_info:
required:
- no_of_jobs
- no_of_producers
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: <br/>REGISTERED: the information type has
+ been registered <br/>DEREGISTERED: the information type has been removed'
+ enum:
+ - REGISTERED
+ - DEREGISTERED
+ description: Information for an Information type
ProblemDetails:
type: object
properties:
properties:
eiJobStatus:
type: string
- description: Allowed values for EI job status
+ description: 'Allowed values for EI job status: <br/>ENABLED: the A1-EI
+ producer is able to deliver EI result for the EI job <br/>DISABLED: the
+ A1-EI producer is unable to deliver EI result for the EI job'
enum:
- ENABLED
- DISABLED
properties:
info_job_status:
type: string
- description: Allowed values for Information Job status
+ description: 'Allowed values: <br/>ENABLED: the A1-Information producer
+ is able to deliver result for the Information Job <br/>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:
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
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
+ "<ul>" //
+ "<li>Querying of available types of data to consume.</li>" //
+ "<li>Management of data subscription jobs</li>" //
+ + "<li>Optional callback API provided by consumers to get notification on added and removed information types.</li>" //
+ "</ul>" //
+ "</p>" //
+ "<h4>Service status</h4>" //
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")
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: <br/>" //
+ + "ENABLED: the A1-EI producer is able to deliver EI result for the EI job <br/>" //
+ "DISABLED: the A1-EI producer is unable to deliver EI result for the EI job";
@Schema(name = "eiJobStatus", description = OPERATIONAL_STATE_DESCRIPTION, required = true)
--- /dev/null
+/*-
+ * ========================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);
+ }
+
+}
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";
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() {
}
}
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;
private InfoProducers infoProducers;
@Autowired
- ProducerCallbacks producerCallbacks;
+ private ConsumerCallbacks consumerCallbacks;
+
+ @Autowired
+ private ProducerCallbacks producerCallbacks;
+
+ @Autowired
+ private InfoTypeSubscriptions infoTypeSubscriptions;
private static Gson gson = new GsonBuilder().create();
.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<Object> getInfoTypeSubscriptions( //
+
+ @Parameter(
+ name = ConsumerConsts.OWNER_PARAM,
+ required = false, //
+ description = ConsumerConsts.OWNER_PARAM_DESCRIPTION) //
+ @RequestParam(name = ConsumerConsts.OWNER_PARAM, required = false) String owner) {
+ try {
+ List<String> 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<Object> 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<ResponseEntity<Object>> 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<Object> 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<InfoJob> startInfoSubscriptionJob(InfoJob newInfoJob) {
return this.producerCallbacks.startInfoSubscriptionJob(newInfoJob, infoProducers) //
.doOnNext(noOfAcceptingProducers -> this.logger.debug("Started job {}, number of activated producers: {}",
}
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) {
* ========================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.
@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: <br/>" //
+ + "ENABLED: one or several producers for the information type are available <br/>" //
+ + "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() {
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(
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: <br/>" //
+ + "ENABLED: the A1-Information producer is able to deliver result for the Information Job <br/>" //
+ "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")
--- /dev/null
+/*-
+ * ========================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: <br/>" //
+ + "REGISTERED: the information type has been registered <br/>" //
+ + "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() {
+ }
+
+}
--- /dev/null
+/*-
+ * ========================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;
+ }
+}
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;
@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(
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);
}
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);
}
--- /dev/null
+/*-
+ * ========================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<String, SubscriptionInfo> allSubscriptions = new HashMap<>();
+ private final MultiMap<SubscriptionInfo> 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<SubscriptionInfo> 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<SubscriptionInfo> 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));
+ }
+
+}
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;
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;
@Autowired
ProducerCallbacks producerCallbacks;
+ @Autowired
+ InfoTypeSubscriptions infoTypeSubscriptions;
+
private static Gson gson = new GsonBuilder().create();
/**
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
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);
@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
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
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
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");
}
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
// 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");
}
// 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");
}
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<String> 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<String> 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<String> 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<String> 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();
ResponseEntity<String> 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)
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;
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<A1eEiJobStatus> status = Collections.synchronizedList(new ArrayList<A1eEiJobStatus>());
+ public List<A1eEiJobStatus> eiJobStatusCallbacks =
+ Collections.synchronizedList(new ArrayList<A1eEiJobStatus>());
+ public List<ConsumerTypeRegistrationInfo> typeRegistrationInfoCallbacks =
+ Collections.synchronizedList(new ArrayList<ConsumerTypeRegistrationInfo>());
public void reset() {
- status.clear();
+ eiJobStatusCallbacks.clear();
+ typeRegistrationInfoCallbacks.clear();
}
}
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)
@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. <br/>"
+ + "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<Object> typeStatusCallback( //
+ @RequestBody ConsumerTypeRegistrationInfo status) {
+ logger.info("Job type registration status callback status: {}", status);
+ this.testResults.typeRegistrationInfoCallbacks.add(status);
return new ResponseEntity<>(HttpStatus.OK);
}
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 {
}
}
- @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.")