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