1acbe5f5ebbbe8b79f4eb532e99c7bedf4dddebf
[policy/common.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
7  * ================================================================================
8  * Licensed 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  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.common.endpoints.http.server.test;
23
24 import static org.assertj.core.api.Assertions.assertThat;
25 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34
35 import io.prometheus.client.exporter.MetricsServlet;
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 import java.net.HttpURLConnection;
39 import java.net.URL;
40 import java.nio.charset.StandardCharsets;
41 import java.util.Arrays;
42 import java.util.Base64;
43 import java.util.List;
44 import java.util.Properties;
45 import javax.servlet.FilterChain;
46 import javax.servlet.ServletException;
47 import javax.servlet.ServletRequest;
48 import javax.servlet.ServletResponse;
49 import javax.servlet.http.HttpServletRequest;
50 import javax.ws.rs.Consumes;
51 import javax.ws.rs.POST;
52 import javax.ws.rs.Path;
53 import javax.ws.rs.Produces;
54 import javax.ws.rs.core.MediaType;
55 import javax.ws.rs.core.Response;
56 import lombok.Getter;
57 import org.apache.commons.io.IOUtils;
58 import org.junit.AfterClass;
59 import org.junit.Before;
60 import org.junit.BeforeClass;
61 import org.junit.Test;
62 import org.mockito.ArgumentCaptor;
63 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
64 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactory;
65 import org.onap.policy.common.endpoints.http.server.JsonExceptionMapper;
66 import org.onap.policy.common.endpoints.http.server.RestServer;
67 import org.onap.policy.common.endpoints.http.server.RestServer.Factory;
68 import org.onap.policy.common.endpoints.http.server.YamlExceptionMapper;
69 import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler;
70 import org.onap.policy.common.endpoints.http.server.aaf.AafAuthFilter;
71 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
72 import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
73 import org.onap.policy.common.gson.GsonMessageBodyHandler;
74 import org.onap.policy.common.utils.coder.StandardCoder;
75 import org.onap.policy.common.utils.network.NetworkUtil;
76 import org.powermock.reflect.Whitebox;
77
78 public class RestServerTest {
79     private static final String METRICS_URI = "/metrics";
80     private static final String SERVER1 = "my-server-A";
81     private static final String SERVER2 = "my-server-B";
82     private static final String FACTORY_FIELD = "factory";
83     private static final String HOST = "my-host";
84     private static final String PARAM_NAME = "my-param";
85     private static final String PASS = "my-pass";
86     private static final Integer PORT = 9876;
87     private static final String USER = "my-user";
88
89     private static Factory saveFactory;
90     private static RestServer realRest;
91     private static int realPort;
92     private static RestServerParameters params;
93
94     private RestServer rest;
95     private HttpServletServer server1;
96     private HttpServletServer server2;
97     private Factory factory;
98     private HttpServletServerFactory serverFactory;
99     private String errorMsg;
100
101     /**
102      * Starts the REST server.
103      * @throws Exception if an error occurs
104      */
105     @BeforeClass
106     public static void setUpBeforeClass() throws Exception {
107         saveFactory = Whitebox.getInternalState(RestServer.class, FACTORY_FIELD);
108
109         realPort = NetworkUtil.allocPort();
110
111         initRealParams();
112
113         realRest = new RestServer(params, (Class<AafAuthFilter>) null, RealProvider.class) {
114             @Override
115             protected Properties getServerProperties(RestServerParameters restServerParameters, String names) {
116                 Properties props = super.getServerProperties(restServerParameters, names);
117
118                 String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "."
119                                 + restServerParameters.getName();
120                 props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
121
122                 return props;
123             }
124         };
125
126         realRest.start();
127         assertTrue(NetworkUtil.isTcpPortOpen(params.getHost(), params.getPort(), 100, 100));
128     }
129
130     /**
131      * Restores the factory and stops the REST server.
132      */
133     @AfterClass
134     public static void tearDownAfterClass() {
135         Whitebox.setInternalState(RestServer.class, FACTORY_FIELD, saveFactory);
136
137         realRest.stop();
138     }
139
140     /**
141      * Initializes mocks.
142      */
143     @Before
144     public void setUp() {
145         server1 = mock(HttpServletServer.class);
146         server2 = mock(HttpServletServer.class);
147         factory = mock(Factory.class);
148         serverFactory = mock(HttpServletServerFactory.class);
149
150         initParams();
151
152         when(factory.getServerFactory()).thenReturn(serverFactory);
153         when(serverFactory.build(any())).thenReturn(Arrays.asList(server1, server2));
154
155         when(server1.getName()).thenReturn(SERVER1);
156         when(server2.getName()).thenReturn(SERVER2);
157
158         Whitebox.setInternalState(RestServer.class, FACTORY_FIELD, factory);
159     }
160
161     @Test
162     public void testRestServer() {
163         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
164
165         rest.start();
166         verify(server1).start();
167         verify(server2).start();
168
169         rest.stop();
170         verify(server1).stop();
171         verify(server2).stop();
172     }
173
174     @Test
175     public void testRestServerListList() {
176         rest = new RestServer(params, List.of(Filter.class, Filter2.class), List.of(Provider1.class, Provider2.class));
177
178         rest.start();
179         verify(server1).start();
180         verify(server2).start();
181
182         rest.stop();
183         verify(server1).stop();
184         verify(server2).stop();
185     }
186
187     @Test
188     public void testRestServer_NoAaf() {
189         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
190         verify(server1, never()).addFilterClass(any(), any());
191         verify(server2, never()).addFilterClass(any(), any());
192     }
193
194     @Test
195     public void testRestServer_OnlyOneAaf() {
196         when(server2.isAaf()).thenReturn(true);
197
198         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
199
200         verify(server1, never()).addFilterClass(any(), any());
201         verify(server2).addFilterClass(null, Filter.class.getName());
202     }
203
204     @Test
205     public void testRestServer_BothAaf() {
206         when(server1.isAaf()).thenReturn(true);
207         when(server2.isAaf()).thenReturn(true);
208
209         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
210
211         verify(server1).addFilterClass(null, Filter.class.getName());
212         verify(server2).addFilterClass(null, Filter.class.getName());
213     }
214
215     @Test
216     public void testRestServer_BothAaf_NoFilter() {
217         when(server1.isAaf()).thenReturn(true);
218         when(server2.isAaf()).thenReturn(true);
219
220         rest = new RestServer(params, (Class<AafAuthFilter>) null, Provider1.class, Provider2.class);
221
222         verify(server1, never()).addFilterClass(any(), any());
223         verify(server2, never()).addFilterClass(any(), any());
224     }
225
226     @Test
227     public void testRestServer_MissingProviders() {
228         assertThatIllegalArgumentException().isThrownBy(() -> new RestServer(params, Filter.class));
229     }
230
231     @Test
232     public void testGetServerProperties_testGetProviderNames() {
233         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
234
235         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
236         verify(serverFactory).build(cap.capture());
237
238         Properties props = cap.getValue();
239         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
240
241         assertEquals(HOST, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX));
242         assertEquals(String.valueOf(PORT),
243                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX));
244         assertEquals(Provider1.class.getName() + "," + Provider2.class.getName(),
245                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX));
246         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX));
247         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX));
248         assertEquals(USER, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_USERNAME_SUFFIX));
249         assertEquals(PASS, props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_AUTH_PASSWORD_SUFFIX));
250         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HTTPS_SUFFIX));
251         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_AAF_SUFFIX));
252         assertEquals(String.join(",", GsonMessageBodyHandler.class.getName(), YamlMessageBodyHandler.class.getName(),
253                         JsonExceptionMapper.class.getName(), YamlExceptionMapper.class.getName()),
254                         props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER));
255         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
256     }
257
258     @Test
259     public void testExplicitPrometheusAddedToProperty() {
260         when(params.isPrometheus()).thenReturn(true);
261         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
262         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
263         verify(serverFactory).build(cap.capture());
264
265         Properties props = cap.getValue();
266         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
267
268         assertEquals("true", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
269         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX)).isBlank();
270         assertThat(props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX)).isBlank();
271     }
272
273     @Test
274     public void testStandardSevletAddedToProperty() {
275         when(params.getServletUriPath()).thenReturn("/metrics");
276         when(params.getServletClass()).thenReturn(MetricsServlet.class.getName());
277         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
278         ArgumentCaptor<Properties> cap = ArgumentCaptor.forClass(Properties.class);
279         verify(serverFactory).build(cap.capture());
280
281         Properties props = cap.getValue();
282         String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + PARAM_NAME;
283
284         assertEquals("false", props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PROMETHEUS_SUFFIX));
285         assertEquals(METRICS_URI,
286             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_URIPATH_SUFFIX));
287         assertEquals(MetricsServlet.class.getName(),
288             props.getProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERVLET_CLASS_SUFFIX));
289     }
290
291     @Test
292     public void testInvalidJson() throws Exception {
293         initRealParams();
294
295         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest())));
296         assertEquals(400, roundTrip("{'bogus-json'"));
297         assertThat(errorMsg).contains("Invalid request");
298     }
299
300     @Test
301     public void testInvalidYaml() throws Exception {
302         initRealParams();
303
304         assertEquals(200, roundTrip(new StandardCoder().encode(new MyRequest()),
305                         YamlMessageBodyHandler.APPLICATION_YAML));
306         assertEquals(400, roundTrip("<bogus yaml", YamlMessageBodyHandler.APPLICATION_YAML));
307         assertThat(errorMsg).contains("Invalid request");
308     }
309
310     private int roundTrip(String request) throws IOException {
311         return roundTrip(request, MediaType.APPLICATION_JSON);
312     }
313
314     private int roundTrip(String request, String mediaType) throws IOException {
315         URL url = new URL("http://" + params.getHost() + ":" + params.getPort() + "/request");
316         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
317         conn.setDoInput(true);
318         conn.setDoOutput(true);
319         conn.setRequestMethod("POST");
320         String auth = params.getUserName() + ":" + params.getPassword();
321         conn.setRequestProperty("Authorization", "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()));
322         conn.setRequestProperty("Content-type", mediaType);
323         conn.setRequestProperty("Accept", mediaType);
324         conn.connect();
325
326         try (PrintWriter wtr = new PrintWriter(conn.getOutputStream())) {
327             wtr.write(request);
328         }
329
330         int code = conn.getResponseCode();
331
332         if (code == 200) {
333             errorMsg = "";
334         } else {
335             errorMsg = IOUtils.toString(conn.getErrorStream(), StandardCharsets.UTF_8);
336         }
337
338         return code;
339     }
340
341     @Test
342     public void testToString() {
343         rest = new RestServer(params, Filter.class, Provider1.class, Provider2.class);
344         assertNotNull(rest.toString());
345     }
346
347     @Test
348     public void testFactory() {
349         assertNotNull(saveFactory);
350         assertNotNull(saveFactory.getServerFactory());
351     }
352
353     private static void initRealParams() {
354         initParams();
355
356         when(params.getHost()).thenReturn("localhost");
357         when(params.getPort()).thenReturn(realPort);
358         when(params.isHttps()).thenReturn(false);
359         when(params.isAaf()).thenReturn(false);
360     }
361
362     private static void initParams() {
363         params = mock(RestServerParameters.class);
364
365         when(params.getHost()).thenReturn(HOST);
366         when(params.getName()).thenReturn(PARAM_NAME);
367         when(params.getPassword()).thenReturn(PASS);
368         when(params.getPort()).thenReturn(PORT);
369         when(params.getUserName()).thenReturn(USER);
370         when(params.isAaf()).thenReturn(true);
371         when(params.isHttps()).thenReturn(true);
372     }
373
374     private static class Filter extends AafAuthFilter {
375         @Override
376         protected String getPermissionType(HttpServletRequest request) {
377             return "";
378         }
379
380         @Override
381         protected String getPermissionInstance(HttpServletRequest request) {
382             return "";
383         }
384     }
385
386     private static class Filter2 implements javax.servlet.Filter {
387         @Override
388         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
389                         throws IOException, ServletException {
390             // do nothing
391         }
392     }
393
394     private static class Provider1 {
395         private Provider1() {
396             // do nothing
397         }
398     }
399
400     private static class Provider2 {
401         private Provider2() {
402             // do nothing
403         }
404     }
405
406     @Path("/")
407     @Produces({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
408     @Consumes({MediaType.APPLICATION_JSON, YamlMessageBodyHandler.APPLICATION_YAML})
409     public static class RealProvider {
410         @POST
411         @Path("/request")
412         public Response decision(MyRequest body) {
413             return Response.status(Response.Status.OK).entity(new MyResponse()).build();
414         }
415     }
416
417     @Getter
418     public static class MyRequest {
419         private String data;
420     }
421
422     @Getter
423     public static class MyResponse {
424         private String text = "hello";
425     }
426 }