Federation 3.2.0 - Model Data api
[federation.git] / gateway / src / test / java / org / acumos / federation / gateway / GatewayControllerTest.java
1 /*-
2  * ===============LICENSE_START=======================================================
3  * Acumos
4  * ===================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property & Tech Mahindra. All rights reserved.
6  * ===================================================================================
7  * This Acumos software file is distributed by AT&T and Tech Mahindra
8  * under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * This file 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 package org.acumos.federation.gateway;
21
22 import java.util.concurrent.CountDownLatch;
23 import java.util.concurrent.TimeUnit;
24 import java.util.function.Consumer;
25 import com.fasterxml.jackson.databind.ObjectMapper;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.fail;
30 import org.junit.Test;
31 import org.junit.Before;
32 import org.junit.runner.RunWith;
33
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.when;
36 import static org.mockito.Mockito.any;
37
38 import org.springframework.beans.factory.annotation.Autowired;
39 import org.springframework.boot.web.server.LocalServerPort;
40 import org.springframework.boot.test.context.SpringBootTest;
41 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
42 import org.springframework.boot.test.mock.mockito.MockBean;
43 import org.springframework.core.ParameterizedTypeReference;
44 import org.springframework.http.HttpMethod;
45 import org.springframework.test.context.junit4.SpringRunner;
46 import org.springframework.test.context.ContextConfiguration;
47 import org.springframework.web.client.HttpClientErrorException.Forbidden;
48 import org.springframework.web.client.HttpClientErrorException.NotFound;
49
50 import org.acumos.cds.client.ICommonDataServiceRestClient;
51 import org.acumos.cds.client.CommonDataServiceRestClientImpl;
52
53 import org.acumos.securityverification.service.ISecurityVerificationClientService;
54
55 import org.acumos.federation.client.FederationClient;
56 import org.acumos.federation.client.GatewayClient;
57 import org.acumos.federation.client.ClientBase;
58 import org.acumos.federation.client.config.ClientConfig;
59 import org.acumos.federation.client.config.BasicAuthConfig;
60 import org.acumos.federation.client.config.TlsConfig;
61 import org.acumos.federation.client.data.JsonResponse;
62 import org.acumos.federation.client.data.ModelData;
63
64 import org.acumos.federation.client.test.ClientMocking;
65 import static org.acumos.federation.client.test.ClientMocking.getConfig;
66 import static org.acumos.federation.client.test.ClientMocking.xq;
67
68 @RunWith(SpringRunner.class)
69 @ContextConfiguration(classes=GatewayServer.class)
70 @SpringBootTest(
71     classes = Application.class,
72     webEnvironment = WebEnvironment.RANDOM_PORT,
73     properties = {
74         "spring.main.allow-bean-definition-overriding=true",
75         "local.ssl.key-store=classpath:acumosa.pkcs12",
76         "local.ssl.key-store-password=acumosa",
77         "local.ssl.key-store-type=PKCS12",
78         "local.ssl.trust-store=classpath:acumosTrustStore.jks",
79         "local.ssl.trust-store-password=acumos",
80         "nexus.group-id=nxsgrpid",
81         "nexus.name-separator=,",
82         "docker.registry-url=someregistry:9999",
83         "federation.operator=defuserid",
84         "logstash.url=http://logstash:2345",
85     }
86 )
87 public class GatewayControllerTest {
88         @LocalServerPort
89         private int port;
90
91         @Autowired
92         private ServerConfig local;
93
94         @MockBean
95         private Clients clients;
96
97         private CountDownLatch steps;
98
99         private final Consumer<ClientMocking.RequestInfo> count = x -> this.steps.countDown();
100
101         private SimulatedDockerClient docker;
102
103         static ClientConfig anonConfig() {
104                 ClientConfig ret = getConfig("bogus");
105                 ret.getSsl().setKeyStore(null);
106                 ret.setCreds(null);
107                 return ret;
108         }
109
110         private static class RawAnonClient extends ClientBase {
111                 public RawAnonClient(String url) throws Exception {
112                         super(url, anonConfig(), null, null);
113                 }
114
115                 public byte[] get(String uri) {
116                         return handle(uri, HttpMethod.GET, new ParameterizedTypeReference<byte[]>(){});
117                 }
118         }
119
120         @Before
121         public void init() throws Exception {
122                 ICommonDataServiceRestClient cdsClient = CommonDataServiceRestClientImpl.getInstance("http://cds:999", ClientBase.buildRestTemplate("http://cds:999", new ClientConfig(), null, null));
123
124                 (new ClientMocking())
125                     .on("GET /peer/search?self=true&subjectName=gateway.acumosa.org&_j=a&page=0&size=100", xq("{ 'content': [ {'peerId': '1', 'subjectName': 'gateway.acumosa.org', 'statusCode': 'AC', 'self': true } ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 1 }"))
126                     .on("GET /peer/somepeer", xq("{ 'peerId': 'somepeer', 'apiUrl': 'https://somepeer.org:999'}"))
127                     .on("GET /peer/unknownpeer", "")
128                     .on("GET /peer/search?subjectName=gateway.acumosa.org&_j=a&page=0&size=100", xq("{ 'content': [ {'peerId': 'acumosa', 'subjectName': 'gateway.acumosa.org', 'statusCode': 'AC', 'self': true } ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 1 }"))
129                     .on("GET /peer/search?subjectName=gateway.acumosb.org&_j=a&page=0&size=100", xq("{ 'content': [ {'peerId': 'acumosb', 'subjectName': 'gateway.acumosb.org', 'statusCode': 'AC', 'self': false } ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 1 }"))
130                     .on("GET /peer/search?subjectName=gateway.acumosc.org&_j=a&page=0&size=100", xq("{ 'content': [ ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 0 }"))
131                     .on("GET /peer/sub/999", xq("{ 'subId': 999, 'peerId': 'somepeer', 'selector': '{ \\'catalogId\\': \\'somecatalog\\' } ', 'userId': 'someUser' }"))
132                     .on("GET /peer/sub/998", "")
133                     .on("GET /peer/sub/997", xq("{ 'subId': 997, 'peerId': 'someotherpeer' }"))
134                     .on("GET /peer/sub/992", xq("{ 'subId': 992, 'peerId': 'somepeer', 'selector': '{ \\'catalogId\\': [ \\'firstcatalog\\', \\'secondcatalog\\' ] }', 'refreshInterval': 3600, 'userId': 'someUser' }"))
135                     .on("GET /peer/sub/993", xq("{ 'subId': 993, 'peerId': 'somepeer', 'selector': '{ \\'catalogId\\': true }', 'refreshInterval': 3600, 'userId': 'someUser' }"))
136                     .on("GET /peer/sub/994", xq("{ 'subId': 994, 'peerId': 'somepeer', 'selector': '{ \\'catalogId\\': [ \\'x\\', true ] }', 'refreshInterval': 3600, 'userId': 'someUser' }"))
137                     .on("GET /peer/sub/995", xq("{ 'subId': 995, 'peerId': 'somepeer', 'selector': '}', 'refreshInterval': 3600, 'userId': 'someUser' }"))
138                     .on("GET /peer/sub/996", xq("{ 'subId': 996, 'peerId': 'somepeer', 'selector': '{}', 'refreshInterval': 3600, 'userId': 'someUser' }"))
139                     .on("PUT /peer/sub/999", "", count)
140                     .on("GET /catalog/solution?ctlg=somecatalog&page=0&size=100", xq("{ 'content': [], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 0 }"))
141                     .on("GET /catalog?page=0&size=100", xq("{ 'content': [], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 0 }"))
142                     .on("POST /catalog", "{}", count)
143                     .on("GET /solution/somesolution", "")
144                     .on("GET /solution/somesolution/revision", "[]")
145                     .on("POST /solution", "{}", count)
146                     .on("POST /catalog/somecatalog/solution/somesolution", "", count)
147                     .on("PUT /solution/somesolution/pic", "", count)
148                     .on("GET /solution/ignored/revision/revid1", "")
149                     .on("POST /solution/somesolution/revision", xq("{ 'solutionId': 'somesolution', 'revisionId': 'revid1' }"), count)
150                     .on("POST /revision/revid1/catalog/somecatalog/descr", "", count)
151                     .on("GET /artifact/artid1", "")
152                     .on("POST /artifact", "", count)
153                     .on("POST /revision/revid1/artifact/artid1", "", count)
154                     .on("GET /document/docid1", "")
155                     .on("POST /document", "", count)
156                     .on("POST /revision/revid1/catalog/somecatalog/document/docid1", "", count)
157                     .on("GET /catalog/solution?ctlg=firstcatalog&page=0&size=100", xq("{ 'content': [ ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 0 }"))
158                     .on("GET /catalog/solution?ctlg=secondcatalog&page=0&size=100", xq("{ 'content': [ { 'solutionId': 'cat2soln' } ], 'last': true, 'number': 1, 'size': 100, 'numberOfElements': 1 }"))
159                     .on("GET /solution/cat2soln", xq("{ 'solutionId': 'cat2soln' }"))
160                     .on("GET /solution/cat2soln/revision", xq("[ { 'revisionId': 'cat2rev', 'solutionId': 'cat2sol' }, { 'revisionId': 'cat2rev2', 'solutionId': 'cat2sol' } ]"))
161                     .on("GET /solution/cat2soln/pic", "asdf")
162                     .on("GET /solution/ignored/revision/cat2rev", xq("{ 'revisionId': 'cat2rev', 'solutionId': 'cat2sol' }"))
163                     .on("GET /solution/ignored/revision/cat2rev2", xq("{ 'revisionId': 'cat2rev2', 'solutionId': 'cat2sol' }"))
164                     .on("GET /revision/cat2rev/artifact", xq("[ { 'artifactId': 'artid2', 'filename': 'artfile2.arttype', 'version': 'artversionA' } ]"))
165                     .on("GET /revision/cat2rev2/artifact", "[]")
166                     .on("GET /revision/cat2rev/catalog/secondcatalog/descr", xq("{ 'revisionId': 'cat2rev', 'catalogId': 'secondcatalog', 'description': 'old description' }"))
167                     .on("GET /revision/cat2rev2/catalog/secondcatalog/descr", xq("{ 'revisionId': 'cat2rev2', 'catalogId': 'secondcatalog', 'description': 'description A' }"))
168                     .on("GET /revision/cat2rev/catalog/secondcatalog/document", "[]")
169                     .on("GET /document/docid2", xq("{ 'documentId': 'docid2', 'filename': 'docfile2.doctype', 'version': 'docversionA' }"))
170                     .on("GET /artifact/artid2", xq("{ 'artifactId': 'artid2', 'filename': 'artfile2.arttype', 'version': 'artversionA' }"))
171                     .on("GET /revision/cat2rev2/catalog/secondcatalog/document", "[]")
172                     .on("DELETE /revision/cat2rev/catalog/secondcatalog/descr", "")
173                     .on("PUT /artifact/artid2", "")
174                     .on("PUT /document/docid2", "")
175                     .on("POST /revision/cat2rev/catalog/secondcatalog/document/docid2", "")
176                     .on("PUT /solution/cat2soln/revision/cat2rev", "")
177                     .on("PUT /revision/cat2rev2/catalog/secondcatalog/descr", "")
178                     .on("POST /solution/cat2soln/tag/tag1", "")
179                     .on("PUT /solution/cat2soln", "")
180                     .on("PUT /peer/sub/992", "", count)
181                     .on("POST /notif", xq("{ 'notificationId': 'noteid' }"))
182                     .on("POST /notif/noteid/user/someUser", "")
183                     .applyTo(cdsClient);
184                 when(clients.getCDSClient()).thenReturn(cdsClient);
185
186                 NexusClient nexusClient = new NexusClient("https://nexus:999", new ClientConfig());
187                 (new ClientMocking())
188                     .on("PUT /nxsgrpid,somesolution/somefile/someversion/somefile-someversion.type", "")
189                     .on("PUT /nxsgrpid,somesolution/docfile/na/docfile-na.doctype", "")
190                     .on("PUT /nxsgrpid,cat2soln/docfile2/na/docfile2-na.doctype", "")
191                     .on("PUT /nxsgrpid,cat2soln/artfile2/artversion2B/artfile2-artversion2B.arttype", "")
192                     .applyTo(nexusClient);
193                 when(clients.getNexusClient()).thenReturn(nexusClient);
194
195                 FederationClient fedClient = new FederationClient("https://peer:999", new ClientConfig());
196                 (new ClientMocking())
197                     .on("GET /catalogs", xq("{ 'content': [ {}, {} ]}"))
198                     .on("GET /ping", xq("{ 'content': {}}"))
199                     .on("GET /peers", xq("{ 'content': [{}, {}]}"))
200                     .on("POST /peer/register", xq("{ 'content': {}}"))
201                     .on("GET /solutions?catalogId=somecatalog", xq("{ 'content': [ { 'solutionId': 'somesolution' } ]}"))
202                     .on("GET /solutions/somesolution", xq("{ 'content': { 'picture': 'YXNkZg==', 'revisions': [ { 'revisionId': 'revid1' } ] }}"))
203                     .on("GET /solutions/somesolution/revisions/revid1?catalogId=somecatalog", xq("{ 'content': { 'solutionId': 'somesolution', 'revisionId': 'revid1', 'documents': [ { 'documentId': 'docid1', 'filename': 'docfile.doctype', 'version': 'docversion' } ], 'artifacts': [ { 'artifactId': 'artid1', 'name': 'somename', 'filename': 'someimage', 'version': 'someversion', 'artifactTypeCode': 'DI', 'description': 'thisimage:thistag' } ], 'revCatDescription': { 'revisionId': 'revid1', 'catalogId': 'somecatalog', 'description': 'some description' }}}"))
204                     .on("GET /artifacts/artid1/content", "Artifact Content")
205                     .on("GET /artifacts/artid2/content", "Artifact Content 2")
206                     .on("GET /documents/docid1/content", "Document Content")
207                     .on("GET /documents/docid2/content", "Document Content 2")
208                     .on("GET /solutions?catalogId=firstcatalog", xq("{ 'content': [ ]}"))
209                     .on("GET /solutions?catalogId=secondcatalog", xq("{ 'content': [ { 'solutionId': 'cat2soln' } ]}"))
210                     .on("GET /solutions/cat2soln", xq("{ 'content': { 'solutionId': 'cat2soln', 'picture': 'YXNkZg==', 'revisions': [ { 'revisionId': 'cat2rev' }, { 'revisionId': 'cat2rev2' } ], 'tags': [ { 'tag': 'tag1' } ] }}"))
211                     .on("GET /solutions/cat2soln/revisions/cat2rev?catalogId=secondcatalog", xq("{ 'content': { 'solutionId': 'cat2soln', 'revisionId': 'cat2rev', 'documents': [ { 'documentId': 'docid2', 'filename': 'docfile2.doctype', 'version': 'docversionB' } ], 'artifacts': [ { 'artifactId': 'artid2', 'filename': 'artfile2.arttype', 'version': 'artversion2B' } ] }}"))
212                     .on("GET /solutions/cat2soln/revisions/cat2rev2?catalogId=secondcatalog", xq("{ 'content': { 'solutionId': 'cat2soln', 'revisionId': 'cat2rev2', 'revCatDescription': { 'catalogId': 'secondcatalog', 'revisionId': 'cat2rev2', 'description': 'description B' }, 'documents': [  ], 'artifacts': [ ] }}"))
213                     .applyTo(fedClient);
214                 when(clients.getFederationClient(any(String.class))).thenReturn(fedClient);
215
216                 docker = new SimulatedDockerClient();
217                 when(clients.getDockerClient()).thenReturn(docker.getClient());
218
219                 ISecurityVerificationClientService sv = mock(ISecurityVerificationClientService.class);
220                 when (clients.getSVClient()).thenReturn(sv);
221         }
222
223         @Test
224         public void testConfig() throws Exception {
225                 assertEquals("acumosa", local.getSsl().getKeyStorePassword());
226
227                 GatewayClient self = new GatewayClient("https://localhost:" + port, getConfig("acumosa"));
228                 GatewayClient known = new GatewayClient("https://localhost:" + port, getConfig("acumosb"));
229                 GatewayClient unknown = new GatewayClient("https://localhost:" + port, getConfig("acumosc"));
230                 assertNotNull(self.ping("somepeer"));
231                 assertNotNull(self.register("somepeer"));
232                 assertNotNull(self.getPeers("somepeer"));
233                 try {
234                         known.ping("somepeer");
235                         fail();
236                 } catch (Forbidden ux) {
237                         // expected case
238                 }
239                 try {
240                         unknown.ping("somepeer");
241                         fail();
242                 } catch (Forbidden ux) {
243                         // expected case
244                 }
245                 try {
246                         self.ping("unknownpeer");
247                         fail();
248                 } catch (NotFound nf) {
249                         // expected case
250                 }
251                 assertEquals(2, self.getCatalogs("somepeer").size());
252                 assertEquals(1, self.getSolutions("somepeer", "somecatalog").size());
253                 assertNotNull(self.getSolution("somepeer", "somesolution"));
254                 try {
255                         self.triggerPeerSubscription("unknownpeer", 999);
256                         fail();
257                 } catch (NotFound nf) {
258                         // expected case
259                 }
260                 try {
261                         self.triggerPeerSubscription("somepeer", 998);
262                         fail();
263                 } catch (NotFound nf) {
264                         // expected case
265                 }
266                 try {
267                         self.triggerPeerSubscription("somepeer", 997);
268                         fail();
269                 } catch (NotFound nf) {
270                         // expected case
271                 }
272                 docker.clearImages();
273                 docker.addImage("imageid1", "tagA:1", "tagB:2");
274                 docker.addImage("imageid2", "tagX:1", "thisimage:thistag");
275                 steps = new CountDownLatch(12 + 1);
276                 self.triggerPeerSubscription("somepeer", 992);
277                 self.triggerPeerSubscription("somepeer", 993);
278                 self.triggerPeerSubscription("somepeer", 994);
279                 self.triggerPeerSubscription("somepeer", 995);
280                 self.triggerPeerSubscription("somepeer", 996);
281                 self.triggerPeerSubscription("somepeer", 999);
282                 steps.await(2, TimeUnit.SECONDS);
283                 assertEquals("Incomplete steps remain", 0, steps.getCount() - 1);
284         }
285
286         @Test
287         public void testModelDataNoPeerLookup() throws Exception {
288                 GatewayClient self = new GatewayClient("https://localhost:" + port, getConfig("acumosa"));
289
290                 ICommonDataServiceRestClient cdsClient =
291                                 CommonDataServiceRestClientImpl.getInstance("http://cds:999",
292                                                 ClientBase.buildRestTemplate("http://cds:999", new ClientConfig(), null, null));
293                 String peerUrl = "https://somepeer.org:999";
294
295                 (new ClientMocking())
296                     .on("GET /peer/peerid", xq("{ 'peerId': 'peerid', 'apiUrl': \'" + peerUrl + "\'}"))
297                     .on("GET /solution/cat2soln", xq("{ 'solutionId': 'cat2soln', 'sourceId': 'peerid' }"))
298                     .on("GET /peer/search?subjectName=gateway.acumosa.org&_j=a&page=0&size=100", xq("{ 'content': [ {'peerId': 'acumosa', 'subjectName': 'gateway.acumosa.org', 'statusCode': 'AC', 'self': true } ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 1 }"))
299                     .applyTo(cdsClient);
300                 when(clients.getCDSClient()).thenReturn(cdsClient);
301
302                 FederationClient fedClient = new FederationClient(peerUrl, new ClientConfig());
303                 (new ClientMocking())
304                     .on("POST /modeldata", xq("{'message': 'model data posted and sent to peer'}"))
305                     .applyTo(fedClient);
306                 when(clients.getFederationClient(any(String.class))).thenReturn(fedClient);
307
308                 ObjectMapper objectMapper = new ObjectMapper();
309                 ModelData payloadObjectNode =
310                                 objectMapper.readValue("{\"model\": { \"solutionId\": \"cat2soln\"}}", ModelData.class);
311                 System.out.println("Justin " + payloadObjectNode);
312                 try {
313                         self.sendModelData("peerid", payloadObjectNode);
314                 } catch (Exception e) {
315                         fail("model data not sent to peer");
316                 }
317         }
318
319         @Test
320         public void testModelDataWithPeerLookup() throws Exception {
321                 GatewayClient self = new GatewayClient("https://localhost:" + port, getConfig("acumosa"));
322
323                 ICommonDataServiceRestClient cdsClient =
324                                 CommonDataServiceRestClientImpl.getInstance("http://cds:999",
325                                                 ClientBase.buildRestTemplate("http://cds:999", new ClientConfig(), null, null));
326                 String peerUrl = "https://somepeer.org:999";
327
328                 (new ClientMocking())
329                     .on("GET /peer/peerid", xq("{ 'peerId': 'peerid', 'apiUrl': \'" + peerUrl + "\'}"))
330                     .on("GET /solution/cat2soln", xq("{ 'solutionId': 'cat2soln', 'sourceId': 'peerid' }"))
331                     .on("GET /peer/search?subjectName=gateway.acumosa.org&_j=a&page=0&size=100", xq("{ 'content': [ {'peerId': 'acumosa', 'subjectName': 'gateway.acumosa.org', 'statusCode': 'AC', 'self': true } ], 'last': true, 'number': 0, 'size': 100, 'numberOfElements': 1 }"))
332                     .applyTo(cdsClient);
333                 when(clients.getCDSClient()).thenReturn(cdsClient);
334
335                 FederationClient fedClient = new FederationClient(peerUrl, new ClientConfig());
336                 (new ClientMocking())
337                     .on("POST /modeldata", xq("{'message': 'successfully posted model data'}"))
338                     .applyTo(fedClient);
339                 when(clients.getFederationClient(any(String.class))).thenReturn(fedClient);
340
341                 ObjectMapper objectMapper = new ObjectMapper();
342                 ModelData payloadObjectNode =
343                                 objectMapper.readValue("{\"model\": { \"solutionId\": \"cat2soln\"}}", ModelData.class);
344                 try {
345                         self.sendModelData("USE_SOLUTION_SOURCE", payloadObjectNode);
346                 } catch (Exception e) {
347                         fail("was not able to send modeldata to peer");
348                 }
349         }
350
351
352         @Test
353         public void testSwagger() throws Exception {
354                 RawAnonClient rac = new RawAnonClient("https://localhost:" + port);
355                 assertNotNull(rac);
356                 rac.get("/swagger-ui.html");
357                 rac.get("/v2/api-docs");
358         }
359 }