Restarting jobs in producer supervision
[nonrtric.git] / enrichment-coordinator-service / src / test / java / org / oransc / enrichment / ApplicationTest.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 Nordix Foundation
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.oransc.enrichment;
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.assertTrue;
26
27 import com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.databind.JsonMappingException;
29 import com.google.gson.Gson;
30 import com.google.gson.GsonBuilder;
31 import com.google.gson.JsonParser;
32
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.PrintStream;
36 import java.lang.invoke.MethodHandles;
37 import java.util.Arrays;
38
39 import org.json.JSONObject;
40 import org.junit.jupiter.api.AfterEach;
41 import org.junit.jupiter.api.BeforeEach;
42 import org.junit.jupiter.api.Test;
43 import org.junit.jupiter.api.extension.ExtendWith;
44 import org.oransc.enrichment.clients.AsyncRestClient;
45 import org.oransc.enrichment.clients.AsyncRestClientFactory;
46 import org.oransc.enrichment.configuration.ApplicationConfig;
47 import org.oransc.enrichment.configuration.ImmutableHttpProxyConfig;
48 import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
49 import org.oransc.enrichment.configuration.WebClientConfig;
50 import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
51 import org.oransc.enrichment.controller.ConsumerSimulatorController;
52 import org.oransc.enrichment.controller.ProducerSimulatorController;
53 import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
54 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
55 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus;
56 import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo;
57 import org.oransc.enrichment.controllers.producer.ProducerCallbacks;
58 import org.oransc.enrichment.controllers.producer.ProducerConsts;
59 import org.oransc.enrichment.controllers.producer.ProducerEiTypeInfo;
60 import org.oransc.enrichment.controllers.producer.ProducerJobInfo;
61 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
62 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
63 import org.oransc.enrichment.controllers.producer.ProducerStatusInfo;
64 import org.oransc.enrichment.exceptions.ServiceException;
65 import org.oransc.enrichment.repository.EiJob;
66 import org.oransc.enrichment.repository.EiJobs;
67 import org.oransc.enrichment.repository.EiProducer;
68 import org.oransc.enrichment.repository.EiProducers;
69 import org.oransc.enrichment.repository.EiType;
70 import org.oransc.enrichment.repository.EiTypes;
71 import org.oransc.enrichment.tasks.ProducerSupervision;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74 import org.springframework.beans.factory.annotation.Autowired;
75 import org.springframework.boot.test.context.SpringBootTest;
76 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
77 import org.springframework.boot.test.context.TestConfiguration;
78 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
79 import org.springframework.boot.web.server.LocalServerPort;
80 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
81 import org.springframework.context.ApplicationContext;
82 import org.springframework.context.annotation.Bean;
83 import org.springframework.http.HttpStatus;
84 import org.springframework.http.MediaType;
85 import org.springframework.http.ResponseEntity;
86 import org.springframework.test.context.TestPropertySource;
87 import org.springframework.test.context.junit.jupiter.SpringExtension;
88 import org.springframework.web.reactive.function.client.WebClientResponseException;
89
90 import reactor.core.publisher.Mono;
91 import reactor.test.StepVerifier;
92
93 @ExtendWith(SpringExtension.class)
94 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
95 @TestPropertySource(
96     properties = { //
97         "server.ssl.key-store=./config/keystore.jks", //
98         "app.webclient.trust-store=./config/truststore.jks", //
99         "app.vardata-directory=./target"})
100 class ApplicationTest {
101     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
102
103     private final String EI_TYPE_ID = "typeId";
104     private final String EI_PRODUCER_ID = "producerId";
105     private final String EI_JOB_PROPERTY = "\"property1\"";
106     private final String EI_JOB_ID = "jobId";
107
108     @Autowired
109     ApplicationContext context;
110
111     @Autowired
112     EiJobs eiJobs;
113
114     @Autowired
115     EiTypes eiTypes;
116
117     @Autowired
118     EiProducers eiProducers;
119
120     @Autowired
121     ApplicationConfig applicationConfig;
122
123     @Autowired
124     ProducerSimulatorController producerSimulator;
125
126     @Autowired
127     ConsumerSimulatorController consumerSimulator;
128
129     @Autowired
130     ProducerSupervision producerSupervision;
131
132     @Autowired
133     ProducerCallbacks producerCallbacks;
134
135     private static Gson gson = new GsonBuilder().create();
136
137     /**
138      * Overrides the BeanFactory.
139      */
140     @TestConfiguration
141     static class TestBeanFactory {
142         @Bean
143         public ServletWebServerFactory servletContainer() {
144             return new TomcatServletWebServerFactory();
145         }
146     }
147
148     @LocalServerPort
149     private int port;
150
151     @BeforeEach
152     void reset() {
153         this.eiJobs.clear();
154         this.eiTypes.clear();
155         this.eiProducers.clear();
156         this.producerSimulator.getTestResults().reset();
157         this.consumerSimulator.getTestResults().reset();
158     }
159
160     @AfterEach
161     void check() {
162         assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
163     }
164
165     @Test
166     void createApiDoc() throws FileNotFoundException {
167         String url = "/v2/api-docs";
168         ResponseEntity<String> resp = restClient().getForEntity(url).block();
169         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
170
171         JSONObject jsonObj = new JSONObject(resp.getBody());
172         jsonObj.remove("host");
173         String indented = jsonObj.toString(4);
174         try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
175             out.print(indented);
176         }
177     }
178
179     @Test
180     void testGetEiTypes() throws Exception {
181         putEiProducerWithOneType(EI_PRODUCER_ID, "test");
182         String url = ConsumerConsts.API_ROOT + "/eitypes";
183         String rsp = restClient().get(url).block();
184         assertThat(rsp).isEqualTo("[\"test\"]");
185     }
186
187     @Test
188     void testGetEiTypesEmpty() throws Exception {
189         String url = ConsumerConsts.API_ROOT + "/eitypes";
190         String rsp = restClient().get(url).block();
191         assertThat(rsp).isEqualTo("[]");
192     }
193
194     @Test
195     void testPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
196         assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.CREATED);
197         assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.OK);
198     }
199
200     @Test
201     void testGetEiType() throws Exception {
202         putEiProducerWithOneType(EI_PRODUCER_ID, "test");
203         String url = ConsumerConsts.API_ROOT + "/eitypes/test";
204         String rsp = restClient().get(url).block();
205         ConsumerEiTypeInfo info = gson.fromJson(rsp, ConsumerEiTypeInfo.class);
206         assertThat(info).isNotNull();
207     }
208
209     @Test
210     void testDeleteEiType() throws Exception {
211         putEiType(EI_TYPE_ID);
212         String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
213         restClient().delete(url).block();
214         assertThat(this.eiTypes.size()).isEqualTo(0);
215
216         testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "EI type not found");
217     }
218
219     @Test
220     void testDeleteEiTypeExistingProducer() throws Exception {
221         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
222         String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
223         testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
224             "The type has active producers: " + EI_PRODUCER_ID);
225         assertThat(this.eiTypes.size()).isEqualTo(1);
226     }
227
228     @Test
229     void testGetEiTypeNotFound() throws Exception {
230         String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
231         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "EI type not found: junk");
232     }
233
234     @Test
235     void testGetEiJobsIds() throws Exception {
236         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
237         putEiJob(EI_TYPE_ID, "jobId");
238         final String JOB_ID_JSON = "[\"jobId\"]";
239         String url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId";
240         String rsp = restClient().get(url).block();
241         assertThat(rsp).isEqualTo(JOB_ID_JSON);
242
243         url = ConsumerConsts.API_ROOT + "/eijobs?owner=owner";
244         rsp = restClient().get(url).block();
245         assertThat(rsp).isEqualTo(JOB_ID_JSON);
246
247         url = ConsumerConsts.API_ROOT + "/eijobs?owner=JUNK";
248         rsp = restClient().get(url).block();
249         assertThat(rsp).isEqualTo("[]");
250
251         url = ConsumerConsts.API_ROOT + "/eijobs";
252         rsp = restClient().get(url).block();
253         assertThat(rsp).isEqualTo(JOB_ID_JSON);
254
255         url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
256         rsp = restClient().get(url).block();
257         assertThat(rsp).isEqualTo(JOB_ID_JSON);
258
259         url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
260         rsp = restClient().get(url).block();
261         assertThat(rsp).isEqualTo("[]");
262     }
263
264     @Test
265     void testGetEiJob() throws Exception {
266         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
267         putEiJob(EI_TYPE_ID, "jobId");
268         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
269         String rsp = restClient().get(url).block();
270         ConsumerEiJobInfo info = gson.fromJson(rsp, ConsumerEiJobInfo.class);
271         assertThat(info.owner).isEqualTo("owner");
272         assertThat(info.eiTypeId).isEqualTo(EI_TYPE_ID);
273     }
274
275     @Test
276     void testGetEiJobNotFound() throws Exception {
277         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
278         String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
279         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
280     }
281
282     @Test
283     void testGetEiJobStatus() throws Exception {
284         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
285         putEiJob(EI_TYPE_ID, "jobId");
286
287         verifyJobStatus("jobId", "ENABLED");
288     }
289
290     @Test
291     void testDeleteEiJob() throws Exception {
292         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
293         putEiJob(EI_TYPE_ID, "jobId");
294         assertThat(this.eiJobs.size()).isEqualTo(1);
295         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
296         restClient().delete(url).block();
297         assertThat(this.eiJobs.size()).isZero();
298
299         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
300         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
301         assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
302     }
303
304     @Test
305     void testDeleteEiJobNotFound() throws Exception {
306         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
307         String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
308         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
309     }
310
311     @Test
312     void testPutEiJob() throws Exception {
313         // Test that one producer accepting a job is enough
314         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
315         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
316
317         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
318         String body = gson.toJson(eiJobInfo());
319         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
320         assertThat(this.eiJobs.size()).isEqualTo(1);
321         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
322
323         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
324         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
325         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
326         assertThat(request.id).isEqualTo("jobId");
327
328         // One retry --> two calls
329         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
330         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
331
332         resp = restClient().putForEntity(url, body).block();
333         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
334         EiJob job = this.eiJobs.getJob("jobId");
335         assertThat(job.getOwner()).isEqualTo("owner");
336
337         verifyJobStatus(EI_JOB_ID, "ENABLED");
338     }
339
340     @Test
341     void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException {
342         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
343         String url = ConsumerConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
344         String body = gson.toJson(eiJobInfo());
345         restClient().put(url, body).block();
346
347         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
348         // There is one retry -> 2 calls
349         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
350         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
351
352         verifyJobStatus(EI_JOB_ID, "DISABLED");
353
354     }
355
356     @Test
357     void testPutEiJob_jsonSchemavalidationError() throws Exception {
358         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
359
360         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
361         // The element with name "property1" is mandatory in the schema
362         ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
363             "targetUri", "jobStatusUrl");
364         String body = gson.toJson(jobInfo);
365
366         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
367     }
368
369     @Test
370     void testGetEiProducerTypes() throws Exception {
371         final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
372         putEiProducerWithOneType("producer1", EI_TYPE_ID);
373         putEiJob(EI_TYPE_ID, "jobId");
374         putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
375         putEiJob(EI_TYPE_ID_2, "jobId2");
376         String url = ProducerConsts.API_ROOT + "/eitypes";
377
378         ResponseEntity<String> resp = restClient().getForEntity(url).block();
379         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
380         assertThat(resp.getBody()).contains(EI_TYPE_ID);
381         assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
382     }
383
384     @Test
385     void testChangingEiTypeGetRejected() throws Exception {
386         putEiProducerWithOneType("producer1", "typeId1");
387         putEiProducerWithOneType("producer2", "typeId2");
388         putEiJob("typeId1", "jobId");
389
390         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
391         String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
392         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
393             "Not allowed to change type for existing EI job");
394     }
395
396     @Test
397     void testPutEiProducer() throws Exception {
398         this.putEiType(EI_TYPE_ID);
399         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
400         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
401
402         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
403         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
404
405         assertThat(this.eiTypes.size()).isEqualTo(1);
406         assertThat(this.eiProducers.getProducersForType(EI_TYPE_ID).size()).isEqualTo(1);
407         assertThat(this.eiProducers.size()).isEqualTo(1);
408         assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
409
410         resp = restClient().putForEntity(url, body).block();
411         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
412
413         resp = restClient().getForEntity(url).block();
414         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
415         assertThat(resp.getBody()).isEqualTo(body);
416     }
417
418     @Test
419     void testPutEiProducerExistingJob() throws Exception {
420         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
421         putEiJob(EI_TYPE_ID, "jobId");
422         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
423         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
424         restClient().putForEntity(url, body).block();
425
426         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
427         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
428         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
429         assertThat(request.id).isEqualTo("jobId");
430     }
431
432     @Test
433     void testPutProducerAndEiJob() throws Exception {
434         this.putEiType(EI_TYPE_ID);
435         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
436         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
437         restClient().putForEntity(url, body).block();
438         assertThat(this.eiTypes.size()).isEqualTo(1);
439         this.eiTypes.getType(EI_TYPE_ID);
440
441         url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
442         body = gson.toJson(eiJobInfo());
443         restClient().putForEntity(url, body).block();
444
445         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
446         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
447         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
448         assertThat(request.id).isEqualTo("jobId");
449     }
450
451     @Test
452     void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
453         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
454         putEiJob(EI_TYPE_ID, "jobId1");
455         putEiJob(EI_TYPE_ID, "jobId2");
456
457         // PUT a consumer
458         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
459         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
460         restClient().putForEntity(url, body).block();
461
462         url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
463         ResponseEntity<String> resp = restClient().getForEntity(url).block();
464         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
465
466         ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
467         assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
468         assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
469     }
470
471     @Test
472     void testDeleteEiProducer() throws Exception {
473         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
474         putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
475
476         assertThat(this.eiProducers.size()).isEqualTo(2);
477         EiType type = this.eiTypes.getType(EI_TYPE_ID);
478         assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId");
479         assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId2");
480         putEiJob(EI_TYPE_ID, "jobId");
481         assertThat(this.eiJobs.size()).isEqualTo(1);
482
483         deleteEiProducer("eiProducerId");
484         assertThat(this.eiProducers.size()).isEqualTo(1);
485         assertThat(this.eiProducers.getProducerIdsForType(EI_TYPE_ID)).doesNotContain("eiProducerId");
486         verifyJobStatus("jobId", "ENABLED");
487
488         deleteEiProducer("eiProducerId2");
489         assertThat(this.eiProducers.size()).isZero();
490         assertThat(this.eiTypes.size()).isEqualTo(1);
491         verifyJobStatus("jobId", "DISABLED");
492     }
493
494     @Test
495     void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
496         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
497         ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
498
499         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
500         putEiJob(EI_TYPE_ID, "jobId");
501         putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
502         await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
503
504         deleteEiProducer("eiProducerId2");
505         assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left
506         deleteEiProducer("eiProducerId");
507         assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
508         assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains
509         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
510         assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
511
512         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
513         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
514         assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
515     }
516
517     @Test
518     void testJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
519         // Test replacing a producer with new and removed types
520
521         // Create a job
522         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
523         putEiJob(EI_TYPE_ID, EI_JOB_ID);
524
525         // change the type for the producer, the job shall be disabled
526         putEiProducerWithOneType(EI_PRODUCER_ID, "junk");
527         verifyJobStatus(EI_JOB_ID, "DISABLED");
528         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
529         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
530         assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
531
532         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
533         verifyJobStatus(EI_JOB_ID, "ENABLED");
534         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
535         assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
536     }
537
538     @Test
539     void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
540         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
541         String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
542         ResponseEntity<String> resp = restClient().getForEntity(url).block();
543         ProducerEiTypeInfo info = gson.fromJson(resp.getBody(), ProducerEiTypeInfo.class);
544         assertThat(info.jobDataSchema).isNotNull();
545     }
546
547     @Test
548     void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
549         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
550         String url = ProducerConsts.API_ROOT + "/eiproducers";
551         ResponseEntity<String> resp = restClient().getForEntity(url).block();
552         assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
553
554         url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=" + EI_TYPE_ID;
555         resp = restClient().getForEntity(url).block();
556         assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
557
558         url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=junk";
559         resp = restClient().getForEntity(url).block();
560         assertThat(resp.getBody()).isEqualTo("[]");
561     }
562
563     @Test
564     void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
565
566         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
567         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
568
569         {
570             // Create a job
571             putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
572             putEiJob(EI_TYPE_ID, EI_JOB_ID);
573             verifyJobStatus(EI_JOB_ID, "ENABLED");
574             deleteEiProducer(EI_PRODUCER_ID);
575             // A Job disabled status notification shall now be received
576             await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
577             assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
578             verifyJobStatus(EI_JOB_ID, "DISABLED");
579         }
580
581         assertThat(this.eiProducers.size()).isEqualTo(1);
582         assertThat(this.eiTypes.size()).isEqualTo(1);
583         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
584
585         this.producerSupervision.createTask().blockLast();
586         this.producerSupervision.createTask().blockLast();
587
588         // Now we have one producer that is disabled
589         assertThat(this.eiProducers.size()).isEqualTo(1);
590         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
591
592         // After 3 failed checks, the producer shall be deregisterred
593         this.producerSupervision.createTask().blockLast();
594         assertThat(this.eiProducers.size()).isEqualTo(0); // The producer is removed
595         assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
596
597         // Now we have one disabled job, and no producer.
598         // PUT a producer, then a Job ENABLED status notification shall be received
599         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
600         await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
601         assertThat(consumerResults.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
602         verifyJobStatus(EI_JOB_ID, "ENABLED");
603     }
604
605     @Test
606     void testProducerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
607         // Test that supervision enables not enabled jobs and sends a notification when
608         // suceeded
609
610         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
611         putEiJob(EI_TYPE_ID, EI_JOB_ID);
612
613         EiProducer producer = this.eiProducers.getProducer(EI_PRODUCER_ID);
614         EiJob job = this.eiJobs.getJob(EI_JOB_ID);
615         // Pretend that the producer did reject the job and the a DISABLED notification
616         // is sent for the job
617         producer.setJobDisabled(job);
618         job.setLastReportedStatus(false);
619         verifyJobStatus(EI_JOB_ID, "DISABLED");
620
621         // Run the supervision and wait for the job to get started in the producer
622         this.producerSupervision.createTask().blockLast();
623         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
624         await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
625         assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
626         verifyJobStatus(EI_JOB_ID, "ENABLED");
627     }
628
629     @Test
630     void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
631         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
632         putEiProducerWithOneTypeRejecting("simulateProducerError2", EI_TYPE_ID);
633
634         String url = "/status";
635         ResponseEntity<String> resp = restClient().getForEntity(url).block();
636         assertThat(resp.getBody()).contains("hunky dory");
637     }
638
639     @Test
640     void testEiJobDatabase() throws Exception {
641         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
642         putEiJob(EI_TYPE_ID, "jobId1");
643         putEiJob(EI_TYPE_ID, "jobId2");
644
645         assertThat(this.eiJobs.size()).isEqualTo(2);
646
647         {
648             EiJob savedJob = this.eiJobs.getJob("jobId1");
649             // Restore the jobs
650             EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
651             jobs.restoreJobsFromDatabase();
652             assertThat(jobs.size()).isEqualTo(2);
653             EiJob restoredJob = jobs.getJob("jobId1");
654             assertThat(restoredJob.getId()).isEqualTo("jobId1");
655             assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
656
657             jobs.remove("jobId1", this.eiProducers);
658             jobs.remove("jobId2", this.eiProducers);
659         }
660         {
661             // Restore the jobs, no jobs in database
662             EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
663             jobs.restoreJobsFromDatabase();
664             assertThat(jobs.size()).isEqualTo(0);
665         }
666         logger.warn("Test removing a job when the db file is gone");
667         this.eiJobs.remove("jobId1", this.eiProducers);
668         assertThat(this.eiJobs.size()).isEqualTo(1);
669
670         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
671         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
672     }
673
674     @Test
675     void testEiTypesDatabase() throws Exception {
676         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
677
678         assertThat(this.eiTypes.size()).isEqualTo(1);
679
680         {
681             // Restore the types
682             EiTypes types = new EiTypes(this.applicationConfig);
683             types.restoreTypesFromDatabase();
684             assertThat(types.size()).isEqualTo(1);
685
686         }
687         {
688             // Restore the jobs, no jobs in database
689             EiTypes types = new EiTypes(this.applicationConfig);
690             types.clear();
691             types.restoreTypesFromDatabase();
692             assertThat(types.size()).isEqualTo(0);
693         }
694         logger.warn("Test removing a job when the db file is gone");
695         this.eiTypes.remove(this.eiTypes.getType(EI_TYPE_ID));
696         assertThat(this.eiJobs.size()).isEqualTo(0);
697     }
698
699     private void deleteEiProducer(String eiProducerId) {
700         String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
701         restClient().deleteForEntity(url).block();
702     }
703
704     private void verifyJobStatus(String jobId, String expStatus) {
705         String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status";
706         String rsp = restClient().get(url).block();
707         assertThat(rsp).contains(expStatus);
708     }
709
710     private void assertProducerOpState(String producerId,
711         ProducerStatusInfo.OperationalState expectedOperationalState) {
712         String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
713         ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
714         ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
715         assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
716     }
717
718     ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
719         throws JsonMappingException, JsonProcessingException {
720         return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
721     }
722
723     ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
724         throws JsonMappingException, JsonProcessingException {
725         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
726             baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
727             baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
728     }
729
730     ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
731         throws JsonMappingException, JsonProcessingException {
732         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
733             baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
734     }
735
736     private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
737         return eiJobInfo(EI_TYPE_ID, EI_JOB_ID);
738     }
739
740     ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException {
741         return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri",
742             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId));
743     }
744
745     private Object jsonObject(String json) {
746         try {
747             return JsonParser.parseString(json).getAsJsonObject();
748         } catch (Exception e) {
749             throw new NullPointerException(e.toString());
750         }
751     }
752
753     private Object jsonSchemaObject() {
754         // a json schema with one mandatory property named "string"
755         String schemaStr = "{" //
756             + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
757             + "\"type\": \"object\"," //
758             + "\"properties\": {" //
759             + EI_JOB_PROPERTY + " : {" //
760             + "    \"type\": \"string\"" //
761             + "  }" //
762             + "}," //
763             + "\"required\": [" //
764             + EI_JOB_PROPERTY //
765             + "]" //
766             + "}"; //
767         return jsonObject(schemaStr);
768     }
769
770     private Object jsonObject() {
771         return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
772     }
773
774     private EiJob putEiJob(String eiTypeId, String jobId)
775         throws JsonMappingException, JsonProcessingException, ServiceException {
776
777         String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId;
778         String body = gson.toJson(eiJobInfo(eiTypeId, jobId));
779         restClient().putForEntity(url, body).block();
780
781         return this.eiJobs.getJob(jobId);
782     }
783
784     private HttpStatus putEiType(String eiTypeId)
785         throws JsonMappingException, JsonProcessingException, ServiceException {
786         String url = ProducerConsts.API_ROOT + "/eitypes/" + eiTypeId;
787         String body = gson.toJson(producerEiTypeRegistrationInfo(eiTypeId));
788         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
789         this.eiTypes.getType(eiTypeId);
790         return resp.getStatusCode();
791
792     }
793
794     private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId)
795         throws JsonMappingException, JsonProcessingException, ServiceException {
796         this.putEiType(eiTypeId);
797         String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
798         String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId));
799         restClient().putForEntity(url, body).block();
800         return this.eiTypes.getType(eiTypeId);
801     }
802
803     private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
804         throws JsonMappingException, JsonProcessingException, ServiceException {
805         this.putEiType(eiTypeId);
806
807         String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
808         String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
809
810         restClient().putForEntity(url, body).block();
811
812         return this.eiTypes.getType(eiTypeId);
813     }
814
815     private String baseUrl() {
816         return "https://localhost:" + this.port;
817     }
818
819     private AsyncRestClient restClient(boolean useTrustValidation) {
820         WebClientConfig config = this.applicationConfig.getWebClientConfig();
821         HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
822             .httpProxyHost("") //
823             .httpProxyPort(0) //
824             .build();
825         config = ImmutableWebClientConfig.builder() //
826             .keyStoreType(config.keyStoreType()) //
827             .keyStorePassword(config.keyStorePassword()) //
828             .keyStore(config.keyStore()) //
829             .keyPassword(config.keyPassword()) //
830             .isTrustStoreUsed(useTrustValidation) //
831             .trustStore(config.trustStore()) //
832             .trustStorePassword(config.trustStorePassword()) //
833             .httpProxyConfig(httpProxyConfig).build();
834
835         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
836         return restClientFactory.createRestClientNoHttpProxy(baseUrl());
837     }
838
839     private AsyncRestClient restClient() {
840         return restClient(false);
841     }
842
843     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
844         testErrorCode(request, expStatus, responseContains, true);
845     }
846
847     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
848         boolean expectApplicationProblemJsonMediaType) {
849         StepVerifier.create(request) //
850             .expectSubscription() //
851             .expectErrorMatches(
852                 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
853             .verify();
854     }
855
856     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
857         boolean expectApplicationProblemJsonMediaType) {
858         assertTrue(throwable instanceof WebClientResponseException);
859         WebClientResponseException responseException = (WebClientResponseException) throwable;
860         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
861         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
862         if (expectApplicationProblemJsonMediaType) {
863             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
864         }
865         return true;
866     }
867
868 }