2 * ========================LICENSE_START=================================
4 * ======================================================================
5 * Copyright (C) 2019-2020 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 com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
26 import io.swagger.v3.oas.annotations.Operation;
27 import io.swagger.v3.oas.annotations.Parameter;
28 import io.swagger.v3.oas.annotations.media.Content;
29 import io.swagger.v3.oas.annotations.media.Schema;
30 import io.swagger.v3.oas.annotations.responses.ApiResponse;
31 import io.swagger.v3.oas.annotations.responses.ApiResponses;
32 import io.swagger.v3.oas.annotations.tags.Tag;
34 import java.net.MalformedURLException;
36 import java.time.Duration;
37 import java.util.ArrayList;
38 import java.util.Collection;
40 import org.onap.ccsdk.oran.a1policymanagementservice.controllers.VoidResponse;
41 import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
42 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
43 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
44 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
45 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
46 import org.springframework.beans.factory.annotation.Autowired;
47 import org.springframework.http.HttpStatus;
48 import org.springframework.http.MediaType;
49 import org.springframework.http.ResponseEntity;
50 import org.springframework.web.bind.annotation.DeleteMapping;
51 import org.springframework.web.bind.annotation.GetMapping;
52 import org.springframework.web.bind.annotation.PathVariable;
53 import org.springframework.web.bind.annotation.PutMapping;
54 import org.springframework.web.bind.annotation.RequestBody;
55 import org.springframework.web.bind.annotation.RequestParam;
56 import org.springframework.web.bind.annotation.RestController;
58 @RestController("ServiceControllerV2")
60 name = ServiceController.API_NAME, //
61 description = ServiceController.API_DESCRIPTION //
64 public class ServiceController {
66 public static final String API_NAME = "Service Registry and Supervision";
67 public static final String API_DESCRIPTION = "";
69 private final Services services;
70 private final Policies policies;
72 private static Gson gson = new GsonBuilder().create();
75 ServiceController(Services services, Policies policies) {
76 this.services = services;
77 this.policies = policies;
80 private static final String GET_SERVICE_DETAILS =
81 "Either information about a registered service with given identity or all registered services are returned.";
83 @GetMapping(path = Consts.V2_API_ROOT + "/services", produces = MediaType.APPLICATION_JSON_VALUE) //
84 @Operation(summary = "Returns service information", description = GET_SERVICE_DETAILS) //
85 @ApiResponses(value = { //
86 @ApiResponse(responseCode = "200", //
87 description = "OK", //
88 content = @Content(schema = @Schema(implementation = ServiceStatusList.class))), //
89 @ApiResponse(responseCode = "404", //
90 description = "Service is not found", //
91 content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))//
93 public ResponseEntity<Object> getServices(//
94 @Parameter(name = Consts.SERVICE_ID_PARAM, required = false, description = "The identity of the service") //
95 @RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String name) {
96 if (name != null && this.services.get(name) == null) {
97 return ErrorResponse.create("Service not found", HttpStatus.NOT_FOUND);
100 Collection<ServiceStatus> servicesStatus = new ArrayList<>();
101 for (Service s : this.services.getAll()) {
102 if (name == null || name.equals(s.getName())) {
103 servicesStatus.add(toServiceStatus(s));
107 String res = gson.toJson(new ServiceStatusList(servicesStatus));
108 return new ResponseEntity<>(res, HttpStatus.OK);
111 private ServiceStatus toServiceStatus(Service s) {
112 return new ServiceStatus(s.getName(), s.getKeepAliveInterval().toSeconds(), s.timeSinceLastPing().toSeconds(),
116 private void validateRegistrationInfo(ServiceRegistrationInfo registrationInfo)
117 throws ServiceException, MalformedURLException {
118 if (registrationInfo.serviceId.isEmpty()) {
119 throw new ServiceException("Missing mandatory parameter 'service-id'");
121 if (registrationInfo.keepAliveIntervalSeconds < 0) {
122 throw new ServiceException("Keepalive interval shoul be greater or equal to 0");
124 if (!registrationInfo.callbackUrl.isEmpty()) {
125 new URL(registrationInfo.callbackUrl);
129 private static final String REGISTER_SERVICE_DETAILS = "Registering a service is needed to:" //
131 + "<li>Get callbacks.</li>" //
132 + "<li>Activate supervision of the service. If a service is inactive, its policies will be deleted.</li>"//
136 @PutMapping(Consts.V2_API_ROOT + "/services")
137 @Operation(summary = "Register a service", description = REGISTER_SERVICE_DETAILS)
138 @ApiResponses(value = { //
139 @ApiResponse(responseCode = "200", description = "Service updated"),
140 @ApiResponse(responseCode = "201", description = "Service created"), //
141 @ApiResponse(responseCode = "400", //
142 description = "The ServiceRegistrationInfo is not accepted", //
143 content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))})
145 public ResponseEntity<Object> putService(//
146 @RequestBody ServiceRegistrationInfo registrationInfo) {
148 validateRegistrationInfo(registrationInfo);
149 final boolean isCreate = this.services.get(registrationInfo.serviceId) == null;
150 this.services.put(toService(registrationInfo));
151 return new ResponseEntity<>(isCreate ? HttpStatus.CREATED : HttpStatus.OK);
152 } catch (Exception e) {
153 return ErrorResponse.create(e, HttpStatus.BAD_REQUEST);
157 @DeleteMapping(Consts.V2_API_ROOT + "/services/{service_id:.+}")
158 @Operation(summary = "Unregister a service")
159 @ApiResponses(value = { //
160 @ApiResponse(responseCode = "204", description = "Service unregistered"),
161 @ApiResponse(responseCode = "200", description = "Not used", //
162 content = @Content(schema = @Schema(implementation = VoidResponse.class))),
163 @ApiResponse(responseCode = "404", description = "Service not found", //
164 content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))
167 public ResponseEntity<Object> deleteService(//
168 @PathVariable("service_id") String serviceId) {
170 Service service = removeService(serviceId);
171 // Remove the policies from the repo and let the consistency monitoring
173 removePolicies(service);
174 return new ResponseEntity<>(HttpStatus.NO_CONTENT);
175 } catch (ServiceException e) {
176 return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
180 @Operation(summary = "Heartbeat indicates that the service is running",
181 description = "A registerred service must call this in regular intervals to indicate that it is in operation. Absence of this call will lead to that the service will be deregisterred and all its policies are removed.")
182 @ApiResponses(value = { //
183 @ApiResponse(responseCode = "200", description = "Service supervision timer refreshed, OK"), //
184 @ApiResponse(responseCode = "404", description = "The service is not found, needs re-registration",
185 content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))
189 @PutMapping(Consts.V2_API_ROOT + "/services/{service_id}/keepalive")
190 public ResponseEntity<Object> keepAliveService(//
191 @PathVariable(Consts.SERVICE_ID_PARAM) String serviceId) {
193 services.getService(serviceId).keepAlive();
194 return new ResponseEntity<>(HttpStatus.OK);
195 } catch (ServiceException e) {
196 return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
200 private Service removeService(String name) throws ServiceException {
201 Service service = this.services.getService(name); // Just to verify that it exists
202 this.services.remove(service.getName());
206 private void removePolicies(Service service) {
207 Collection<Policy> policyList = this.policies.getForService(service.getName());
208 for (Policy policy : policyList) {
209 this.policies.remove(policy);
213 private Service toService(ServiceRegistrationInfo s) {
214 return new Service(s.serviceId, Duration.ofSeconds(s.keepAliveIntervalSeconds), s.callbackUrl);