30ce4c3e6296bd188ada350c75cefb78ecdf447a
[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 static org.assertj.core.api.Assertions.assertThat;
24 import static org.awaitility.Awaitility.await;
25 import static org.junit.jupiter.api.Assertions.assertEquals;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.Mockito.doReturn;
29
30 import com.google.gson.Gson;
31 import com.google.gson.GsonBuilder;
32
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.io.PrintStream;
36 import java.nio.charset.StandardCharsets;
37 import java.nio.file.Files;
38 import java.nio.file.Paths;
39 import java.time.Duration;
40 import java.time.Instant;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44
45 import org.json.JSONObject;
46 import org.junit.jupiter.api.AfterEach;
47 import org.junit.jupiter.api.BeforeEach;
48 import org.junit.jupiter.api.Test;
49 import org.junit.jupiter.api.extension.ExtendWith;
50 import org.onap.ccsdk.oran.a1policymanagementservice.clients.AsyncRestClient;
51 import org.onap.ccsdk.oran.a1policymanagementservice.clients.AsyncRestClientFactory;
52 import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationConfig;
53 import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ImmutableRicConfig;
54 import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ImmutableWebClientConfig;
55 import org.onap.ccsdk.oran.a1policymanagementservice.configuration.RicConfig;
56 import org.onap.ccsdk.oran.a1policymanagementservice.configuration.WebClientConfig;
57 import org.onap.ccsdk.oran.a1policymanagementservice.controllers.ServiceCallbackInfo;
58 import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
59 import org.onap.ccsdk.oran.a1policymanagementservice.repository.ImmutablePolicy;
60 import org.onap.ccsdk.oran.a1policymanagementservice.repository.ImmutablePolicyType;
61 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock.LockType;
62 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
63 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
64 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyType;
65 import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyTypes;
66 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Ric;
67 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Ric.RicState;
68 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Rics;
69 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
70 import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
71 import org.onap.ccsdk.oran.a1policymanagementservice.tasks.RicSupervision;
72 import org.onap.ccsdk.oran.a1policymanagementservice.tasks.ServiceSupervision;
73 import org.onap.ccsdk.oran.a1policymanagementservice.utils.MockA1Client;
74 import org.onap.ccsdk.oran.a1policymanagementservice.utils.MockA1ClientFactory;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77 import org.springframework.beans.factory.annotation.Autowired;
78 import org.springframework.boot.test.context.SpringBootTest;
79 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
80 import org.springframework.boot.test.context.TestConfiguration;
81 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
82 import org.springframework.boot.web.server.LocalServerPort;
83 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
84 import org.springframework.context.ApplicationContext;
85 import org.springframework.context.annotation.Bean;
86 import org.springframework.http.HttpStatus;
87 import org.springframework.http.MediaType;
88 import org.springframework.http.ResponseEntity;
89 import org.springframework.test.context.TestPropertySource;
90 import org.springframework.test.context.junit.jupiter.SpringExtension;
91 import org.springframework.web.reactive.function.client.WebClientResponseException;
92
93 import reactor.core.publisher.Mono;
94 import reactor.test.StepVerifier;
95 import reactor.util.annotation.Nullable;
96
97 @ExtendWith(SpringExtension.class)
98 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
99 @TestPropertySource(properties = { //
100         "server.ssl.key-store=./config/keystore.jks", //
101         "app.webclient.trust-store=./config/truststore.jks"})
102 class ApplicationTest {
103     private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class);
104
105     @Autowired
106     ApplicationContext context;
107
108     @Autowired
109     private Rics rics;
110
111     @Autowired
112     private Policies policies;
113
114     @Autowired
115     private PolicyTypes policyTypes;
116
117     @Autowired
118     MockA1ClientFactory a1ClientFactory;
119
120     @Autowired
121     RicSupervision supervision;
122
123     @Autowired
124     ApplicationConfig applicationConfig;
125
126     @Autowired
127     Services services;
128
129     @Autowired
130     RappSimulatorController rAppSimulator;
131
132     private static Gson gson = new GsonBuilder().create();
133
134     public static class MockApplicationConfig extends ApplicationConfig {
135         @Override
136         public String getLocalConfigurationFilePath() {
137             return ""; // No config file loaded for the test
138         }
139     }
140
141     /**
142      * Overrides the BeanFactory.
143      */
144     @TestConfiguration
145     static class TestBeanFactory {
146         private final PolicyTypes policyTypes = new PolicyTypes();
147         private final Services services = new Services();
148         private final Policies policies = new Policies();
149         MockA1ClientFactory a1ClientFactory = null;
150
151         @Bean
152         public ApplicationConfig getApplicationConfig() {
153             return new MockApplicationConfig();
154         }
155
156         @Bean
157         MockA1ClientFactory getA1ClientFactory() {
158             if (a1ClientFactory == null) {
159                 this.a1ClientFactory = new MockA1ClientFactory(this.policyTypes);
160             }
161             return this.a1ClientFactory;
162         }
163
164         @Bean
165         public PolicyTypes getPolicyTypes() {
166             return this.policyTypes;
167         }
168
169         @Bean
170         Policies getPolicies() {
171             return this.policies;
172         }
173
174         @Bean
175         Services getServices() {
176             return this.services;
177         }
178
179         @Bean
180         public ServiceSupervision getServiceSupervision() {
181             Duration checkInterval = Duration.ofMillis(1);
182             return new ServiceSupervision(this.services, this.policies, this.getA1ClientFactory(), checkInterval);
183         }
184
185         @Bean
186         public ServletWebServerFactory servletContainer() {
187             return new TomcatServletWebServerFactory();
188         }
189
190     }
191
192     @LocalServerPort
193     private int port;
194
195     @BeforeEach
196     void reset() {
197         rics.clear();
198         policies.clear();
199         policyTypes.clear();
200         services.clear();
201         a1ClientFactory.reset();
202         this.rAppSimulator.getTestResults().clear();
203     }
204
205     @AfterEach
206     void verifyNoRicLocks() {
207         for (Ric ric : this.rics.getRics()) {
208             ric.getLock().lockBlocking(LockType.EXCLUSIVE);
209             ric.getLock().unlockBlocking();
210             assertThat(ric.getLock().getLockCounter()).isZero();
211             assertThat(ric.getState()).isEqualTo(Ric.RicState.AVAILABLE);
212         }
213     }
214
215     @Test
216     void createApiDoc() throws IOException {
217         String url = "https://localhost:" + this.port + "/v2/api-docs";
218         ResponseEntity<String> resp = restClient("", false).getForEntity(url).block();
219         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
220         String indented = (new JSONObject(resp.getBody())).toString(4);
221         String docDir = "../docs/offeredapis/swagger/";
222         Files.createDirectories(Paths.get(docDir));
223         try (PrintStream out = new PrintStream(new FileOutputStream(docDir + "pms-api.json"))) {
224             out.print(indented);
225         }
226     }
227
228     @Test
229     void testGetRics() throws Exception {
230         addRic("ric1");
231         this.addPolicyType("type1", "ric1");
232         String url = "/rics?policytype_id=type1";
233         String rsp = restClient().get(url).block();
234         assertThat(rsp).contains("ric1");
235
236         // nameless type for ORAN A1 1.1
237         addRic("ric2");
238         this.addPolicyType("", "ric2");
239         url = "/rics?policytype_id=";
240
241         // This tests also validation of trusted certs restClient(true)
242         rsp = restClient(true).get(url).block();
243         assertThat(rsp).contains("ric2") //
244                 .doesNotContain("ric1") //
245                 .contains("AVAILABLE");
246
247         // All RICs
248         rsp = restClient().get("/rics").block();
249         assertThat(rsp).contains("ric2") //
250                 .contains("ric1");
251
252         // Non existing policy type
253         url = "/rics?policytype_id=XXXX";
254         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
255     }
256
257     @Test
258     void testSynchronization() throws Exception {
259         // Two polictypes will be put in the NearRT RICs
260         PolicyTypes nearRtRicPolicyTypes = new PolicyTypes();
261         nearRtRicPolicyTypes.put(createPolicyType("typeName"));
262         nearRtRicPolicyTypes.put(createPolicyType("typeName2"));
263         this.a1ClientFactory.setPolicyTypes(nearRtRicPolicyTypes);
264
265         // One type and one instance added to the Policy Management Service's storage
266         final String ric1Name = "ric1";
267         Ric ric1 = addRic(ric1Name);
268         Policy policy2 = addPolicy("policyId2", "typeName", "service", ric1Name);
269         Ric ric2 = addRic("ric2");
270
271         getA1Client(ric1Name).putPolicy(policy2); // put it in the RIC (Near-RT RIC)
272         policies.remove(policy2); // Remove it from the repo -> should be deleted in the RIC
273
274         String policyId = "policyId";
275         Policy policy = addPolicy(policyId, "typeName", "service", ric1Name); // This should be created in the RIC
276         supervision.checkAllRics(); // The created policy should be put in the RIC
277
278         // Wait until synch is completed
279         waitForRicState(ric1Name, RicState.SYNCHRONIZING);
280         waitForRicState(ric1Name, RicState.AVAILABLE);
281         waitForRicState("ric2", RicState.AVAILABLE);
282
283         Policies ricPolicies = getA1Client(ric1Name).getPolicies();
284         assertThat(ricPolicies.size()).isEqualTo(1);
285         Policy ricPolicy = ricPolicies.get(policyId);
286         assertThat(ricPolicy.json()).isEqualTo(policy.json());
287
288         // Both types should be in the Policy Management Service's storage after the
289         // synch
290         assertThat(ric1.getSupportedPolicyTypes()).hasSize(2);
291         assertThat(ric2.getSupportedPolicyTypes()).hasSize(2);
292     }
293
294     @Test
295     void testGetRic() throws Exception {
296         String ricId = "ric1";
297         String managedElementId = "kista_1";
298         addRic(ricId, managedElementId);
299
300         String url = "/rics/ric?managed_element_id=" + managedElementId;
301         String rsp = restClient().get(url).block();
302         RicInfo ricInfo = gson.fromJson(rsp, RicInfo.class);
303         assertThat(ricInfo.ricId).isEqualTo(ricId);
304
305         url = "/rics/ric?ric_id=" + ricId;
306         rsp = restClient().get(url).block();
307         ricInfo = gson.fromJson(rsp, RicInfo.class);
308         assertThat(ricInfo.ricId).isEqualTo(ricId);
309
310         // test GET RIC for ManagedElement that does not exist
311         url = "/rics/ric?managed_element_id=" + "junk";
312         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
313
314         url = "/rics/ric";
315         testErrorCode(restClient().get(url), HttpStatus.BAD_REQUEST);
316     }
317
318     private String putPolicyBody(String serviceName, String ricId, String policyTypeName, String policyInstanceId,
319             boolean isTransient) {
320         PolicyInfo info = new PolicyInfo();
321         info.policyId = policyInstanceId;
322         info.policyTypeId = policyTypeName;
323         info.ricId = ricId;
324         info.serviceId = serviceName;
325         info.policyData = gson.fromJson(jsonString(), Object.class);
326
327         if (isTransient) {
328             info.isTransient = isTransient;
329         }
330         info.statusNotificationUri = "statusNotificationUri";
331         return gson.toJson(info);
332     }
333
334     private String putPolicyBody(String serviceName, String ricId, String policyTypeName, String policyInstanceId) {
335         return putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId, false);
336     }
337
338     @Test
339     void testPutPolicy() throws Exception {
340         String serviceName = "service.1";
341         String ricId = "ric.1";
342         String policyTypeName = "type1_1.2.3";
343         String policyInstanceId = "instance_1.2.3";
344
345         putService(serviceName);
346         addPolicyType(policyTypeName, ricId);
347
348         // PUT a transient policy
349         String url = "/policies";
350         String policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId, true);
351         this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
352
353         restClient().put(url, policyBody).block();
354
355         Policy policy = policies.getPolicy(policyInstanceId);
356         assertThat(policy).isNotNull();
357         assertThat(policy.id()).isEqualTo(policyInstanceId);
358         assertThat(policy.ownerServiceId()).isEqualTo(serviceName);
359         assertThat(policy.ric().id()).isEqualTo(ricId);
360         assertThat(policy.isTransient()).isTrue();
361
362         // Put a non transient policy
363         policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId);
364         restClient().put(url, policyBody).block();
365         policy = policies.getPolicy(policyInstanceId);
366         assertThat(policy.isTransient()).isFalse();
367
368         url = "/policy-instances";
369         String rsp = restClient().get(url).block();
370         assertThat(rsp).as("Response contains policy instance ID.").contains(policyInstanceId);
371
372         url = "/policies/" + policyInstanceId;
373         rsp = restClient().get(url).block();
374         assertThat(rsp).contains(policyBody);
375
376         // Test of error codes
377         url = "/policies";
378         policyBody = putPolicyBody(serviceName, ricId + "XX", policyTypeName, policyInstanceId);
379         testErrorCode(restClient().put(url, policyBody), HttpStatus.NOT_FOUND);
380
381         policyBody = putPolicyBody(serviceName, ricId, policyTypeName + "XX", policyInstanceId);
382         addPolicyType(policyTypeName + "XX", "otherRic");
383         testErrorCode(restClient().put(url, policyBody), HttpStatus.NOT_FOUND);
384
385         policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId);
386         this.rics.getRic(ricId).setState(Ric.RicState.SYNCHRONIZING);
387         testErrorCode(restClient().put(url, policyBody), HttpStatus.LOCKED);
388         this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
389     }
390
391     @Test
392     /**
393      * Test that HttpStatus and body from failing REST call to A1 is passed on to
394      * the caller.
395      *
396      * @throws ServiceException
397      */
398     void testErrorFromRic() throws ServiceException {
399         putService("service1");
400         addPolicyType("type1", "ric1");
401
402         MockA1Client a1Client = a1ClientFactory.getOrCreateA1Client("ric1");
403         HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
404         String responseBody = "Refused";
405         byte[] responseBodyBytes = responseBody.getBytes(StandardCharsets.UTF_8);
406
407         WebClientResponseException a1Exception = new WebClientResponseException(httpStatus.value(), "statusText", null,
408                 responseBodyBytes, StandardCharsets.UTF_8, null);
409         doReturn(Mono.error(a1Exception)).when(a1Client).putPolicy(any());
410
411         // PUT Policy
412         String putBody = putPolicyBody("service1", "ric1", "type1", "id1");
413         String url = "/policies";
414         testErrorCode(restClient().put(url, putBody), httpStatus, responseBody);
415
416         // DELETE POLICY
417         this.addPolicy("instance1", "type1", "service1", "ric1");
418         doReturn(Mono.error(a1Exception)).when(a1Client).deletePolicy(any());
419         testErrorCode(restClient().delete("/policies/instance1"), httpStatus, responseBody);
420
421     }
422
423     @Test
424     void testPutTypelessPolicy() throws Exception {
425         putService("service1");
426         addPolicyType("", "ric1");
427         String body = putPolicyBody("service1", "ric1", "", "id1");
428         restClient().put("/policies", body).block();
429
430         String rsp = restClient().get("/policy-instances").block();
431         PolicyInfoList info = gson.fromJson(rsp, PolicyInfoList.class);
432         assertThat(info.policies).hasSize(1);
433         PolicyInfo policyInfo = info.policies.iterator().next();
434         assertThat(policyInfo.policyId).isEqualTo("id1");
435         assertThat(policyInfo.policyTypeId).isEmpty();
436     }
437
438     @Test
439     void testRefuseToUpdatePolicy() throws Exception {
440         // Test that only the json can be changed for a already created policy
441         // In this case service is attempted to be changed
442         this.addRic("ric1");
443         this.addRic("ricXXX");
444         this.addPolicy("instance1", "type1", "service1", "ric1");
445         this.addPolicy("instance2", "type1", "service1", "ricXXX");
446
447         // Try change ric1 -> ricXXX
448         String bodyWrongRic = putPolicyBody("service1", "ricXXX", "type1", "instance1");
449         testErrorCode(restClient().put("/policies", bodyWrongRic), HttpStatus.CONFLICT);
450     }
451
452     @Test
453     void testGetPolicy() throws Exception {
454         String url = "/policies/id";
455         Policy policy = addPolicy("id", "typeName", "service1", "ric1");
456         {
457             String rsp = restClient().get(url).block();
458             PolicyInfo info = gson.fromJson(rsp, PolicyInfo.class);
459             String policyStr = gson.toJson(info.policyData);
460             assertThat(policyStr).isEqualTo(policy.json());
461         }
462         {
463             policies.remove(policy);
464             testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
465         }
466     }
467
468     @Test
469     void testDeletePolicy() throws Exception {
470         String policyId = "id.1";
471         addPolicy(policyId, "typeName", "service1", "ric1");
472         assertThat(policies.size()).isEqualTo(1);
473
474         String url = "/policies/" + policyId;
475         ResponseEntity<String> entity = restClient().deleteForEntity(url).block();
476
477         assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
478         assertThat(policies.size()).isZero();
479
480         // Delete a non existing policy
481         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
482     }
483
484     @Test
485     void testGetPolicyType() throws Exception {
486         String typeId = "AC.D";
487         addPolicyType(typeId, "ric1");
488
489         waitForRicState("ric1", RicState.AVAILABLE);
490
491         String url = "/policy-types/" + typeId;
492
493         String rsp = this.restClient().get(url).block();
494
495         PolicyTypeInfo info = gson.fromJson(rsp, PolicyTypeInfo.class);
496         assertThat(info.schema).isNotNull();
497
498         // Get non existing schema
499         url = "/policy-types/JUNK";
500         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
501     }
502
503     String createPolicyTypesJson(String... types) {
504         List<String> list = new ArrayList<>();
505         Collections.addAll(list, types);
506         PolicyTypeIdList ids = new PolicyTypeIdList(list);
507         return gson.toJson(ids);
508     }
509
510     @Test
511     void testGetPolicyTypes() throws Exception {
512         addPolicyType("type1", "ric1");
513         addPolicyType("type2", "ric2");
514
515         String url = "/policy-types";
516         String rsp = restClient().get(url).block();
517         String expResp = createPolicyTypesJson("type2", "type1");
518         assertThat(rsp).isEqualTo(expResp);
519
520         url = "/policy-types?ric_id=ric1";
521         rsp = restClient().get(url).block();
522         expResp = createPolicyTypesJson("type1");
523         assertThat(rsp).isEqualTo(expResp);
524
525         // Get policy types for non existing RIC
526         url = "/policy-types?ric_id=ric1XXX";
527         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
528     }
529
530     @Test
531     void testGetPolicyInstances() throws Exception {
532         addPolicy("id1", "type1", "service1");
533
534         String url = "/policy-instances";
535         String rsp = restClient().get(url).block();
536         logger.info(rsp);
537         PolicyInfoList info = gson.fromJson(rsp, PolicyInfoList.class);
538         assertThat(info.policies).hasSize(1);
539         PolicyInfo policyInfo = info.policies.iterator().next();
540         assert (policyInfo.validate());
541         assertThat(policyInfo.policyId).isEqualTo("id1");
542         assertThat(policyInfo.policyTypeId).isEqualTo("type1");
543         assertThat(policyInfo.serviceId).isEqualTo("service1");
544     }
545
546     @Test
547     void testGetPolicyInstancesFilter() throws Exception {
548         addPolicy("id1", "type1", "service1");
549         addPolicy("id2", "type1", "service2");
550         addPolicy("id3", "type2", "service1");
551
552         String url = "/policy-instances?policytype_id=type1";
553         String rsp = restClient().get(url).block();
554         logger.info(rsp);
555         assertThat(rsp).contains("id1") //
556                 .contains("id2") //
557                 .doesNotContain("id3");
558
559         url = "/policy-instances?policytype_id=type1&service_id=service2";
560         rsp = restClient().get(url).block();
561         logger.info(rsp);
562         assertThat(rsp).doesNotContain("id1") //
563                 .contains("id2") //
564                 .doesNotContain("id3");
565
566         // Test get policies for non existing type
567         url = "/policy-instances?policytype_id=type1XXX";
568         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
569
570         // Test get policies for non existing RIC
571         url = "/policy-instances?ric_id=XXX";
572         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
573     }
574
575     @Test
576     void testGetPolicyIdsFilter() throws Exception {
577         addPolicy("id1", "type1", "service1", "ric1");
578         addPolicy("id2", "type1", "service2", "ric1");
579         addPolicy("id3", "type2", "service1", "ric1");
580
581         String url = "/policies?policytype_id=type1";
582         String rsp = restClient().get(url).block();
583         logger.info(rsp);
584         assertThat(rsp).contains("id1") //
585                 .contains("id2") //
586                 .doesNotContain("id3");
587
588         url = "/policies?policytype_id=type1&service_id=service1&ric=ric1";
589         rsp = restClient().get(url).block();
590         PolicyIdList respList = gson.fromJson(rsp, PolicyIdList.class);
591         assertThat(respList.policyIds.iterator().next()).isEqualTo("id1");
592
593         // Test get policy ids for non existing type
594         url = "/policies?policytype_id=type1XXX";
595         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
596
597         // Test get policy ids for non existing RIC
598         url = "/policies?ric_id=XXX";
599         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
600     }
601
602     @Test
603     void testPutAndGetService() throws Exception {
604         // PUT
605         String serviceName = "ac.dc";
606         putService(serviceName, 0, HttpStatus.CREATED);
607         putService(serviceName, 0, HttpStatus.OK);
608
609         // GET one service
610         String url = "/services?service_id=" + serviceName;
611         String rsp = restClient().get(url).block();
612         ServiceStatusList info = gson.fromJson(rsp, ServiceStatusList.class);
613         assertThat(info.statusList).hasSize(1);
614         ServiceStatus status = info.statusList.iterator().next();
615         assertThat(status.keepAliveIntervalSeconds).isZero();
616         assertThat(status.serviceId).isEqualTo(serviceName);
617
618         // GET (all)
619         url = "/services";
620         rsp = restClient().get(url).block();
621         assertThat(rsp).as("Response contains service name").contains(serviceName);
622         logger.info(rsp);
623
624         // Keep alive
625         url = "/services/" + serviceName + "/keepalive";
626         ResponseEntity<?> entity = restClient().putForEntity(url).block();
627         assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
628
629         // DELETE service
630         assertThat(services.size()).isEqualTo(1);
631         url = "/services/" + serviceName;
632         restClient().delete(url).block();
633         assertThat(services.size()).isZero();
634
635         // Keep alive, no registered service
636         testErrorCode(restClient().put("/services/junk/keepalive", ""), HttpStatus.NOT_FOUND);
637
638         // PUT service with bad payload
639         testErrorCode(restClient().put("/services", "crap"), HttpStatus.BAD_REQUEST, false);
640         testErrorCode(restClient().put("/services", "{}"), HttpStatus.BAD_REQUEST, false);
641         testErrorCode(restClient().put("/services", createServiceJson(serviceName, -123)), HttpStatus.BAD_REQUEST,
642                 false);
643         testErrorCode(restClient().put("/services", createServiceJson(serviceName, 0, "missing.portandprotocol.com")),
644                 HttpStatus.BAD_REQUEST, false);
645
646         // GET non existing service
647         testErrorCode(restClient().get("/services?service_id=XXX"), HttpStatus.NOT_FOUND);
648     }
649
650     @Test
651     void testServiceSupervision() throws Exception {
652         putService("service1", 1, HttpStatus.CREATED);
653         addPolicyType("type1", "ric1");
654
655         String policyBody = putPolicyBody("service1", "ric1", "type1", "instance1");
656         restClient().put("/policies", policyBody).block();
657
658         assertThat(policies.size()).isEqualTo(1);
659         assertThat(services.size()).isEqualTo(1);
660
661         // Timeout after ~1 second
662         await().untilAsserted(() -> assertThat(policies.size()).isZero());
663         assertThat(services.size()).isZero();
664     }
665
666     @Test
667     void testGetPolicyStatus() throws Exception {
668         addPolicy("id", "typeName", "service1", "ric1");
669         assertThat(policies.size()).isEqualTo(1);
670
671         String url = "/policies/id/status";
672         String rsp = restClient().get(url).block();
673         PolicyStatusInfo info = gson.fromJson(rsp, PolicyStatusInfo.class);
674         assertThat(info.status).isEqualTo("OK");
675
676         // GET non existing policy status
677         url = "/policies/XXX/status";
678         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
679
680         // GET STATUS, the NearRT RIC returns error
681         MockA1Client a1Client = a1ClientFactory.getOrCreateA1Client("ric1");
682         url = "/policies/id/status";
683         WebClientResponseException a1Exception = new WebClientResponseException(404, "", null, null, null);
684         doReturn(Mono.error(a1Exception)).when(a1Client).getPolicyStatus(any());
685         rsp = restClient().get(url).block();
686         info = gson.fromJson(rsp, PolicyStatusInfo.class);
687         assertThat(info.status).hasToString("{}");
688     }
689
690     @Test
691     void testServiceNotification() throws ServiceException {
692         putService("junkService");
693         Service junkService = this.services.get("junkService");
694         junkService.setCallbackUrl("https://junk");
695         putService("service");
696
697         Ric ric = addRic("ric1");
698         ric.setState(Ric.RicState.UNAVAILABLE);
699         supervision.checkAllRics();
700         waitForRicState("ric1", RicState.AVAILABLE);
701
702         RappSimulatorController.TestResults receivedCallbacks = rAppSimulator.getTestResults();
703         assertThat(receivedCallbacks.getReceivedInfo().size()).isEqualTo(1);
704         ServiceCallbackInfo callbackInfo = receivedCallbacks.getReceivedInfo().get(0);
705         assertThat(callbackInfo.ricId).isEqualTo("ric1");
706         assertThat(callbackInfo.eventType).isEqualTo(ServiceCallbackInfo.EventType.AVAILABLE);
707     }
708
709     private Policy addPolicy(String id, String typeName, String service, String ric) throws ServiceException {
710         addRic(ric);
711         Policy policy = ImmutablePolicy.builder() //
712                 .id(id) //
713                 .json(jsonString()) //
714                 .ownerServiceId(service) //
715                 .ric(rics.getRic(ric)) //
716                 .type(addPolicyType(typeName, ric)) //
717                 .lastModified(Instant.now()) //
718                 .isTransient(false) //
719                 .statusNotificationUri("/policy-status?id=XXX") //
720                 .build();
721         policies.put(policy);
722         return policy;
723     }
724
725     private Policy addPolicy(String id, String typeName, String service) throws ServiceException {
726         return addPolicy(id, typeName, service, "ric");
727     }
728
729     private String createServiceJson(String name, long keepAliveIntervalSeconds) {
730         String callbackUrl = baseUrl() + RappSimulatorController.SERVICE_CALLBACK_URL;
731         return createServiceJson(name, keepAliveIntervalSeconds, callbackUrl);
732     }
733
734     private String createServiceJson(String name, long keepAliveIntervalSeconds, String url) {
735         ServiceRegistrationInfo service = new ServiceRegistrationInfo(name, keepAliveIntervalSeconds, url);
736
737         String json = gson.toJson(service);
738         return json;
739     }
740
741     private void putService(String name) {
742         putService(name, 0, null);
743     }
744
745     private void putService(String name, long keepAliveIntervalSeconds, @Nullable HttpStatus expectedStatus) {
746         String url = "/services";
747         String body = createServiceJson(name, keepAliveIntervalSeconds);
748         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
749         if (expectedStatus != null) {
750             assertEquals(expectedStatus, resp.getStatusCode(), "");
751         }
752     }
753
754     private String jsonString() {
755         return "{\"servingCellNrcgi\":\"1\"}";
756     }
757
758     @Test
759     void testConcurrency() throws Exception {
760         final Instant startTime = Instant.now();
761         List<Thread> threads = new ArrayList<>();
762         List<ConcurrencyTestRunnable> tests = new ArrayList<>();
763         a1ClientFactory.setResponseDelay(Duration.ofMillis(1));
764         addRic("ric");
765         addPolicyType("type1", "ric");
766         addPolicyType("type2", "ric");
767
768         for (int i = 0; i < 10; ++i) {
769             AsyncRestClient restClient = restClient();
770             ConcurrencyTestRunnable test =
771                     new ConcurrencyTestRunnable(restClient, supervision, a1ClientFactory, rics, policyTypes);
772             Thread thread = new Thread(test, "TestThread_" + i);
773             thread.start();
774             threads.add(thread);
775             tests.add(test);
776         }
777         for (Thread t : threads) {
778             t.join();
779         }
780         for (ConcurrencyTestRunnable test : tests) {
781             assertThat(test.isFailed()).isFalse();
782         }
783         assertThat(policies.size()).isZero();
784         logger.info("Concurrency test took " + Duration.between(startTime, Instant.now()));
785     }
786
787     private AsyncRestClient restClient(String baseUrl, boolean useTrustValidation) {
788         WebClientConfig config = this.applicationConfig.getWebClientConfig();
789         config = ImmutableWebClientConfig.builder() //
790                 .keyStoreType(config.keyStoreType()) //
791                 .keyStorePassword(config.keyStorePassword()) //
792                 .keyStore(config.keyStore()) //
793                 .keyPassword(config.keyPassword()) //
794                 .isTrustStoreUsed(useTrustValidation) //
795                 .trustStore(config.trustStore()) //
796                 .trustStorePassword(config.trustStorePassword()) //
797                 .httpProxyConfig(config.httpProxyConfig()) //
798                 .build();
799
800         AsyncRestClientFactory f = new AsyncRestClientFactory(config);
801         return f.createRestClientNoHttpProxy(baseUrl);
802
803     }
804
805     private String baseUrl() {
806         return "https://localhost:" + port;
807     }
808
809     private AsyncRestClient restClient(boolean useTrustValidation) {
810         String baseUrl = "https://localhost:" + port + Consts.V2_API_ROOT;
811         return restClient(baseUrl, useTrustValidation);
812     }
813
814     private AsyncRestClient restClient() {
815         return restClient(false);
816     }
817
818     private void testErrorCode(Mono<?> request, HttpStatus expStatus) {
819         testErrorCode(request, expStatus, "", true);
820     }
821
822     private void testErrorCode(Mono<?> request, HttpStatus expStatus, boolean expectApplicationProblemJsonMediaType) {
823         testErrorCode(request, expStatus, "", expectApplicationProblemJsonMediaType);
824     }
825
826     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
827         testErrorCode(request, expStatus, responseContains, true);
828     }
829
830     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
831             boolean expectApplicationProblemJsonMediaType) {
832         StepVerifier.create(request) //
833                 .expectSubscription() //
834                 .expectErrorMatches(
835                         t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
836                 .verify();
837     }
838
839     private void waitForRicState(String ricId, RicState state) throws ServiceException {
840         Ric ric = rics.getRic(ricId);
841         await().untilAsserted(() -> state.equals(ric.getState()));
842     }
843
844     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
845             boolean expectApplicationProblemJsonMediaType) {
846         assertTrue(throwable instanceof WebClientResponseException);
847         WebClientResponseException responseException = (WebClientResponseException) throwable;
848         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
849         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
850         if (expectApplicationProblemJsonMediaType) {
851             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
852         }
853         return true;
854     }
855
856     private MockA1Client getA1Client(String ricId) throws ServiceException {
857         return a1ClientFactory.getOrCreateA1Client(ricId);
858     }
859
860     private PolicyType createPolicyType(String policyTypeName) {
861         return ImmutablePolicyType.builder() //
862                 .id(policyTypeName) //
863                 .schema("{\"title\":\"" + policyTypeName + "\"}") //
864                 .build();
865     }
866
867     private PolicyType addPolicyType(String policyTypeName, String ricId) {
868         PolicyType type = createPolicyType(policyTypeName);
869         policyTypes.put(type);
870         addRic(ricId).addSupportedPolicyType(type);
871         return type;
872     }
873
874     private Ric addRic(String ricId) {
875         return addRic(ricId, null);
876     }
877
878     private Ric addRic(String ricId, String managedElement) {
879         if (rics.get(ricId) != null) {
880             return rics.get(ricId);
881         }
882         List<String> mes = new ArrayList<>();
883         if (managedElement != null) {
884             mes.add(managedElement);
885         }
886         RicConfig conf = ImmutableRicConfig.builder() //
887                 .ricId(ricId) //
888                 .baseUrl(ricId) //
889                 .managedElementIds(mes) //
890                 .controllerName("") //
891                 .build();
892         Ric ric = new Ric(conf);
893         ric.setState(Ric.RicState.AVAILABLE);
894         this.rics.put(ric);
895         return ric;
896     }
897
898 }