90666e7af2a2adc4c73e80dddaa98a2544d850a5
[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.v1;
22
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25
26 import io.swagger.annotations.Api;
27 import io.swagger.annotations.ApiOperation;
28 import io.swagger.annotations.ApiParam;
29 import io.swagger.annotations.ApiResponse;
30 import io.swagger.annotations.ApiResponses;
31
32 import java.lang.invoke.MethodHandles;
33 import java.time.Instant;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.List;
37
38 import lombok.Getter;
39
40 import org.onap.ccsdk.oran.a1policymanagementservice.clients.A1ClientFactory;
41 import org.onap.ccsdk.oran.a1policymanagementservice.controllers.VoidResponse;
42 import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
43 import org.onap.ccsdk.oran.a1policymanagementservice.repository.ImmutablePolicy;
44 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock.LockType;
45 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
46 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
47 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyType;
48 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyTypes;
49 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Ric;
50 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Rics;
51 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
52 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.beans.factory.annotation.Autowired;
56 import org.springframework.http.HttpStatus;
57 import org.springframework.http.ResponseEntity;
58 import org.springframework.web.bind.annotation.DeleteMapping;
59 import org.springframework.web.bind.annotation.GetMapping;
60 import org.springframework.web.bind.annotation.PutMapping;
61 import org.springframework.web.bind.annotation.RequestBody;
62 import org.springframework.web.bind.annotation.RequestParam;
63 import org.springframework.web.bind.annotation.RestController;
64 import org.springframework.web.reactive.function.client.WebClientResponseException;
65 import reactor.core.publisher.Mono;
66
67 @RestController
68 @Api(tags = Consts.V1_API_NAME)
69 public class PolicyController {
70
71     public static class RejectionException extends Exception {
72         private static final long serialVersionUID = 1L;
73         @Getter
74         private final HttpStatus status;
75
76         public RejectionException(String message, HttpStatus status) {
77             super(message);
78             this.status = status;
79         }
80     }
81
82     @Autowired
83     private Rics rics;
84     @Autowired
85     private PolicyTypes policyTypes;
86     @Autowired
87     private Policies policies;
88     @Autowired
89     private A1ClientFactory a1ClientFactory;
90     @Autowired
91     private Services services;
92
93     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
94     private static Gson gson = new GsonBuilder() //
95         .serializeNulls() //
96         .create(); //
97
98     @GetMapping("/policy_schemas")
99     @ApiOperation(value = "Returns policy type schema definitions")
100     @ApiResponses(
101         value = {
102             @ApiResponse(code = 200, message = "Policy schemas", response = Object.class, responseContainer = "List"), //
103             @ApiResponse(code = 404, message = "Near-RT RIC is not found", response = String.class)})
104     public ResponseEntity<String> getPolicySchemas( //
105         @ApiParam(name = "ric", required = false, value = "The name of the Near-RT RIC to get the definitions for.") //
106         @RequestParam(name = "ric", required = false) String ricName) {
107         if (ricName == null) {
108             Collection<PolicyType> types = this.policyTypes.getAll();
109             return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
110         } else {
111             try {
112                 Collection<PolicyType> types = rics.getRic(ricName).getSupportedPolicyTypes();
113                 return new ResponseEntity<>(toPolicyTypeSchemasJson(types), HttpStatus.OK);
114             } catch (ServiceException e) {
115                 return new ResponseEntity<>(e.toString(), HttpStatus.NOT_FOUND);
116             }
117         }
118     }
119
120     @GetMapping("/policy_schema")
121     @ApiOperation(value = "Returns one policy type schema definition")
122     @ApiResponses(
123         value = { //
124             @ApiResponse(code = 200, message = "Policy schema", response = Object.class),
125             @ApiResponse(code = 404, message = "The policy type is not found", response = String.class)})
126     public ResponseEntity<String> getPolicySchema( //
127         @ApiParam(name = "id", required = true, value = "The identity of the policy type to get the definition for.") //
128         @RequestParam(name = "id", required = true) String id) {
129         try {
130             PolicyType type = policyTypes.getType(id);
131             return new ResponseEntity<>(type.schema(), HttpStatus.OK);
132         } catch (ServiceException e) {
133             return new ResponseEntity<>(e.toString(), HttpStatus.NOT_FOUND);
134         }
135     }
136
137     @GetMapping("/policy_types")
138     @ApiOperation(value = "Query policy type names")
139     @ApiResponses(
140         value = {
141             @ApiResponse(
142                 code = 200,
143                 message = "Policy type names",
144                 response = String.class,
145                 responseContainer = "List"),
146             @ApiResponse(code = 404, message = "Near-RT RIC is not found", response = String.class)})
147     public ResponseEntity<String> getPolicyTypes( //
148         @ApiParam(name = "ric", required = false, value = "The name of the Near-RT RIC to get types for.") //
149         @RequestParam(name = "ric", required = false) String ricName) {
150         if (ricName == null) {
151             Collection<PolicyType> types = this.policyTypes.getAll();
152             return new ResponseEntity<>(toPolicyTypeIdsJson(types), HttpStatus.OK);
153         } else {
154             try {
155                 Collection<PolicyType> types = rics.getRic(ricName).getSupportedPolicyTypes();
156                 return new ResponseEntity<>(toPolicyTypeIdsJson(types), HttpStatus.OK);
157             } catch (ServiceException e) {
158                 return new ResponseEntity<>(e.toString(), HttpStatus.NOT_FOUND);
159             }
160         }
161     }
162
163     @GetMapping("/policy")
164     @ApiOperation(value = "Returns a policy configuration") //
165     @ApiResponses(
166         value = { //
167             @ApiResponse(code = 200, message = "Policy found", response = Object.class), //
168             @ApiResponse(code = 404, message = "Policy is not found")} //
169     )
170     public ResponseEntity<String> getPolicy( //
171         @ApiParam(name = "id", required = true, value = "The identity of the policy instance.") //
172         @RequestParam(name = "id", required = true) String id) {
173         try {
174             Policy p = policies.getPolicy(id);
175             return new ResponseEntity<>(p.json(), HttpStatus.OK);
176         } catch (ServiceException e) {
177             return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
178         }
179     }
180
181     @DeleteMapping("/policy")
182     @ApiOperation(value = "Delete a policy", response = Object.class)
183     @ApiResponses(
184         value = { //
185             @ApiResponse(code = 200, message = "Not used", response = VoidResponse.class),
186             @ApiResponse(code = 204, message = "Policy deleted", response = VoidResponse.class),
187             @ApiResponse(code = 404, message = "Policy is not found", response = String.class),
188             @ApiResponse(code = 423, message = "Near-RT RIC is not operational", response = String.class)})
189     public Mono<ResponseEntity<Object>> deletePolicy( //
190         @ApiParam(name = "id", required = true, value = "The identity of the policy instance.") //
191         @RequestParam(name = "id", required = true) String id) {
192         try {
193             Policy policy = policies.getPolicy(id);
194             keepServiceAlive(policy.ownerServiceId());
195             Ric ric = policy.ric();
196             return ric.getLock().lock(LockType.SHARED) //
197                 .flatMap(notUsed -> assertRicStateIdle(ric)) //
198                 .flatMap(notUsed -> a1ClientFactory.createA1Client(policy.ric())) //
199                 .doOnNext(notUsed -> policies.remove(policy)) //
200                 .flatMap(client -> client.deletePolicy(policy)) //
201                 .doOnNext(notUsed -> ric.getLock().unlockBlocking()) //
202                 .doOnError(notUsed -> ric.getLock().unlockBlocking()) //
203                 .flatMap(notUsed -> Mono.just(new ResponseEntity<>(HttpStatus.NO_CONTENT)))
204                 .onErrorResume(this::handleException);
205         } catch (ServiceException e) {
206             return Mono.just(new ResponseEntity<>(HttpStatus.NOT_FOUND));
207         }
208     }
209
210     @PutMapping(path = "/policy")
211     @ApiOperation(value = "Put a policy", response = VoidResponse.class)
212     @ApiResponses(
213         value = { //
214             @ApiResponse(code = 201, message = "Policy created", response = VoidResponse.class), //
215             @ApiResponse(code = 200, message = "Policy updated", response = VoidResponse.class), //
216             @ApiResponse(code = 423, message = "Near-RT RIC is not operational", response = String.class), //
217             @ApiResponse(code = 404, message = "Near-RT RIC or policy type is not found", response = String.class) //
218         })
219     public Mono<ResponseEntity<Object>> putPolicy( //
220         @ApiParam(name = "type", required = false, value = "The name of the policy type.") //
221         @RequestParam(name = "type", required = false, defaultValue = "") String typeName, //
222         @ApiParam(name = "id", required = true, value = "The identity of the policy instance.") //
223         @RequestParam(name = "id", required = true) String instanceId, //
224         @ApiParam(name = "ric", required = true, value = "The name of the Near-RT RIC where the policy will be " + //
225             "created.") //
226         @RequestParam(name = "ric", required = true) String ricName, //
227         @ApiParam(name = "service", required = true, value = "The name of the service creating the policy.") //
228         @RequestParam(name = "service", required = true) String service, //
229         @ApiParam(name = "transient", required = false, value = "If the policy is transient or not (boolean " + //
230             "defaulted to false). A policy is transient if it will be forgotten when the service needs to " + //
231             "reconnect to the Near-RT RIC.") //
232         @RequestParam(name = "transient", required = false, defaultValue = "false") boolean isTransient, //
233         @RequestBody Object jsonBody) {
234
235         String jsonString = gson.toJson(jsonBody);
236         Ric ric = rics.get(ricName);
237         PolicyType type = policyTypes.get(typeName);
238         keepServiceAlive(service);
239         if (ric == null || type == null) {
240             return Mono.just(new ResponseEntity<>(HttpStatus.NOT_FOUND));
241         }
242         Policy policy = ImmutablePolicy.builder() //
243             .id(instanceId) //
244             .json(jsonString) //
245             .type(type) //
246             .ric(ric) //
247             .ownerServiceId(service) //
248             .lastModified(Instant.now()) //
249             .isTransient(isTransient) //
250             .build();
251
252         final boolean isCreate = this.policies.get(policy.id()) == null;
253
254         return ric.getLock().lock(LockType.SHARED) //
255             .flatMap(notUsed -> assertRicStateIdle(ric)) //
256             .flatMap(notUsed -> checkSupportedType(ric, type)) //
257             .flatMap(notUsed -> validateModifiedPolicy(policy)) //
258             .flatMap(notUsed -> a1ClientFactory.createA1Client(ric)) //
259             .flatMap(client -> client.putPolicy(policy)) //
260             .doOnNext(notUsed -> policies.put(policy)) //
261             .doOnNext(notUsed -> ric.getLock().unlockBlocking()) //
262             .doOnError(trowable -> ric.getLock().unlockBlocking()) //
263             .flatMap(notUsed -> Mono.just(new ResponseEntity<>(isCreate ? HttpStatus.CREATED : HttpStatus.OK))) //
264             .onErrorResume(this::handleException);
265     }
266
267     @SuppressWarnings({"unchecked"})
268     private <T> Mono<ResponseEntity<T>> createResponseEntity(String message, HttpStatus status) {
269         ResponseEntity<T> re = new ResponseEntity<>((T) message, status);
270         return Mono.just(re);
271     }
272
273     private <T> Mono<ResponseEntity<T>> handleException(Throwable throwable) {
274         if (throwable instanceof WebClientResponseException) {
275             WebClientResponseException e = (WebClientResponseException) throwable;
276             return createResponseEntity(e.getResponseBodyAsString(), e.getStatusCode());
277         } else if (throwable instanceof RejectionException) {
278             RejectionException e = (RejectionException) throwable;
279             return createResponseEntity(e.getMessage(), e.getStatus());
280         } else {
281             return createResponseEntity(throwable.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
282         }
283     }
284
285     private Mono<Object> validateModifiedPolicy(Policy policy) {
286         // Check that ric is not updated
287         Policy current = this.policies.get(policy.id());
288         if (current != null && !current.ric().id().equals(policy.ric().id())) {
289             RejectionException e = new RejectionException("Policy cannot change RIC, policyId: " + current.id() + //
290                 ", RIC name: " + current.ric().id() + //
291                 ", new name: " + policy.ric().id(), HttpStatus.CONFLICT);
292             logger.debug("Request rejected, {}", e.getMessage());
293             return Mono.error(e);
294         }
295         return Mono.just("OK");
296     }
297
298     private Mono<Object> checkSupportedType(Ric ric, PolicyType type) {
299         if (!ric.isSupportingType(type.id())) {
300             logger.debug("Request rejected, type not supported, RIC: {}", ric);
301             RejectionException e = new RejectionException("Type: " + type.id() + " not supported by RIC: " + ric.id(),
302                 HttpStatus.NOT_FOUND);
303             return Mono.error(e);
304         }
305         return Mono.just("OK");
306     }
307
308     private Mono<Object> assertRicStateIdle(Ric ric) {
309         if (ric.getState() == Ric.RicState.AVAILABLE) {
310             return Mono.just("OK");
311         } else {
312             logger.debug("Request rejected RIC not IDLE, ric: {}", ric);
313             RejectionException e = new RejectionException(
314                 "Ric is not operational, RIC name: " + ric.id() + ", state: " + ric.getState(), HttpStatus.LOCKED);
315             return Mono.error(e);
316         }
317     }
318
319     @GetMapping("/policies")
320     @ApiOperation(value = "Query policies")
321     @ApiResponses(
322         value = {
323             @ApiResponse(code = 200, message = "Policies", response = PolicyInfo.class, responseContainer = "List"),
324             @ApiResponse(code = 404, message = "Near-RT RIC or type not found", response = String.class)})
325     public ResponseEntity<String> getPolicies( //
326         @ApiParam(name = "type", required = false, value = "The name of the policy type to get policies for.") //
327         @RequestParam(name = "type", required = false) String type, //
328         @ApiParam(name = "ric", required = false, value = "The name of the Near-RT RIC to get policies for.") //
329         @RequestParam(name = "ric", required = false) String ric, //
330         @ApiParam(name = "service", required = false, value = "The name of the service to get policies for.") //
331         @RequestParam(name = "service", required = false) String service) //
332     {
333         if ((type != null && this.policyTypes.get(type) == null)) {
334             return new ResponseEntity<>("Policy type not found", HttpStatus.NOT_FOUND);
335         }
336         if ((ric != null && this.rics.get(ric) == null)) {
337             return new ResponseEntity<>("Near-RT RIC not found", HttpStatus.NOT_FOUND);
338         }
339
340         String filteredPolicies = policiesToJson(filter(type, ric, service));
341         return new ResponseEntity<>(filteredPolicies, HttpStatus.OK);
342     }
343
344     @GetMapping("/policy_ids")
345     @ApiOperation(value = "Query policies, only policy identities returned")
346     @ApiResponses(
347         value = {
348             @ApiResponse(
349                 code = 200,
350                 message = "Policy identitiess",
351                 response = String.class,
352                 responseContainer = "List"),
353             @ApiResponse(code = 404, message = "Near-RT RIC or type not found", response = String.class)})
354     public ResponseEntity<String> getPolicyIds( //
355         @ApiParam(name = "type", required = false, value = "The name of the policy type to get policies for.") //
356         @RequestParam(name = "type", required = false) String type, //
357         @ApiParam(name = "ric", required = false, value = "The name of the Near-RT RIC to get policies for.") //
358         @RequestParam(name = "ric", required = false) String ric, //
359         @ApiParam(name = "service", required = false, value = "The name of the service to get policies for.") //
360         @RequestParam(name = "service", required = false) String service) //
361     {
362         if ((type != null && this.policyTypes.get(type) == null)) {
363             return new ResponseEntity<>("Policy type not found", HttpStatus.NOT_FOUND);
364         }
365         if ((ric != null && this.rics.get(ric) == null)) {
366             return new ResponseEntity<>("Near-RT RIC not found", HttpStatus.NOT_FOUND);
367         }
368
369         String policyIdsJson = toPolicyIdsJson(filter(type, ric, service));
370         return new ResponseEntity<>(policyIdsJson, HttpStatus.OK);
371     }
372
373     @GetMapping("/policy_status")
374     @ApiOperation(value = "Returns a policy status") //
375     @ApiResponses(
376         value = { //
377             @ApiResponse(code = 200, message = "Policy status", response = Object.class), //
378             @ApiResponse(code = 404, message = "Policy is not found", response = String.class)} //
379     )
380     public Mono<ResponseEntity<String>> getPolicyStatus( //
381         @ApiParam(name = "id", required = true, value = "The identity of the policy.") @RequestParam(
382             name = "id", //
383             required = true) String id) {
384         try {
385             Policy policy = policies.getPolicy(id);
386
387             return a1ClientFactory.createA1Client(policy.ric()) //
388                 .flatMap(client -> client.getPolicyStatus(policy)) //
389                 .flatMap(status -> Mono.just(new ResponseEntity<>(status, HttpStatus.OK)))
390                 .onErrorResume(this::handleException);
391         } catch (ServiceException e) {
392             return Mono.just(new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND));
393         }
394     }
395
396     private void keepServiceAlive(String name) {
397         Service s = this.services.get(name);
398         if (s != null) {
399             s.keepAlive();
400         }
401     }
402
403     private boolean include(String filter, String value) {
404         return filter == null || value.equals(filter);
405     }
406
407     private Collection<Policy> filter(Collection<Policy> collection, String type, String ric, String service) {
408         if (type == null && ric == null && service == null) {
409             return collection;
410         }
411         List<Policy> filtered = new ArrayList<>();
412         for (Policy p : collection) {
413             if (include(type, p.type().id()) && include(ric, p.ric().id()) && include(service, p.ownerServiceId())) {
414                 filtered.add(p);
415             }
416         }
417         return filtered;
418     }
419
420     private Collection<Policy> filter(String type, String ric, String service) {
421         if (type != null) {
422             return filter(policies.getForType(type), null, ric, service);
423         } else if (service != null) {
424             return filter(policies.getForService(service), type, ric, null);
425         } else if (ric != null) {
426             return filter(policies.getForRic(ric), type, null, service);
427         } else {
428             return policies.getAll();
429         }
430     }
431
432     private String policiesToJson(Collection<Policy> policies) {
433         List<PolicyInfo> v = new ArrayList<>(policies.size());
434         for (Policy p : policies) {
435             PolicyInfo policyInfo = new PolicyInfo();
436             policyInfo.id = p.id();
437             policyInfo.json = fromJson(p.json());
438             policyInfo.ric = p.ric().id();
439             policyInfo.type = p.type().id();
440             policyInfo.service = p.ownerServiceId();
441             policyInfo.lastModified = p.lastModified().toString();
442             if (!policyInfo.validate()) {
443                 logger.error("BUG, all fields must be set");
444             }
445             v.add(policyInfo);
446         }
447         return gson.toJson(v);
448     }
449
450     private Object fromJson(String jsonStr) {
451         return gson.fromJson(jsonStr, Object.class);
452     }
453
454     private String toPolicyTypeSchemasJson(Collection<PolicyType> types) {
455         StringBuilder result = new StringBuilder();
456         result.append("[");
457         boolean first = true;
458         for (PolicyType t : types) {
459             if (!first) {
460                 result.append(",");
461             }
462             first = false;
463             result.append(t.schema());
464         }
465         result.append("]");
466         return result.toString();
467     }
468
469     private String toPolicyTypeIdsJson(Collection<PolicyType> types) {
470         List<String> v = new ArrayList<>(types.size());
471         for (PolicyType t : types) {
472             v.add(t.id());
473         }
474         return gson.toJson(v);
475     }
476
477     private String toPolicyIdsJson(Collection<Policy> policies) {
478         List<String> v = new ArrayList<>(policies.size());
479         for (Policy p : policies) {
480             v.add(p.id());
481         }
482         return gson.toJson(v);
483     }
484
485 }