From: Henrik Andersson Date: Wed, 4 Nov 2020 08:48:58 +0000 (+0000) Subject: Merge "Integrated PMS 2.0 to test env and test cases" X-Git-Tag: 2.1.0~21 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=647744d8ab625d28ea0ecda3e6741e23ba9b0bc7;hp=4207b8389320d90274b12be78732304034bbc116;p=nonrtric.git Merge "Integrated PMS 2.0 to test env and test cases" --- diff --git a/.gitignore b/.gitignore index e5a2f721..26c6731d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,12 @@ docs/_build/ # Eclipse .checkstyle +.classpath +target/ .sts4-cache .project .settings .pydevproject infer-out/ -.vscode \ No newline at end of file +.vscode diff --git a/docs/.project b/docs/.project deleted file mode 100644 index e248a4fa..00000000 --- a/docs/.project +++ /dev/null @@ -1,11 +0,0 @@ - - - docs - - - - - - - - diff --git a/docs/api-docs.rst b/docs/api-docs.rst index 12ff5167..9b0608a6 100644 --- a/docs/api-docs.rst +++ b/docs/api-docs.rst @@ -15,9 +15,10 @@ This is the API-docs of Non-RT RIC. :depth: 3 :local: -The Non-RT RIC consists of two parts, described in the sections below: +The Non-RT RIC consists of three parts, described in the sections below: * The Policy Agent * The SDNC A1 Controller + * The rAPP Catalogue Policy Agent @@ -44,6 +45,26 @@ See :ref:`sdnc-a1-controller-api` for how to use the API. See the README.md file in the nonrtric/sdnc-a1-controller repo for info about how to use it. +rAPP Catalogue +============== + +The Non RT-RIC Service Catalogue provides a way for services to register themselves for other services to discover. + +See `RAC API <./rac-api.html>`_ for how to use the API. + +.. |swagger-icon| image:: ./images/swagger.png + :width: 40px + +.. |yaml-icon| image:: ./images/yaml_logo.png + :width: 40px + + +.. csv-table:: + :header: "API name", "|swagger-icon|", "|yaml-icon|" + :widths: 10,5, 5 + + "RAC API", ":download:`link <../r-app-catalogue/api/rac-api.json>`", ":download:`link <../r-app-catalogue/api/rac-api.yaml>`" + Complementary tools =================== diff --git a/docs/conf.py b/docs/conf.py index d6202898..a6ae7f96 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,23 @@ branch = 'latest' linkcheck_ignore = [ 'http://localhost.*', 'http://127.0.0.1.*', - 'https://gerrit.o-ran-sc.org.*' + 'https://gerrit.o-ran-sc.org.*', + './rac-api.html' #Generated file that doesn't exist at link check. ] +extensions = ['sphinxcontrib.redoc', 'sphinx.ext.intersphinx',] + +redoc = [ + { + 'name': 'RAC API', + 'page': 'rac-api', + 'spec': '../r-app-catalogue/api/rac-api.json', + 'embed': True, + } + ] + +redoc_uri = 'https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js' + #intershpinx mapping with other projects intersphinx_mapping = {} diff --git a/docs/images/swagger.png b/docs/images/swagger.png new file mode 100644 index 00000000..f5a9e0c0 Binary files /dev/null and b/docs/images/swagger.png differ diff --git a/docs/images/yaml_logo.png b/docs/images/yaml_logo.png new file mode 100644 index 00000000..0492eb4b Binary files /dev/null and b/docs/images/yaml_logo.png differ diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 09a0c1cb..78db6850 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,12 @@ -sphinx -sphinx-rtd-theme -sphinxcontrib-httpdomain -recommonmark -lfdocs-conf +tox +Sphinx>=2,<4 +doc8 +docutils +setuptools +six +sphinx_rtd_theme>=0.4.3 +sphinxcontrib-needs>=0.2.3 +sphinxcontrib-swaggerdoc +sphinx_bootstrap_theme +sphinxcontrib-redoc +lfdocs-conf \ No newline at end of file diff --git a/enrichment-coordinator-service/docs/api.json b/enrichment-coordinator-service/docs/api.json index 71f64db5..a8df3c7c 100644 --- a/enrichment-coordinator-service/docs/api.json +++ b/enrichment-coordinator-service/docs/api.json @@ -1,21 +1,27 @@ { "basePath": "/", "paths": { - "/producer_simulator/supervision": {"get": { - "summary": "Producer supervision", + "/producer_simulator/ei_job": {"post": { + "summary": "Callback for EI job creation", "deprecated": false, "produces": ["application/json"], - "operationId": "producerSupervisionUsingGET", + "operationId": "jobCreatedCallbackUsingPOST", "responses": { - "200": { - "schema": {"type": "string"}, - "description": "OK" - }, + "200": {"description": "OK"}, + "201": {"description": "Created"}, "401": {"description": "Unauthorized"}, "403": {"description": "Forbidden"}, "404": {"description": "Not Found"} }, - "tags": ["Producer Callbacks"] + "parameters": [{ + "schema": {"$ref": "#/definitions/producer_ei_job_request"}, + "in": "body", + "name": "request", + "description": "request", + "required": true + }], + "tags": ["Producer Callbacks"], + "consumes": ["application/json"] }}, "/A1-EI/v1/eitypes/{eiTypeId}": {"get": { "summary": "Individual EI type", @@ -43,6 +49,37 @@ }], "tags": ["A1-EI (enrichment information)"] }}, + "/consumer_simulator/eijobs/{eiJobId}/status": {"post": { + "summary": "Callback for EI job status", + "deprecated": false, + "produces": ["application/json"], + "operationId": "jobStatusCallbackUsingPOST", + "responses": { + "200": {"description": "OK"}, + "201": {"description": "Created"}, + "401": {"description": "Unauthorized"}, + "403": {"description": "Forbidden"}, + "404": {"description": "Not Found"} + }, + "parameters": [ + { + "in": "path", + "name": "eiJobId", + "description": "eiJobId", + "type": "string", + "required": true + }, + { + "schema": {"$ref": "#/definitions/EiJobStatusObject"}, + "in": "body", + "name": "status", + "description": "status", + "required": true + } + ], + "tags": ["Consumer Callbacks"], + "consumes": ["application/json"] + }}, "/ei-producer/v1/eitypes": {"get": { "summary": "EI type identifiers", "deprecated": false, @@ -81,28 +118,6 @@ }, "tags": ["A1-EI (enrichment information)"] }}, - "/producer_simulator/job_deleted": {"post": { - "summary": "Callback for EI job deletion", - "deprecated": false, - "produces": ["application/json"], - "operationId": "jobDeletedCallbackUsingPOST", - "responses": { - "200": {"description": "OK"}, - "201": {"description": "Created"}, - "401": {"description": "Unauthorized"}, - "403": {"description": "Forbidden"}, - "404": {"description": "Not Found"} - }, - "parameters": [{ - "schema": {"$ref": "#/definitions/producer_ei_job_request"}, - "in": "body", - "name": "request", - "description": "request", - "required": true - }], - "tags": ["Producer Callbacks"], - "consumes": ["application/json"] - }}, "/ei-producer/v1/eiproducers/{eiProducerId}/status": {"get": { "summary": "EI producer status", "deprecated": false, @@ -129,6 +144,26 @@ }], "tags": ["Enrichment Data Producer API"] }}, + "/producer_simulator/ei_job/{eiJobId}": {"delete": { + "summary": "Callback for EI job deletion", + "deprecated": false, + "produces": ["application/json"], + "operationId": "jobDeletedCallbackUsingDELETE", + "responses": { + "200": {"description": "OK"}, + "401": {"description": "Unauthorized"}, + "204": {"description": "No Content"}, + "403": {"description": "Forbidden"} + }, + "parameters": [{ + "in": "path", + "name": "eiJobId", + "description": "eiJobId", + "type": "string", + "required": true + }], + "tags": ["Producer Callbacks"] + }}, "/ei-producer/v1/eiproducers": {"get": { "summary": "EI producer identifiers", "deprecated": false, @@ -359,6 +394,22 @@ "consumes": ["application/json"] } }, + "/producer_simulator/health_check": {"get": { + "summary": "Producer supervision", + "deprecated": false, + "produces": ["application/json"], + "operationId": "producerSupervisionUsingGET", + "responses": { + "200": { + "schema": {"type": "string"}, + "description": "OK" + }, + "401": {"description": "Unauthorized"}, + "403": {"description": "Forbidden"}, + "404": {"description": "Not Found"} + }, + "tags": ["Producer Callbacks"] + }}, "/ei-producer/v1/eiproducers/{eiProducerId}/eijobs": {"get": { "summary": "EI job definitions", "deprecated": false, @@ -455,31 +506,9 @@ "required": true }], "tags": ["A1-EI (enrichment information)"] - }}, - "/producer_simulator/job_created": {"post": { - "summary": "Callback for EI job creation", - "deprecated": false, - "produces": ["application/json"], - "operationId": "jobCreatedCallbackUsingPOST", - "responses": { - "200": {"description": "OK"}, - "201": {"description": "Created"}, - "401": {"description": "Unauthorized"}, - "403": {"description": "Forbidden"}, - "404": {"description": "Not Found"} - }, - "parameters": [{ - "schema": {"$ref": "#/definitions/producer_ei_job_request"}, - "in": "body", - "name": "request", - "description": "request", - "required": true - }], - "tags": ["Producer Callbacks"], - "consumes": ["application/json"] }} }, - "host": "localhost:42127", + "host": "localhost:41549", "definitions": { "producer_ei_job_request": { "description": "The body of the EI producer callbacks for EI job creation and deletion", @@ -576,8 +605,7 @@ "type": "object", "title": "producer_registration_info", "required": [ - "ei_job_creation_callback_url", - "ei_job_deletion_callback_url", + "ei_job_callback_url", "ei_producer_supervision_callback_url", "supported_ei_types" ], @@ -587,17 +615,13 @@ "type": "array", "items": {"$ref": "#/definitions/producer_ei_type_registration_info"} }, - "ei_job_creation_callback_url": { - "description": "callback for job creation", - "type": "string" - }, - "ei_job_deletion_callback_url": { - "description": "callback for job deletion", - "type": "string" - }, "ei_producer_supervision_callback_url": { "description": "callback for producer supervision", "type": "string" + }, + "ei_job_callback_url": { + "description": "callback for EI job", + "type": "string" } } }, @@ -697,6 +721,10 @@ "name": "A1-EI (enrichment information)", "description": "Consumer Controller" }, + { + "name": "Consumer Callbacks", + "description": "Consumer Simulator Controller" + }, { "name": "Enrichment Data Producer API", "description": "Producer Controller" diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java index ce419562..f4cf9dcc 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/BeanFactory.java @@ -23,7 +23,6 @@ package org.oransc.enrichment; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.catalina.connector.Connector; -import org.oransc.enrichment.clients.ProducerCallbacks; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.repository.EiJobs; import org.oransc.enrichment.repository.EiProducers; @@ -76,11 +75,6 @@ class BeanFactory { return this.applicationConfig; } - @Bean - public ProducerCallbacks getProducerCallbacks() { - return new ProducerCallbacks(this.applicationConfig); - } - private static Connector getHttpConnector(int httpPort) { Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setScheme("http"); diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java new file mode 100644 index 00000000..cded9535 --- /dev/null +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java @@ -0,0 +1,89 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2020 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.consumer; + +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.EiJob; +import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducer; +import org.oransc.enrichment.repository.EiType; +import org.oransc.enrichment.repository.EiTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Callbacks to the EiProducer + */ +@Component +@SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. +public class ConsumerCallbacks { + + private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static Gson gson = new GsonBuilder().create(); + + private final AsyncRestClient restClient; + private final EiTypes eiTypes; + private final EiJobs eiJobs; + + @Autowired + public ConsumerCallbacks(ApplicationConfig config, EiTypes eiTypes, EiJobs eiJobs) { + AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config.getWebClientConfig()); + this.restClient = restClientFactory.createRestClient(""); + this.eiTypes = eiTypes; + this.eiJobs = eiJobs; + } + + public void notifyConsumersProducerDeleted(EiProducer eiProducer) { + for (EiType type : eiProducer.getEiTypes()) { + if (this.eiTypes.get(type.getId()) == null) { + for (EiJob job : this.eiJobs.getJobsForType(type)) { + noifyJobOwner(job, new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED)); + } + } + } + } + + public void notifyConsumersTypeAdded(EiType eiType) { + for (EiJob job : this.eiJobs.getJobsForType(eiType)) { + noifyJobOwner(job, new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED)); + } + } + + private void noifyJobOwner(EiJob job, ConsumerEiJobStatus status) { + if (!job.jobStatusUrl().isEmpty()) { + String body = gson.toJson(status); + this.restClient.post(job.jobStatusUrl(), body) // + .subscribe(notUsed -> logger.debug("Consumer notified OK {}", job.id()), // + throwable -> logger.warn("Consumer notify failed {} {}", job.jobStatusUrl(), throwable.toString()), // + null); + } + } + +} diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java index bead8265..b194dc1f 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerController.java @@ -31,18 +31,21 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Vector; import org.everit.json.schema.Schema; import org.everit.json.schema.loader.SchemaLoader; import org.json.JSONObject; -import org.oransc.enrichment.clients.ProducerCallbacks; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.controllers.ErrorResponse; import org.oransc.enrichment.controllers.VoidResponse; +import org.oransc.enrichment.controllers.producer.ProducerCallbacks; import org.oransc.enrichment.exceptions.ServiceException; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; +import org.oransc.enrichment.repository.EiProducer; import org.oransc.enrichment.repository.EiType; import org.oransc.enrichment.repository.EiTypes; import org.oransc.enrichment.repository.ImmutableEiJob; @@ -78,8 +81,7 @@ public class ConsumerController { @Autowired ProducerCallbacks producerCallbacks; - private static Gson gson = new GsonBuilder() // - .create(); // + private static Gson gson = new GsonBuilder().create(); @GetMapping(path = "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "EI type identifiers", notes = "") @@ -149,7 +151,7 @@ public class ConsumerController { List result = new ArrayList<>(); if (owner != null) { for (EiJob job : this.eiJobs.getJobsForOwner(owner)) { - if (eiTypeId == null || job.type().getId().equals(eiTypeId)) { + if (eiTypeId == null || job.typeId().equals(eiTypeId)) { result.add(job.id()); } } @@ -204,9 +206,21 @@ public class ConsumerController { } } + private Collection getProducers(EiJob eiJob) { + try { + return this.eiTypes.getType(eiJob.typeId()).getProducers(); + } catch (Exception e) { + return new Vector<>(); + } + } + private ConsumerEiJobStatus toEiJobStatus(EiJob job) { - // TODO - return new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED); + for (EiProducer producer : getProducers(job)) { + if (producer.isAvailable()) { + return new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.ENABLED); + } + } + return new ConsumerEiJobStatus(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); } @DeleteMapping(path = "/eijobs/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -274,7 +288,7 @@ public class ConsumerController { validateJsonObjectAgainstSchema(eiType.getJobDataSchema(), eiJobInfo.jobData); EiJob existingEiJob = this.eiJobs.get(eiJobId); - if (existingEiJob != null && !existingEiJob.type().getId().equals(eiJobInfo.eiTypeId)) { + if (existingEiJob != null && !existingEiJob.typeId().equals(eiJobInfo.eiTypeId)) { throw new ServiceException("Not allowed to change type for existing EI job", HttpStatus.CONFLICT); } return Mono.just(toEiJob(eiJobInfo, eiJobId, eiType)); @@ -301,15 +315,14 @@ public class ConsumerController { } } - // Status TBD - private EiJob toEiJob(ConsumerEiJobInfo info, String id, EiType type) { return ImmutableEiJob.builder() // .id(id) // - .type(type) // + .typeId(type.getId()) // .owner(info.owner) // .jobData(info.jobData) // - .targetUri(info.targetUri) // + .targetUrl(info.targetUri) // + .jobStatusUrl(info.statusNotificationUri == null ? "" : info.statusNotificationUri) // .build(); } @@ -318,6 +331,6 @@ public class ConsumerController { } private ConsumerEiJobInfo toEiJobInfo(EiJob s) { - return new ConsumerEiJobInfo(s.type().getId(), s.jobData(), s.owner(), s.targetUri()); + return new ConsumerEiJobInfo(s.typeId(), s.jobData(), s.owner(), s.targetUrl(), s.jobStatusUrl()); } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java index d6996ce5..d88091f5 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobInfo.java @@ -60,10 +60,12 @@ public class ConsumerEiJobInfo { public ConsumerEiJobInfo() { } - public ConsumerEiJobInfo(String eiTypeId, Object jobData, String owner, String targetUri) { + public ConsumerEiJobInfo(String eiTypeId, Object jobData, String owner, String targetUri, + String statusNotificationUri) { this.eiTypeId = eiTypeId; this.jobData = jobData; this.owner = owner; this.targetUri = targetUri; + this.statusNotificationUri = statusNotificationUri; } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java index b2f657f9..60752ec2 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerEiJobStatus.java @@ -45,7 +45,10 @@ public class ConsumerEiJobStatus { @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "eiJobStatus", required = true) @SerializedName("eiJobStatus") @JsonProperty(value = "eiJobStatus", required = true) - public final EiJobStatusValues state; + public EiJobStatusValues state; + + public ConsumerEiJobStatus() { + } public ConsumerEiJobStatus(EiJobStatusValues state) { this.state = state; diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java similarity index 68% rename from enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java rename to enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java index 87d1dba1..5a47b58f 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerCallbacks.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java @@ -18,18 +18,25 @@ * ========================LICENSE_END=================================== */ -package org.oransc.enrichment.clients; +package org.oransc.enrichment.controllers.producer; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.Vector; +import org.oransc.enrichment.clients.AsyncRestClient; +import org.oransc.enrichment.clients.AsyncRestClientFactory; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiProducer; +import org.oransc.enrichment.repository.EiTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -37,6 +44,7 @@ import reactor.core.publisher.Mono; /** * Callbacks to the EiProducer */ +@Component @SuppressWarnings("java:S3457") // No need to call "toString()" method as formatting and string .. public class ProducerCallbacks { @@ -44,19 +52,23 @@ public class ProducerCallbacks { private static Gson gson = new GsonBuilder().create(); private final AsyncRestClient restClient; + private final EiTypes eiTypes; - public ProducerCallbacks(ApplicationConfig config) { + @Autowired + public ProducerCallbacks(ApplicationConfig config, EiTypes eiTypes) { AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config.getWebClientConfig()); this.restClient = restClientFactory.createRestClient(""); + this.eiTypes = eiTypes; } public void notifyProducersJobDeleted(EiJob eiJob) { - ProducerJobInfo request = new ProducerJobInfo(eiJob); - String body = gson.toJson(request); - for (EiProducer producer : eiJob.type().getProducers()) { - restClient.post(producer.getJobDeletionCallbackUrl(), body) // - .subscribe(notUsed -> logger.debug("Job deleted OK {}", producer.getId()), // - throwable -> logger.warn("Job delete failed {}", producer.getId(), throwable.toString()), null); + for (EiProducer producer : getProducers(eiJob)) { + String url = producer.getJobCallbackUrl() + "/" + eiJob.id(); + restClient.delete(url) // + .subscribe(notUsed -> logger.debug("Producer job deleted OK {}", producer.getId()), // + throwable -> logger.warn("Producer job delete failed {} {}", producer.getId(), + throwable.getMessage()), + null); } } @@ -67,7 +79,7 @@ public class ProducerCallbacks { * @return the number of producers that returned OK */ public Mono notifyProducersJobStarted(EiJob eiJob) { - return Flux.fromIterable(eiJob.type().getProducers()) // + return Flux.fromIterable(getProducers(eiJob)) // .flatMap(eiProducer -> notifyProducerJobStarted(eiProducer, eiJob)) // .collectList() // .flatMap(okResponses -> Mono.just(Integer.valueOf(okResponses.size()))); // @@ -84,7 +96,7 @@ public class ProducerCallbacks { ProducerJobInfo request = new ProducerJobInfo(eiJob); String body = gson.toJson(request); - return restClient.post(producer.getJobCreationCallbackUrl(), body) + return restClient.post(producer.getJobCallbackUrl(), body) .doOnNext(resp -> logger.debug("Job subscription started OK {}", producer.getId())) .onErrorResume(throwable -> { logger.warn("Job subscription failed {}", producer.getId(), throwable.toString()); @@ -92,4 +104,12 @@ public class ProducerCallbacks { }); } + private Collection getProducers(EiJob eiJob) { + try { + return this.eiTypes.getType(eiJob.typeId()).getProducers(); + } catch (Exception e) { + return new Vector<>(); + } + } + } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java index 306e3a91..c670ea4a 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerController.java @@ -33,10 +33,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.oransc.enrichment.clients.ProducerCallbacks; -import org.oransc.enrichment.clients.ProducerJobInfo; import org.oransc.enrichment.controllers.ErrorResponse; import org.oransc.enrichment.controllers.VoidResponse; +import org.oransc.enrichment.controllers.consumer.ConsumerCallbacks; import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; import org.oransc.enrichment.repository.EiJob; import org.oransc.enrichment.repository.EiJobs; @@ -78,6 +77,9 @@ public class ProducerController { @Autowired ProducerCallbacks producerCallbacks; + @Autowired + ConsumerCallbacks consumerCallbacks; + @GetMapping(path = ProducerConsts.API_ROOT + "/eitypes", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "EI type identifiers", notes = "") @ApiResponses( @@ -214,6 +216,7 @@ public class ProducerController { ProducerStatusInfo.OperationalState opState = producer.isAvailable() ? ProducerStatusInfo.OperationalState.ENABLED : ProducerStatusInfo.OperationalState.DISABLED; + this.logger.debug("opState {}", opState); return new ProducerStatusInfo(opState); } @@ -251,7 +254,7 @@ public class ProducerController { private void purgeTypes(Collection types) { for (EiType type : types) { if (type.getProducerIds().isEmpty()) { - this.eiTypes.deregisterType(type, this.eiJobs); + this.eiTypes.remove(type); } } } @@ -269,6 +272,7 @@ public class ProducerController { try { final EiProducer producer = this.eiProducers.getProducer(eiProducerId); this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs); + this.consumerCallbacks.notifyConsumersProducerDeleted(producer); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); @@ -280,13 +284,14 @@ public class ProducerController { if (type == null) { type = new EiType(typeInfo.eiTypeId, typeInfo.jobDataSchema); this.eiTypes.put(type); + this.consumerCallbacks.notifyConsumersTypeAdded(type); } return type; } EiProducer createProducer(Collection types, String producerId, ProducerRegistrationInfo registrationInfo) { - return new EiProducer(producerId, types, registrationInfo.jobCreationCallbackUrl, - registrationInfo.jobDeletionCallbackUrl, registrationInfo.producerSupervisionCallbackUrl); + return new EiProducer(producerId, types, registrationInfo.jobCallbackUrl, + registrationInfo.producerSupervisionCallbackUrl); } private EiProducer registerProducer(String producerId, ProducerRegistrationInfo registrationInfo) { @@ -312,8 +317,7 @@ public class ProducerController { for (EiType type : p.getEiTypes()) { types.add(toEiTypeRegistrationInfo(type)); } - return new ProducerRegistrationInfo(types, p.getJobCreationCallbackUrl(), p.getJobDeletionCallbackUrl(), - p.getProducerSupervisionCallbackUrl()); + return new ProducerRegistrationInfo(types, p.getJobCallbackUrl(), p.getProducerSupervisionCallbackUrl()); } private ProducerEiTypeRegistrationInfo toEiTypeRegistrationInfo(EiType type) { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerJobInfo.java similarity index 94% rename from enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java rename to enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerJobInfo.java index ada16e2f..a2f5b89c 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/ProducerJobInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerJobInfo.java @@ -18,7 +18,7 @@ * ========================LICENSE_END=================================== */ -package org.oransc.enrichment.clients; +package org.oransc.enrichment.controllers.producer; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName; @@ -63,7 +63,7 @@ public class ProducerJobInfo { } public ProducerJobInfo(EiJob job) { - this(job.jobData(), job.id(), job.type().getId(), job.targetUri()); + this(job.jobData(), job.id(), job.typeId(), job.targetUrl()); } public ProducerJobInfo() { diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java index 859443d6..3f785803 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerRegistrationInfo.java @@ -62,26 +62,20 @@ public class ProducerRegistrationInfo { @JsonProperty(value = "supported_ei_types", required = true) public Collection types; - @ApiModelProperty(value = "callback for job creation", required = true) - @SerializedName("ei_job_creation_callback_url") - @JsonProperty(value = "ei_job_creation_callback_url", required = true) - public String jobCreationCallbackUrl; - - @ApiModelProperty(value = "callback for job deletion", required = true) - @SerializedName("ei_job_deletion_callback_url") - @JsonProperty(value = "ei_job_deletion_callback_url", required = true) - public String jobDeletionCallbackUrl; + @ApiModelProperty(value = "callback for EI job", required = true) + @SerializedName("ei_job_callback_url") + @JsonProperty(value = "ei_job_callback_url", required = true) + public String jobCallbackUrl; @ApiModelProperty(value = "callback for producer supervision", required = true) @SerializedName("ei_producer_supervision_callback_url") @JsonProperty(value = "ei_producer_supervision_callback_url", required = true) public String producerSupervisionCallbackUrl; - public ProducerRegistrationInfo(Collection types, String jobCreationCallbackUrl, - String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) { + public ProducerRegistrationInfo(Collection types, String jobCallbackUrl, + String producerSupervisionCallbackUrl) { this.types = types; - this.jobCreationCallbackUrl = jobCreationCallbackUrl; - this.jobDeletionCallbackUrl = jobDeletionCallbackUrl; + this.jobCallbackUrl = jobCallbackUrl; this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl; } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java index 8c1206f4..95bbc036 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJob.java @@ -32,11 +32,13 @@ public interface EiJob { String id(); - EiType type(); + String typeId(); String owner(); Object jobData(); - String targetUri(); + String targetUrl(); + + String jobStatusUrl(); } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java index f0e4051c..706c8ddd 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiJobs.java @@ -38,7 +38,7 @@ public class EiJobs { public synchronized void put(EiJob job) { allEiJobs.put(job.id(), job); - jobsByType.put(job.type().getId(), job.id(), job); + jobsByType.put(job.typeId(), job.id(), job); jobsByOwner.put(job.owner(), job.id(), job); } @@ -80,7 +80,7 @@ public class EiJobs { public synchronized void remove(EiJob job) { this.allEiJobs.remove(job.id()); - jobsByType.remove(job.type().getId(), job.id()); + jobsByType.remove(job.typeId(), job.id()); jobsByOwner.remove(job.owner(), job.id()); } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java index 99932a72..63e5d4c9 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducer.java @@ -32,22 +32,18 @@ public class EiProducer { private final Collection eiTypes; @Getter - private final String jobCreationCallbackUrl; - - @Getter - private final String jobDeletionCallbackUrl; + private final String jobCallbackUrl; @Getter private final String producerSupervisionCallbackUrl; private int unresponsiveCounter = 0; - public EiProducer(String id, Collection eiTypes, String jobCreationCallbackUrl, - String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) { + public EiProducer(String id, Collection eiTypes, String jobCallbackUrl, + String producerSupervisionCallbackUrl) { this.id = id; this.eiTypes = eiTypes; - this.jobCreationCallbackUrl = jobCreationCallbackUrl; - this.jobDeletionCallbackUrl = jobDeletionCallbackUrl; + this.jobCallbackUrl = jobCallbackUrl; this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl; } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java index b3cd8956..801e7fcc 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiProducers.java @@ -36,11 +36,10 @@ import org.slf4j.LoggerFactory; @SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally public class EiProducers { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private Map allEiProducers = new HashMap<>(); + private final Map allEiProducers = new HashMap<>(); public synchronized void put(EiProducer producer) { allEiProducers.put(producer.getId(), producer); - } public synchronized Collection getAllProducers() { @@ -79,7 +78,7 @@ public class EiProducers { this.logger.error("Bug, no producer found"); } if (type.getProducerIds().isEmpty()) { - eiTypes.deregisterType(type, eiJobs); + eiTypes.remove(type); } } } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java index 6397c2f0..d0bf53a5 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/repository/EiTypes.java @@ -73,13 +73,4 @@ public class EiTypes { public synchronized void clear() { this.allEiTypes.clear(); } - - public void deregisterType(EiType type, EiJobs eiJobs) { - this.remove(type); - for (EiJob job : eiJobs.getJobsForType(type.getId())) { - eiJobs.remove(job); - this.logger.warn("Deleted job {} because no producers left", job.id()); - } - } - } diff --git a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java index f239a487..e2421665 100644 --- a/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java +++ b/enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java @@ -23,6 +23,7 @@ package org.oransc.enrichment.tasks; import org.oransc.enrichment.clients.AsyncRestClient; import org.oransc.enrichment.clients.AsyncRestClientFactory; import org.oransc.enrichment.configuration.ApplicationConfig; +import org.oransc.enrichment.controllers.consumer.ConsumerCallbacks; import org.oransc.enrichment.repository.EiJobs; import org.oransc.enrichment.repository.EiProducer; import org.oransc.enrichment.repository.EiProducers; @@ -50,15 +51,17 @@ public class ProducerSupervision { private final EiJobs eiJobs; private final EiTypes eiTypes; private final AsyncRestClient restClient; + private final ConsumerCallbacks consumerCallbacks; @Autowired public ProducerSupervision(ApplicationConfig applicationConfig, EiProducers eiProducers, EiJobs eiJobs, - EiTypes eiTypes) { + EiTypes eiTypes, ConsumerCallbacks consumerCallbacks) { AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(applicationConfig.getWebClientConfig()); this.restClient = restClientFactory.createRestClient(""); this.eiJobs = eiJobs; this.eiProducers = eiProducers; this.eiTypes = eiTypes; + this.consumerCallbacks = consumerCallbacks; } @Scheduled(fixedRate = 1000 * 60 * 5) @@ -87,6 +90,7 @@ public class ProducerSupervision { producer.setAliveStatus(false); if (producer.isDead()) { this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs); + this.consumerCallbacks.notifyConsumersProducerDeleted(producer); } } diff --git a/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java index e272faa5..3dab4608 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 @@ -43,15 +43,17 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.oransc.enrichment.clients.AsyncRestClient; import org.oransc.enrichment.clients.AsyncRestClientFactory; -import org.oransc.enrichment.clients.ProducerJobInfo; import org.oransc.enrichment.configuration.ApplicationConfig; import org.oransc.enrichment.configuration.ImmutableWebClientConfig; import org.oransc.enrichment.configuration.WebClientConfig; +import org.oransc.enrichment.controller.ConsumerSimulatorController; import org.oransc.enrichment.controller.ProducerSimulatorController; import org.oransc.enrichment.controllers.consumer.ConsumerConsts; import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo; +import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus; import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo; import org.oransc.enrichment.controllers.producer.ProducerConsts; +import org.oransc.enrichment.controllers.producer.ProducerJobInfo; import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo; import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo; import org.oransc.enrichment.controllers.producer.ProducerStatusInfo; @@ -91,6 +93,7 @@ class ApplicationTest { private final String EI_TYPE_ID = "typeId"; private final String EI_PRODUCER_ID = "producerId"; private final String EI_JOB_PROPERTY = "\"property1\""; + private final String EI_JOB_ID = "jobId"; @Autowired ApplicationContext context; @@ -110,6 +113,9 @@ class ApplicationTest { @Autowired ProducerSimulatorController producerSimulator; + @Autowired + ConsumerSimulatorController consumerSimulator; + @Autowired ProducerSupervision producerSupervision; @@ -135,6 +141,7 @@ class ApplicationTest { this.eiTypes.clear(); this.eiProducers.clear(); this.producerSimulator.getTestResults().reset(); + this.consumerSimulator.getTestResults().reset(); } @AfterEach @@ -236,12 +243,9 @@ class ApplicationTest { void testGetEiJobStatus() throws Exception { putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); putEiJob(EI_TYPE_ID, "jobId"); - String url = ConsumerConsts.API_ROOT + "/eijobs/jobId/status"; - String rsp = restClient().get(url).block(); - assertThat(rsp).contains("ENABLED"); - } - // Status TBD + verifyJobStatus("jobId", "ENABLED"); + } @Test void testDeleteEiJob() throws Exception { @@ -254,7 +258,7 @@ class ApplicationTest { ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults(); await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1)); - assertThat(simulatorResults.jobsStopped.get(0).id).isEqualTo("jobId"); + assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId"); } @Test @@ -306,8 +310,8 @@ class ApplicationTest { String url = ConsumerConsts.API_ROOT + "/eijobs/jobId"; // The element with name "property1" is mandatory in the schema - ConsumerEiJobInfo jobInfo = - new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri"); + ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", + "targetUri", "jobStatusUrl"); String body = gson.toJson(jobInfo); testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure"); @@ -349,7 +353,7 @@ class ApplicationTest { putEiJob("typeId1", "jobId"); String url = ConsumerConsts.API_ROOT + "/eijobs/jobId"; - String body = gson.toJson(eiJobInfo("typeId2")); + String body = gson.toJson(eiJobInfo("typeId2", "jobId")); testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Not allowed to change type for existing EI job"); } @@ -440,17 +444,32 @@ class ApplicationTest { putEiJob(EI_TYPE_ID, "jobId"); assertThat(this.eiJobs.size()).isEqualTo(1); - String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId"; - restClient().deleteForEntity(url).block(); + deleteEiProducer("eiProducerId"); assertThat(this.eiProducers.size()).isEqualTo(1); assertThat(this.eiTypes.getType(EI_TYPE_ID).getProducerIds()).doesNotContain("eiProducerId"); - assertThat(this.eiJobs.size()).isEqualTo(1); + verifyJobStatus("jobId", "ENABLED"); - String url2 = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId2"; - restClient().deleteForEntity(url2).block(); + deleteEiProducer("eiProducerId2"); assertThat(this.eiProducers.size()).isZero(); assertThat(this.eiTypes.size()).isZero(); - assertThat(this.eiJobs.size()).isZero(); + verifyJobStatus("jobId", "DISABLED"); + } + + @Test + void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException { + putEiProducerWithOneType("eiProducerId", EI_TYPE_ID); + putEiJob(EI_TYPE_ID, "jobId"); + + deleteEiProducer("eiProducerId"); + assertThat(this.eiTypes.size()).isZero(); // The type is gone + assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains + ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); + assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); + + putEiProducerWithOneType("eiProducerId", EI_TYPE_ID); + await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2)); + assertThat(consumerResults.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED); } @Test @@ -469,18 +488,17 @@ class ApplicationTest { assertThat(resp.getBody()).contains(EI_PRODUCER_ID); } - private void assertProducerOpState(String producerId, - ProducerStatusInfo.OperationalState expectedOperationalState) { - String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status"; - ResponseEntity resp = restClient().getForEntity(statusUrl).block(); - ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class); - assertThat(statusInfo.opState).isEqualTo(expectedOperationalState); - } - @Test void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException { putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID); + { + // Create a job + putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID); + putEiJob(EI_TYPE_ID, "jobId"); + deleteEiProducer(EI_PRODUCER_ID); + } + assertThat(this.eiProducers.size()).isEqualTo(1); assertThat(this.eiTypes.size()).isEqualTo(1); assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED); @@ -490,10 +508,15 @@ class ApplicationTest { assertThat(this.eiProducers.size()).isEqualTo(1); assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED); - // After 3 failed checks, the producer shall be deregisterred + // After 3 failed checks, the producer and the type shall be deregisterred this.producerSupervision.createTask().blockLast(); assertThat(this.eiProducers.size()).isEqualTo(0); assertThat(this.eiTypes.size()).isEqualTo(0); + + // Job disabled status notification shall be received + ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults(); + await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1)); + assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED); } @Test @@ -506,6 +529,25 @@ class ApplicationTest { assertThat(resp.getBody()).contains("hunky dory"); } + private void deleteEiProducer(String eiProducerId) { + String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId; + restClient().deleteForEntity(url).block(); + } + + private void verifyJobStatus(String jobId, String expStatus) { + String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status"; + String rsp = restClient().get(url).block(); + assertThat(rsp).contains(expStatus); + } + + private void assertProducerOpState(String producerId, + ProducerStatusInfo.OperationalState expectedOperationalState) { + String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status"; + ResponseEntity resp = restClient().getForEntity(statusUrl).block(); + ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class); + assertThat(statusInfo.opState).isEqualTo(expectedOperationalState); + } + ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId) throws JsonMappingException, JsonProcessingException { return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId); @@ -516,8 +558,7 @@ class ApplicationTest { Collection types = new ArrayList<>(); types.add(producerEiTypeRegistrationInfo(typeId)); return new ProducerRegistrationInfo(types, // - baseUrl() + ProducerSimulatorController.JOB_CREATED_ERROR_URL, - baseUrl() + ProducerSimulatorController.JOB_DELETED_ERROR_URL, + baseUrl() + ProducerSimulatorController.JOB_ERROR_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL); } @@ -526,20 +567,19 @@ class ApplicationTest { Collection types = new ArrayList<>(); types.add(producerEiTypeRegistrationInfo(typeId)); return new ProducerRegistrationInfo(types, // - baseUrl() + ProducerSimulatorController.JOB_CREATED_URL, - baseUrl() + ProducerSimulatorController.JOB_DELETED_URL, - baseUrl() + ProducerSimulatorController.SUPERVISION_URL); + baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL); } - ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException { - return eiJobInfo(EI_TYPE_ID); + private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException { + return eiJobInfo(EI_TYPE_ID, EI_JOB_ID); } - ConsumerEiJobInfo eiJobInfo(String typeId) throws JsonMappingException, JsonProcessingException { - return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri"); + ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException { + return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri", + baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId)); } - Object jsonObject(String json) { + private Object jsonObject(String json) { try { return JsonParser.parseString(json).getAsJsonObject(); } catch (Exception e) { @@ -547,7 +587,7 @@ class ApplicationTest { } } - Object jsonSchemaObject() { + private Object jsonSchemaObject() { // a json schema with one mandatory property named "string" String schemaStr = "{" // + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," // @@ -564,7 +604,7 @@ class ApplicationTest { return jsonObject(schemaStr); } - Object jsonObject() { + private Object jsonObject() { return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }"); } @@ -572,7 +612,7 @@ class ApplicationTest { throws JsonMappingException, JsonProcessingException, ServiceException { String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId; - String body = gson.toJson(eiJobInfo(eiTypeId)); + String body = gson.toJson(eiJobInfo(eiTypeId, jobId)); restClient().putForEntity(url, body).block(); return this.eiJobs.getJob(jobId); 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 new file mode 100644 index 00000000..562f2869 --- /dev/null +++ b/enrichment-coordinator-service/src/test/java/org/oransc/enrichment/controller/ConsumerSimulatorController.java @@ -0,0 +1,83 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2020 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.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.Getter; + +import org.oransc.enrichment.controllers.VoidResponse; +import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController("ConsumerSimulatorController") +@Api(tags = {"Consumer Callbacks"}) +public class ConsumerSimulatorController { + + private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static class TestResults { + + public List status = Collections.synchronizedList(new ArrayList()); + + public void reset() { + status.clear(); + } + } + + @Getter + private TestResults testResults = new TestResults(); + + public static String getJobStatusUrl(String eiJobId) { + return "/consumer_simulator/eijobs/" + eiJobId + "/status"; + } + + @PostMapping(path = "/consumer_simulator/eijobs/{eiJobId}/status", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Callback for EI job status", notes = "") + @ApiResponses( + value = { // + @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)} // + ) + public ResponseEntity jobStatusCallback( // + @PathVariable("eiJobId") String eiJobId, // + @RequestBody ConsumerEiJobStatus status) { + logger.info("Job status callback status: {} eiJobId: {}", status.state, eiJobId); + this.testResults.status.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 4c57abdd..2a3688cf 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 @@ -32,15 +32,17 @@ import java.util.List; import lombok.Getter; -import org.oransc.enrichment.clients.ProducerJobInfo; import org.oransc.enrichment.controllers.ErrorResponse; import org.oransc.enrichment.controllers.VoidResponse; +import org.oransc.enrichment.controllers.producer.ProducerJobInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -51,18 +53,16 @@ public class ProducerSimulatorController { private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - public static final String JOB_CREATED_URL = "/producer_simulator/job_created"; - public static final String JOB_DELETED_URL = "/producer_simulator/job_deleted"; - public static final String JOB_CREATED_ERROR_URL = "/producer_simulator/job_created_error"; - public static final String JOB_DELETED_ERROR_URL = "/producer_simulator/job_deleted_error"; + public static final String JOB_URL = "/producer_simulator/ei_job"; + public static final String JOB_ERROR_URL = "/producer_simulator/ei_job_error"; - public static final String SUPERVISION_URL = "/producer_simulator/supervision"; - public static final String SUPERVISION_ERROR_URL = "/producer_simulator/supervision_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 class TestResults { public List jobsStarted = Collections.synchronizedList(new ArrayList()); - public List jobsStopped = Collections.synchronizedList(new ArrayList()); + public List jobsStopped = Collections.synchronizedList(new ArrayList()); public int noOfRejectedCreate = 0; public int noOfRejectedDelete = 0; public boolean errorFound = false; @@ -82,7 +82,7 @@ public class ProducerSimulatorController { @Getter private TestResults testResults = new TestResults(); - @PostMapping(path = JOB_CREATED_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = JOB_URL, produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Callback for EI job creation", notes = "") @ApiResponses( value = { // @@ -103,24 +103,24 @@ public class ProducerSimulatorController { } } - @PostMapping(path = JOB_DELETED_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @DeleteMapping(path = "/producer_simulator/ei_job/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Callback for EI job deletion", notes = "") @ApiResponses( value = { // @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}// ) public ResponseEntity jobDeletedCallback( // - @RequestBody ProducerJobInfo request) { + @PathVariable("eiJobId") String eiJobId) { try { - logger.info("Job deleted callback {}", request.id); - this.testResults.jobsStopped.add(request); + logger.info("Job deleted callback {}", eiJobId); + this.testResults.jobsStopped.add(eiJobId); return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { return ErrorResponse.create(e, HttpStatus.NOT_FOUND); } } - @PostMapping(path = JOB_CREATED_ERROR_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(path = JOB_ERROR_URL, produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Callback for EI job creation, returns error", notes = "", hidden = true) @ApiResponses( value = { // @@ -133,7 +133,7 @@ public class ProducerSimulatorController { return ErrorResponse.create("Producer returns error on create job", HttpStatus.NOT_FOUND); } - @PostMapping(path = JOB_DELETED_ERROR_URL, produces = MediaType.APPLICATION_JSON_VALUE) + @DeleteMapping(path = JOB_ERROR_URL + "/{eiJobId}", produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Callback for EI job creation, returns error", notes = "", hidden = true) @ApiResponses( value = { // diff --git a/pom.xml b/pom.xml index cb8302ef..f21502fe 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ policy-agent sdnc-a1-controller enrichment-coordinator-service + r-app-catalogue diff --git a/r-app-catalogue/.gitignore b/r-app-catalogue/.gitignore new file mode 100644 index 00000000..ad56f2de --- /dev/null +++ b/r-app-catalogue/.gitignore @@ -0,0 +1,3 @@ +.swagger-codegen-ignore +.swagger-codegen/ +api/README.md diff --git a/r-app-catalogue/Dockerfile b/r-app-catalogue/Dockerfile new file mode 100644 index 00000000..a85f57d6 --- /dev/null +++ b/r-app-catalogue/Dockerfile @@ -0,0 +1,39 @@ +# +# ============LICENSE_START======================================================= +# Copyright (C) 2020 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. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= +# +FROM openjdk:11-jre-slim + +ARG JAR + +WORKDIR /opt/app/r-app-catalogue +RUN mkdir -p /var/log/r-app-catalogue + +EXPOSE 8081 8433 + +ADD /config/application.yaml /opt/app/r-app-catalogue/config/application.yaml +ADD target/${JAR} /opt/app/r-app-catalogue/r-app-catalogue.jar + + +RUN chmod -R 777 /opt/app/r-app-catalogue/config/ + +CMD ["java", "-jar", "/opt/app/r-app-catalogue/r-app-catalogue.jar"] + + + + diff --git a/r-app-catalogue/README.md b/r-app-catalogue/README.md new file mode 100644 index 00000000..863713db --- /dev/null +++ b/r-app-catalogue/README.md @@ -0,0 +1,27 @@ +# O-RAN-SC Non-RT RIC rAPP Catalogue + +The O-RAN Non-RT RIC rApp Catalogue provides an OpenApi 3.0 REST API for services to register themselves and discover +other services. + +**NOTE!** The definition of the REST API is done in the `api/rac-api.json` file. The yaml version of the file is +generated during compilation. + +The application is a SpringBoot application generated using the openapitools openapi-generator-maven-plugin. + +To start the application run: +`mvn spring-boot:run` + +## License + +Copyright (C) 2020 Nordix Foundation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/r-app-catalogue/api/rac-api.json b/r-app-catalogue/api/rac-api.json new file mode 100644 index 00000000..3741bdd1 --- /dev/null +++ b/r-app-catalogue/api/rac-api.json @@ -0,0 +1,246 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "rAPP Catalogue API", + "description": "The Non RT-RIC Service Catalogue provides a way for services to register themselves for other services to discover.", + "version": "1.0.0" + }, + "paths": { + "/services": { + "get": { + "summary": "Service names", + "deprecated": false, + "operationId": "getServiceNamesUsingGET", + "responses": { + "200": { + "description": "Service names", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "DroneIdentifier", + "Collector" + ] + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not used" + } + }, + "tags": [ + "rAPP Catalogue API" + ] + } + }, + "/services/{serviceName}": { + "get": { + "summary": "Individual Service", + "deprecated": false, + "operationId": "getIndividualServiceUsingGET", + "responses": { + "200": { + "description": "EI Job", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/service" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Service is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_information" + } + } + } + } + }, + "parameters": [ + { + "in": "path", + "name": "serviceName", + "description": "serviceName", + "schema": { + "type": "string" + }, + "required": true, + "example": "DroneIdentifier" + } + ], + "tags": [ + "rAPP Catalogue API" + ] + }, + "put": { + "summary": "Create or update a Service", + "deprecated": false, + "operationId": "putIndividualServiceUsingPUT", + "responses": { + "200": { + "description": "Service updated" + }, + "201": { + "description": "Service created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Provided service is not correct", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_information" + }, + "example": { + "detail": "Service is missing required property version", + "status": 404 + } + } + } + } + }, + "parameters": [ + { + "name": "serviceName", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "example": "DroneIdentifier" + } + ], + "requestBody": { + "description": "Service to create/update", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/service" + } + } + } + }, + "tags": [ + "rAPP Catalogue API" + ] + }, + "delete": { + "summary": "Remove a Service from the catalogue", + "deprecated": false, + "operationId": "deleteIndividualServiceUsingDELETE", + "responses": { + "200": { + "description": "Not used" + }, + "204": { + "description": "Job deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Service is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_information" + } + } + } + } + }, + "parameters": [ + { + "name": "serviceName", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "example": "DroneIdentifier" + } + ], + "tags": [ + "rAPP Catalogue API" + ] + } + } + }, + "components": { + "schemas": { + "service": { + "description": "A Service", + "type": "object", + "title": "service", + "required": [ + "version" + ], + "properties": { + "version": { + "description": "Version of the Service", + "type": "string", + "example": "1.0.0" + }, + "display_name": { + "description": "Display name for the Service", + "type": "string", + "example": "Drone Identifier" + }, + "description": { + "description": "Description of the Service", + "type": "string", + "example": "Detects if a UE is a drone" + } + } + }, + "error_information": { + "description": "Problem as defined in https://tools.ietf.org/html/rfc7807", + "type": "object", + "title": "error_information", + "properties": { + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "type": "string", + "example": "Service not found" + }, + "status": { + "format": "int32", + "description": "The HTTP status code generated by the origin server for this occurrence of the problem.", + "type": "integer", + "example": 404 + } + } + } + } + } +} \ No newline at end of file diff --git a/r-app-catalogue/api/rac-api.yaml b/r-app-catalogue/api/rac-api.yaml new file mode 100644 index 00000000..87e2eb97 --- /dev/null +++ b/r-app-catalogue/api/rac-api.yaml @@ -0,0 +1,175 @@ +openapi: 3.0.0 +info: + title: rAPP Catalogue API + description: The Non RT-RIC Service Catalogue provides a way for services to register + themselves for other services to discover. + version: 1.0.0 +servers: +- url: / +paths: + /services: + get: + tags: + - rAPP Catalogue API + summary: Service names + operationId: getServiceNamesUsingGET + responses: + 200: + description: Service names + content: + application/json: + schema: + type: array + items: + type: string + example: + - DroneIdentifier + - Collector + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: Not used + deprecated: false + /services/{serviceName}: + get: + tags: + - rAPP Catalogue API + summary: Individual Service + operationId: getIndividualServiceUsingGET + parameters: + - name: serviceName + in: path + description: serviceName + required: true + style: simple + explode: false + schema: + type: string + example: DroneIdentifier + responses: + 200: + description: EI Job + content: + application/json: + schema: + $ref: '#/components/schemas/service' + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: Service is not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_information' + deprecated: false + put: + tags: + - rAPP Catalogue API + summary: Create or update a Service + operationId: putIndividualServiceUsingPUT + parameters: + - name: serviceName + in: path + required: true + style: simple + explode: false + schema: + type: string + example: DroneIdentifier + requestBody: + description: Service to create/update + content: + application/json: + schema: + $ref: '#/components/schemas/service' + required: true + responses: + 200: + description: Service updated + 201: + description: Service created + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: Provided service is not correct + content: + application/json: + schema: + $ref: '#/components/schemas/error_information' + example: + detail: Service is missing required property version + status: 404 + deprecated: false + delete: + tags: + - rAPP Catalogue API + summary: Remove a Service from the catalogue + operationId: deleteIndividualServiceUsingDELETE + parameters: + - name: serviceName + in: path + required: true + style: simple + explode: false + schema: + type: string + example: DroneIdentifier + responses: + 200: + description: Not used + 204: + description: Job deleted + 401: + description: Unauthorized + 403: + description: Forbidden + 404: + description: Service is not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_information' + deprecated: false +components: + schemas: + service: + title: service + required: + - version + type: object + properties: + version: + type: string + description: Version of the Service + example: 1.0.0 + display_name: + type: string + description: Display name for the Service + example: Drone Identifier + description: + type: string + description: Description of the Service + example: Detects if a UE is a drone + description: A Service + error_information: + title: error_information + type: object + properties: + detail: + type: string + description: A human-readable explanation specific to this occurrence of + the problem. + example: Service not found + status: + type: integer + description: The HTTP status code generated by the origin server for this + occurrence of the problem. + format: int32 + example: 404 + description: Problem as defined in https://tools.ietf.org/html/rfc7807 diff --git a/r-app-catalogue/config/application.yaml b/r-app-catalogue/config/application.yaml new file mode 100644 index 00000000..fadf7d24 --- /dev/null +++ b/r-app-catalogue/config/application.yaml @@ -0,0 +1,4 @@ +spring: + profiles: + active: prod + diff --git a/r-app-catalogue/pom.xml b/r-app-catalogue/pom.xml new file mode 100644 index 00000000..3ac85627 --- /dev/null +++ b/r-app-catalogue/pom.xml @@ -0,0 +1,250 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.3.4.RELEASE + + + org.o-ran-sc.nonrtric + r-app-catalogue + 1.0.0-SNAPSHOT + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + 11 + 1.5.22 + 2.9.2 + 0.2.1 + 4.3.1 + 3.0.11 + 0.30.0 + + + + + io.swagger + swagger-annotations + ${swagger-annotations.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + org.springframework + spring-beans + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework + spring-web + + + org.springframework.boot + spring-boot + + + org.springframework + spring-webmvc + + + org.springframework + spring-context + + + io.springfox + springfox-swagger2 + ${springfox.version} + + + io.springfox + springfox-core + ${springfox.version} + + + io.springfox + springfox-spring-web + ${springfox.version} + + + io.springfox + springfox-spi + ${springfox.version} + + + org.assertj + assertj-core + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable.version} + + + javax.validation + validation-api + + + com.fasterxml.jackson.core + jackson-databind + + + org.yaml + snakeyaml + runtime + + + + org.springframework + spring-test + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + org.openapitools + openapi-generator-maven-plugin + ${openapi-generator-maven-plugin.version} + + + + generate + + + ${project.basedir}/api/rac-api.json + spring + org.oransc.rappcatalogue.api + org.oransc.rappcatalogue.model + + true + + + + + + + io.swagger.codegen.v3 + swagger-codegen-maven-plugin + ${swagger-codegen-maven-plugin.version} + + + + generate + + + ${project.basedir}/api/rac-api.json + openapi-yaml + ${project.basedir}/api/ + + rac-api.yaml + + + + + + + io.fabric8 + docker-maven-plugin + ${docker-maven-plugin.version} + false + + + generate-r-app-catalogue-image + package + + build + + + ${env.CONTAINER_PULL_REGISTRY} + + + o-ran-sc/nonrtric-r-app-catalogue:${project.version} + + try + ${basedir} + Dockerfile + + ${project.build.finalName}.jar + + + ${project.version} + + + + + + + + push-r-app-catalogue-image + + build + push + + + ${env.CONTAINER_PULL_REGISTRY} + ${env.CONTAINER_PUSH_REGISTRY} + + + o-ran-sc/nonrtric-r-app-catalogue:${project.version} + + ${basedir} + Dockerfile + + ${project.build.finalName}.jar + + + ${project.version} + latest + + + + + + + + + + + \ No newline at end of file diff --git a/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImpl.java b/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImpl.java new file mode 100644 index 00000000..701f1d8b --- /dev/null +++ b/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImpl.java @@ -0,0 +1,34 @@ +package org.oransc.rappcatalogue.api; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@org.springframework.stereotype.Service +public class ServicesApiDelegateImpl implements ServicesApiDelegate { + + @Override + public ResponseEntity deleteIndividualServiceUsingDELETE(String serviceName) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + // @Override + // public ResponseEntity getIndividualServiceUsingGET(String serviceName) { + // return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + + // } + + @Override + public ResponseEntity> getServiceNamesUsingGET() { + List services = Arrays.asList("a", "b"); + return ResponseEntity.ok(services); + } + + // @Override + // public ResponseEntity putIndividualServiceUsingPUT(String serviceName, Service service) { + // return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + + // } +} diff --git a/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImplTest.java b/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImplTest.java new file mode 100644 index 00000000..53dfc1a5 --- /dev/null +++ b/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/api/ServicesApiDelegateImplTest.java @@ -0,0 +1,33 @@ +package org.oransc.rappcatalogue.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class ServicesApiDelegateImplTest { + + @Test + void putValidService_shouldBeOk() { + ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(); + + ResponseEntity> response = delegateUnderTest.getServiceNamesUsingGET(); + } + + @Test + void getServices_shouldProvideArrayOfServices() throws Exception { + ServicesApiDelegateImpl delegateUnderTest = new ServicesApiDelegateImpl(); + + ResponseEntity> response = delegateUnderTest.getServiceNamesUsingGET(); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isEqualTo(Arrays.asList("a", "b")); + } +} diff --git a/tox.ini b/tox.ini index 4491722a..2705e16e 100644 --- a/tox.ini +++ b/tox.ini @@ -24,23 +24,14 @@ skipsdist = true [testenv:docs] basepython = python3 -deps = - sphinx - sphinx-rtd-theme - sphinxcontrib-httpdomain - recommonmark - lfdocs-conf +deps = -r{toxinidir}/docs/requirements-docs.txt commands = - sphinx-build -W -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html + sphinx-build -W -b html -n -d {envtmpdir}/docs/doctrees ./docs/ {toxinidir}/docs/_build/html echo "Generated docs available in {toxinidir}/docs/_build/html" whitelist_externals = echo [testenv:docs-linkcheck] basepython = python3 -deps = sphinx - sphinx-rtd-theme - sphinxcontrib-httpdomain - recommonmark - lfdocs-conf +deps = -r{toxinidir}/docs/requirements-docs.txt commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck