1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.web.health;
17
18 import bitronix.tm.resource.jdbc.PoolingDataSource;
19 import org.apache.commons.dbcp.BasicDataSource;
20 import org.apache.commons.lang.StringUtils;
21 import org.codehaus.jackson.JsonNode;
22 import org.codehaus.jackson.map.ObjectMapper;
23 import org.enhydra.jdbc.pool.StandardXAPoolDataSource;
24 import org.junit.After;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 import org.kuali.rice.core.api.config.property.ConfigContext;
29 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
30 import org.kuali.rice.core.api.util.RiceConstants;
31 import org.kuali.rice.core.framework.config.property.SimpleConfig;
32 import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
33 import org.kuali.rice.core.framework.resourceloader.BaseResourceLoader;
34 import org.kuali.rice.core.framework.resourceloader.SimpleServiceLocator;
35 import org.mockito.Mock;
36 import org.mockito.runners.MockitoJUnitRunner;
37 import org.springframework.mock.web.MockHttpServletRequest;
38 import org.springframework.mock.web.MockHttpServletResponse;
39
40 import javax.sql.DataSource;
41 import javax.xml.namespace.QName;
42 import java.sql.Connection;
43 import java.sql.SQLException;
44 import java.sql.Statement;
45 import java.util.*;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48
49 import static org.junit.Assert.*;
50 import static org.mockito.Mockito.mock;
51 import static org.mockito.Mockito.when;
52
53
54
55
56
57
58 @RunWith(MockitoJUnitRunner.class)
59 public class HealthServletTest {
60
61 @Mock
62 private StandardXAPoolDataSource primaryDataSource;
63 @Mock
64 private BasicDataSource nonTransactionalDataSource;
65 @Mock
66 private PoolingDataSource serverDataSource;
67 @Mock
68 private DatabasePlatform databasePlatform;
69
70 private SimpleConfig config;
71 private SimpleServiceLocator serviceLocator;
72 private HealthServlet healthServlet;
73
74 @Before
75 public void setUp() throws Exception {
76 this.config = new SimpleConfig();
77 this.config.putProperty("application.id", HealthServletTest.class.getName());
78 this.config.putObject(RiceConstants.DATASOURCE_OBJ, primaryDataSource);
79 this.config.putObject(RiceConstants.NON_TRANSACTIONAL_DATASOURCE_OBJ, nonTransactionalDataSource);
80 this.config.putObject(RiceConstants.SERVER_DATASOURCE_OBJ, serverDataSource);
81 ConfigContext.init(this.config);
82 stubDataSource(primaryDataSource);
83 stubDataSource(nonTransactionalDataSource);
84 stubDataSource(serverDataSource);
85
86 this.serviceLocator = new SimpleServiceLocator();
87 this.serviceLocator.addService(new QName(RiceConstants.DB_PLATFORM), databasePlatform);
88 GlobalResourceLoader.addResourceLoaderFirst(new BaseResourceLoader(new QName(HealthServletTest.class.getName()), this.serviceLocator));
89 GlobalResourceLoader.start();
90
91 stubDatabasePlatform(databasePlatform);
92
93 this.healthServlet = new HealthServlet();
94 }
95
96 private void stubDataSource(DataSource dataSource) throws SQLException {
97 Connection connection = mock(Connection.class);
98 when(dataSource.getConnection()).thenReturn(connection);
99 Statement statement = mock(Statement.class);
100 when(connection.createStatement()).thenReturn(statement);
101
102 if (dataSource instanceof StandardXAPoolDataSource) {
103 StandardXAPoolDataSource ds = (StandardXAPoolDataSource)dataSource;
104 when(ds.getLockedObjectCount()).thenReturn(10);
105 when(ds.getMinSize()).thenReturn(5);
106 when(ds.getMaxSize()).thenReturn(20);
107 } else if (dataSource instanceof PoolingDataSource) {
108 PoolingDataSource ds = (PoolingDataSource)dataSource;
109 when(ds.getTotalPoolSize()).thenReturn(15L);
110 when(ds.getInPoolSize()).thenReturn(5L);
111 when(ds.getMinPoolSize()).thenReturn(5);
112 when(ds.getMaxPoolSize()).thenReturn(20);
113 } else if (dataSource instanceof BasicDataSource) {
114 BasicDataSource ds = (BasicDataSource)dataSource;
115 when(ds.getNumActive()).thenReturn(10);
116 when(ds.getMinIdle()).thenReturn(5);
117 when(ds.getMaxActive()).thenReturn(20);
118 } else {
119 fail("Invalid datasource class: " + dataSource.getClass());
120 }
121
122 }
123
124 private void stubDatabasePlatform(DatabasePlatform platform) {
125 when(platform.getValidationQuery()).thenReturn("select 1");
126 assertEquals("select 1", platform.getValidationQuery());
127 }
128
129 @After
130 public void tearDown() throws Exception {
131 ConfigContext.init(new SimpleConfig());
132 GlobalResourceLoader.stop();
133 }
134
135 @Test
136 public void testService_No_Details_Ok() throws Exception {
137 healthServlet.init();
138 MockHttpServletRequest request = new MockHttpServletRequest();
139 request.setRequestURI("http://localhost:8080/rice-standalone/health");
140 request.setMethod("GET");
141 MockHttpServletResponse response = new MockHttpServletResponse();
142 healthServlet.service(request, response);
143 assertEquals("Response code should be 204", 204, response.getStatus());
144 String content = response.getContentAsString();
145 assertTrue("Content should be empty", content.isEmpty());
146 }
147
148 @Test
149 public void testService_No_Details_Failed() throws Exception {
150
151 this.config.putProperty("rice.health.memory.total.usageThreshold", "0.0");
152
153 healthServlet.init();
154 MockHttpServletRequest request = new MockHttpServletRequest();
155 request.setRequestURI("http://localhost:8080/rice-standalone/health");
156 request.setMethod("GET");
157 MockHttpServletResponse response = new MockHttpServletResponse();
158 healthServlet.service(request, response);
159 assertEquals("Response code should be 503", 503, response.getStatus());
160 String content = response.getContentAsString();
161 assertTrue("Content should be empty", content.isEmpty());
162 }
163
164 @Test
165 public void testService_Details_Ok() throws Exception {
166
167 ConfigContext.init(this.config);
168
169 MockHttpServletResponse response = initAndExecuteDetailedCheck(healthServlet);
170 assertEquals("Response code should be 200", 200, response.getStatus());
171 JsonNode root = parseContent(response.getContentAsString());
172 assertEquals("Ok", root.get("Status").asText());
173 assertFalse(root.has("Message"));
174 Map<String, String> metricMap = loadMetricMap(root);
175
176
177 assertEquals("true", metricMap.get("database.primary:connected"));
178 assertEquals("true", metricMap.get("database.non-transactional:connected"));
179 assertEquals("true", metricMap.get("database.server:connected"));
180
181
182 assertEquals("20", metricMap.get("database.primary:pool.max"));
183 assertEquals("20", metricMap.get("database.non-transactional:pool.max"));
184 assertEquals("20", metricMap.get("database.server:pool.max"));
185
186
187
188
189
190 assertTrue("At least one metric name should start with 'buffer-pool:'", containsKeyStartsWith("buffer-pool:", metricMap));
191
192
193 String classloaderLoadedValue = metricMap.get("classloader:loaded");
194 assertNotNull(classloaderLoadedValue);
195 assertTrue(Long.parseLong(classloaderLoadedValue) > 0);
196
197
198 String fileDescriptorUsageValue = metricMap.get("file-descriptor:usage");
199 assertNotNull(fileDescriptorUsageValue);
200 double fileDescriptorUsage = Double.parseDouble(fileDescriptorUsageValue);
201 assertTrue(fileDescriptorUsage > 0);
202 assertTrue(fileDescriptorUsage < 1);
203
204
205
206
207
208 assertTrue("At least one metric name should start with 'garbage-collector:'", containsKeyStartsWith("garbage-collector:", metricMap));
209
210
211 String totalMemoryUsageValue = metricMap.get("memory:total.usage");
212 assertNotNull(totalMemoryUsageValue);
213 double totalMemoryUsage = Double.parseDouble(totalMemoryUsageValue);
214 assertTrue(totalMemoryUsage > 0);
215 assertTrue(totalMemoryUsage < 1);
216
217
218 String uptimeValue = metricMap.get("runtime:uptime");
219 assertNotNull(uptimeValue);
220 assertTrue(Long.parseLong(uptimeValue) > 0);
221
222
223 String deadlockCountValue = metricMap.get("thread:deadlock.count");
224 assertNotNull(deadlockCountValue);
225 assertEquals(0, Integer.parseInt(deadlockCountValue));
226
227 }
228
229 @Test
230 public void testService_Details_Failed_HeapMemoryThreshold() throws Exception {
231
232 this.config.putProperty(HealthServlet.Config.HEAP_MEMORY_THRESHOLD_PROPERTY, "0");
233 ConfigContext.init(this.config);
234
235 MockHttpServletResponse response = initAndExecuteDetailedCheck(healthServlet);
236 assertEquals("Response code should be 503", 503, response.getStatus());
237 JsonNode root = parseContent(response.getContentAsString());
238 assertEquals("Failed", root.get("Status").asText());
239 assertTrue(root.has("Message"));
240 }
241
242
243
244
245 @Test
246 public void testService_Details_Failed_TotalMemoryThreshold() throws Exception {
247
248 this.config.putProperty(HealthServlet.Config.TOTAL_MEMORY_THRESHOLD_PROPERTY, "0");
249 ConfigContext.init(this.config);
250 assertFailedResponse(healthServlet);
251 }
252
253 @Test
254 public void testService_Details_Failed_DeadlockThreshold() throws Exception {
255
256 this.config.putProperty(HealthServlet.Config.DEADLOCK_THRESHOLD_PROPERTY, "0");
257 ConfigContext.init(this.config);
258 assertFailedResponse(healthServlet);
259 }
260
261 @Test
262 public void testService_Details_Failed_FileDescriptorThreshold() throws Exception {
263
264 this.config.putProperty(HealthServlet.Config.FILE_DESCRIPTOR_THRESHOLD_PROPERTY, "0");
265 ConfigContext.init(this.config);
266 assertFailedResponse(healthServlet);
267 }
268
269 @Test
270 public void testService_Details_Failed_PrimaryPoolUsageThreshold() throws Exception {
271
272 this.config.putProperty(HealthServlet.Config.PRIMARY_POOL_USAGE_THRESHOLD_PROPERTY, "0");
273 ConfigContext.init(this.config);
274 assertFailedResponse(healthServlet);
275 }
276
277 @Test
278 public void testService_Details_Failed_NonTransactionalPoolUsageThreshold() throws Exception {
279
280 this.config.putProperty(HealthServlet.Config.NON_TRANSACTIONAL_POOL_USAGE_THRESHOLD_PROPERTY, "0");
281 ConfigContext.init(this.config);
282 assertFailedResponse(healthServlet);
283 }
284
285 @Test
286 public void testService_Details_Failed_ServerPoolUsageThreshold() throws Exception {
287
288 this.config.putProperty(HealthServlet.Config.SERVER_POOL_USAGE_THRESHOLD_PROPERTY, "0");
289 ConfigContext.init(this.config);
290 assertFailedResponse(healthServlet);
291 }
292
293 @Test
294 public void testService_Details_Multiple_Failures() throws Exception {
295
296 this.config.putProperty(HealthServlet.Config.PRIMARY_POOL_USAGE_THRESHOLD_PROPERTY, "0");
297 this.config.putProperty(HealthServlet.Config.NON_TRANSACTIONAL_POOL_USAGE_THRESHOLD_PROPERTY, "0");
298 this.config.putProperty(HealthServlet.Config.SERVER_POOL_USAGE_THRESHOLD_PROPERTY, "0");
299 ConfigContext.init(this.config);
300
301 MockHttpServletResponse response = initAndExecuteDetailedCheck(healthServlet);
302 assertEquals("Response code should be 503", 503, response.getStatus());
303 JsonNode root = parseContent(response.getContentAsString());
304 assertEquals("Failed", root.get("Status").asText());
305 assertTrue(root.has("Message"));
306 String message = root.get("Message").asText();
307 assertFalse(StringUtils.isBlank(message));
308
309 Pattern pattern = Pattern.compile("\\* database\\.primary:pool\\.usage -> .+");
310 Matcher matcher = pattern.matcher(message);
311 assertTrue(matcher.find());
312
313 pattern = Pattern.compile("\\* database\\.non-transactional:pool\\.usage -> .+");
314 matcher = pattern.matcher(message);
315 assertTrue(matcher.find());
316
317 pattern = Pattern.compile("\\* database\\.server:pool\\.usage -> .+");
318 matcher = pattern.matcher(message);
319 assertTrue(matcher.find());
320
321 pattern = Pattern.compile("\\* ");
322 matcher = pattern.matcher(message);
323
324 assertTrue(matcher.find());
325 assertTrue(matcher.find());
326 assertTrue(matcher.find());
327
328 assertFalse(matcher.find());
329 }
330
331 private MockHttpServletResponse initAndExecuteDetailedCheck(HealthServlet healthServlet) throws Exception {
332 healthServlet.init();
333 MockHttpServletRequest request = new MockHttpServletRequest();
334 request.setRequestURI("http://localhost:8080/rice-standalone/health");
335 request.setMethod("GET");
336 request.setParameter("detail", "true");
337 MockHttpServletResponse response = new MockHttpServletResponse();
338 healthServlet.service(request, response);
339 String content = response.getContentAsString();
340 assertEquals("application/json", response.getContentType());
341 assertFalse(content.isEmpty());
342 return response;
343 }
344
345 private JsonNode parseContent(String content) throws Exception {
346 ObjectMapper mapper = new ObjectMapper();
347 return mapper.readTree(content);
348
349 }
350
351 private Map<String, String> loadMetricMap(JsonNode root) {
352 Map<String, String> metricMap = new HashMap<>();
353 Iterator<JsonNode> metricsIt = root.get("Metrics").getElements();
354 while (metricsIt.hasNext()) {
355 JsonNode metricNode = metricsIt.next();
356 String measure = metricNode.get("Measure").asText();
357 String metric = metricNode.get("Metric").asText();
358 String value = metricNode.get("Value").asText();
359 metricMap.put(measure + ":" + metric, value);
360 }
361 return metricMap;
362 }
363
364 private void assertFailedResponse(HealthServlet healthServlet) throws Exception {
365 MockHttpServletResponse response = initAndExecuteDetailedCheck(healthServlet);
366 assertEquals("Response code should be 503", 503, response.getStatus());
367 JsonNode root = parseContent(response.getContentAsString());
368 assertEquals("Failed", root.get("Status").asText());
369 assertTrue(root.has("Message"));
370 assertFalse(StringUtils.isBlank(root.get("Message").asText()));
371 }
372
373 private boolean containsKeyStartsWith(String keyPrefix, Map<String, String> map) {
374 for (String name : map.keySet()) {
375 if (name.startsWith(keyPrefix)) {
376 return true;
377 }
378 }
379 return false;
380 }
381
382 }