7f34215093abd2e0eec8df322748cb7b26cf39cf
[ccsdk/oran.git] /
1 /*-
2  * ========================LICENSE_START=================================
3  * ONAP : ccsdk oran
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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===================================
19  */
20
21 package org.onap.ccsdk.oran.a1policymanagementservice.controllers.v2;
22
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25
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;
33
34 import java.net.MalformedURLException;
35 import java.net.URL;
36 import java.time.Duration;
37 import java.util.ArrayList;
38 import java.util.Collection;
39
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;
57
58 @RestController("ServiceControllerV2")
59 @Tag( //
60         name = ServiceController.API_NAME, //
61         description = ServiceController.API_DESCRIPTION //
62
63 )
64 public class ServiceController {
65
66     public static final String API_NAME = "Service Registry and Supervision";
67     public static final String API_DESCRIPTION = "";
68
69     private final Services services;
70     private final Policies policies;
71
72     private static Gson gson = new GsonBuilder().create();
73
74     @Autowired
75     ServiceController(Services services, Policies policies) {
76         this.services = services;
77         this.policies = policies;
78     }
79
80     private static final String GET_SERVICE_DETAILS =
81             "Either information about a registered service with given identity or all registered services are returned.";
82
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)))//
92     })
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);
98         }
99
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));
104             }
105         }
106
107         String res = gson.toJson(new ServiceStatusList(servicesStatus));
108         return new ResponseEntity<>(res, HttpStatus.OK);
109     }
110
111     private ServiceStatus toServiceStatus(Service s) {
112         return new ServiceStatus(s.getName(), s.getKeepAliveInterval().toSeconds(), s.timeSinceLastPing().toSeconds(),
113                 s.getCallbackUrl());
114     }
115
116     private void validateRegistrationInfo(ServiceRegistrationInfo registrationInfo)
117             throws ServiceException, MalformedURLException {
118         if (registrationInfo.serviceId.isEmpty()) {
119             throw new ServiceException("Missing mandatory parameter 'service-id'");
120         }
121         if (registrationInfo.keepAliveIntervalSeconds < 0) {
122             throw new ServiceException("Keepalive interval shoul be greater or equal to 0");
123         }
124         if (!registrationInfo.callbackUrl.isEmpty()) {
125             new URL(registrationInfo.callbackUrl);
126         }
127     }
128
129     private static final String REGISTER_SERVICE_DETAILS = "Registering a service is needed to:" //
130             + "<ul>" //
131             + "<li>Get callbacks.</li>" //
132             + "<li>Activate supervision of the service. If a service is inactive, its policies will be deleted.</li>"//
133             + "</ul>" //
134     ;
135
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)))})
144
145     public ResponseEntity<Object> putService(//
146             @RequestBody ServiceRegistrationInfo registrationInfo) {
147         try {
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);
154         }
155     }
156
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)))
165
166     })
167     public ResponseEntity<Object> deleteService(//
168             @PathVariable("service_id") String serviceId) {
169         try {
170             Service service = removeService(serviceId);
171             // Remove the policies from the repo and let the consistency monitoring
172             // do the rest.
173             removePolicies(service);
174             return new ResponseEntity<>(HttpStatus.NO_CONTENT);
175         } catch (ServiceException e) {
176             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
177         }
178     }
179
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)))
186
187     })
188
189     @PutMapping(Consts.V2_API_ROOT + "/services/{service_id}/keepalive")
190     public ResponseEntity<Object> keepAliveService(//
191             @PathVariable(Consts.SERVICE_ID_PARAM) String serviceId) {
192         try {
193             services.getService(serviceId).keepAlive();
194             return new ResponseEntity<>(HttpStatus.OK);
195         } catch (ServiceException e) {
196             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
197         }
198     }
199
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());
203         return service;
204     }
205
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);
210         }
211     }
212
213     private Service toService(ServiceRegistrationInfo s) {
214         return new Service(s.serviceId, Duration.ofSeconds(s.keepAliveIntervalSeconds), s.callbackUrl);
215     }
216
217 }