1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.http.log;
19
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotEquals;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25
26 import com.google.common.base.Joiner;
27 import java.io.File;
28 import java.net.BindException;
29 import java.net.SocketException;
30 import java.net.URI;
31 import java.security.PrivilegedExceptionAction;
32 import java.util.Properties;
33 import javax.net.ssl.SSLException;
34
35 import org.apache.commons.io.FileUtils;
36 import org.apache.hadoop.HadoopIllegalArgumentException;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.fs.CommonConfigurationKeys;
39 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
40 import org.apache.hadoop.fs.FileUtil;
41 import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
42 import org.apache.hadoop.hbase.http.HttpConfig;
43 import org.apache.hadoop.hbase.http.HttpServer;
44 import org.apache.hadoop.hbase.http.log.LogLevel.CLI;
45 import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil;
46 import org.apache.hadoop.hbase.testclassification.MiscTests;
47 import org.apache.hadoop.hbase.testclassification.SmallTests;
48 import org.apache.hadoop.hdfs.DFSConfigKeys;
49 import org.apache.hadoop.minikdc.MiniKdc;
50 import org.apache.hadoop.net.NetUtils;
51 import org.apache.hadoop.security.UserGroupInformation;
52 import org.apache.hadoop.security.authorize.AccessControlList;
53 import org.apache.hadoop.security.ssl.SSLFactory;
54 import org.apache.hadoop.test.GenericTestUtils;
55 import org.apache.hadoop.util.StringUtils;
56 import org.apache.log4j.Level;
57 import org.apache.log4j.LogManager;
58 import org.apache.log4j.Logger;
59
60 import org.junit.AfterClass;
61 import org.junit.BeforeClass;
62 import org.junit.Test;
63 import org.junit.experimental.categories.Category;
64
65
66
67
68 @Category({MiscTests.class, SmallTests.class})
69 public class TestLogLevel {
70 private static File BASEDIR;
71 private static String keystoresDir;
72 private static String sslConfDir;
73 private static Configuration serverConf;
74 private static Configuration clientConf;
75 private static Configuration sslConf;
76 private static final String logName = TestLogLevel.class.getName();
77 private static final Logger log = LogManager.getLogger(logName);
78 private final static String PRINCIPAL = "loglevel.principal";
79 private final static String KEYTAB = "loglevel.keytab";
80
81 private static MiniKdc kdc;
82 private static HBaseCommonTestingUtility htu = new HBaseCommonTestingUtility();
83
84 private static final String LOCALHOST = "localhost";
85 private static final String clientPrincipal = "client/" + LOCALHOST;
86 private static String HTTP_PRINCIPAL = "HTTP/" + LOCALHOST;
87
88 private static final File KEYTAB_FILE = new File(
89 htu.getDataTestDir("keytab").toUri().getPath());
90
91 @BeforeClass
92 public static void setUp() throws Exception {
93 BASEDIR = new File(htu.getDataTestDir().toUri().getPath());
94
95 FileUtil.fullyDelete(BASEDIR);
96 if (!BASEDIR.mkdirs()) {
97 throw new Exception("unable to create the base directory for testing");
98 }
99 serverConf = new Configuration();
100 clientConf = new Configuration();
101
102 setupSSL(BASEDIR);
103
104 kdc = setupMiniKdc();
105
106 kdc.createPrincipal(KEYTAB_FILE, clientPrincipal, HTTP_PRINCIPAL);
107 }
108
109
110
111
112
113 static private MiniKdc setupMiniKdc() throws Exception {
114 Properties conf = MiniKdc.createConf();
115 conf.put(MiniKdc.DEBUG, true);
116 MiniKdc kdc = null;
117 File dir = null;
118
119
120 boolean bindException;
121 int numTries = 0;
122 do {
123 try {
124 bindException = false;
125 dir = new File(htu.getDataTestDir("kdc").toUri().getPath());
126 kdc = new MiniKdc(conf, dir);
127 kdc.start();
128 } catch (BindException e) {
129 FileUtils.deleteDirectory(dir);
130 numTries++;
131 if (numTries == 3) {
132 log.error("Failed setting up MiniKDC. Tried " + numTries + " times.");
133 throw e;
134 }
135 log.error("BindException encountered when setting up MiniKdc. Trying again.");
136 bindException = true;
137 }
138 } while (bindException);
139 return kdc;
140 }
141
142 static private void setupSSL(File base) throws Exception {
143 Configuration conf = new Configuration();
144 conf.set(DFSConfigKeys.DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTPS_ONLY.name());
145 conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0");
146 conf.set(DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0");
147
148 keystoresDir = base.getAbsolutePath();
149 sslConfDir = KeyStoreTestUtil.getClasspathDir(TestLogLevel.class);
150 KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false);
151
152 sslConf = getSslConfig();
153 }
154
155
156
157
158
159
160 private static Configuration getSslConfig() {
161 Configuration sslConf = new Configuration(false);
162 String sslServerConfFile = "ssl-server.xml";
163 String sslClientConfFile = "ssl-client.xml";
164 sslConf.addResource(sslServerConfFile);
165 sslConf.addResource(sslClientConfFile);
166 sslConf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile);
167 sslConf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile);
168 return sslConf;
169 }
170
171 @AfterClass
172 public static void tearDown() {
173 if (kdc != null) {
174 kdc.stop();
175 }
176
177 FileUtil.fullyDelete(BASEDIR);
178 }
179
180
181
182
183
184 @Test(timeout=120000)
185 public void testCommandOptions() throws Exception {
186 final String className = this.getClass().getName();
187
188 assertFalse(validateCommand(new String[] {"-foo" }));
189
190 assertFalse(validateCommand(new String[] {}));
191 assertFalse(validateCommand(new String[] {"-getlevel" }));
192 assertFalse(validateCommand(new String[] {"-setlevel" }));
193 assertFalse(validateCommand(new String[] {"-getlevel", "foo.bar:8080" }));
194
195
196 assertTrue(validateCommand(
197 new String[] {"-getlevel", "foo.bar:8080", className }));
198 assertTrue(validateCommand(
199 new String[] {"-setlevel", "foo.bar:8080", className, "DEBUG" }));
200 assertTrue(validateCommand(
201 new String[] {"-getlevel", "foo.bar:8080", className }));
202 assertTrue(validateCommand(
203 new String[] {"-setlevel", "foo.bar:8080", className, "DEBUG" }));
204
205
206 assertFalse(validateCommand(
207 new String[] {"-getlevel", "foo.bar:8080", className, "blah" }));
208 assertFalse(validateCommand(
209 new String[] {"-setlevel", "foo.bar:8080", className, "DEBUG", "blah" }));
210 assertFalse(validateCommand(
211 new String[] {
212 "-getlevel", "foo.bar:8080", className, "-setlevel", "foo.bar:8080", className }));
213 }
214
215
216
217
218
219
220
221 private boolean validateCommand(String[] args) {
222 CLI cli = new CLI(clientConf);
223 try {
224 cli.parseArguments(args);
225 } catch (HadoopIllegalArgumentException e) {
226 return false;
227 } catch (Exception e) {
228
229
230 return true;
231 }
232 return true;
233 }
234
235
236
237
238
239
240
241
242
243 private HttpServer createServer(String protocol, boolean isSpnego)
244 throws Exception {
245 HttpServer.Builder builder = new HttpServer.Builder()
246 .setName("..")
247 .addEndpoint(new URI(protocol + "://localhost:0"))
248 .setFindPort(true)
249 .setConf(serverConf);
250 if (isSpnego) {
251
252
253
254 builder.setSecurityEnabled(true)
255 .setUsernameConfKey(PRINCIPAL)
256 .setKeytabConfKey(KEYTAB)
257 .setACL(new AccessControlList("client"));
258 }
259
260
261 if (protocol.equals(LogLevel.PROTOCOL_HTTPS)) {
262 builder = builder.keyPassword(sslConf.get("ssl.server.keystore.keypassword"))
263 .keyStore(sslConf.get("ssl.server.keystore.location"),
264 sslConf.get("ssl.server.keystore.password"),
265 sslConf.get("ssl.server.keystore.type", "jks"))
266 .trustStore(sslConf.get("ssl.server.truststore.location"),
267 sslConf.get("ssl.server.truststore.password"),
268 sslConf.get("ssl.server.truststore.type", "jks"));
269 }
270
271 HttpServer server = builder.build();
272 server.start();
273 return server;
274 }
275
276 private void testDynamicLogLevel(final String bindProtocol, final String connectProtocol,
277 final boolean isSpnego) throws Exception {
278 testDynamicLogLevel(bindProtocol, connectProtocol, isSpnego, Level.DEBUG.toString());
279 }
280
281
282
283
284
285
286
287
288
289 private void testDynamicLogLevel(final String bindProtocol, final String connectProtocol,
290 final boolean isSpnego, final String newLevel) throws Exception {
291 if (!LogLevel.isValidProtocol(bindProtocol)) {
292 throw new Exception("Invalid server protocol " + bindProtocol);
293 }
294 if (!LogLevel.isValidProtocol(connectProtocol)) {
295 throw new Exception("Invalid client protocol " + connectProtocol);
296 }
297 Level oldLevel = log.getEffectiveLevel();
298 assertNotEquals("Get default Log Level which shouldn't be ERROR.",
299 Level.ERROR, oldLevel);
300
301
302 if (isSpnego) {
303 serverConf.set(PRINCIPAL, HTTP_PRINCIPAL);
304 serverConf.set(KEYTAB, KEYTAB_FILE.getAbsolutePath());
305 serverConf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
306 serverConf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true);
307 UserGroupInformation.setConfiguration(serverConf);
308 } else {
309 serverConf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "simple");
310 serverConf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false);
311 UserGroupInformation.setConfiguration(serverConf);
312 }
313
314 final HttpServer server = createServer(bindProtocol, isSpnego);
315
316 final String authority = NetUtils.getHostPortString(server.getConnectorAddress(0));
317
318 String keytabFilePath = KEYTAB_FILE.getAbsolutePath();
319
320 UserGroupInformation clientUGI = UserGroupInformation.
321 loginUserFromKeytabAndReturnUGI(clientPrincipal, keytabFilePath);
322 try {
323 clientUGI.doAs(new PrivilegedExceptionAction<Void>() {
324 @Override public Void run() throws Exception {
325
326 getLevel(connectProtocol, authority);
327 setLevel(connectProtocol, authority, newLevel);
328 return null;
329 }
330 });
331 } finally {
332 server.stop();
333 }
334
335
336 GenericTestUtils.setLogLevel(log, oldLevel);
337 }
338
339
340
341
342
343
344
345
346
347 private void getLevel(String protocol, String authority) throws Exception {
348 String[] getLevelArgs = {"-getlevel", authority, logName, "-protocol", protocol};
349 CLI cli = new CLI(clientConf);
350 cli.run(getLevelArgs);
351 }
352
353
354
355
356
357
358
359
360
361 private void setLevel(String protocol, String authority, String newLevel)
362 throws Exception {
363 String[] setLevelArgs = {"-setlevel", authority, logName, newLevel, "-protocol", protocol};
364 CLI cli = new CLI(clientConf);
365 cli.run(setLevelArgs);
366
367 assertEquals("new level not equal to expected: ", newLevel.toUpperCase(),
368 log.getEffectiveLevel().toString());
369 }
370
371
372
373
374
375
376 @Test(timeout=60000)
377 public void testInfoLogLevel() throws Exception {
378 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTP, true, "INFO");
379 }
380
381
382
383
384
385
386 @Test(timeout=60000)
387 public void testErrorLogLevel() throws Exception {
388 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTP, true, "ERROR");
389 }
390
391
392
393
394
395
396
397 @Test(timeout=60000)
398 public void testLogLevelByHttp() throws Exception {
399 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTP, false);
400 try {
401 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTPS, false);
402 fail("A HTTPS Client should not have succeeded in connecting to a HTTP server");
403 } catch (SSLException e) {
404 exceptionShouldContains(e, "Unrecognized SSL message");
405 }
406 }
407
408
409
410
411
412
413
414 @Test(timeout=60000)
415 public void testLogLevelByHttpWithSpnego() throws Exception {
416 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTP, true);
417 try {
418 testDynamicLogLevel(LogLevel.PROTOCOL_HTTP, LogLevel.PROTOCOL_HTTPS, true);
419 fail("A HTTPS Client should not have succeeded in connecting to a HTTP server");
420 } catch (SSLException e) {
421 exceptionShouldContains(e, "Unrecognized SSL message");
422 }
423 }
424
425
426
427
428
429
430
431 @Test(timeout=60000)
432 public void testLogLevelByHttps() throws Exception {
433 testDynamicLogLevel(LogLevel.PROTOCOL_HTTPS, LogLevel.PROTOCOL_HTTPS, false);
434 try {
435 testDynamicLogLevel(LogLevel.PROTOCOL_HTTPS, LogLevel.PROTOCOL_HTTP, false);
436 fail("A HTTP Client should not have succeeded in connecting to a HTTPS server");
437 } catch (SocketException e) {
438
439
440
441 exceptionShouldContains(e, "Unexpected end of file from server", "Connection reset");
442 }
443 }
444
445
446
447
448
449
450
451 @Test(timeout=60000)
452 public void testLogLevelByHttpsWithSpnego() throws Exception {
453 testDynamicLogLevel(LogLevel.PROTOCOL_HTTPS, LogLevel.PROTOCOL_HTTPS, true);
454 try {
455 testDynamicLogLevel(LogLevel.PROTOCOL_HTTPS, LogLevel.PROTOCOL_HTTP,
456 true);
457 fail("A HTTP Client should not have succeeded in connecting to a " +
458 "HTTPS server");
459 } catch (SocketException e) {
460 exceptionShouldContains(e, "Unexpected end of file from server", "Connection reset");
461 }
462 }
463
464
465
466
467
468
469
470
471
472
473 private static void exceptionShouldContains(Throwable throwable, String... substr) {
474 for (String s: substr) {
475 Throwable t = throwable;
476 while (t != null) {
477 String msg = t.toString();
478 if (msg != null && msg.toLowerCase().contains(s.toLowerCase())) {
479 return;
480 }
481 t = t.getCause();
482 }
483 }
484 String debug = "[" + Joiner.on(" , ").join(substr) + "]";
485 throw new AssertionError("Expected to find any of " + debug + " but got unexpected"
486 + " exception:" + StringUtils.stringifyException(throwable), throwable);
487 }
488 }