d3ff999aaf70d690baad224433d4a7ae9bc2c15c
[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(name = ServiceController.API_NAME)
60 public class ServiceController {
61
62     public static final String API_NAME = "Service Registry and Supervision";
63     public static final String API_DESCRIPTION = "";
64
65     private final Services services;
66     private final Policies policies;
67
68     private static Gson gson = new GsonBuilder().create();
69
70     @Autowired
71     ServiceController(Services services, Policies policies) {
72         this.services = services;
73         this.policies = policies;
74     }
75
76     private static final String GET_SERVICE_DETAILS =
77             "Either information about a registered service with given identity or all registered services are returned.";
78
79     @GetMapping(path = Consts.V2_API_ROOT + "/services", produces = MediaType.APPLICATION_JSON_VALUE) //
80     @Operation(summary = "Returns service information", description = GET_SERVICE_DETAILS) //
81     @ApiResponses(value = { //
82             @ApiResponse(responseCode = "200", //
83                     description = "OK", //
84                     content = @Content(schema = @Schema(implementation = ServiceStatusList.class))), //
85             @ApiResponse(responseCode = "404", //
86                     description = "Service is not found", //
87                     content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))//
88     })
89     public ResponseEntity<Object> getServices(//
90             @Parameter(name = Consts.SERVICE_ID_PARAM, required = false, description = "The identity of the service") //
91             @RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String name) {
92         if (name != null && this.services.get(name) == null) {
93             return ErrorResponse.create("Service not found", HttpStatus.NOT_FOUND);
94         }
95
96         Collection<ServiceStatus> servicesStatus = new ArrayList<>();
97         for (Service s : this.services.getAll()) {
98             if (name == null || name.equals(s.getName())) {
99                 servicesStatus.add(toServiceStatus(s));
100             }
101         }
102
103         String res = gson.toJson(new ServiceStatusList(servicesStatus));
104         return new ResponseEntity<>(res, HttpStatus.OK);
105     }
106
107     private ServiceStatus toServiceStatus(Service s) {
108         return new ServiceStatus(s.getName(), s.getKeepAliveInterval().toSeconds(), s.timeSinceLastPing().toSeconds(),
109                 s.getCallbackUrl());
110     }
111
112     private void validateRegistrationInfo(ServiceRegistrationInfo registrationInfo)
113             throws ServiceException, MalformedURLException {
114         if (registrationInfo.serviceId.isEmpty()) {
115             throw new ServiceException("Missing mandatory parameter 'service-id'");
116         }
117         if (registrationInfo.keepAliveIntervalSeconds < 0) {
118             throw new ServiceException("Keepalive interval shoul be greater or equal to 0");
119         }
120         if (!registrationInfo.callbackUrl.isEmpty()) {
121             new URL(registrationInfo.callbackUrl);
122         }
123     }
124
125     private static final String REGISTER_SERVICE_DETAILS = "Registering a service is needed to:" //
126             + "<ul>" //
127             + "<li>Get callbacks.</li>" //
128             + "<li>Activate supervision of the service. If a service is inactive, its policies will be deleted.</li>"//
129             + "</ul>" //
130     ;
131
132     @PutMapping(Consts.V2_API_ROOT + "/services")
133     @Operation(summary = "Register a service", description = REGISTER_SERVICE_DETAILS)
134     @ApiResponses(value = { //
135             @ApiResponse(responseCode = "200", description = "Service updated"),
136             @ApiResponse(responseCode = "201", description = "Service created"), //
137             @ApiResponse(responseCode = "400", //
138                     description = "The ServiceRegistrationInfo is not accepted", //
139                     content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))})
140
141     public ResponseEntity<Object> putService(//
142             @RequestBody ServiceRegistrationInfo registrationInfo) {
143         try {
144             validateRegistrationInfo(registrationInfo);
145             final boolean isCreate = this.services.get(registrationInfo.serviceId) == null;
146             this.services.put(toService(registrationInfo));
147             return new ResponseEntity<>(isCreate ? HttpStatus.CREATED : HttpStatus.OK);
148         } catch (Exception e) {
149             return ErrorResponse.create(e, HttpStatus.BAD_REQUEST);
150         }
151     }
152
153     @Operation(summary = "Unregister a service")
154     @ApiResponses(value = { //
155             @ApiResponse(responseCode = "204", description = "Service unregistered"),
156             @ApiResponse(responseCode = "200", description = "Not used",
157                     content = @Content(schema = @Schema(implementation = VoidResponse.class))),
158             @ApiResponse(responseCode = "404", description = "Service not found",
159                     content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))
160
161     })
162
163     @DeleteMapping(Consts.V2_API_ROOT + "/services/{service_id:.+}")
164     public ResponseEntity<Object> deleteService(//
165             @PathVariable("service_id") String serviceId) {
166         try {
167             Service service = removeService(serviceId);
168             // Remove the policies from the repo and let the consistency monitoring
169             // do the rest.
170             removePolicies(service);
171             return new ResponseEntity<>(HttpStatus.NO_CONTENT);
172         } catch (ServiceException e) {
173             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
174         }
175     }
176
177     @Operation(summary = "Heartbeat indicates that the service is running",
178             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 teh service will be deregisterred and all its policies are removed.")
179     @ApiResponses(value = { //
180             @ApiResponse(responseCode = "200", description = "Service supervision timer refreshed, OK"), //
181             @ApiResponse(responseCode = "404", description = "The service is not found, needs re-registration",
182                     content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class)))
183
184     })
185
186     @PutMapping(Consts.V2_API_ROOT + "/services/{service_id}/keepalive")
187     public ResponseEntity<Object> keepAliveService(//
188             @PathVariable(Consts.SERVICE_ID_PARAM) String serviceId) {
189         try {
190             services.getService(serviceId).keepAlive();
191             return new ResponseEntity<>(HttpStatus.OK);
192         } catch (ServiceException e) {
193             return ErrorResponse.create(e, HttpStatus.NOT_FOUND);
194         }
195     }
196
197     private Service removeService(String name) throws ServiceException {
198         Service service = this.services.getService(name); // Just to verify that it exists
199         this.services.remove(service.getName());
200         return service;
201     }
202
203     private void removePolicies(Service service) {
204         Collection<Policy> policyList = this.policies.getForService(service.getName());
205         for (Policy policy : policyList) {
206             this.policies.remove(policy);
207         }
208     }
209
210     private Service toService(ServiceRegistrationInfo s) {
211         return new Service(s.serviceId, Duration.ofSeconds(s.keepAliveIntervalSeconds), s.callbackUrl);
212     }
213
214 }