description: This page lists all the rest apis for the service.
version: '1.0'
title: Enrichment Data service
-host: 'localhost:8081'
+host: 'localhost:8082'
basePath: /
tags:
- name: A1-E Enrichment Data Consumer API
'200':
description: EI type
schema:
- $ref: '#/definitions/ei_type_info'
+ $ref: '#/definitions/EiType'
'401':
description: Unauthorized
'403':
'200':
description: EI Job
schema:
- $ref: '#/definitions/ei_job_info'
+ $ref: '#/definitions/EiJob'
'401':
description: Unauthorized
'403':
description: eiJobInfo
required: true
schema:
- $ref: '#/definitions/ei_job_info'
+ $ref: '#/definitions/EiJob'
- name: eiTypeId
in: path
description: eiTypeId
responses:
'200':
description: Job updated
- schema:
- type: object
'201':
description: Job created
- schema:
- type: object
'401':
description: Unauthorized
'403':
responses:
'200':
description: Not used
- schema:
- type: object
'204':
description: Job deleted
- schema:
- type: object
'401':
description: Unauthorized
'403':
'200':
description: EI Job status
schema:
- $ref: '#/definitions/ei_job_status'
+ $ref: '#/definitions/EiJobStatus'
'401':
description: Unauthorized
'403':
responses:
'200':
description: Producer updated
- schema:
- type: object
'201':
description: Producer created
- schema:
- type: object
'401':
description: Unauthorized
'403':
responses:
'200':
description: Not used
- schema:
- type: object
'204':
description: Producer deleted
- schema:
- type: object
'401':
description: Unauthorized
'403':
schema:
$ref: '#/definitions/error_information'
deprecated: false
+ '/ei-producer/v1/eiproducers/{eiProducerId}/status':
+ get:
+ tags:
+ - Enrichment Data Producer API
+ summary: EI producer status
+ operationId: getEiProducerStatusUsingGET
+ produces:
+ - application/json
+ parameters:
+ - name: eiProducerId
+ in: path
+ description: eiProducerId
+ required: true
+ type: string
+ responses:
+ '200':
+ description: EI jobs
+ schema:
+ $ref: '#/definitions/producer_status'
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Enrichment Information producer is not found
+ schema:
+ $ref: '#/definitions/error_information'
+ deprecated: false
/ei-producer/v1/eitypes:
get:
tags:
responses:
'200':
description: OK
+ '201':
+ description: Created
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Not Found
+ deprecated: false
+ /producer_simulator/job_created_error:
+ post:
+ tags:
+ - Producer Simulator
+ summary: 'Callback for EI job creation, returns error'
+ operationId: jobCreatedCallbackReturnErrorUsingPOST
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: request
+ description: request
+ required: true
schema:
- type: object
+ $ref: '#/definitions/producer_ei_job_request'
+ responses:
+ '200':
+ description: OK
'201':
description: Created
'401':
responses:
'200':
description: OK
+ '201':
+ description: Created
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Not Found
+ deprecated: false
+ /producer_simulator/job_deleted_error:
+ post:
+ tags:
+ - Producer Simulator
+ summary: 'Callback for EI job creation, returns error'
+ operationId: jobDeletedCallbackReturnErrorUsingPOST
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - in: body
+ name: request
+ description: request
+ required: true
schema:
- type: object
+ $ref: '#/definitions/producer_ei_job_request'
+ responses:
+ '200':
+ description: OK
'201':
description: Created
'401':
'404':
description: Not Found
deprecated: false
+ /producer_simulator/supervision:
+ get:
+ tags:
+ - Producer Simulator
+ summary: Producer supervision
+ operationId: producerSupervisionUsingGET
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: OK
+ schema:
+ type: string
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Not Found
+ deprecated: false
+ /producer_simulator/supervision_error:
+ get:
+ tags:
+ - Producer Simulator
+ summary: Producer supervision error
+ operationId: producerSupervisionErrorUsingGET
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: OK
+ schema:
+ type: string
+ '401':
+ description: Unauthorized
+ '403':
+ description: Forbidden
+ '404':
+ description: Not Found
+ deprecated: false
definitions:
- ei_job_info:
+ EiJob:
type: object
required:
- - job_data
- - job_owner
- - target_uri
+ - jobOwner
+ - jobParameters
+ - targetUri
properties:
- job_data:
- type: object
- description: EI Type specific job data
- job_owner:
+ jobOwner:
type: string
description: Identity of the owner of the job
- target_uri:
+ jobParameters:
+ type: object
+ description: EI Type specific job data
+ targetUri:
type: string
description: The target of the EI data
- title: ei_job_info
- description: Information for a Enrichment Information Job
- ei_job_status:
+ title: EiJob
+ description: Information for an Enrichment Information Job
+ EiJobStatus:
type: object
required:
- - operational_state
+ - operationalState
properties:
- operational_state:
+ operationalState:
type: string
description: |-
Operational state, values:
enum:
- ENABLED
- DISABLED
- title: ei_job_status
+ title: EiJobStatus
description: Status for an EI Job
- ei_type_info:
+ EiType:
type: object
properties:
- job_data_schema:
+ eiJobParametersSchema:
type: object
description: Json schema for the job data
- title: ei_type_info
+ title: EiType
description: Information for an EI type
+ Mono«ResponseEntity«object»»:
+ type: object
+ title: Mono«ResponseEntity«object»»
error_information:
type: object
properties:
required:
- ei_job_creation_callback_url
- ei_job_deletion_callback_url
+ - ei_producer_supervision_callback_url
- supported_ei_types
properties:
ei_job_creation_callback_url:
ei_job_deletion_callback_url:
type: string
description: callback for job deletion
+ ei_producer_supervision_callback_url:
+ type: string
+ description: callback for producer supervision
supported_ei_types:
type: array
description: Supported EI types
$ref: '#/definitions/producer_ei_type_registration_info'
title: producer_registration_info
description: Information for an EI producer
+ producer_status:
+ type: object
+ required:
+ - operational_state
+ properties:
+ operational_state:
+ type: string
+ description: |-
+ Operational state, values:
+ ENABLED: TBD
+ DISABLED: TBD.
+ enum:
+ - ENABLED
+ - DISABLED
+ title: producer_status
+ description: Status for an EI Producer
+ void:
+ type: object
+ title: void
+ description: Void/empty
import java.lang.invoke.MethodHandles;
import org.oransc.enrichment.configuration.ApplicationConfig;
-import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
-import org.oransc.enrichment.configuration.WebClientConfig;
import org.oransc.enrichment.repository.EiJob;
import org.oransc.enrichment.repository.EiProducer;
import org.slf4j.Logger;
ApplicationConfig applicationConfig;
public void notifyProducersJobDeleted(EiJob eiJob) {
- AsyncRestClient restClient = restClient(false);
+ AsyncRestClient restClient = restClient();
ProducerJobInfo request = new ProducerJobInfo(eiJob);
String body = gson.toJson(request);
for (EiProducer producer : eiJob.type().getProducers()) {
- restClient.post(producer.jobDeletionCallbackUrl(), body) //
- .subscribe(notUsed -> logger.debug("Job subscription started OK {}", producer.id()), //
- throwable -> logger.warn("Job subscription failed {}", producer.id(), throwable.toString()), null);
+ restClient.post(producer.getJobDeletionCallbackUrl(), body) //
+ .subscribe(notUsed -> logger.debug("Job deleted OK {}", producer.getId()), //
+ throwable -> logger.warn("Job delete failed {}", producer.getId(), throwable.toString()), null);
}
}
+ /**
+ * Calls all producers for an EiJob activation.
+ *
+ * @param eiJob an EI job
+ * @return the number of producers that returned OK
+ */
public Mono<Integer> notifyProducersJobStarted(EiJob eiJob) {
return Flux.fromIterable(eiJob.type().getProducers()) //
.flatMap(eiProducer -> notifyProducerJobStarted(eiProducer, eiJob)) //
.flatMap(okResponses -> Mono.just(Integer.valueOf(okResponses.size()))); //
}
+ /**
+ * Calls one producer for an EiJob activation.
+ *
+ * @param producer a producer
+ * @param eiJob an EI job
+ * @return the body of the response from the REST call
+ */
public Mono<String> notifyProducerJobStarted(EiProducer producer, EiJob eiJob) {
- AsyncRestClient restClient = restClient(false);
+ AsyncRestClient restClient = restClient();
ProducerJobInfo request = new ProducerJobInfo(eiJob);
String body = gson.toJson(request);
- return restClient.post(producer.jobCreationCallbackUrl(), body)
- .doOnNext(resp -> logger.debug("Job subscription started OK {}", producer.id()))
+ return restClient.post(producer.getJobCreationCallbackUrl(), body)
+ .doOnNext(resp -> logger.debug("Job subscription started OK {}", producer.getId()))
.onErrorResume(throwable -> {
- logger.warn("Job subscription failed {}", producer.id(), throwable.toString());
+ logger.warn("Job subscription failed {}", producer.getId(), throwable.toString());
return Mono.empty();
});
}
- private AsyncRestClient restClient(boolean useTrustValidation) {
- WebClientConfig config = this.applicationConfig.getWebClientConfig();
- config = ImmutableWebClientConfig.builder() //
- .keyStoreType(config.keyStoreType()) //
- .keyStorePassword(config.keyStorePassword()) //
- .keyStore(config.keyStore()) //
- .keyPassword(config.keyPassword()) //
- .isTrustStoreUsed(useTrustValidation) //
- .trustStore(config.trustStore()) //
- .trustStorePassword(config.trustStorePassword()) //
- .build();
-
- return new AsyncRestClient("", config);
+ private AsyncRestClient restClient() {
+ return new AsyncRestClient("", this.applicationConfig.getWebClientConfig());
}
}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.controllers;
+
+import io.swagger.annotations.ApiModel;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "void", description = "Void/empty")
+public class VoidResponse {
+ private VoidResponse() {
+ }
+}
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
-import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import org.oransc.enrichment.clients.ProducerCallbacks;
import org.oransc.enrichment.configuration.ApplicationConfig;
import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
import org.oransc.enrichment.exceptions.ServiceException;
import org.oransc.enrichment.repository.EiJob;
import org.oransc.enrichment.repository.EiJobs;
import org.oransc.enrichment.repository.EiType;
import org.oransc.enrichment.repository.EiTypes;
import org.oransc.enrichment.repository.ImmutableEiJob;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@Api(tags = {ConsumerConsts.CONSUMER_API_NAME})
public class ConsumerController {
- private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
@Autowired
ApplicationConfig applicationConfig;
@ApiOperation(value = "Individual EI Job", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "Not used", response = void.class),
- @ApiResponse(code = 204, message = "Job deleted", response = void.class),
+ @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
+ @ApiResponse(code = 204, message = "Job deleted", response = VoidResponse.class),
@ApiResponse(
code = 404,
message = "Enrichment Information type or job is not found",
@ApiOperation(value = "Individual EI Job", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 201, message = "Job created", response = void.class), //
- @ApiResponse(code = 200, message = "Job updated", response = void.class), // ,
+ @ApiResponse(code = 201, message = "Job created", response = VoidResponse.class), //
+ @ApiResponse(code = 200, message = "Job updated", response = VoidResponse.class), // ,
@ApiResponse(
code = 404,
message = "Enrichment Information type is not found",
import org.oransc.enrichment.clients.ProducerCallbacks;
import org.oransc.enrichment.clients.ProducerJobInfo;
import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
import org.oransc.enrichment.repository.EiJob;
import org.oransc.enrichment.repository.EiJobs;
import org.oransc.enrichment.repository.EiProducers;
import org.oransc.enrichment.repository.EiType;
import org.oransc.enrichment.repository.EiTypes;
-import org.oransc.enrichment.repository.ImmutableEiProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
) {
List<String> result = new ArrayList<>();
for (EiProducer eiProducer : this.eiProducers.getAllProducers()) {
- result.add(eiProducer.id());
+ result.add(eiProducer.getId());
}
return new ResponseEntity<>(gson.toJson(result), HttpStatus.OK);
try {
EiProducer producer = this.eiProducers.getProducer(eiProducerId);
Collection<ProducerJobInfo> producerJobs = new ArrayList<>();
- for (EiType type : producer.eiTypes()) {
+ for (EiType type : producer.getEiTypes()) {
for (EiJob eiJob : this.eiJobs.getJobsForType(type)) {
ProducerJobInfo request = new ProducerJobInfo(eiJob);
producerJobs.add(request);
}
}
+ @GetMapping(
+ path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}/status",
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "EI producer status")
+ @ApiResponses(
+ value = { //
+ @ApiResponse(code = 200, message = "EI jobs", response = ProducerStatusInfo.class), //
+ @ApiResponse(
+ code = 404,
+ message = "Enrichment Information producer is not found",
+ response = ErrorResponse.ErrorInfo.class)})
+ public ResponseEntity<Object> getEiProducerStatus( //
+ @PathVariable("eiProducerId") String eiProducerId) {
+ try {
+ EiProducer producer = this.eiProducers.getProducer(eiProducerId);
+ return new ResponseEntity<>(gson.toJson(producerStatusInfo(producer)), HttpStatus.OK);
+ } catch (Exception e) {
+ return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
+ }
+ }
+
+ private ProducerStatusInfo producerStatusInfo(EiProducer producer) {
+ ProducerStatusInfo.OperationalState opState =
+ producer.isAvailable() ? ProducerStatusInfo.OperationalState.ENABLED
+ : ProducerStatusInfo.OperationalState.DISABLED;
+ return new ProducerStatusInfo(opState);
+ }
+
@PutMapping(
path = ProducerConsts.API_ROOT + "/eiproducers/{eiProducerId}",
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Individual EI producer", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 201, message = "Producer created", response = void.class), //
- @ApiResponse(code = 200, message = "Producer updated", response = void.class)}//
+ @ApiResponse(code = 201, message = "Producer created", response = VoidResponse.class), //
+ @ApiResponse(code = 200, message = "Producer updated", response = VoidResponse.class)}//
)
public ResponseEntity<Object> putEiProducer( //
@PathVariable("eiProducerId") String eiProducerId, //
try {
EiProducer previousDefinition = this.eiProducers.get(eiProducerId);
if (previousDefinition != null) {
- for (EiType type : previousDefinition.eiTypes()) {
+ for (EiType type : previousDefinition.getEiTypes()) {
type.removeProducer(previousDefinition);
}
}
registerProducer(eiProducerId, registrationInfo);
if (previousDefinition != null) {
- purgeTypes(previousDefinition.eiTypes());
+ purgeTypes(previousDefinition.getEiTypes());
}
return new ResponseEntity<>(previousDefinition == null ? HttpStatus.CREATED : HttpStatus.OK);
private void purgeTypes(Collection<EiType> types) {
for (EiType type : types) {
if (type.getProducerIds().isEmpty()) {
- this.deregisterType(type);
+ this.eiTypes.deregisterType(type, this.eiJobs);
}
}
}
@ApiOperation(value = "Individual EI producer", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "Not used", response = void.class),
- @ApiResponse(code = 204, message = "Producer deleted", response = void.class),
+ @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
+ @ApiResponse(code = 204, message = "Producer deleted", response = VoidResponse.class),
@ApiResponse(code = 404, message = "Producer is not found", response = ErrorResponse.ErrorInfo.class)})
public ResponseEntity<Object> deleteEiProducer(@PathVariable("eiProducerId") String eiProducerId) {
try {
final EiProducer producer = this.eiProducers.getProducer(eiProducerId);
- deregisterProducer(producer);
+ this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
this.eiTypes.put(type);
}
return type;
-
}
EiProducer createProducer(Collection<EiType> types, String producerId, ProducerRegistrationInfo registrationInfo) {
- return ImmutableEiProducer.builder() //
- .id(producerId) //
- .eiTypes(types) //
- .jobCreationCallbackUrl(registrationInfo.jobCreationCallbackUrl) //
- .jobDeletionCallbackUrl(registrationInfo.jobDeletionCallbackUrl) //
- .build();
+ return new EiProducer(producerId, types, registrationInfo.jobCreationCallbackUrl,
+ registrationInfo.jobDeletionCallbackUrl, registrationInfo.producerSupervisionCallbackUrl);
}
private EiProducer registerProducer(String producerId, ProducerRegistrationInfo registrationInfo) {
for (EiType type : types) {
for (EiJob job : this.eiJobs.getJobsForType(type)) {
this.producerCallbacks.notifyProducerJobStarted(producer, job) //
- .subscribe(//
- response -> logger.debug("Producer notified OK"), //
- throwable -> logger.warn("Producer rejected job {}", throwable.getMessage()) //
- );
+ .subscribe();
}
type.addProducer(producer);
}
return producer;
}
- private void deregisterType(EiType type) {
- this.eiTypes.remove(type);
- for (EiJob job : this.eiJobs.getJobsForType(type.getId())) {
- this.eiJobs.remove(job);
- this.logger.warn("Deleted job {} because no producers left", job.id());
- }
- }
-
- private void deregisterProducer(EiProducer producer) {
- this.eiProducers.remove(producer);
- for (EiType type : producer.eiTypes()) {
- boolean removed = type.removeProducer(producer) != null;
- if (!removed) {
- this.logger.error("Bug, no producer found");
- }
- if (type.getProducerIds().isEmpty()) {
- deregisterType(type);
- }
- }
- }
-
ProducerRegistrationInfo toEiProducerRegistrationInfo(EiProducer p) {
Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
- for (EiType type : p.eiTypes()) {
+ for (EiType type : p.getEiTypes()) {
types.add(toEiTypeRegistrationInfo(type));
}
- return new ProducerRegistrationInfo(types, p.jobCreationCallbackUrl(), p.jobDeletionCallbackUrl());
+ return new ProducerRegistrationInfo(types, p.getJobCreationCallbackUrl(), p.getJobDeletionCallbackUrl(),
+ p.getProducerSupervisionCallbackUrl());
}
private ProducerEiTypeRegistrationInfo toEiTypeRegistrationInfo(EiType type) {
@JsonProperty(value = "ei_job_deletion_callback_url", required = true)
public String jobDeletionCallbackUrl;
+ @ApiModelProperty(value = "callback for producer supervision", required = true)
+ @SerializedName("ei_producer_supervision_callback_url")
+ @JsonProperty(value = "ei_producer_supervision_callback_url", required = true)
+ public String producerSupervisionCallbackUrl;
+
public ProducerRegistrationInfo(Collection<ProducerEiTypeRegistrationInfo> types, String jobCreationCallbackUrl,
- String jobDeletionCallbackUrl) {
+ String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) {
this.types = types;
this.jobCreationCallbackUrl = jobCreationCallbackUrl;
this.jobDeletionCallbackUrl = jobDeletionCallbackUrl;
+ this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl;
}
public ProducerRegistrationInfo() {
--- /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.controllers.producer;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import org.immutables.gson.Gson;
+
+@Gson.TypeAdapters
+@ApiModel(value = "producer_status", description = "Status for an EI Producer")
+public class ProducerStatusInfo {
+
+ @Gson.TypeAdapters
+ @ApiModel(value = "producer_operational_state", description = "Represents the operational states")
+ public enum OperationalState {
+ ENABLED, DISABLED
+ }
+
+ private static final String OPERATIONAL_STATE_DESCRIPTION = "Operational state, values:\n" //
+ + "ENABLED: TBD\n" //
+ + "DISABLED: TBD.";
+
+ @ApiModelProperty(value = OPERATIONAL_STATE_DESCRIPTION, name = "operational_state", required = true)
+ @SerializedName("operational_state")
+ @JsonProperty(value = "operational_state", required = true)
+ public final OperationalState opState;
+
+ public ProducerStatusInfo(OperationalState state) {
+ this.opState = state;
+ }
+
+}
import java.util.Collection;
-import org.immutables.gson.Gson;
-import org.immutables.value.Value;
+import lombok.Getter;
-@Value.Immutable
-@Gson.TypeAdapters
-public interface EiProducer {
- public String id();
+public class EiProducer {
+ @Getter
+ private final String id;
- public Collection<EiType> eiTypes();
+ @Getter
+ private final Collection<EiType> eiTypes;
- public String jobCreationCallbackUrl();
+ @Getter
+ private final String jobCreationCallbackUrl;
- public String jobDeletionCallbackUrl();
+ @Getter
+ private final String jobDeletionCallbackUrl;
+
+ @Getter
+ private final String producerSupervisionCallbackUrl;
+
+ private int unresponsiveCounter = 0;
+
+ public EiProducer(String id, Collection<EiType> eiTypes, String jobCreationCallbackUrl,
+ String jobDeletionCallbackUrl, String producerSupervisionCallbackUrl) {
+ this.id = id;
+ this.eiTypes = eiTypes;
+ this.jobCreationCallbackUrl = jobCreationCallbackUrl;
+ this.jobDeletionCallbackUrl = jobDeletionCallbackUrl;
+ this.producerSupervisionCallbackUrl = producerSupervisionCallbackUrl;
+ }
+
+ public synchronized void setAliveStatus(boolean isAlive) {
+ if (isAlive) {
+ unresponsiveCounter = 0;
+ } else {
+ unresponsiveCounter++;
+ }
+ }
+
+ public synchronized boolean isDead() {
+ return this.unresponsiveCounter >= 3;
+ }
+
+ public synchronized boolean isAvailable() {
+ return this.unresponsiveCounter == 0;
+ }
}
package org.oransc.enrichment.repository;
+import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import org.oransc.enrichment.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Dynamic representation of all Rics in the system.
+ * Dynamic representation of all EiProducers.
*/
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
public class EiProducers {
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private Map<String, EiProducer> allEiProducers = new HashMap<>();
public synchronized void put(EiProducer producer) {
- allEiProducers.put(producer.id(), producer);
+ allEiProducers.put(producer.getId(), producer);
}
this.allEiProducers.remove(id);
}
- public synchronized void remove(EiProducer producer) {
- this.allEiProducers.remove(producer.id());
- }
-
public synchronized int size() {
return allEiProducers.size();
}
public synchronized void clear() {
this.allEiProducers.clear();
}
+
+ public void deregisterProducer(EiProducer producer, EiTypes eiTypes, EiJobs eiJobs) {
+ this.remove(producer);
+ for (EiType type : producer.getEiTypes()) {
+ boolean removed = type.removeProducer(producer) != null;
+ if (!removed) {
+ this.logger.error("Bug, no producer found");
+ }
+ if (type.getProducerIds().isEmpty()) {
+ eiTypes.deregisterType(type, eiJobs);
+ }
+ }
+ }
+
+ private synchronized void remove(EiProducer producer) {
+ this.allEiProducers.remove(producer.getId());
+ }
+
}
}
public synchronized void addProducer(EiProducer producer) {
- this.producers.put(producer.id(), producer);
+ this.producers.put(producer.getId(), producer);
}
public synchronized EiProducer removeProducer(EiProducer producer) {
- return this.producers.remove(producer.id());
+ return this.producers.remove(producer.getId());
}
}
package org.oransc.enrichment.repository;
+import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import org.oransc.enrichment.exceptions.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Dynamic representation of all EI Types in the system.
*/
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
public class EiTypes {
- Map<String, EiType> allEiTypes = new HashMap<>();
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private final Map<String, EiType> allEiTypes = new HashMap<>();
public synchronized void put(EiType type) {
allEiTypes.put(type.getId(), type);
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());
+ }
+ }
+
}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.enrichment.tasks;
+
+import org.oransc.enrichment.clients.AsyncRestClient;
+import org.oransc.enrichment.configuration.ApplicationConfig;
+import org.oransc.enrichment.repository.EiJobs;
+import org.oransc.enrichment.repository.EiProducer;
+import org.oransc.enrichment.repository.EiProducers;
+import org.oransc.enrichment.repository.EiTypes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Regularly checks the availability of the EI Producers
+ */
+@Component
+@EnableScheduling
+@SuppressWarnings("squid:S2629") // Invoke method(s) only conditionally
+public class ProducerSupervision {
+ private static final Logger logger = LoggerFactory.getLogger(ProducerSupervision.class);
+
+ @Autowired
+ ApplicationConfig applicationConfig;
+
+ @Autowired
+ EiProducers eiProducers;
+
+ @Autowired
+ EiJobs eiJobs;
+
+ @Autowired
+ EiTypes eiTypes;
+
+ @Scheduled(fixedRate = 1000 * 60 * 5)
+ public void checkAllProducers() {
+ logger.debug("Checking producers starting");
+ createTask().subscribe(null, null, () -> logger.debug("Checking all Producers completed"));
+ }
+
+ public Flux<EiProducer> createTask() {
+ return Flux.fromIterable(eiProducers.getAllProducers()) //
+ .flatMap(this::checkOneProducer);
+ }
+
+ private Mono<EiProducer> checkOneProducer(EiProducer producer) {
+ return restClient().get(producer.getProducerSupervisionCallbackUrl()) //
+ .onErrorResume(throwable -> {
+ handleNonRespondingProducer(throwable, producer);
+ return Mono.empty();
+ })//
+ .doOnNext(response -> handleRespondingProducer(response, producer))
+ .flatMap(response -> Mono.just(producer));
+ }
+
+ private void handleNonRespondingProducer(Throwable throwable, EiProducer producer) {
+ logger.warn("Unresponsive producer: {} exception: {}", producer.getId(), throwable.getMessage());
+ producer.setAliveStatus(false);
+ if (producer.isDead()) {
+ this.eiProducers.deregisterProducer(producer, this.eiTypes, this.eiJobs);
+ }
+ }
+
+ private void handleRespondingProducer(String response, EiProducer producer) {
+ logger.debug("{}", response);
+ producer.setAliveStatus(true);
+ }
+
+ private AsyncRestClient restClient() {
+ return new AsyncRestClient("", this.applicationConfig.getWebClientConfig());
+ }
+
+}
import org.oransc.enrichment.controllers.producer.ProducerConsts;
import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
+import org.oransc.enrichment.controllers.producer.ProducerStatusInfo;
import org.oransc.enrichment.exceptions.ServiceException;
import org.oransc.enrichment.repository.EiJob;
import org.oransc.enrichment.repository.EiJobs;
import org.oransc.enrichment.repository.EiProducers;
import org.oransc.enrichment.repository.EiType;
import org.oransc.enrichment.repository.EiTypes;
+import org.oransc.enrichment.tasks.ProducerSupervision;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@Autowired
ProducerSimulatorController producerSimulator;
+ @Autowired
+ ProducerSupervision producerSupervision;
+
private static Gson gson = new GsonBuilder() //
.serializeNulls() //
.create(); //
EiType type = this.eiTypes.getType(EI_TYPE_ID);
assertThat(type.getProducerIds()).contains("eiProducerId");
assertThat(this.eiProducers.size()).isEqualTo(1);
- assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
+ assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
resp = restClient().putForEntity(url, body).block();
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
}
+ private void assertProducerOpState(String producerId,
+ ProducerStatusInfo.OperationalState expectedOperationalState) {
+ String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
+ ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
+ ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
+ assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
+ }
+
+ @Test
+ void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
+ putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
+
+ assertThat(this.eiProducers.size()).isEqualTo(1);
+ assertThat(this.eiTypes.size()).isEqualTo(1);
+ assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
+
+ this.producerSupervision.createTask().blockLast();
+ this.producerSupervision.createTask().blockLast();
+ assertThat(this.eiProducers.size()).isEqualTo(1);
+ assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
+
+ // After 3 failed checks, the producer shall be deregisterred
+ this.producerSupervision.createTask().blockLast();
+ assertThat(this.eiProducers.size()).isEqualTo(0);
+ assertThat(this.eiTypes.size()).isEqualTo(0);
+ }
+
ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
throws JsonMappingException, JsonProcessingException {
return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
throws JsonMappingException, JsonProcessingException {
Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
types.add(producerEiTypeRegistrationInfo(typeId));
- return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_ERROR_URL,
- baseUrl() + ProducerSimulatorController.JOB_DELETED_ERROR_URL);
+ return new ProducerRegistrationInfo(types, //
+ baseUrl() + ProducerSimulatorController.JOB_CREATED_ERROR_URL,
+ baseUrl() + ProducerSimulatorController.JOB_DELETED_ERROR_URL,
+ baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
}
ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
throws JsonMappingException, JsonProcessingException {
Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
types.add(producerEiTypeRegistrationInfo(typeId));
- return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
- baseUrl() + ProducerSimulatorController.JOB_DELETED_URL);
+ return new ProducerRegistrationInfo(types, //
+ baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
+ baseUrl() + ProducerSimulatorController.JOB_DELETED_URL,
+ baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
}
ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
import org.oransc.enrichment.clients.ProducerJobInfo;
import org.oransc.enrichment.controllers.ErrorResponse;
+import org.oransc.enrichment.controllers.VoidResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
public static final String JOB_CREATED_ERROR_URL = "/producer_simulator/job_created_error";
public static final String JOB_DELETED_ERROR_URL = "/producer_simulator/job_deleted_error";
+ public static final String SUPERVISION_URL = "/producer_simulator/supervision";
+ public static final String SUPERVISION_ERROR_URL = "/producer_simulator/supervision_error";
+
public static class TestResults {
public List<ProducerJobInfo> jobsStarted = Collections.synchronizedList(new ArrayList<ProducerJobInfo>());
@ApiOperation(value = "Callback for EI job creation", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "OK", response = void.class)}//
+ @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
)
public ResponseEntity<Object> jobCreatedCallback( //
@RequestBody ProducerJobInfo request) {
@ApiOperation(value = "Callback for EI job deletion", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "OK", response = void.class)}//
+ @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
)
public ResponseEntity<Object> jobDeletedCallback( //
@RequestBody ProducerJobInfo request) {
@ApiOperation(value = "Callback for EI job creation, returns error", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "OK", response = void.class)}//
+ @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
)
public ResponseEntity<Object> jobCreatedCallbackReturnError( //
@RequestBody ProducerJobInfo request) {
@ApiOperation(value = "Callback for EI job creation, returns error", notes = "")
@ApiResponses(
value = { //
- @ApiResponse(code = 200, message = "OK", response = void.class)}//
+ @ApiResponse(code = 200, message = "OK", response = VoidResponse.class)}//
)
public ResponseEntity<Object> jobDeletedCallbackReturnError( //
@RequestBody ProducerJobInfo request) {
return ErrorResponse.create("Producer returns error on delete job", HttpStatus.NOT_FOUND);
}
+ @GetMapping(path = SUPERVISION_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Producer supervision", notes = "")
+ @ApiResponses(
+ value = { //
+ @ApiResponse(code = 200, message = "OK", response = String.class)}//
+ )
+ public ResponseEntity<Object> producerSupervision() {
+ logger.info("Producer supervision");
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+
+ @GetMapping(path = SUPERVISION_ERROR_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Producer supervision error", notes = "")
+ @ApiResponses(
+ value = { //
+ @ApiResponse(code = 200, message = "OK", response = String.class)}//
+ )
+ public ResponseEntity<Object> producerSupervisionError() {
+ logger.info("Producer supervision error");
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+
}