1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package org.apache.hadoop.yarn.server.resourcemanager.webapp; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assume.assumeTrue; 24 25 import java.io.*; 26 import java.net.URI; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collection; 30 import java.util.Enumeration; 31 import java.util.HashMap; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Properties; 36 import java.util.Set; 37 38 import javax.servlet.FilterConfig; 39 import javax.servlet.ServletException; 40 import javax.ws.rs.core.HttpHeaders; 41 import javax.ws.rs.core.MediaType; 42 import javax.xml.parsers.DocumentBuilder; 43 import javax.xml.parsers.DocumentBuilderFactory; 44 import javax.xml.parsers.ParserConfigurationException; 45 46 import com.sun.jersey.api.client.config.DefaultClientConfig; 47 import org.apache.commons.codec.binary.Base64; 48 import org.apache.hadoop.conf.Configuration; 49 import org.apache.hadoop.io.Text; 50 import org.apache.hadoop.security.Credentials; 51 import org.apache.hadoop.security.authentication.server.AuthenticationFilter; 52 import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler; 53 import org.apache.hadoop.yarn.api.records.ApplicationAccessType; 54 import org.apache.hadoop.yarn.api.records.ContainerLaunchContext; 55 import org.apache.hadoop.yarn.api.records.LocalResource; 56 import org.apache.hadoop.yarn.api.records.LocalResourceType; 57 import org.apache.hadoop.yarn.api.records.LocalResourceVisibility; 58 import org.apache.hadoop.yarn.api.records.QueueACL; 59 import org.apache.hadoop.yarn.api.records.YarnApplicationState; 60 import org.apache.hadoop.yarn.conf.YarnConfiguration; 61 import org.apache.hadoop.yarn.server.resourcemanager.MockNM; 62 import org.apache.hadoop.yarn.server.resourcemanager.MockRM; 63 import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; 64 import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; 65 import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState; 66 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; 67 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration; 68 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; 69 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairSchedulerConfiguration; 70 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppState; 71 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ApplicationSubmissionContextInfo; 72 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CredentialsInfo; 73 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.LocalResourceInfo; 74 import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.*; 75 import org.apache.hadoop.yarn.util.ConverterUtils; 76 import org.apache.hadoop.yarn.webapp.GenericExceptionHandler; 77 import org.apache.hadoop.yarn.webapp.JerseyTestBase; 78 import org.apache.hadoop.yarn.webapp.WebServicesTestUtils; 79 import org.codehaus.jettison.json.JSONException; 80 import org.codehaus.jettison.json.JSONObject; 81 import org.junit.After; 82 import org.junit.Assert; 83 import org.junit.Before; 84 import org.junit.Test; 85 import org.junit.runner.RunWith; 86 import org.junit.runners.Parameterized; 87 import org.junit.runners.Parameterized.Parameters; 88 import org.w3c.dom.Document; 89 import org.w3c.dom.Element; 90 import org.w3c.dom.NodeList; 91 import org.xml.sax.InputSource; 92 import org.xml.sax.SAXException; 93 94 import com.google.inject.Guice; 95 import com.google.inject.Injector; 96 import com.google.inject.Singleton; 97 import com.google.inject.servlet.GuiceServletContextListener; 98 import com.google.inject.servlet.ServletModule; 99 import com.sun.jersey.api.client.Client; 100 import com.sun.jersey.api.client.ClientResponse; 101 import com.sun.jersey.api.client.ClientResponse.Status; 102 import com.sun.jersey.api.client.WebResource; 103 import com.sun.jersey.api.client.filter.LoggingFilter; 104 import com.sun.jersey.api.json.JSONJAXBContext; 105 import com.sun.jersey.api.json.JSONMarshaller; 106 import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; 107 import com.sun.jersey.test.framework.WebAppDescriptor; 108 109 @RunWith(Parameterized.class) 110 public class TestRMWebServicesAppsModification extends JerseyTestBase { 111 private static MockRM rm; 112 113 private static final int CONTAINER_MB = 1024; 114 115 private static Injector injector; 116 private String webserviceUserName = "testuser"; 117 118 private boolean setAuthFilter = false; 119 120 private static final String TEST_DIR = new File(System.getProperty( 121 "test.build.data", "/tmp")).getAbsolutePath(); 122 private static final String FS_ALLOC_FILE = new File(TEST_DIR, 123 "test-fs-queues.xml").getAbsolutePath(); 124 125 public static class GuiceServletConfig extends GuiceServletContextListener { 126 127 @Override getInjector()128 protected Injector getInjector() { 129 return injector; 130 } 131 } 132 133 /* 134 * Helper class to allow testing of RM web services which require 135 * authorization Add this class as a filter in the Guice injector for the 136 * MockRM 137 */ 138 139 @Singleton 140 public static class TestRMCustomAuthFilter extends AuthenticationFilter { 141 142 @Override getConfiguration(String configPrefix, FilterConfig filterConfig)143 protected Properties getConfiguration(String configPrefix, 144 FilterConfig filterConfig) throws ServletException { 145 Properties props = new Properties(); 146 Enumeration<?> names = filterConfig.getInitParameterNames(); 147 while (names.hasMoreElements()) { 148 String name = (String) names.nextElement(); 149 if (name.startsWith(configPrefix)) { 150 String value = filterConfig.getInitParameter(name); 151 props.put(name.substring(configPrefix.length()), value); 152 } 153 } 154 props.put(AuthenticationFilter.AUTH_TYPE, "simple"); 155 props.put(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "false"); 156 return props; 157 } 158 159 } 160 161 private abstract class TestServletModule extends ServletModule { 162 public Configuration conf = new Configuration(); 163 configureScheduler()164 public abstract void configureScheduler(); 165 166 @Override configureServlets()167 protected void configureServlets() { 168 configureScheduler(); 169 bind(JAXBContextResolver.class); 170 bind(RMWebServices.class); 171 bind(GenericExceptionHandler.class); 172 conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, 173 YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS); 174 rm = new MockRM(conf); 175 bind(ResourceManager.class).toInstance(rm); 176 if (setAuthFilter) { 177 filter("/*").through(TestRMCustomAuthFilter.class); 178 } 179 serve("/*").with(GuiceContainer.class); 180 } 181 } 182 183 private class CapTestServletModule extends TestServletModule { 184 @Override configureScheduler()185 public void configureScheduler() { 186 conf.set("yarn.resourcemanager.scheduler.class", 187 CapacityScheduler.class.getName()); 188 } 189 } 190 191 private class FairTestServletModule extends TestServletModule { 192 @Override configureScheduler()193 public void configureScheduler() { 194 try { 195 PrintWriter out = new PrintWriter(new FileWriter(FS_ALLOC_FILE)); 196 out.println("<?xml version=\"1.0\"?>"); 197 out.println("<allocations>"); 198 out.println("<queue name=\"root\">"); 199 out.println(" <aclAdministerApps>someuser </aclAdministerApps>"); 200 out.println(" <queue name=\"default\">"); 201 out.println(" <aclAdministerApps>someuser </aclAdministerApps>"); 202 out.println(" </queue>"); 203 out.println(" <queue name=\"test\">"); 204 out.println(" <aclAdministerApps>someuser </aclAdministerApps>"); 205 out.println(" </queue>"); 206 out.println("</queue>"); 207 out.println("</allocations>"); 208 out.close(); 209 } catch(IOException e) { 210 } 211 conf.set(FairSchedulerConfiguration.ALLOCATION_FILE, FS_ALLOC_FILE); 212 conf.set("yarn.resourcemanager.scheduler.class", 213 FairScheduler.class.getName()); 214 } 215 } 216 getNoAuthInjectorCap()217 private Injector getNoAuthInjectorCap() { 218 return Guice.createInjector(new CapTestServletModule() { 219 @Override 220 protected void configureServlets() { 221 setAuthFilter = false; 222 super.configureServlets(); 223 } 224 }); 225 } 226 227 private Injector getSimpleAuthInjectorCap() { 228 return Guice.createInjector(new CapTestServletModule() { 229 @Override 230 protected void configureServlets() { 231 setAuthFilter = true; 232 conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); 233 // set the admin acls otherwise all users are considered admins 234 // and we can't test authorization 235 conf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "testuser1"); 236 super.configureServlets(); 237 } 238 }); 239 } 240 241 private Injector getNoAuthInjectorFair() { 242 return Guice.createInjector(new FairTestServletModule() { 243 @Override 244 protected void configureServlets() { 245 setAuthFilter = false; 246 super.configureServlets(); 247 } 248 }); 249 } 250 251 private Injector getSimpleAuthInjectorFair() { 252 return Guice.createInjector(new FairTestServletModule() { 253 @Override 254 protected void configureServlets() { 255 setAuthFilter = true; 256 conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); 257 // set the admin acls otherwise all users are considered admins 258 // and we can't test authorization 259 conf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "testuser1"); 260 super.configureServlets(); 261 } 262 }); 263 } 264 265 @Parameters 266 public static Collection<Object[]> guiceConfigs() { 267 return Arrays.asList(new Object[][] { { 0 }, { 1 }, { 2 }, { 3 } }); 268 } 269 270 @Before 271 @Override 272 public void setUp() throws Exception { 273 super.setUp(); 274 } 275 276 public TestRMWebServicesAppsModification(int run) { 277 super(new WebAppDescriptor.Builder( 278 "org.apache.hadoop.yarn.server.resourcemanager.webapp") 279 .contextListenerClass(GuiceServletConfig.class) 280 .filterClass(com.google.inject.servlet.GuiceFilter.class) 281 .clientConfig(new DefaultClientConfig(JAXBContextResolver.class)) 282 .contextPath("jersey-guice-filter").servletPath("/").build()); 283 switch (run) { 284 case 0: 285 default: 286 // No Auth Capacity Scheduler 287 injector = getNoAuthInjectorCap(); 288 break; 289 case 1: 290 // Simple Auth Capacity Scheduler 291 injector = getSimpleAuthInjectorCap(); 292 break; 293 case 2: 294 // No Auth Fair Scheduler 295 injector = getNoAuthInjectorFair(); 296 break; 297 case 3: 298 // Simple Auth Fair Scheduler 299 injector = getSimpleAuthInjectorFair(); 300 break; 301 } 302 } 303 304 private boolean isAuthenticationEnabled() { 305 return setAuthFilter; 306 } 307 308 private WebResource constructWebResource(WebResource r, String... paths) { 309 WebResource rt = r; 310 for (String path : paths) { 311 rt = rt.path(path); 312 } 313 if (isAuthenticationEnabled()) { 314 rt = rt.queryParam("user.name", webserviceUserName); 315 } 316 return rt; 317 } 318 319 private WebResource constructWebResource(String... paths) { 320 WebResource r = resource(); 321 WebResource ws = r.path("ws").path("v1").path("cluster"); 322 return this.constructWebResource(ws, paths); 323 } 324 325 @Test 326 public void testSingleAppState() throws Exception { 327 rm.start(); 328 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 329 String[] mediaTypes = 330 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 331 for (String mediaType : mediaTypes) { 332 RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); 333 amNodeManager.nodeHeartbeat(true); 334 ClientResponse response = 335 this 336 .constructWebResource("apps", app.getApplicationId().toString(), 337 "state").accept(mediaType).get(ClientResponse.class); 338 assertEquals(Status.OK, response.getClientResponseStatus()); 339 if (mediaType.equals(MediaType.APPLICATION_JSON)) { 340 verifyAppStateJson(response, RMAppState.ACCEPTED); 341 } else if (mediaType.equals(MediaType.APPLICATION_XML)) { 342 verifyAppStateXML(response, RMAppState.ACCEPTED); 343 } 344 } 345 rm.stop(); 346 } 347 348 @Test(timeout = 120000) 349 public void testSingleAppKill() throws Exception { 350 rm.start(); 351 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 352 String[] mediaTypes = 353 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 354 MediaType[] contentTypes = 355 { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE }; 356 for (String mediaType : mediaTypes) { 357 for (MediaType contentType : contentTypes) { 358 RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); 359 amNodeManager.nodeHeartbeat(true); 360 361 AppState targetState = 362 new AppState(YarnApplicationState.KILLED.toString()); 363 364 Object entity; 365 if (contentType.equals(MediaType.APPLICATION_JSON_TYPE)) { 366 entity = appStateToJSON(targetState); 367 } else { 368 entity = targetState; 369 } 370 ClientResponse response = 371 this 372 .constructWebResource("apps", app.getApplicationId().toString(), 373 "state").entity(entity, contentType).accept(mediaType) 374 .put(ClientResponse.class); 375 376 if (!isAuthenticationEnabled()) { 377 assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); 378 continue; 379 } 380 assertEquals(Status.ACCEPTED, response.getClientResponseStatus()); 381 if (mediaType.equals(MediaType.APPLICATION_JSON)) { 382 verifyAppStateJson(response, RMAppState.FINAL_SAVING, 383 RMAppState.KILLED, RMAppState.KILLING, RMAppState.ACCEPTED); 384 } else { 385 verifyAppStateXML(response, RMAppState.FINAL_SAVING, 386 RMAppState.KILLED, RMAppState.KILLING, RMAppState.ACCEPTED); 387 } 388 389 String locationHeaderValue = 390 response.getHeaders().getFirst(HttpHeaders.LOCATION); 391 Client c = Client.create(); 392 WebResource tmp = c.resource(locationHeaderValue); 393 if (isAuthenticationEnabled()) { 394 tmp = tmp.queryParam("user.name", webserviceUserName); 395 } 396 response = tmp.get(ClientResponse.class); 397 assertEquals(Status.OK, response.getClientResponseStatus()); 398 assertTrue(locationHeaderValue.endsWith("/ws/v1/cluster/apps/" 399 + app.getApplicationId().toString() + "/state")); 400 401 while (true) { 402 Thread.sleep(100); 403 response = 404 this 405 .constructWebResource("apps", 406 app.getApplicationId().toString(), "state").accept(mediaType) 407 .entity(entity, contentType).put(ClientResponse.class); 408 assertTrue((response.getClientResponseStatus() == Status.ACCEPTED) 409 || (response.getClientResponseStatus() == Status.OK)); 410 if (response.getClientResponseStatus() == Status.OK) { 411 assertEquals(RMAppState.KILLED, app.getState()); 412 if (mediaType.equals(MediaType.APPLICATION_JSON)) { 413 verifyAppStateJson(response, RMAppState.KILLED); 414 } else { 415 verifyAppStateXML(response, RMAppState.KILLED); 416 } 417 break; 418 } 419 } 420 } 421 } 422 423 rm.stop(); 424 } 425 426 @Test 427 public void testSingleAppKillInvalidState() throws Exception { 428 rm.start(); 429 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 430 431 String[] mediaTypes = 432 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 433 MediaType[] contentTypes = 434 { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE }; 435 String[] targetStates = 436 { YarnApplicationState.FINISHED.toString(), "blah" }; 437 438 for (String mediaType : mediaTypes) { 439 for (MediaType contentType : contentTypes) { 440 for (String targetStateString : targetStates) { 441 RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); 442 amNodeManager.nodeHeartbeat(true); 443 ClientResponse response; 444 AppState targetState = new AppState(targetStateString); 445 Object entity; 446 if (contentType.equals(MediaType.APPLICATION_JSON_TYPE)) { 447 entity = appStateToJSON(targetState); 448 } else { 449 entity = targetState; 450 } 451 response = 452 this 453 .constructWebResource("apps", 454 app.getApplicationId().toString(), "state") 455 .entity(entity, contentType).accept(mediaType) 456 .put(ClientResponse.class); 457 458 if (!isAuthenticationEnabled()) { 459 assertEquals(Status.UNAUTHORIZED, 460 response.getClientResponseStatus()); 461 continue; 462 } 463 assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); 464 } 465 } 466 } 467 468 rm.stop(); 469 } 470 471 private static String appStateToJSON(AppState state) throws Exception { 472 StringWriter sw = new StringWriter(); 473 JSONJAXBContext ctx = new JSONJAXBContext(AppState.class); 474 JSONMarshaller jm = ctx.createJSONMarshaller(); 475 jm.marshallToJSON(state, sw); 476 return sw.toString(); 477 } 478 479 protected static void verifyAppStateJson(ClientResponse response, 480 RMAppState... states) throws JSONException { 481 482 assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); 483 JSONObject json = response.getEntity(JSONObject.class); 484 assertEquals("incorrect number of elements", 1, json.length()); 485 String responseState = json.getString("state"); 486 boolean valid = false; 487 for (RMAppState state : states) { 488 if (state.toString().equals(responseState)) { 489 valid = true; 490 } 491 } 492 String msg = "app state incorrect, got " + responseState; 493 assertTrue(msg, valid); 494 } 495 496 protected static void verifyAppStateXML(ClientResponse response, 497 RMAppState... appStates) throws ParserConfigurationException, 498 IOException, SAXException { 499 assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); 500 String xml = response.getEntity(String.class); 501 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 502 DocumentBuilder db = dbf.newDocumentBuilder(); 503 InputSource is = new InputSource(); 504 is.setCharacterStream(new StringReader(xml)); 505 Document dom = db.parse(is); 506 NodeList nodes = dom.getElementsByTagName("appstate"); 507 assertEquals("incorrect number of elements", 1, nodes.getLength()); 508 Element element = (Element) nodes.item(0); 509 String state = WebServicesTestUtils.getXmlString(element, "state"); 510 boolean valid = false; 511 for (RMAppState appState : appStates) { 512 if (appState.toString().equals(state)) { 513 valid = true; 514 } 515 } 516 String msg = "app state incorrect, got " + state; 517 assertTrue(msg, valid); 518 } 519 520 @Test(timeout = 60000) 521 public void testSingleAppKillUnauthorized() throws Exception { 522 523 boolean isCapacityScheduler = 524 rm.getResourceScheduler() instanceof CapacityScheduler; 525 boolean isFairScheduler = 526 rm.getResourceScheduler() instanceof FairScheduler; 527 assumeTrue("This test is only supported on Capacity and Fair Scheduler", 528 isCapacityScheduler || isFairScheduler); 529 // FairScheduler use ALLOCATION_FILE to configure ACL 530 if (isCapacityScheduler) { 531 // default root queue allows anyone to have admin acl 532 CapacitySchedulerConfiguration csconf = 533 new CapacitySchedulerConfiguration(); 534 csconf.setAcl("root", QueueACL.ADMINISTER_QUEUE, "someuser"); 535 csconf.setAcl("root.default", QueueACL.ADMINISTER_QUEUE, "someuser"); 536 rm.getResourceScheduler().reinitialize(csconf, rm.getRMContext()); 537 } 538 539 rm.start(); 540 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 541 542 String[] mediaTypes = 543 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 544 for (String mediaType : mediaTypes) { 545 RMApp app = rm.submitApp(CONTAINER_MB, "test", "someuser"); 546 amNodeManager.nodeHeartbeat(true); 547 ClientResponse response = 548 this 549 .constructWebResource("apps", app.getApplicationId().toString(), 550 "state").accept(mediaType).get(ClientResponse.class); 551 AppState info = response.getEntity(AppState.class); 552 info.setState(YarnApplicationState.KILLED.toString()); 553 554 response = 555 this 556 .constructWebResource("apps", app.getApplicationId().toString(), 557 "state").accept(mediaType) 558 .entity(info, MediaType.APPLICATION_XML).put(ClientResponse.class); 559 validateResponseStatus(response, Status.FORBIDDEN); 560 } 561 rm.stop(); 562 } 563 564 @Test 565 public void testSingleAppKillInvalidId() throws Exception { 566 rm.start(); 567 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 568 amNodeManager.nodeHeartbeat(true); 569 String[] testAppIds = { "application_1391705042196_0001", "random_string" }; 570 for (String testAppId : testAppIds) { 571 AppState info = new AppState("KILLED"); 572 ClientResponse response = 573 this.constructWebResource("apps", testAppId, "state") 574 .accept(MediaType.APPLICATION_XML) 575 .entity(info, MediaType.APPLICATION_XML).put(ClientResponse.class); 576 if (!isAuthenticationEnabled()) { 577 assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); 578 continue; 579 } 580 assertEquals(Status.NOT_FOUND, response.getClientResponseStatus()); 581 } 582 rm.stop(); 583 } 584 585 @After 586 @Override 587 public void tearDown() throws Exception { 588 if (rm != null) { 589 rm.stop(); 590 } 591 super.tearDown(); 592 } 593 594 /** 595 * Helper function to wrap frequently used code. It checks the response status 596 * and checks if it UNAUTHORIZED if we are running with authorization turned 597 * off or the param passed if we are running with authorization turned on. 598 * 599 * @param response 600 * the ClientResponse object to be checked 601 * @param expectedAuthorizedMode 602 * the expected Status in authorized mode. 603 */ 604 public void validateResponseStatus(ClientResponse response, 605 Status expectedAuthorizedMode) { 606 validateResponseStatus(response, Status.UNAUTHORIZED, 607 expectedAuthorizedMode); 608 } 609 610 /** 611 * Helper function to wrap frequently used code. It checks the response status 612 * and checks if it is the param expectedUnauthorizedMode if we are running 613 * with authorization turned off or the param expectedAuthorizedMode passed if 614 * we are running with authorization turned on. 615 * 616 * @param response 617 * the ClientResponse object to be checked 618 * @param expectedUnauthorizedMode 619 * the expected Status in unauthorized mode. 620 * @param expectedAuthorizedMode 621 * the expected Status in authorized mode. 622 */ 623 public void validateResponseStatus(ClientResponse response, 624 Status expectedUnauthorizedMode, Status expectedAuthorizedMode) { 625 if (!isAuthenticationEnabled()) { 626 assertEquals(expectedUnauthorizedMode, response.getClientResponseStatus()); 627 } else { 628 assertEquals(expectedAuthorizedMode, response.getClientResponseStatus()); 629 } 630 } 631 632 // Simple test - just post to /apps/new-application and validate the response 633 @Test 634 public void testGetNewApplication() throws Exception { 635 client().addFilter(new LoggingFilter(System.out)); 636 rm.start(); 637 String mediaTypes[] = 638 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 639 for (String acceptMedia : mediaTypes) { 640 testGetNewApplication(acceptMedia); 641 } 642 rm.stop(); 643 } 644 645 protected String testGetNewApplication(String mediaType) throws JSONException, 646 ParserConfigurationException, IOException, SAXException { 647 ClientResponse response = 648 this.constructWebResource("apps", "new-application").accept(mediaType) 649 .post(ClientResponse.class); 650 validateResponseStatus(response, Status.OK); 651 if (!isAuthenticationEnabled()) { 652 return ""; 653 } 654 return validateGetNewApplicationResponse(response); 655 } 656 657 protected String validateGetNewApplicationResponse(ClientResponse resp) 658 throws JSONException, ParserConfigurationException, IOException, 659 SAXException { 660 String ret = ""; 661 if (resp.getType().equals(MediaType.APPLICATION_JSON_TYPE)) { 662 JSONObject json = resp.getEntity(JSONObject.class); 663 ret = validateGetNewApplicationJsonResponse(json); 664 } else if (resp.getType().equals(MediaType.APPLICATION_XML_TYPE)) { 665 String xml = resp.getEntity(String.class); 666 ret = validateGetNewApplicationXMLResponse(xml); 667 } else { 668 // we should not be here 669 assertTrue(false); 670 } 671 return ret; 672 } 673 674 protected String validateGetNewApplicationJsonResponse(JSONObject json) 675 throws JSONException { 676 String appId = json.getString("application-id"); 677 assertTrue(!appId.isEmpty()); 678 JSONObject maxResources = json.getJSONObject("maximum-resource-capability"); 679 long memory = maxResources.getLong("memory"); 680 long vCores = maxResources.getLong("vCores"); 681 assertTrue(memory != 0); 682 assertTrue(vCores != 0); 683 return appId; 684 } 685 686 protected String validateGetNewApplicationXMLResponse(String response) 687 throws ParserConfigurationException, IOException, SAXException { 688 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 689 DocumentBuilder db = dbf.newDocumentBuilder(); 690 InputSource is = new InputSource(); 691 is.setCharacterStream(new StringReader(response)); 692 Document dom = db.parse(is); 693 NodeList nodes = dom.getElementsByTagName("NewApplication"); 694 assertEquals("incorrect number of elements", 1, nodes.getLength()); 695 Element element = (Element) nodes.item(0); 696 String appId = WebServicesTestUtils.getXmlString(element, "application-id"); 697 assertTrue(!appId.isEmpty()); 698 NodeList maxResourceNodes = 699 element.getElementsByTagName("maximum-resource-capability"); 700 assertEquals(1, maxResourceNodes.getLength()); 701 Element maxResourceCapability = (Element) maxResourceNodes.item(0); 702 long memory = 703 WebServicesTestUtils.getXmlLong(maxResourceCapability, "memory"); 704 long vCores = 705 WebServicesTestUtils.getXmlLong(maxResourceCapability, "vCores"); 706 assertTrue(memory != 0); 707 assertTrue(vCores != 0); 708 return appId; 709 } 710 711 // Test to validate the process of submitting apps - test for appropriate 712 // errors as well 713 @Test 714 public void testGetNewApplicationAndSubmit() throws Exception { 715 rm.start(); 716 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 717 amNodeManager.nodeHeartbeat(true); 718 String mediaTypes[] = 719 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 720 for (String acceptMedia : mediaTypes) { 721 for (String contentMedia : mediaTypes) { 722 testAppSubmit(acceptMedia, contentMedia); 723 testAppSubmitErrors(acceptMedia, contentMedia); 724 } 725 } 726 rm.stop(); 727 } 728 729 public void testAppSubmit(String acceptMedia, String contentMedia) 730 throws Exception { 731 732 // create a test app and submit it via rest(after getting an app-id) then 733 // get the app details from the rmcontext and check that everything matches 734 735 client().addFilter(new LoggingFilter(System.out)); 736 String lrKey = "example"; 737 String queueName = "testqueue"; 738 String appName = "test"; 739 String appType = "test-type"; 740 String urlPath = "apps"; 741 String appId = testGetNewApplication(acceptMedia); 742 List<String> commands = new ArrayList<>(); 743 commands.add("/bin/sleep 5"); 744 HashMap<String, String> environment = new HashMap<>(); 745 environment.put("APP_VAR", "ENV_SETTING"); 746 HashMap<ApplicationAccessType, String> acls = new HashMap<>(); 747 acls.put(ApplicationAccessType.MODIFY_APP, "testuser1, testuser2"); 748 acls.put(ApplicationAccessType.VIEW_APP, "testuser3, testuser4"); 749 Set<String> tags = new HashSet<>(); 750 tags.add("tag1"); 751 tags.add("tag 2"); 752 CredentialsInfo credentials = new CredentialsInfo(); 753 HashMap<String, String> tokens = new HashMap<>(); 754 HashMap<String, String> secrets = new HashMap<>(); 755 secrets.put("secret1", Base64.encodeBase64String( 756 "mysecret".getBytes("UTF8"))); 757 credentials.setSecrets(secrets); 758 credentials.setTokens(tokens); 759 ApplicationSubmissionContextInfo appInfo = new ApplicationSubmissionContextInfo(); 760 appInfo.setApplicationId(appId); 761 appInfo.setApplicationName(appName); 762 appInfo.setPriority(3); 763 appInfo.setMaxAppAttempts(2); 764 appInfo.setQueue(queueName); 765 appInfo.setApplicationType(appType); 766 HashMap<String, LocalResourceInfo> lr = new HashMap<>(); 767 LocalResourceInfo y = new LocalResourceInfo(); 768 y.setUrl(new URI("http://www.test.com/file.txt")); 769 y.setSize(100); 770 y.setTimestamp(System.currentTimeMillis()); 771 y.setType(LocalResourceType.FILE); 772 y.setVisibility(LocalResourceVisibility.APPLICATION); 773 lr.put(lrKey, y); 774 appInfo.getContainerLaunchContextInfo().setResources(lr); 775 appInfo.getContainerLaunchContextInfo().setCommands(commands); 776 appInfo.getContainerLaunchContextInfo().setEnvironment(environment); 777 appInfo.getContainerLaunchContextInfo().setAcls(acls); 778 appInfo.getContainerLaunchContextInfo().getAuxillaryServiceData() 779 .put("test", Base64.encodeBase64URLSafeString("value12".getBytes("UTF8"))); 780 appInfo.getContainerLaunchContextInfo().setCredentials(credentials); 781 appInfo.getResource().setMemory(1024); 782 appInfo.getResource().setvCores(1); 783 appInfo.setApplicationTags(tags); 784 785 ClientResponse response = 786 this.constructWebResource(urlPath).accept(acceptMedia) 787 .entity(appInfo, contentMedia).post(ClientResponse.class); 788 789 if (!this.isAuthenticationEnabled()) { 790 assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); 791 return; 792 } 793 assertEquals(Status.ACCEPTED, response.getClientResponseStatus()); 794 assertTrue(!response.getHeaders().getFirst(HttpHeaders.LOCATION).isEmpty()); 795 String locURL = response.getHeaders().getFirst(HttpHeaders.LOCATION); 796 assertTrue(locURL.contains("/apps/application")); 797 appId = locURL.substring(locURL.indexOf("/apps/") + "/apps/".length()); 798 799 WebResource res = resource().uri(new URI(locURL)); 800 res = res.queryParam("user.name", webserviceUserName); 801 response = res.get(ClientResponse.class); 802 assertEquals(Status.OK, response.getClientResponseStatus()); 803 804 RMApp app = 805 rm.getRMContext().getRMApps() 806 .get(ConverterUtils.toApplicationId(appId)); 807 assertEquals(appName, app.getName()); 808 assertEquals(webserviceUserName, app.getUser()); 809 assertEquals(2, app.getMaxAppAttempts()); 810 if (app.getQueue().contains("root.")) { 811 queueName = "root." + queueName; 812 } 813 assertEquals(queueName, app.getQueue()); 814 assertEquals(appType, app.getApplicationType()); 815 assertEquals(tags, app.getApplicationTags()); 816 ContainerLaunchContext ctx = 817 app.getApplicationSubmissionContext().getAMContainerSpec(); 818 assertEquals(commands, ctx.getCommands()); 819 assertEquals(environment, ctx.getEnvironment()); 820 assertEquals(acls, ctx.getApplicationACLs()); 821 Map<String, LocalResource> appLRs = ctx.getLocalResources(); 822 assertTrue(appLRs.containsKey(lrKey)); 823 LocalResource exampleLR = appLRs.get(lrKey); 824 assertEquals(ConverterUtils.getYarnUrlFromURI(y.getUrl()), 825 exampleLR.getResource()); 826 assertEquals(y.getSize(), exampleLR.getSize()); 827 assertEquals(y.getTimestamp(), exampleLR.getTimestamp()); 828 assertEquals(y.getType(), exampleLR.getType()); 829 assertEquals(y.getPattern(), exampleLR.getPattern()); 830 assertEquals(y.getVisibility(), exampleLR.getVisibility()); 831 Credentials cs = new Credentials(); 832 ByteArrayInputStream str = 833 new ByteArrayInputStream(app.getApplicationSubmissionContext() 834 .getAMContainerSpec().getTokens().array()); 835 DataInputStream di = new DataInputStream(str); 836 cs.readTokenStorageStream(di); 837 Text key = new Text("secret1"); 838 assertTrue("Secrets missing from credentials object", cs 839 .getAllSecretKeys().contains(key)); 840 assertEquals("mysecret", new String(cs.getSecretKey(key), "UTF-8")); 841 842 response = 843 this.constructWebResource("apps", appId).accept(acceptMedia) 844 .get(ClientResponse.class); 845 assertEquals(Status.OK, response.getClientResponseStatus()); 846 } 847 848 public void testAppSubmitErrors(String acceptMedia, String contentMedia) 849 throws Exception { 850 851 // submit a bunch of bad requests(correct format but bad values) via the 852 // REST API and make sure we get the right error response codes 853 854 String urlPath = "apps"; 855 ApplicationSubmissionContextInfo appInfo = new ApplicationSubmissionContextInfo(); 856 ClientResponse response = 857 this.constructWebResource(urlPath).accept(acceptMedia) 858 .entity(appInfo, contentMedia).post(ClientResponse.class); 859 validateResponseStatus(response, Status.BAD_REQUEST); 860 861 String appId = "random"; 862 appInfo.setApplicationId(appId); 863 response = 864 this.constructWebResource(urlPath).accept(acceptMedia) 865 .entity(appInfo, contentMedia).post(ClientResponse.class); 866 validateResponseStatus(response, Status.BAD_REQUEST); 867 868 appId = "random_junk"; 869 appInfo.setApplicationId(appId); 870 response = 871 this.constructWebResource(urlPath).accept(acceptMedia) 872 .entity(appInfo, contentMedia).post(ClientResponse.class); 873 validateResponseStatus(response, Status.BAD_REQUEST); 874 875 // bad resource info 876 appInfo.getResource().setMemory( 877 rm.getConfig().getInt( 878 YarnConfiguration.RM_SCHEDULER_MAXIMUM_ALLOCATION_MB, 879 YarnConfiguration.DEFAULT_RM_SCHEDULER_MAXIMUM_ALLOCATION_MB) + 1); 880 appInfo.getResource().setvCores(1); 881 response = 882 this.constructWebResource(urlPath).accept(acceptMedia) 883 .entity(appInfo, contentMedia).post(ClientResponse.class); 884 885 validateResponseStatus(response, Status.BAD_REQUEST); 886 887 appInfo.getResource().setvCores( 888 rm.getConfig().getInt( 889 YarnConfiguration.RM_SCHEDULER_MAXIMUM_ALLOCATION_VCORES, 890 YarnConfiguration.DEFAULT_RM_SCHEDULER_MAXIMUM_ALLOCATION_VCORES) + 1); 891 appInfo.getResource().setMemory(CONTAINER_MB); 892 response = 893 this.constructWebResource(urlPath).accept(acceptMedia) 894 .entity(appInfo, contentMedia).post(ClientResponse.class); 895 validateResponseStatus(response, Status.BAD_REQUEST); 896 } 897 898 @Test 899 public void testAppSubmitBadJsonAndXML() throws Exception { 900 901 // submit a bunch of bad XML and JSON via the 902 // REST API and make sure we get error response codes 903 904 String urlPath = "apps"; 905 rm.start(); 906 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 907 amNodeManager.nodeHeartbeat(true); 908 909 ApplicationSubmissionContextInfo appInfo = new ApplicationSubmissionContextInfo(); 910 appInfo.setApplicationName("test"); 911 appInfo.setPriority(3); 912 appInfo.setMaxAppAttempts(2); 913 appInfo.setQueue("testqueue"); 914 appInfo.setApplicationType("test-type"); 915 HashMap<String, LocalResourceInfo> lr = new HashMap<>(); 916 LocalResourceInfo y = new LocalResourceInfo(); 917 y.setUrl(new URI("http://www.test.com/file.txt")); 918 y.setSize(100); 919 y.setTimestamp(System.currentTimeMillis()); 920 y.setType(LocalResourceType.FILE); 921 y.setVisibility(LocalResourceVisibility.APPLICATION); 922 lr.put("example", y); 923 appInfo.getContainerLaunchContextInfo().setResources(lr); 924 appInfo.getResource().setMemory(1024); 925 appInfo.getResource().setvCores(1); 926 927 String body = 928 "<?xml version=\"1.0\" encoding=\"UTF-8\" " 929 + "standalone=\"yes\"?><blah/>"; 930 ClientResponse response = 931 this.constructWebResource(urlPath).accept(MediaType.APPLICATION_XML) 932 .entity(body, MediaType.APPLICATION_XML).post(ClientResponse.class); 933 assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); 934 body = "{\"a\" : \"b\"}"; 935 response = 936 this.constructWebResource(urlPath).accept(MediaType.APPLICATION_XML) 937 .entity(body, MediaType.APPLICATION_JSON).post(ClientResponse.class); 938 validateResponseStatus(response, Status.BAD_REQUEST); 939 rm.stop(); 940 } 941 942 @Test 943 public void testGetAppQueue() throws Exception { 944 client().addFilter(new LoggingFilter(System.out)); 945 boolean isCapacityScheduler = 946 rm.getResourceScheduler() instanceof CapacityScheduler; 947 rm.start(); 948 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 949 String[] contentTypes = 950 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 951 for (String contentType : contentTypes) { 952 RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); 953 amNodeManager.nodeHeartbeat(true); 954 ClientResponse response = 955 this 956 .constructWebResource("apps", app.getApplicationId().toString(), 957 "queue").accept(contentType).get(ClientResponse.class); 958 assertEquals(Status.OK, response.getClientResponseStatus()); 959 String expectedQueue = "default"; 960 if(!isCapacityScheduler) { 961 expectedQueue = "root." + webserviceUserName; 962 } 963 if (contentType.equals(MediaType.APPLICATION_JSON)) { 964 verifyAppQueueJson(response, expectedQueue); 965 } else { 966 verifyAppQueueXML(response, expectedQueue); 967 } 968 } 969 rm.stop(); 970 } 971 972 @Test(timeout = 90000) 973 public void testAppMove() throws Exception { 974 975 client().addFilter(new LoggingFilter(System.out)); 976 977 boolean isCapacityScheduler = 978 rm.getResourceScheduler() instanceof CapacityScheduler; 979 980 // default root queue allows anyone to have admin acl 981 CapacitySchedulerConfiguration csconf = 982 new CapacitySchedulerConfiguration(); 983 String[] queues = { "default", "test" }; 984 csconf.setQueues("root", queues); 985 csconf.setCapacity("root.default", 50.0f); 986 csconf.setCapacity("root.test", 50.0f); 987 csconf.setAcl("root", QueueACL.ADMINISTER_QUEUE, "someuser"); 988 csconf.setAcl("root.default", QueueACL.ADMINISTER_QUEUE, "someuser"); 989 csconf.setAcl("root.test", QueueACL.ADMINISTER_QUEUE, "someuser"); 990 rm.getResourceScheduler().reinitialize(csconf, rm.getRMContext()); 991 992 rm.start(); 993 MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); 994 String[] mediaTypes = 995 { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; 996 MediaType[] contentTypes = 997 { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE }; 998 for (String mediaType : mediaTypes) { 999 for (MediaType contentType : contentTypes) { 1000 RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); 1001 amNodeManager.nodeHeartbeat(true); 1002 AppQueue targetQueue = new AppQueue("test"); 1003 Object entity; 1004 if (contentType.equals(MediaType.APPLICATION_JSON_TYPE)) { 1005 entity = appQueueToJSON(targetQueue); 1006 } else { 1007 entity = targetQueue; 1008 } 1009 ClientResponse response = 1010 this 1011 .constructWebResource("apps", app.getApplicationId().toString(), 1012 "queue").entity(entity, contentType).accept(mediaType) 1013 .put(ClientResponse.class); 1014 1015 if (!isAuthenticationEnabled()) { 1016 assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); 1017 continue; 1018 } 1019 assertEquals(Status.OK, response.getClientResponseStatus()); 1020 String expectedQueue = "test"; 1021 if(!isCapacityScheduler) { 1022 expectedQueue = "root.test"; 1023 } 1024 if (mediaType.equals(MediaType.APPLICATION_JSON)) { 1025 verifyAppQueueJson(response, expectedQueue); 1026 } else { 1027 verifyAppQueueXML(response, expectedQueue); 1028 } 1029 Assert.assertEquals(expectedQueue, app.getQueue()); 1030 1031 // check unauthorized 1032 app = rm.submitApp(CONTAINER_MB, "", "someuser"); 1033 amNodeManager.nodeHeartbeat(true); 1034 response = 1035 this 1036 .constructWebResource("apps", app.getApplicationId().toString(), 1037 "queue").entity(entity, contentType).accept(mediaType) 1038 .put(ClientResponse.class); 1039 assertEquals(Status.FORBIDDEN, response.getClientResponseStatus()); 1040 if(isCapacityScheduler) { 1041 Assert.assertEquals("default", app.getQueue()); 1042 } 1043 else { 1044 Assert.assertEquals("root.someuser", app.getQueue()); 1045 } 1046 1047 } 1048 } 1049 rm.stop(); 1050 } 1051 1052 protected static String appQueueToJSON(AppQueue targetQueue) throws Exception { 1053 StringWriter sw = new StringWriter(); 1054 JSONJAXBContext ctx = new JSONJAXBContext(AppQueue.class); 1055 JSONMarshaller jm = ctx.createJSONMarshaller(); 1056 jm.marshallToJSON(targetQueue, sw); 1057 return sw.toString(); 1058 } 1059 1060 protected static void 1061 verifyAppQueueJson(ClientResponse response, String queue) 1062 throws JSONException { 1063 1064 assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); 1065 JSONObject json = response.getEntity(JSONObject.class); 1066 assertEquals("incorrect number of elements", 1, json.length()); 1067 String responseQueue = json.getString("queue"); 1068 assertEquals(queue, responseQueue); 1069 } 1070 1071 protected static void 1072 verifyAppQueueXML(ClientResponse response, String queue) 1073 throws ParserConfigurationException, IOException, SAXException { 1074 assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); 1075 String xml = response.getEntity(String.class); 1076 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1077 DocumentBuilder db = dbf.newDocumentBuilder(); 1078 InputSource is = new InputSource(); 1079 is.setCharacterStream(new StringReader(xml)); 1080 Document dom = db.parse(is); 1081 NodeList nodes = dom.getElementsByTagName("appqueue"); 1082 assertEquals("incorrect number of elements", 1, nodes.getLength()); 1083 Element element = (Element) nodes.item(0); 1084 String responseQueue = WebServicesTestUtils.getXmlString(element, "queue"); 1085 assertEquals(queue, responseQueue); 1086 } 1087 1088 } 1089