2  * ========================LICENSE_START=================================
 
   4  * ======================================================================
 
   5  * Copyright (C) 2019-2023 Nordix Foundation. All rights reserved.
 
   6  * ======================================================================
 
   7  * Licensed under the Apache License, Version 2.0 (the "License");
 
   8  * you may not use this file except in compliance with the License.
 
   9  * You may obtain a copy of the License at
 
  11  *      http://www.apache.org/licenses/LICENSE-2.0
 
  13  * Unless required by applicable law or agreed to in writing, software
 
  14  * distributed under the License is distributed on an "AS IS" BASIS,
 
  15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  16  * See the License for the specific language governing permissions and
 
  17  * limitations under the License.
 
  18  * ========================LICENSE_END===================================
 
  21 package org.onap.ccsdk.oran.a1policymanagementservice.controllers.v2;
 
  23 import io.swagger.v3.oas.annotations.tags.Tag;
 
  24 import org.onap.ccsdk.oran.a1policymanagementservice.controllers.api.v2.ServiceRegistryAndSupervisionApi;
 
  25 import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
 
  26 import org.onap.ccsdk.oran.a1policymanagementservice.models.v2.ServiceRegistrationInfo;
 
  27 import org.onap.ccsdk.oran.a1policymanagementservice.models.v2.ServiceStatus;
 
  28 import org.onap.ccsdk.oran.a1policymanagementservice.models.v2.ServiceStatusList;
 
  29 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
 
  30 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
 
  31 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
 
  32 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
 
  33 import org.slf4j.Logger;
 
  34 import org.slf4j.LoggerFactory;
 
  35 import org.springframework.beans.factory.annotation.Autowired;
 
  36 import org.springframework.http.HttpStatus;
 
  37 import org.springframework.http.ResponseEntity;
 
  38 import org.springframework.web.bind.annotation.RestController;
 
  39 import org.springframework.web.server.ServerWebExchange;
 
  40 import reactor.core.publisher.Mono;
 
  42 import java.lang.invoke.MethodHandles;
 
  43 import java.net.MalformedURLException;
 
  45 import java.time.Duration;
 
  46 import java.util.ArrayList;
 
  47 import java.util.Collection;
 
  48 import java.util.List;
 
  50 @RestController("ServiceControllerV2")
 
  52         name = ServiceController.API_NAME, //
 
  53         description = ServiceController.API_DESCRIPTION //
 
  56 public class ServiceController implements ServiceRegistryAndSupervisionApi {
 
  58     public static final String API_NAME = "Service Registry and Supervision";
 
  59     public static final String API_DESCRIPTION = "";
 
  61     private final Services services;
 
  62     private final Policies policies;
 
  64     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
  67     private PolicyController policyController;
 
  69     ServiceController(Services services, Policies policies) {
 
  70         this.services = services;
 
  71         this.policies = policies;
 
  74     private static final String GET_SERVICE_DETAILS =
 
  75             "Either information about a registered service with given identity or all registered services are returned.";
 
  78     public Mono<ResponseEntity<ServiceStatusList>> getServices(final String name, final ServerWebExchange exchange) throws Exception {
 
  79         if (name != null && this.services.get(name) == null) {
 
  80             throw new ServiceException("Service not found", HttpStatus.NOT_FOUND);
 
  83         List<ServiceStatus> servicesStatus = new ArrayList<>();
 
  84         for (Service s : this.services.getAll()) {
 
  85             if (name == null || name.equals(s.getName())) {
 
  86                 servicesStatus.add(toServiceStatus(s));
 
  89         return Mono.just(new ResponseEntity<>(new ServiceStatusList().serviceList(servicesStatus), HttpStatus.OK));
 
  92     private ServiceStatus toServiceStatus(Service s) {
 
  93         return new ServiceStatus()
 
  94                 .serviceId(s.getName())
 
  95                 .keepAliveIntervalSeconds(s.getKeepAliveInterval().toSeconds())
 
  96                 .timeSinceLastActivitySeconds(s.timeSinceLastPing().toSeconds())
 
  97                 .callbackUrl(s.getCallbackUrl());
 
 100     private void validateRegistrationInfo(ServiceRegistrationInfo registrationInfo)
 
 101             throws ServiceException, MalformedURLException {
 
 102         if (registrationInfo.getServiceId().isEmpty()) {
 
 103             throw new ServiceException("Missing mandatory parameter 'service-id'");
 
 105         if (registrationInfo.getKeepAliveIntervalSeconds() < 0) {
 
 106             throw new ServiceException("Keep alive interval should be greater or equal to 0");
 
 108         if (!registrationInfo.getCallbackUrl().isEmpty()) {
 
 109             new URL(registrationInfo.getCallbackUrl());
 
 113     private static final String REGISTER_SERVICE_DETAILS = "Registering a service is needed to:" //
 
 115             + "<li>Get callbacks about available NearRT RICs.</li>" //
 
 116             + "<li>Activate supervision of the service. If a service is inactive, its policies will automatically be deleted.</li>"//
 
 118             + "Policies can be created even if the service is not registerred. This is a feature which it is optional to use.";
 
 121     public Mono<ResponseEntity<Object>> putService(
 
 122             final Mono<ServiceRegistrationInfo> registrationInfo, final ServerWebExchange exchange) {
 
 123             return registrationInfo.flatMap(info -> {
 
 125                     validateRegistrationInfo(info);
 
 126                 } catch(Exception e) {
 
 127                     return ErrorResponse.createMono(e, HttpStatus.BAD_REQUEST);
 
 129                 final boolean isCreate = this.services.get(info.getServiceId()) == null;
 
 130                 this.services.put(toService(info));
 
 131                 return Mono.just(new ResponseEntity<>(isCreate ? HttpStatus.CREATED : HttpStatus.OK));
 
 132             }).onErrorResume(Exception.class, e -> ErrorResponse.createMono(e, HttpStatus.BAD_REQUEST));
 
 136     public Mono<ResponseEntity<Object>> deleteService(final String serviceId, final ServerWebExchange exchange) {
 
 138             Service service = removeService(serviceId);
 
 139             removePolicies(service, exchange);
 
 140             return Mono.just(new ResponseEntity<>(HttpStatus.NO_CONTENT));
 
 141         } catch (ServiceException | NullPointerException e) {
 
 142             logger.warn("Exception caught during service deletion while deleting service {}: {}", serviceId, e.getMessage());
 
 143             return ErrorResponse.createMono(e, HttpStatus.NOT_FOUND);
 
 148     public Mono<ResponseEntity<Object>> keepAliveService(final String serviceId, final ServerWebExchange exchange) throws ServiceException {
 
 149             services.getService(serviceId).keepAlive();
 
 150             return Mono.just(new ResponseEntity<>(HttpStatus.OK));
 
 153     private Service removeService(String name) throws ServiceException {
 
 154         Service service = this.services.getService(name); // Just to verify that it exists
 
 155         logger.trace("Service name to be deleted: {}", service.getName());
 
 156         this.services.remove(service.getName());
 
 160     private void removePolicies(Service service, ServerWebExchange exchange) {
 
 161         Collection<Policy> policyList = this.policies.getForService(service.getName());
 
 162         logger.trace("Policies to be deleted: {}", policyList);
 
 163         for (Policy policy : policyList) {
 
 165                 policyController.deletePolicy(policy.getId(), exchange).doOnNext(resp -> {
 
 166                     if (resp.getStatusCode().is2xxSuccessful()) {
 
 167                         logger.trace("Deleting Policy '{}' when deleting Service '{}'", policy.getId(),
 
 170                         logger.warn("Possible problem deleting Policy '{}' when deleting Service '{}'. Continuing, "
 
 171                                 + "but might trigger a re-sync with affected ric '{}'. Repsonse: \"{}\"",
 
 172                                 policy.getId(), service.getName(), policy.getRic().getConfig().getRicId(),
 
 176             } catch (Exception e) {
 
 177                 logger.warn("Problem deleting Policy '{}' when deleting Service '{}'."
 
 178                         + " Continuing, but might trigger a re-sync with affected ric '{}'. Problem: \"{}\"",
 
 179                         policy.getId(), service.getName(), policy.getRic().getConfig().getRicId(), e.getMessage());
 
 184     private Service toService(ServiceRegistrationInfo s) {
 
 185         return new Service(s.getServiceId(), Duration.ofSeconds(s.getKeepAliveIntervalSeconds()), s.getCallbackUrl());