/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.gateway; import java.io.File; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.hadoop.gateway.security.ldap.SimpleLdapDirectoryServer; import org.apache.hadoop.gateway.services.DefaultGatewayServices; import org.apache.hadoop.gateway.services.GatewayServices; import org.apache.hadoop.gateway.services.ServiceLifecycleException; import org.apache.hadoop.gateway.services.topology.TopologyService; import org.apache.hadoop.test.TestUtils; import org.apache.hadoop.test.mock.MockServer; import org.apache.http.HttpStatus; import org.apache.log4j.Appender; import org.hamcrest.MatcherAssert; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.jayway.restassured.RestAssured.given; import static org.apache.hadoop.test.TestUtils.LOG_ENTER; import static org.apache.hadoop.test.TestUtils.LOG_EXIT; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.hasItemInArray; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertThat; import static org.xmlmatchers.transform.XmlConverters.the; import static org.xmlmatchers.xpath.HasXPath.hasXPath; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; public class GatewayAppFuncTest { private static Logger LOG = LoggerFactory.getLogger( GatewayAppFuncTest.class ); private static Class DAT = GatewayAppFuncTest.class; private static Enumeration<Appender> appenders; private static GatewayTestConfig config; private static DefaultGatewayServices services; private static GatewayServer gateway; private static int gatewayPort; private static String gatewayUrl; private static String clusterUrl; private static SimpleLdapDirectoryServer ldap; private static TcpTransport ldapTransport; private static int ldapPort; private static Properties params; private static TopologyService topos; private static MockServer mockWebHdfs; @BeforeClass public static void setupSuite() throws Exception { LOG_ENTER(); //appenders = NoOpAppender.setUp(); setupLdap(); setupGateway(); LOG_EXIT(); } @AfterClass public static void cleanupSuite() throws Exception { LOG_ENTER(); gateway.stop(); ldap.stop( true ); FileUtils.deleteQuietly( new File( config.getGatewayHomeDir() ) ); //NoOpAppender.tearDown( appenders ); LOG_EXIT(); } @After public void cleanupTest() throws Exception { FileUtils.cleanDirectory( new File( config.getGatewayTopologyDir() ) ); FileUtils.cleanDirectory( new File( config.getGatewayDeploymentDir() ) ); } public static void setupLdap() throws Exception { URL usersUrl = TestUtils.getResourceUrl( DAT, "users.ldif" ); ldapTransport = new TcpTransport( 0 ); ldap = new SimpleLdapDirectoryServer( "dc=hadoop,dc=apache,dc=org", new File( usersUrl.toURI() ), ldapTransport ); ldap.start(); ldapPort = ldapTransport.getAcceptor().getLocalAddress().getPort(); LOG.info( "LDAP port = " + ldapPort ); } public static void setupGateway() throws Exception { File targetDir = new File( System.getProperty( "user.dir" ), "target" ); File gatewayDir = new File( targetDir, "gateway-home-" + UUID.randomUUID() ); gatewayDir.mkdirs(); config = new GatewayTestConfig(); config.setGatewayHomeDir( gatewayDir.getAbsolutePath() ); URL svcsFileUrl = TestUtils.getResourceUrl( DAT, "test-svcs/readme.txt" ); File svcsFile = new File( svcsFileUrl.getFile() ); File svcsDir = svcsFile.getParentFile(); config.setGatewayServicesDir( svcsDir.getAbsolutePath() ); URL appsFileUrl = TestUtils.getResourceUrl( DAT, "test-apps/readme.txt" ); File appsFile = new File( appsFileUrl.getFile() ); File appsDir = appsFile.getParentFile(); config.setGatewayApplicationsDir( appsDir.getAbsolutePath() ); File topoDir = new File( config.getGatewayTopologyDir() ); topoDir.mkdirs(); File deployDir = new File( config.getGatewayDeploymentDir() ); deployDir.mkdirs(); setupMockServers(); startGatewayServer(); } public static void setupMockServers() throws Exception { mockWebHdfs = new MockServer( "WEBHDFS", true ); } public static void startGatewayServer() throws Exception { services = new DefaultGatewayServices(); Map<String,String> options = new HashMap<String,String>(); options.put( "persist-master", "false" ); options.put( "master", "password" ); try { services.init( config, options ); } catch ( ServiceLifecycleException e ) { e.printStackTrace(); // I18N not required. } topos = services.getService(GatewayServices.TOPOLOGY_SERVICE); gateway = GatewayServer.startGateway( config, services ); MatcherAssert.assertThat( "Failed to start gateway.", gateway, notNullValue() ); gatewayPort = gateway.getAddresses()[0].getPort(); gatewayUrl = "http://localhost:" + gatewayPort + "/" + config.getGatewayPath(); clusterUrl = gatewayUrl + "/test-topology"; LOG.info( "Gateway port = " + gateway.getAddresses()[ 0 ].getPort() ); params = new Properties(); params.put( "LDAP_URL", "ldap://localhost:" + ldapPort ); params.put( "WEBHDFS_URL", "http://localhost:" + mockWebHdfs.getPort() ); } @Test( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testSimpleStaticHelloAppDeployUndeploy() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-static-hello-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); String username = "guest"; String password = "guest-password"; String serviceUrl = clusterUrl + "/static-hello-app-path/index.html"; String body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "text/html" ) .when().get( serviceUrl ).asString(); assertThat( the(body), hasXPath( "/html/head/title/text()", equalTo("Static Hello Application") ) ); serviceUrl = clusterUrl + "/static-hello-app-path/"; body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "text/html" ) .when().get( serviceUrl ).asString(); assertThat( the(body), hasXPath( "/html/head/title/text()", equalTo("Static Hello Application") ) ); serviceUrl = clusterUrl + "/static-hello-app-path"; body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "text/html" ) .when().get( serviceUrl ).asString(); assertThat( the(body), hasXPath( "/html/head/title/text()", equalTo("Static Hello Application") ) ); assertThat( "Failed to delete test topology file", FileUtils.deleteQuietly( topoFile ), is(true) ); topos.reloadTopologies(); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( serviceUrl ); LOG_EXIT(); } @Test( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testSimpleDynamicAppDeployUndeploy() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-dynamic-app-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); String username = "guest"; String password = "guest-password"; given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); assertThat( "Failed to delete test topology file", FileUtils.deleteQuietly( topoFile ), is(true) ); topos.reloadTopologies(); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when() .get( clusterUrl + "/dynamic-app-path" ); LOG_EXIT(); } @Test( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testNakedAppDeploy() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-naked-app-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); given() //.log().all() .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( gatewayUrl + "/test-topology/dynamic-app/?null" ) ) .when().get( gatewayUrl + "/test-topology/dynamic-app" ); LOG_EXIT(); } @Test//( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testDefaultAppName() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-default-app-name-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); String username = "guest"; String password = "guest-password"; given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app/?null" ) ) .when().get( clusterUrl + "/dynamic-app" ); assertThat( "Failed to delete test topology file", FileUtils.deleteQuietly( topoFile ), is(true) ); topos.reloadTopologies(); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when() .get( clusterUrl + "/dynamic-app" ); File deployDir = new File( config.getGatewayDeploymentDir() ); assertThat( deployDir.listFiles(), is(arrayWithSize(0)) ); LOG_EXIT(); } @Test//( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testMultiApps() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-multi-apps-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); String username = "guest"; String password = "guest-password"; String body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "text/html" ) .when().get( clusterUrl + "/static-hello-app-path/index.html" ).asString(); assertThat( the(body), hasXPath( "/html/head/title/text()", equalTo("Static Hello Application") ) ); body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() .contentType( "" ) //.log().all() .statusCode( HttpStatus.SC_OK ) .when().get( clusterUrl + "/static-json-app/one.json" ).asString(); assertThat( body, sameJSONAs( "{'test-name-one':'test-value-one'}" ) ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() .contentType( "application/xml" ) //.log().all() .statusCode( HttpStatus.SC_OK ) .when().get( clusterUrl + "/test.xml" ).asString(); assertThat( the(body), hasXPath( "/test" ) ); assertThat( FileUtils.deleteQuietly( topoFile ), is(true) ); topos.reloadTopologies(); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/static-hello-app-path/index.html" ); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/static-json-app/one.json" ); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/dynamic-app-path" ); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/test.xml" ); LOG_EXIT(); } @Test( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testServicesAndApplications() throws Exception { LOG_ENTER(); String topoStr = TestUtils.merge( DAT, "test-svcs-and-apps-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); String username = "guest"; String password = "guest-password"; mockWebHdfs.expect() .method( "GET" ) .pathInfo( "/v1/" ) .queryParam( "op", "GETHOMEDIRECTORY" ) .queryParam( "user.name", "guest" ) .respond() .status( HttpStatus.SC_OK ) .content( "{\"path\":\"/users/guest\"}", Charset.forName("UTF-8") ) .contentType( "application/json" ); given() //.log().all() .auth().preemptive().basic( username, password ) .queryParam( "op", "GETHOMEDIRECTORY" ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "application/json" ) .body( "path", is( "/users/guest") ) .when().get( clusterUrl + "/webhdfs/v1" ); assertThat( mockWebHdfs.isEmpty(), is(true) ); String body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .contentType( "application/xml" ) .when().get( clusterUrl + "/static-xml-app/test.xml" ).asString(); assertThat( the(body), hasXPath( "test" ) ); body = given() //.log().all() .auth().preemptive().basic( username, password ) .expect() .contentType( "" ) //.log().all() .statusCode( HttpStatus.SC_OK ) .when().get( clusterUrl + "/app-two/one.json" ).asString(); assertThat( body, sameJSONAs( "{'test-name-one':'test-value-one'}" ) ); assertThat( FileUtils.deleteQuietly( topoFile ), is(true) ); topos.reloadTopologies(); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/app-one/index.html" ); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/app-two/one.json" ); given() .auth().preemptive().basic( username, password ) .expect() .statusCode( HttpStatus.SC_NOT_FOUND ) .when().get( clusterUrl + "/test.xml" ); File deployDir = new File( config.getGatewayDeploymentDir() ); assertThat( deployDir.listFiles(), is(arrayWithSize(0)) ); LOG_EXIT(); } @Test//( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testDeploymentCleanup() throws Exception { LOG_ENTER(); String username = "guest"; String password = "guest-password"; int oldVersionLimit = config.getGatewayDeploymentsBackupVersionLimit(); try { gateway.stop(); config.setGatewayDeploymentsBackupVersionLimit( 1 ); startGatewayServer(); String topoStr = TestUtils.merge( DAT, "test-dynamic-app-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); File deployDir = new File( config.getGatewayDeploymentDir() ); String[] topoDirs1 = deployDir.list(); assertThat( topoDirs1, is(arrayWithSize(1)) ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); TestUtils.waitUntilNextSecond(); FileUtils.touch( topoFile ); topos.reloadTopologies(); String[] topoDirs2 = deployDir.list(); assertThat( topoDirs2, is(arrayWithSize(2)) ); assertThat( topoDirs2, hasItemInArray(topoDirs1[0]) ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); TestUtils.waitUntilNextSecond(); FileUtils.touch( topoFile ); topos.reloadTopologies(); String[] topoDirs3 = deployDir.list(); assertThat( topoDirs3, is(arrayWithSize(2)) ); assertThat( topoDirs3, not(hasItemInArray(topoDirs1[0])) ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); } finally { gateway.stop(); config.setGatewayDeploymentsBackupAgeLimit( oldVersionLimit ); startGatewayServer(); } LOG_EXIT(); } @Test( timeout = TestUtils.MEDIUM_TIMEOUT ) public void testDefaultTopology() throws Exception { LOG_ENTER(); try { gateway.stop(); config.setGatewayDeploymentsBackupVersionLimit( 1 ); startGatewayServer(); String topoStr = TestUtils.merge( DAT, "test-dynamic-app-topology.xml", params ); File topoFile = new File( config.getGatewayTopologyDir(), "test-topology.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); File deployDir = new File( config.getGatewayDeploymentDir() ); String[] topoDirs = deployDir.list(); assertThat( topoDirs, is(arrayWithSize(1)) ); String username = "guest"; String password = "guest-password"; given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); topoStr = TestUtils.merge( DAT, "test-dynamic-app-topology.xml", params ); topoFile = new File( config.getGatewayTopologyDir(), "test-topology-2.xml" ); FileUtils.writeStringToFile( topoFile, topoStr ); topos.reloadTopologies(); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( gatewayUrl + "/test-topology" + "/dynamic-app-path/?null" ) ) .when().get( gatewayUrl + "/test-topology/dynamic-app-path" ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( gatewayUrl + "/test-topology-2" + "/dynamic-app-path/?null" ) ) .when().get( gatewayUrl + "/test-topology-2/dynamic-app-path" ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_NOT_FOUND ) .body( is( clusterUrl + "/dynamic-app-path/?null" ) ); gateway.stop(); config.setDefaultTopologyName( "test-topology" ); startGatewayServer(); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( gatewayUrl + "/test-topology" + "/dynamic-app-path/?null" ) ) .when().get( gatewayUrl + "/test-topology/dynamic-app-path" ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .statusCode( HttpStatus.SC_OK ) .body( is( gatewayUrl + "/test-topology-2" + "/dynamic-app-path/?null" ) ) .when().get( gatewayUrl + "/test-topology-2/dynamic-app-path" ); given() //.log().all() .auth().preemptive().basic( username, password ) .expect() //.log().all() .body( is( clusterUrl + "/dynamic-app-path/?null" ) ) .when().get( clusterUrl + "/dynamic-app-path" ); } finally { gateway.stop(); config.setDefaultTopologyName( null ); startGatewayServer(); } LOG_EXIT(); } public static Collection<String> toNames( File[] files ) { List<String> names = new ArrayList<String>( files.length ); for( File file : files ) { names.add( file.getAbsolutePath() ); } return names; } }