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