/**
* 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.filter.rewrite.impl;
import com.jayway.jsonassert.JsonAssert;
import org.apache.hadoop.gateway.filter.AbstractGatewayFilter;
import org.apache.hadoop.gateway.filter.rewrite.api.FrontendFunctionDescriptor;
import org.apache.hadoop.gateway.filter.rewrite.api.UrlRewriteEnvironment;
import org.apache.hadoop.gateway.filter.rewrite.api.UrlRewriteServletContextListener;
import org.apache.hadoop.gateway.filter.rewrite.api.UrlRewriteServletFilter;
import org.apache.hadoop.gateway.filter.rewrite.spi.UrlRewriteFunctionProcessor;
import org.apache.hadoop.gateway.services.GatewayServices;
import org.apache.hadoop.gateway.services.registry.ServiceRegistry;
import org.apache.hadoop.gateway.util.urltemplate.Parser;
import org.apache.hadoop.test.TestUtils;
import org.apache.hadoop.test.log.NoOpLogger;
import org.apache.hadoop.test.mock.MockInteraction;
import org.apache.hadoop.test.mock.MockServlet;
import org.apache.http.auth.BasicUserPrincipal;
import org.easymock.EasyMock;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.log.Log;
import org.hamcrest.core.Is;
import org.junit.Test;
import javax.security.auth.Subject;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.fail;
public class FrontendFunctionProcessorTest {
private ServletTester server;
private HttpTester.Request request;
private HttpTester.Response response;
private ArrayQueue<MockInteraction> interactions;
private MockInteraction interaction;
@SuppressWarnings("rawtypes")
@Test
public void testServiceLoader() throws Exception {
ServiceLoader loader = ServiceLoader.load( UrlRewriteFunctionProcessor.class );
Iterator iterator = loader.iterator();
assertThat( "Service iterator empty.", iterator.hasNext() );
while( iterator.hasNext() ) {
Object object = iterator.next();
if( object instanceof FrontendFunctionProcessor ) {
return;
}
}
fail( "Failed to find " + FrontendFunctionProcessor.class.getName() + " via service loader." );
}
@Test
public void testName() throws Exception {
FrontendFunctionProcessor processor = new FrontendFunctionProcessor();
assertThat( processor.name(), is( "frontend" ) );
}
@Test
public void testNullHandling() throws Exception {
UrlRewriteEnvironment badEnv = EasyMock.createNiceMock( UrlRewriteEnvironment.class );
UrlRewriteEnvironment goodEnv = EasyMock.createNiceMock( UrlRewriteEnvironment.class );
EasyMock.expect( goodEnv.getAttribute(FrontendFunctionDescriptor.FRONTEND_URI_ATTRIBUTE) ).andReturn( new URI( "http://mock-host:80/mock-root/mock-topo" ) ).anyTimes();
EasyMock.replay( badEnv,goodEnv );
FrontendFunctionProcessor processor = new FrontendFunctionProcessor();
try {
processor.initialize( null, null );
} catch ( IllegalArgumentException e ) {
assertThat( e.getMessage(), containsString( "environment" ) );
}
try {
processor.initialize( badEnv, null );
} catch ( IllegalArgumentException e ) {
assertThat( e.getMessage(), containsString( "org.apache.hadoop.knox.frontend.context.uri" ) );
}
processor.initialize( goodEnv, null );
processor.resolve( null, null );
}
public void setUp( String username, Map<String, String> initParams, Attributes attributes ) throws Exception {
ServiceRegistry mockServiceRegistry = EasyMock.createNiceMock( ServiceRegistry.class );
EasyMock.expect( mockServiceRegistry.lookupServiceURL( "test-cluster", "NAMENODE" ) ).andReturn( "test-nn-scheme://test-nn-host:411" ).anyTimes();
EasyMock.expect( mockServiceRegistry.lookupServiceURL( "test-cluster", "JOBTRACKER" ) ).andReturn( "test-jt-scheme://test-jt-host:511" ).anyTimes();
GatewayServices mockGatewayServices = EasyMock.createNiceMock( GatewayServices.class );
EasyMock.expect( mockGatewayServices.getService(GatewayServices.SERVICE_REGISTRY_SERVICE) ).andReturn( mockServiceRegistry ).anyTimes();
EasyMock.replay( mockServiceRegistry, mockGatewayServices );
String descriptorUrl = TestUtils.getResourceUrl( FrontendFunctionProcessorTest.class, "rewrite.xml" ).toExternalForm();
Log.setLog( new NoOpLogger() );
server = new ServletTester();
server.setContextPath( "/" );
server.getContext().addEventListener( new UrlRewriteServletContextListener() );
server.getContext().setInitParameter(
UrlRewriteServletContextListener.DESCRIPTOR_LOCATION_INIT_PARAM_NAME, descriptorUrl );
if( attributes != null ) {
server.getContext().setAttributes( attributes );
}
server.getContext().setAttribute( GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE, "test-cluster" );
server.getContext().setAttribute( GatewayServices.GATEWAY_SERVICES_ATTRIBUTE, mockGatewayServices );
FilterHolder setupFilter = server.addFilter( SetupFilter.class, "/*", EnumSet.of( DispatcherType.REQUEST ) );
setupFilter.setFilter( new SetupFilter( username ) );
FilterHolder rewriteFilter = server.addFilter( UrlRewriteServletFilter.class, "/*", EnumSet.of( DispatcherType.REQUEST ) );
if( initParams != null ) {
for( Map.Entry<String,String> entry : initParams.entrySet() ) {
rewriteFilter.setInitParameter( entry.getKey(), entry.getValue() );
}
}
rewriteFilter.setFilter( new UrlRewriteServletFilter() );
interactions = new ArrayQueue<MockInteraction>();
ServletHolder servlet = server.addServlet( MockServlet.class, "/" );
servlet.setServlet( new MockServlet( "mock-servlet", interactions ) );
server.start();
interaction = new MockInteraction();
request = HttpTester.newRequest();
response = null;
}
@Test
public void testFrontendFunctionsOnJsonRequestBody() throws Exception {
Map<String,String> initParams = new HashMap<String,String>();
initParams.put( "response.body", "test-filter" );
setUp( "test-user", initParams, null );
String input = TestUtils.getResourceString( FrontendFunctionProcessorTest.class, "test-input-body.json", "UTF-8" );
interaction.expect()
.method( "GET" )
.requestUrl( "http://test-host:42/test-path" );
interaction.respond()
.status( 200 )
.contentType( "application/json" )
.characterEncoding( "UTF-8" )
.content( input, Charset.forName( "UTF-8" ) );
interactions.add( interaction );
request.setMethod( "GET" );
request.setURI( "/test-path" );
//request.setVersion( "HTTP/1.1" );
request.setHeader( "Host", "test-host:42" );
response = TestUtils.execute( server, request );
assertThat( response.getStatus(), Is.is( 200 ) );
String json = response.getContent();
// Note: The Jetty ServletTester/HttpTester doesn't return very good values.
JsonAssert.with( json ).assertThat( "$.url", anyOf( is( "http://localhost:0" ), is( "http://0.0.0.0:0" ) ) );
JsonAssert.with( json ).assertThat( "$.scheme", is( "http" ) );
JsonAssert.with( json ).assertThat( "$.host", anyOf( is( "localhost" ), is( "0.0.0.0" ) ) );
JsonAssert.with( json ).assertThat( "$.port", is( "0" ) );
JsonAssert.with( json ).assertThat( "$.addr", anyOf( is( "localhost:0" ), is( "0.0.0.0:0" ) ) );
JsonAssert.with( json ).assertThat( "$.address", anyOf( is( "localhost:0" ), is( "0.0.0.0:0" ) ) );
JsonAssert.with( json ).assertThat( "$.path", is( "" ) );
JsonAssert.with( json ).assertThat( "$.topology", is( "test-cluster" ) );
}
@Test
public void testFrontendFunctionsWithFrontendUriConfigOnJsonRequestBody() throws Exception {
// This hooks up the filter in rewrite.xml in this class' test resource directory.
Map<String,String> initParams = new HashMap<String,String>();
initParams.put( "response.body", "test-filter" );
// This simulates having gateway.frontend.uri in gateway-site.xml
Attributes attributes = new AttributesMap( );
attributes.setAttribute( FrontendFunctionDescriptor.FRONTEND_URI_ATTRIBUTE, new URI( "mock-frontend-scheme://mock-frontend-host:777/mock-frontend-path" ) );
setUp( "test-user", initParams, attributes );
String input = TestUtils.getResourceString( FrontendFunctionProcessorTest.class, "test-input-body.json", "UTF-8" );
interaction.expect()
.method( "GET" )
.requestUrl( "http://test-host:42/test-path" );
interaction.respond()
.status( 200 )
.contentType( "application/json" )
.characterEncoding( "UTF-8" )
.content( input, Charset.forName( "UTF-8" ) );
interactions.add( interaction );
request.setMethod( "GET" );
request.setURI( "/test-path" );
//request.setVersion( "HTTP/1.1" );
request.setHeader( "Host", "test-host:42" );
response = TestUtils.execute( server, request );
assertThat( response.getStatus(), Is.is( 200 ) );
String json = response.getContent();
// Note: The Jetty ServletTester/HttpTester doesn't return very good values.
JsonAssert.with( json ).assertThat( "$.url", is( "mock-frontend-scheme://mock-frontend-host:777/mock-frontend-path" ) );
JsonAssert.with( json ).assertThat( "$.scheme", is( "mock-frontend-scheme" ) );
JsonAssert.with( json ).assertThat( "$.host", is( "mock-frontend-host" ) );
JsonAssert.with( json ).assertThat( "$.port", is( "777" ) );
JsonAssert.with( json ).assertThat( "$.addr", is( "mock-frontend-host:777" ) );
JsonAssert.with( json ).assertThat( "$.address", is( "mock-frontend-host:777" ) );
JsonAssert.with( json ).assertThat( "$.path", is( "/mock-frontend-path" ) );
}
private static class SetupFilter implements Filter {
private Subject subject;
public SetupFilter( String userName ) {
subject = new Subject();
subject.getPrincipals().add( new BasicUserPrincipal( userName ) );
}
@Override
public void init( FilterConfig filterConfig ) throws ServletException {
}
@Override
public void doFilter( final ServletRequest request, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {
HttpServletRequest httpRequest = ((HttpServletRequest)request);
StringBuffer sourceUrl = httpRequest.getRequestURL();
String queryString = httpRequest.getQueryString();
if( queryString != null ) {
sourceUrl.append( "?" );
sourceUrl.append( queryString );
}
try {
request.setAttribute(
AbstractGatewayFilter.SOURCE_REQUEST_URL_ATTRIBUTE_NAME,
Parser.parseLiteral( sourceUrl.toString() ) );
} catch( URISyntaxException e ) {
throw new ServletException( e );
}
try {
Subject.doAs( subject, new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
chain.doFilter( request, response );
return null;
}
} );
} catch( PrivilegedActionException e ) {
throw new ServletException( e );
}
}
@Override
public void destroy() {
}
}
}