5edee1503522153d3ce9b1833d3bbaedb1942c08
[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         JSONObject jsonObj = new JSONObject(resp.getBody());
221         jsonObj.remove("host");
222         String indented = (jsonObj).toString(4);
223         String docDir = "api/";
224         Files.createDirectories(Paths.get(docDir));
225         try (PrintStream out = new PrintStream(new FileOutputStream(docDir + "pms-api.json"))) {
226             out.print(indented);
227         }
228     }
229
230     @Test
231     void testGetRics() throws Exception {
232         addRic("ric1");
233         this.addPolicyType("type1", "ric1");
234         String url = "/rics?policytype_id=type1";
235         String rsp = restClient().get(url).block();
236         assertThat(rsp).contains("ric1");
237
238         // nameless type for ORAN A1 1.1
239         addRic("ric2");
240         this.addPolicyType("", "ric2");
241         url = "/rics?policytype_id=";
242
243         // This tests also validation of trusted certs restClient(true)
244         rsp = restClient(true).get(url).block();
245         assertThat(rsp).contains("ric2") //
246                 .doesNotContain("ric1") //
247                 .contains("AVAILABLE");
248
249         // All RICs
250         rsp = restClient().get("/rics").block();
251         assertThat(rsp).contains("ric2") //
252                 .contains("ric1");
253
254         // Non existing policy type
255         url = "/rics?policytype_id=XXXX";
256         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
257     }
258
259     @Test
260     void testSynchronization() throws Exception {
261         // Two polictypes will be put in the NearRT RICs
262         PolicyTypes nearRtRicPolicyTypes = new PolicyTypes();
263         nearRtRicPolicyTypes.put(createPolicyType("typeName"));
264         nearRtRicPolicyTypes.put(createPolicyType("typeName2"));
265         this.a1ClientFactory.setPolicyTypes(nearRtRicPolicyTypes);
266
267         // One type and one instance added to the Policy Management Service's storage
268         final String ric1Name = "ric1";
269         Ric ric1 = addRic(ric1Name);
270         Policy policy2 = addPolicy("policyId2", "typeName", "service", ric1Name);
271         Ric ric2 = addRic("ric2");
272
273         getA1Client(ric1Name).putPolicy(policy2); // put it in the RIC (Near-RT RIC)
274         policies.remove(policy2); // Remove it from the repo -> should be deleted in the RIC
275
276         String policyId = "policyId";
277         Policy policy = addPolicy(policyId, "typeName", "service", ric1Name); // This should be created in the RIC
278         supervision.checkAllRics(); // The created policy should be put in the RIC
279
280         // Wait until synch is completed
281         waitForRicState(ric1Name, RicState.SYNCHRONIZING);
282         waitForRicState(ric1Name, RicState.AVAILABLE);
283         waitForRicState("ric2", RicState.AVAILABLE);
284
285         Policies ricPolicies = getA1Client(ric1Name).getPolicies();
286         assertThat(ricPolicies.size()).isEqualTo(1);
287         Policy ricPolicy = ricPolicies.get(policyId);
288         assertThat(ricPolicy.json()).isEqualTo(policy.json());
289
290         // Both types should be in the Policy Management Service's storage after the
291         // synch
292         assertThat(ric1.getSupportedPolicyTypes()).hasSize(2);
293         assertThat(ric2.getSupportedPolicyTypes()).hasSize(2);
294     }
295
296     @Test
297     void testGetRic() throws Exception {
298         String ricId = "ric1";
299         String managedElementId = "kista_1";
300         addRic(ricId, managedElementId);
301
302         String url = "/rics/ric?managed_element_id=" + managedElementId;
303         String rsp = restClient().get(url).block();
304         RicInfo ricInfo = gson.fromJson(rsp, RicInfo.class);
305         assertThat(ricInfo.ricId).isEqualTo(ricId);
306
307         url = "/rics/ric?ric_id=" + ricId;
308         rsp = restClient().get(url).block();
309         ricInfo = gson.fromJson(rsp, RicInfo.class);
310         assertThat(ricInfo.ricId).isEqualTo(ricId);
311
312         // test GET RIC for ManagedElement that does not exist
313         url = "/rics/ric?managed_element_id=" + "junk";
314         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
315
316         url = "/rics/ric";
317         testErrorCode(restClient().get(url), HttpStatus.BAD_REQUEST);
318     }
319
320     private String putPolicyBody(String serviceName, String ricId, String policyTypeName, String policyInstanceId,
321             boolean isTransient) {
322         PolicyInfo info = new PolicyInfo();
323         info.policyId = policyInstanceId;
324         info.policyTypeId = policyTypeName;
325         info.ricId = ricId;
326         info.serviceId = serviceName;
327         info.policyData = gson.fromJson(jsonString(), Object.class);
328
329         if (isTransient) {
330             info.isTransient = isTransient;
331         }
332         info.statusNotificationUri = "statusNotificationUri";
333         return gson.toJson(info);
334     }
335
336     private String putPolicyBody(String serviceName, String ricId, String policyTypeName, String policyInstanceId) {
337         return putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId, false);
338     }
339
340     @Test
341     void testPutPolicy() throws Exception {
342         String serviceName = "service.1";
343         String ricId = "ric.1";
344         String policyTypeName = "type1_1.2.3";
345         String policyInstanceId = "instance_1.2.3";
346
347         putService(serviceName);
348         addPolicyType(policyTypeName, ricId);
349
350         // PUT a transient policy
351         String url = "/policies";
352         String policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId, true);
353         this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
354
355         restClient().put(url, policyBody).block();
356
357         Policy policy = policies.getPolicy(policyInstanceId);
358         assertThat(policy).isNotNull();
359         assertThat(policy.id()).isEqualTo(policyInstanceId);
360         assertThat(policy.ownerServiceId()).isEqualTo(serviceName);
361         assertThat(policy.ric().id()).isEqualTo(ricId);
362         assertThat(policy.isTransient()).isTrue();
363
364         // Put a non transient policy
365         policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId);
366         restClient().put(url, policyBody).block();
367         policy = policies.getPolicy(policyInstanceId);
368         assertThat(policy.isTransient()).isFalse();
369
370         url = "/policy-instances";
371         String rsp = restClient().get(url).block();
372         assertThat(rsp).as("Response contains policy instance ID.").contains(policyInstanceId);
373
374         url = "/policies/" + policyInstanceId;
375         rsp = restClient().get(url).block();
376         assertThat(rsp).contains(policyBody);
377
378         // Test of error codes
379         url = "/policies";
380         policyBody = putPolicyBody(serviceName, ricId + "XX", policyTypeName, policyInstanceId);
381         testErrorCode(restClient().put(url, policyBody), HttpStatus.NOT_FOUND);
382
383         policyBody = putPolicyBody(serviceName, ricId, policyTypeName + "XX", policyInstanceId);
384         addPolicyType(policyTypeName + "XX", "otherRic");
385         testErrorCode(restClient().put(url, policyBody), HttpStatus.NOT_FOUND);
386
387         policyBody = putPolicyBody(serviceName, ricId, policyTypeName, policyInstanceId);
388         this.rics.getRic(ricId).setState(Ric.RicState.SYNCHRONIZING);
389         testErrorCode(restClient().put(url, policyBody), HttpStatus.LOCKED);
390         this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
391     }
392
393     @Test
394     /**
395      * Test that HttpStatus and body from failing REST call to A1 is passed on to
396      * the caller.
397      *
398      * @throws ServiceException
399      */
400     void testErrorFromRic() throws ServiceException {
401         putService("service1");
402         addPolicyType("type1", "ric1");
403
404         MockA1Client a1Client = a1ClientFactory.getOrCreateA1Client("ric1");
405         HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
406         String responseBody = "Refused";
407         byte[] responseBodyBytes = responseBody.getBytes(StandardCharsets.UTF_8);
408
409         WebClientResponseException a1Exception = new WebClientResponseException(httpStatus.value(), "statusText", null,
410                 responseBodyBytes, StandardCharsets.UTF_8, null);
411         doReturn(Mono.error(a1Exception)).when(a1Client).putPolicy(any());
412
413         // PUT Policy
414         String putBody = putPolicyBody("service1", "ric1", "type1", "id1");
415         String url = "/policies";
416         testErrorCode(restClient().put(url, putBody), httpStatus, responseBody);
417
418         // DELETE POLICY
419         this.addPolicy("instance1", "type1", "service1", "ric1");
420         doReturn(Mono.error(a1Exception)).when(a1Client).deletePolicy(any());
421         testErrorCode(restClient().delete("/policies/instance1"), httpStatus, responseBody);
422
423     }
424
425     @Test
426     void testPutTypelessPolicy() throws Exception {
427         putService("service1");
428         addPolicyType("", "ric1");
429         String body = putPolicyBody("service1", "ric1", "", "id1");
430         restClient().put("/policies", body).block();
431
432         String rsp = restClient().get("/policy-instances").block();
433         PolicyInfoList info = gson.fromJson(rsp, PolicyInfoList.class);
434         assertThat(info.policies).hasSize(1);
435         PolicyInfo policyInfo = info.policies.iterator().next();
436         assertThat(policyInfo.policyId).isEqualTo("id1");
437         assertThat(policyInfo.policyTypeId).isEmpty();
438     }
439
440     @Test
441     void testRefuseToUpdatePolicy() throws Exception {
442         // Test that only the json can be changed for a already created policy
443         // In this case service is attempted to be changed
444         this.addRic("ric1");
445         this.addRic("ricXXX");
446         this.addPolicy("instance1", "type1", "service1", "ric1");
447         this.addPolicy("instance2", "type1", "service1", "ricXXX");
448
449         // Try change ric1 -> ricXXX
450         String bodyWrongRic = putPolicyBody("service1", "ricXXX", "type1", "instance1");
451         testErrorCode(restClient().put("/policies", bodyWrongRic), HttpStatus.CONFLICT);
452     }
453
454     @Test
455     void testGetPolicy() throws Exception {
456         String url = "/policies/id";
457         Policy policy = addPolicy("id", "typeName", "service1", "ric1");
458         {
459             String rsp = restClient().get(url).block();
460             PolicyInfo info = gson.fromJson(rsp, PolicyInfo.class);
461             String policyStr = gson.toJson(info.policyData);
462             assertThat(policyStr).isEqualTo(policy.json());
463         }
464         {
465             policies.remove(policy);
466             testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
467         }
468     }
469
470     @Test
471     void testDeletePolicy() throws Exception {
472         String policyId = "id.1";
473         addPolicy(policyId, "typeName", "service1", "ric1");
474         assertThat(policies.size()).isEqualTo(1);
475
476         String url = "/policies/" + policyId;
477         ResponseEntity<String> entity = restClient().deleteForEntity(url).block();
478
479         assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
480         assertThat(policies.size()).isZero();
481
482         // Delete a non existing policy
483         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
484     }
485
486     @Test
487     void testGetPolicyType() throws Exception {
488         String typeId = "AC.D";
489         addPolicyType(typeId, "ric1");
490
491         waitForRicState("ric1", RicState.AVAILABLE);
492
493         String url = "/policy-types/" + typeId;
494
495         String rsp = this.restClient().get(url).block();
496
497         PolicyTypeInfo info = gson.fromJson(rsp, PolicyTypeInfo.class);
498         assertThat(info.schema).isNotNull();
499
500         // Get non existing schema
501         url = "/policy-types/JUNK";
502         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
503     }
504
505     String createPolicyTypesJson(String... types) {
506         List<String> list = new ArrayList<>();
507         Collections.addAll(list, types);
508         PolicyTypeIdList ids = new PolicyTypeIdList(list);
509         return gson.toJson(ids);
510     }
511
512     @Test
513     void testGetPolicyTypes() throws Exception {
514         addPolicyType("type1", "ric1");
515         addPolicyType("type2", "ric2");
516
517         String url = "/policy-types";
518         String rsp = restClient().get(url).block();
519         String expResp = createPolicyTypesJson("type2", "type1");
520         assertThat(rsp).isEqualTo(expResp);
521
522         url = "/policy-types?ric_id=ric1";
523         rsp = restClient().get(url).block();
524         expResp = createPolicyTypesJson("type1");
525         assertThat(rsp).isEqualTo(expResp);
526
527         // Get policy types for non existing RIC
528         url = "/policy-types?ric_id=ric1XXX";
529         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
530     }
531
532     @Test
533     void testGetPolicyInstances() throws Exception {
534         addPolicy("id1", "type1", "service1");
535
536         String url = "/policy-instances";
537         String rsp = restClient().get(url).block();
538         logger.info(rsp);
539         PolicyInfoList info = gson.fromJson(rsp, PolicyInfoList.class);
540         assertThat(info.policies).hasSize(1);
541         PolicyInfo policyInfo = info.policies.iterator().next();
542         assert (policyInfo.validate());
543         assertThat(policyInfo.policyId).isEqualTo("id1");
544         assertThat(policyInfo.policyTypeId).isEqualTo("type1");
545         assertThat(policyInfo.serviceId).isEqualTo("service1");
546     }
547
548     @Test
549     void testGetPolicyInstancesFilter() throws Exception {
550         addPolicy("id1", "type1", "service1");
551         addPolicy("id2", "type1", "service2");
552         addPolicy("id3", "type2", "service1");
553
554         String url = "/policy-instances?policytype_id=type1";
555         String rsp = restClient().get(url).block();
556         logger.info(rsp);
557         assertThat(rsp).contains("id1") //
558                 .contains("id2") //
559                 .doesNotContain("id3");
560
561         url = "/policy-instances?policytype_id=type1&service_id=service2";
562         rsp = restClient().get(url).block();
563         logger.info(rsp);
564         assertThat(rsp).doesNotContain("id1") //
565                 .contains("id2") //
566                 .doesNotContain("id3");
567
568         // Test get policies for non existing type
569         url = "/policy-instances?policytype_id=type1XXX";
570         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
571
572         // Test get policies for non existing RIC
573         url = "/policy-instances?ric_id=XXX";
574         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
575     }
576
577     @Test
578     void testGetPolicyIdsFilter() throws Exception {
579         addPolicy("id1", "type1", "service1", "ric1");
580         addPolicy("id2", "type1", "service2", "ric1");
581         addPolicy("id3", "type2", "service1", "ric1");
582
583         String url = "/policies?policytype_id=type1";
584         String rsp = restClient().get(url).block();
585         logger.info(rsp);
586         assertThat(rsp).contains("id1") //
587                 .contains("id2") //
588                 .doesNotContain("id3");
589
590         url = "/policies?policytype_id=type1&service_id=service1&ric=ric1";
591         rsp = restClient().get(url).block();
592         PolicyIdList respList = gson.fromJson(rsp, PolicyIdList.class);
593         assertThat(respList.policyIds.iterator().next()).isEqualTo("id1");
594
595         // Test get policy ids for non existing type
596         url = "/policies?policytype_id=type1XXX";
597         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
598
599         // Test get policy ids for non existing RIC
600         url = "/policies?ric_id=XXX";
601         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
602     }
603
604     @Test
605     void testPutAndGetService() throws Exception {
606         // PUT
607         String serviceName = "ac.dc";
608         putService(serviceName, 0, HttpStatus.CREATED);
609         putService(serviceName, 0, HttpStatus.OK);
610
611         // GET one service
612         String url = "/services?service_id=" + serviceName;
613         String rsp = restClient().get(url).block();
614         ServiceStatusList info = gson.fromJson(rsp, ServiceStatusList.class);
615         assertThat(info.statusList).hasSize(1);
616         ServiceStatus status = info.statusList.iterator().next();
617         assertThat(status.keepAliveIntervalSeconds).isZero();
618         assertThat(status.serviceId).isEqualTo(serviceName);
619
620         // GET (all)
621         url = "/services";
622         rsp = restClient().get(url).block();
623         assertThat(rsp).as("Response contains service name").contains(serviceName);
624         logger.info(rsp);
625
626         // Keep alive
627         url = "/services/" + serviceName + "/keepalive";
628         ResponseEntity<?> entity = restClient().putForEntity(url).block();
629         assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
630
631         // DELETE service
632         assertThat(services.size()).isEqualTo(1);
633         url = "/services/" + serviceName;
634         restClient().delete(url).block();
635         assertThat(services.size()).isZero();
636
637         // Keep alive, no registered service
638         testErrorCode(restClient().put("/services/junk/keepalive", ""), HttpStatus.NOT_FOUND);
639
640         // PUT service with bad payload
641         testErrorCode(restClient().put("/services", "crap"), HttpStatus.BAD_REQUEST, false);
642         testErrorCode(restClient().put("/services", "{}"), HttpStatus.BAD_REQUEST, false);
643         testErrorCode(restClient().put("/services", createServiceJson(serviceName, -123)), HttpStatus.BAD_REQUEST,
644                 false);
645         testErrorCode(restClient().put("/services", createServiceJson(serviceName, 0, "missing.portandprotocol.com")),
646                 HttpStatus.BAD_REQUEST, false);
647
648         // GET non existing service
649         testErrorCode(restClient().get("/services?service_id=XXX"), HttpStatus.NOT_FOUND);
650     }
651
652     @Test
653     void testServiceSupervision() throws Exception {
654         putService("service1", 1, HttpStatus.CREATED);
655         addPolicyType("type1", "ric1");
656
657         String policyBody = putPolicyBody("service1", "ric1", "type1", "instance1");
658         restClient().put("/policies", policyBody).block();
659
660         assertThat(policies.size()).isEqualTo(1);
661         assertThat(services.size()).isEqualTo(1);
662
663         // Timeout after ~1 second
664         await().untilAsserted(() -> assertThat(policies.size()).isZero());
665         assertThat(services.size()).isZero();
666     }
667
668     @Test
669     void testGetPolicyStatus() throws Exception {
670         addPolicy("id", "typeName", "service1", "ric1");
671         assertThat(policies.size()).isEqualTo(1);
672
673         String url = "/policies/id/status";
674         String rsp = restClient().get(url).block();
675         PolicyStatusInfo info = gson.fromJson(rsp, PolicyStatusInfo.class);
676         assertThat(info.status).isEqualTo("OK");
677
678         // GET non existing policy status
679         url = "/policies/XXX/status";
680         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND);
681
682         // GET STATUS, the NearRT RIC returns error
683         MockA1Client a1Client = a1ClientFactory.getOrCreateA1Client("ric1");
684         url = "/policies/id/status";
685         WebClientResponseException a1Exception = new WebClientResponseException(404, "", null, null, null);
686         doReturn(Mono.error(a1Exception)).when(a1Client).getPolicyStatus(any());
687         rsp = restClient().get(url).block();
688         info = gson.fromJson(rsp, PolicyStatusInfo.class);
689         assertThat(info.status).hasToString("{}");
690     }
691
692     @Test
693     void testServiceNotification() throws ServiceException {
694         putService("junkService");
695         Service junkService = this.services.get("junkService");
696         junkService.setCallbackUrl("https://junk");
697         putService("service");
698
699         Ric ric = addRic("ric1");
700         ric.setState(Ric.RicState.UNAVAILABLE);
701         supervision.checkAllRics();
702         waitForRicState("ric1", RicState.AVAILABLE);
703
704         RappSimulatorController.TestResults receivedCallbacks = rAppSimulator.getTestResults();
705         assertThat(receivedCallbacks.getReceivedInfo().size()).isEqualTo(1);
706         ServiceCallbackInfo callbackInfo = receivedCallbacks.getReceivedInfo().get(0);
707         assertThat(callbackInfo.ricId).isEqualTo("ric1");
708         assertThat(callbackInfo.eventType).isEqualTo(ServiceCallbackInfo.EventType.AVAILABLE);
709     }
710
711     private Policy addPolicy(String id, String typeName, String service, String ric) throws ServiceException {
712         addRic(ric);
713         Policy policy = ImmutablePolicy.builder() //
714                 .id(id) //
715                 .json(jsonString()) //
716                 .ownerServiceId(service) //
717                 .ric(rics.getRic(ric)) //
718                 .type(addPolicyType(typeName, ric)) //
719                 .lastModified(Instant.now()) //
720                 .isTransient(false) //
721                 .statusNotificationUri("/policy-status?id=XXX") //
722                 .build();
723         policies.put(policy);
724         return policy;
725     }
726
727     private Policy addPolicy(String id, String typeName, String service) throws ServiceException {
728         return addPolicy(id, typeName, service, "ric");
729     }
730
731     private String createServiceJson(String name, long keepAliveIntervalSeconds) {
732         String callbackUrl = baseUrl() + RappSimulatorController.SERVICE_CALLBACK_URL;
733         return createServiceJson(name, keepAliveIntervalSeconds, callbackUrl);
734     }
735
736     private String createServiceJson(String name, long keepAliveIntervalSeconds, String url) {
737         ServiceRegistrationInfo service = new ServiceRegistrationInfo(name, keepAliveIntervalSeconds, url);
738
739         String json = gson.toJson(service);
740         return json;
741     }
742
743     private void putService(String name) {
744         putService(name, 0, null);
745     }
746
747     private void putService(String name, long keepAliveIntervalSeconds, @Nullable HttpStatus expectedStatus) {
748         String url = "/services";
749         String body = createServiceJson(name, keepAliveIntervalSeconds);
750         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
751         if (expectedStatus != null) {
752             assertEquals(expectedStatus, resp.getStatusCode(), "");
753         }
754     }
755
756     private String jsonString() {
757         return "{\"servingCellNrcgi\":\"1\"}";
758     }
759
760     @Test
761     void testConcurrency() throws Exception {
762         final Instant startTime = Instant.now();
763         List<Thread> threads = new ArrayList<>();
764         List<ConcurrencyTestRunnable> tests = new ArrayList<>();
765         a1ClientFactory.setResponseDelay(Duration.ofMillis(1));
766         addRic("ric");
767         addPolicyType("type1", "ric");
768         addPolicyType("type2", "ric");
769
770         for (int i = 0; i < 10; ++i) {
771             AsyncRestClient restClient = restClient();
772             ConcurrencyTestRunnable test =
773                     new ConcurrencyTestRunnable(restClient, supervision, a1ClientFactory, rics, policyTypes);
774             Thread thread = new Thread(test, "TestThread_" + i);
775             thread.start();
776             threads.add(thread);
777             tests.add(test);
778         }
779         for (Thread t : threads) {
780             t.join();
781         }
782         for (ConcurrencyTestRunnable test : tests) {
783             assertThat(test.isFailed()).isFalse();
784         }
785         assertThat(policies.size()).isZero();
786         logger.info("Concurrency test took " + Duration.between(startTime, Instant.now()));
787     }
788
789     private AsyncRestClient restClient(String baseUrl, boolean useTrustValidation) {
790         WebClientConfig config = this.applicationConfig.getWebClientConfig();
791         config = ImmutableWebClientConfig.builder() //
792                 .keyStoreType(config.keyStoreType()) //
793                 .keyStorePassword(config.keyStorePassword()) //
794                 .keyStore(config.keyStore()) //
795                 .keyPassword(config.keyPassword()) //
796                 .isTrustStoreUsed(useTrustValidation) //
797                 .trustStore(config.trustStore()) //
798                 .trustStorePassword(config.trustStorePassword()) //
799                 .httpProxyConfig(config.httpProxyConfig()) //
800                 .build();
801
802         AsyncRestClientFactory f = new AsyncRestClientFactory(config);
803         return f.createRestClientNoHttpProxy(baseUrl);
804
805     }
806
807     private String baseUrl() {
808         return "https://localhost:" + port;
809     }
810
811     private AsyncRestClient restClient(boolean useTrustValidation) {
812         String baseUrl = "https://localhost:" + port + Consts.V2_API_ROOT;
813         return restClient(baseUrl, useTrustValidation);
814     }
815
816     private AsyncRestClient restClient() {
817         return restClient(false);
818     }
819
820     private void testErrorCode(Mono<?> request, HttpStatus expStatus) {
821         testErrorCode(request, expStatus, "", true);
822     }
823
824     private void testErrorCode(Mono<?> request, HttpStatus expStatus, boolean expectApplicationProblemJsonMediaType) {
825         testErrorCode(request, expStatus, "", expectApplicationProblemJsonMediaType);
826     }
827
828     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
829         testErrorCode(request, expStatus, responseContains, true);
830     }
831
832     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
833             boolean expectApplicationProblemJsonMediaType) {
834         StepVerifier.create(request) //
835                 .expectSubscription() //
836                 .expectErrorMatches(
837                         t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
838                 .verify();
839     }
840
841     private void waitForRicState(String ricId, RicState state) throws ServiceException {
842         Ric ric = rics.getRic(ricId);
843         await().untilAsserted(() -> state.equals(ric.getState()));
844     }
845
846     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
847             boolean expectApplicationProblemJsonMediaType) {
848         assertTrue(throwable instanceof WebClientResponseException);
849         WebClientResponseException responseException = (WebClientResponseException) throwable;
850         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
851         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
852         if (expectApplicationProblemJsonMediaType) {
853             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
854         }
855         return true;
856     }
857
858     private MockA1Client getA1Client(String ricId) throws ServiceException {
859         return a1ClientFactory.getOrCreateA1Client(ricId);
860     }
861
862     private PolicyType createPolicyType(String policyTypeName) {
863         return ImmutablePolicyType.builder() //
864                 .id(policyTypeName) //
865                 .schema("{\"title\":\"" + policyTypeName + "\"}") //
866                 .build();
867     }
868
869     private PolicyType addPolicyType(String policyTypeName, String ricId) {
870         PolicyType type = createPolicyType(policyTypeName);
871         policyTypes.put(type);
872         addRic(ricId).addSupportedPolicyType(type);
873         return type;
874     }
875
876     private Ric addRic(String ricId) {
877         return addRic(ricId, null);
878     }
879
880     private Ric addRic(String ricId, String managedElement) {
881         if (rics.get(ricId) != null) {
882             return rics.get(ricId);
883         }
884         List<String> mes = new ArrayList<>();
885         if (managedElement != null) {
886             mes.add(managedElement);
887         }
888         RicConfig conf = ImmutableRicConfig.builder() //
889                 .ricId(ricId) //
890                 .baseUrl(ricId) //
891                 .managedElementIds(mes) //
892                 .controllerName("") //
893                 .build();
894         Ric ric = new Ric(conf);
895         ric.setState(Ric.RicState.AVAILABLE);
896         this.rics.put(ric);
897         return ric;
898     }
899
900 }