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