com.google.apphosting.vmruntime.VmApiProxyDelegateTest.java Source code

Java tutorial

Introduction

Here is the source code for com.google.apphosting.vmruntime.VmApiProxyDelegateTest.java

Source

/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 * 
 * Licensed 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 com.google.apphosting.vmruntime;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.log.LogServiceException;
import com.google.appengine.api.modules.ModulesException;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.utils.remoteapi.RemoteApiPb;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableMap;

import junit.framework.TestCase;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.mockito.Mockito;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * Tests the delegate for making AppEngine API calls in a Google Compute Engine VM.
 *
 */

public class VmApiProxyDelegateTest extends TestCase {
    private static final String TICKET = "test-ticket";
    public static final String TEST_PACKAGE_NAME = "test package";
    public static final String TEST_METHOD_NAME = "test method";
    public static final String TEST_ERROR_MESSAGE = "test error message";
    public static final int TEST_APPLICATION_ERROR = 505;

    public static Map<RemoteApiPb.RpcError.ErrorCode, ApiProxyException> createErrorToExceptionMap() {
        return new ImmutableMap.Builder<RemoteApiPb.RpcError.ErrorCode, ApiProxyException>()
                .put(RemoteApiPb.RpcError.ErrorCode.UNKNOWN,
                        new ApiProxy.UnknownException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.CALL_NOT_FOUND,
                        new ApiProxy.CallNotFoundException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.PARSE_ERROR,
                        new ApiProxy.ArgumentException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.SECURITY_VIOLATION,
                        new ApiProxy.UnknownException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.OVER_QUOTA,
                        new ApiProxy.OverQuotaException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.REQUEST_TOO_LARGE,
                        new ApiProxy.RequestTooLargeException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.CAPABILITY_DISABLED,
                        new ApiProxy.CapabilityDisabledException(TEST_ERROR_MESSAGE, TEST_PACKAGE_NAME,
                                TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.FEATURE_DISABLED,
                        new ApiProxy.FeatureNotEnabledException(TEST_ERROR_MESSAGE, TEST_PACKAGE_NAME,
                                TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.BAD_REQUEST,
                        new ApiProxy.ArgumentException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.RESPONSE_TOO_LARGE,
                        new ApiProxy.ResponseTooLargeException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.CANCELLED,
                        new ApiProxy.CancelledException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.REPLAY_ERROR,
                        new ApiProxy.UnknownException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .put(RemoteApiPb.RpcError.ErrorCode.DEADLINE_EXCEEDED,
                        new ApiProxy.ApiDeadlineExceededException(TEST_PACKAGE_NAME, TEST_METHOD_NAME))
                .build();
    }

    private HttpClient createMockHttpClient() {
        HttpClient httpClient = mock(HttpClient.class);
        when(httpClient.getConnectionManager()).thenReturn(new PoolingClientConnectionManager());
        return httpClient;
    }

    private HttpResponse createMockHttpResponse(byte[] response, int code)
            throws IllegalStateException, IOException {
        ByteArrayInputStream istream = new ByteArrayInputStream(response);

        HttpResponse resp = mock(HttpResponse.class);
        HttpEntity entity = mock(HttpEntity.class);
        StatusLine statusLine = mock(StatusLine.class);

        when(entity.getContent()).thenReturn(istream);
        when(resp.getEntity()).thenReturn(entity);
        when(statusLine.getStatusCode()).thenReturn(code);
        when(resp.getStatusLine()).thenReturn(statusLine);
        return resp;
    }

    private VmApiProxyEnvironment createMockEnvironment() {
        VmApiProxyEnvironment environment = mock(VmApiProxyEnvironment.class);
        when(environment.getTicket()).thenReturn(TICKET);

        Map<String, Object> attributes = new HashMap<>();
        when(environment.getAttributes()).thenReturn(attributes);
        return environment;
    }

    private void callDelegateWithSuccess(boolean sync) throws Exception {
        RemoteApiPb.Response response = new RemoteApiPb.Response();
        byte[] pbData = new byte[] { 0, 1, 2, 3, 4, 5 };
        response.setResponseAsBytes(pbData);

        HttpClient mockClient = createMockHttpClient();
        HttpResponse mockHttpResponse = createMockHttpResponse(response.toByteArray(), HttpURLConnection.HTTP_OK);
        when(mockClient.execute(Mockito.any(HttpUriRequest.class), Mockito.any(HttpContext.class)))
                .thenReturn(mockHttpResponse);

        VmApiProxyDelegate delegate = new VmApiProxyDelegate(mockClient);
        VmApiProxyEnvironment environment = createMockEnvironment();

        final Double timeoutInSeconds = 3.0;
        byte[] result = null;
        if (sync) {
            environment.getAttributes().put(VmApiProxyDelegate.API_DEADLINE_KEY, timeoutInSeconds);
            result = delegate.makeSyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, pbData);
        } else {
            ApiConfig apiConfig = new ApiConfig();
            apiConfig.setDeadlineInSeconds(timeoutInSeconds);
            result = delegate.makeAsyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, pbData, apiConfig)
                    .get();
        }
        assertTrue(Arrays.equals(pbData, result));
    }

    private void callDelegateWithOneError(boolean sync, RemoteApiPb.RpcError rpcError,
            RemoteApiPb.ApplicationError appError, ApiProxyException expectedException) throws Exception {
        // Create the response for the mock connection.
        RemoteApiPb.Response response = new RemoteApiPb.Response();
        if (appError != null) {
            response.setApplicationError(appError);
        }
        if (rpcError != null) {
            response.setRpcError(rpcError);
        }

        HttpClient mockClient = createMockHttpClient();
        HttpResponse mockHttpResponse = createMockHttpResponse(response.toByteArray(), HttpURLConnection.HTTP_OK);
        when(mockClient.execute(Mockito.any(HttpUriRequest.class), Mockito.any(HttpContext.class)))
                .thenReturn(mockHttpResponse);

        VmApiProxyDelegate delegate = new VmApiProxyDelegate(mockClient);
        VmApiProxyEnvironment environment = createMockEnvironment();

        byte[] requestData = new byte[] { 0, 1, 2, 3, 4, 5 };
        byte[] result = null;
        final Double timeoutInSeconds = 10.0;

        if (sync) {
            try {
                environment.getAttributes().put(VmApiProxyDelegate.API_DEADLINE_KEY, timeoutInSeconds);
                result = delegate.makeSyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData);
                fail();
            } catch (ApiProxyException exception) {
                assertEquals(expectedException.getClass(), exception.getClass());
            }
        } else {
            try {
                ApiConfig apiConfig = new ApiConfig();
                apiConfig.setDeadlineInSeconds(timeoutInSeconds);
                result = delegate
                        .makeAsyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData, apiConfig)
                        .get();
                fail();
            } catch (ExecutionException exception) {
                // ExecutionException is expected, and make sure the cause is expected as well.
                assertEquals(expectedException.getClass(), exception.getCause().getClass());
            }
        }
        assertNull(result);
    }

    private void callDelegateWithHttpError(boolean sync, ApiProxyException expectedException) throws Exception {
        HttpClient mockClient = createMockHttpClient();
        HttpResponse mockHttpResponse = createMockHttpResponse("Error from RPC proxy".getBytes(),
                HttpURLConnection.HTTP_UNAVAILABLE);
        when(mockClient.execute(Mockito.any(HttpUriRequest.class), Mockito.any(HttpContext.class)))
                .thenReturn(mockHttpResponse);

        VmApiProxyDelegate delegate = new VmApiProxyDelegate(mockClient);
        VmApiProxyEnvironment environment = createMockEnvironment();

        byte[] requestData = new byte[] { 0, 1, 2, 3, 4, 5 };
        byte[] result = null;
        final Double timeoutInSeconds = 10.0;

        if (sync) {
            try {
                environment.getAttributes().put(VmApiProxyDelegate.API_DEADLINE_KEY, timeoutInSeconds);
                result = delegate.makeSyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData);
                fail();
            } catch (ApiProxyException exception) {
                assertEquals(expectedException.getClass(), exception.getClass());
            }
        } else {
            try {
                ApiConfig apiConfig = new ApiConfig();
                apiConfig.setDeadlineInSeconds(timeoutInSeconds);
                result = delegate
                        .makeAsyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData, apiConfig)
                        .get();
                fail();
            } catch (ExecutionException exception) {
                // ExecutionException is expected, and make sure the cause is expected as well.
                assertEquals(expectedException.getClass(), exception.getCause().getClass());
            }
        }
        assertNull(result);
    }

    private void callDelegateWithConnectionError(boolean sync, ApiProxyException expectedException)
            throws Exception {
        HttpClient mockClient = createMockHttpClient();
        createMockHttpResponse(new byte[0], HttpURLConnection.HTTP_UNAVAILABLE);
        when(mockClient.execute(Mockito.any(HttpUriRequest.class), Mockito.any(HttpContext.class)))
                .thenThrow(new IOException("Connection refused"));

        VmApiProxyDelegate delegate = new VmApiProxyDelegate(mockClient);
        VmApiProxyEnvironment environment = createMockEnvironment();

        byte[] requestData = new byte[] { 0, 1, 2, 3, 4, 5 };
        byte[] result = null;
        final Double timeoutInSeconds = 10.0;

        if (sync) {
            try {
                environment.getAttributes().put(VmApiProxyDelegate.API_DEADLINE_KEY, timeoutInSeconds);
                result = delegate.makeSyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData);
                fail();
            } catch (ApiProxyException exception) {
                assertEquals(expectedException.getClass(), exception.getClass());
            }
        } else {
            try {
                ApiConfig apiConfig = new ApiConfig();
                apiConfig.setDeadlineInSeconds(timeoutInSeconds);
                result = delegate
                        .makeAsyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData, apiConfig)
                        .get();
                fail();
            } catch (ExecutionException exception) {
                // ExecutionException is expected, and make sure the cause is expected as well.
                assertEquals(expectedException.getClass(), exception.getCause().getClass());
            }
        }
        assertNull(result);
    }

    private void callDelegateWithParsingError(boolean sync, ApiProxyException expectedException) throws Exception {
        // Create the response for the mock connection.
        byte[] brokenResponse = new byte[] { 47, 11, 17, 32 };
        HttpClient mockClient = createMockHttpClient();
        HttpResponse mockHttpResponse = createMockHttpResponse(brokenResponse, HttpURLConnection.HTTP_OK);
        when(mockClient.execute(Mockito.any(HttpUriRequest.class), Mockito.any(HttpContext.class)))
                .thenReturn(mockHttpResponse);

        VmApiProxyDelegate delegate = new VmApiProxyDelegate(mockClient);
        VmApiProxyEnvironment environment = createMockEnvironment();

        byte[] requestData = new byte[] { 0, 1, 2, 3, 4, 5 };
        byte[] result = null;

        if (sync) {
            try {
                result = delegate.makeSyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData);
                fail();
            } catch (ApiProxyException exception) {
                assertEquals(expectedException.getClass(), exception.getClass());
            }
        } else {
            try {
                result = delegate.makeAsyncCall(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME, requestData,
                        new ApiConfig()).get();
                fail();
            } catch (ExecutionException exception) {
                // ExecutionException is expected, and make sure the cause is expected as well.
                assertEquals(expectedException.getClass(), exception.getCause().getClass());
            }
        }
        assertNull(result);
    }

    private void callDelegateWithAllErrors(boolean sync) throws Exception {
        Map<RemoteApiPb.RpcError.ErrorCode, ApiProxyException> rmtResponseRpcErrorToExceptionMap = createErrorToExceptionMap();
        for (Map.Entry<RemoteApiPb.RpcError.ErrorCode, ApiProxyException> entry : rmtResponseRpcErrorToExceptionMap
                .entrySet()) {
            RemoteApiPb.RpcError rpcError = new RemoteApiPb.RpcError();
            rpcError.setCode(entry.getKey().ordinal());
            ApiProxyException expectedException = entry.getValue();
            callDelegateWithOneError(sync, rpcError, null, expectedException);
        }

        RemoteApiPb.ApplicationError appError = new RemoteApiPb.ApplicationError();
        appError.setCode(13);
        appError.setDetail("blah");
        ApiProxyException expectedException = new ApiProxy.ApplicationException(TEST_APPLICATION_ERROR,
                TEST_ERROR_MESSAGE);
        callDelegateWithOneError(sync, null, appError, expectedException);
    }

    public void testConstructorAndConnectionMonitorThread() throws Exception {
        VmApiProxyDelegate delegate = new VmApiProxyDelegate();
        delegate.monitorThread.interrupt();
        delegate.monitorThread.join();
    }

    public void testMakeSyncCall_Success() throws Exception {
        callDelegateWithSuccess(true);
    }

    public void testMakeAsyncCall_Success() throws Exception {
        callDelegateWithSuccess(false);
    }

    public void testMakeSyncCall_HttpError() throws Exception {
        callDelegateWithHttpError(true, new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeAsyncCall_HttpError() throws Exception {
        callDelegateWithHttpError(false, new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeSyncCall_ConnectionError() throws Exception {
        callDelegateWithConnectionError(true, new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeAsyncCall_ConnectionError() throws Exception {
        callDelegateWithConnectionError(false,
                new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeSyncCall_ParsingError() throws Exception {
        callDelegateWithParsingError(true, new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeAsyncCall_ParsingError() throws Exception {
        callDelegateWithParsingError(false, new ApiProxy.RPCFailedException(TEST_PACKAGE_NAME, TEST_METHOD_NAME));
    }

    public void testMakeSyncCall_AllErrors() throws Exception {
        callDelegateWithAllErrors(true);
    }

    public void testMakeAsyncCall_AllErrors() throws Exception {
        callDelegateWithAllErrors(false);
    }

    public void testUnknownRpcError() throws Exception {
        RemoteApiPb.RpcError rpcError = new RemoteApiPb.RpcError();
        rpcError.setCode(123456);
        ApiProxyException expectedException = new ApiProxy.UnknownException(TEST_PACKAGE_NAME, TEST_METHOD_NAME);
        callDelegateWithOneError(false, rpcError, null, expectedException);
    }

    public void testCreateRequest() throws Exception {
        VmApiProxyEnvironment environment = createMockEnvironment();
        environment.getAttributes().put(VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.attributeKey, "abc123");
        // assertFalse(environment.getForceReuseApiConnection());

        int timeoutMs = 17 * 1000;
        byte[] apiRequestData = new byte[] { 1, 2, 3, 4 };
        HttpPost request = VmApiProxyDelegate.createRequest(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME,
                apiRequestData, timeoutMs);
        assertEquals(request.getFirstHeader(VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.headerKey).getValue(),
                "abc123");

        assertEquals(request.getEntity().getContentType().getValue(), "application/octet-stream");
        assertEquals(request.getFirstHeader(VmApiProxyDelegate.RPC_STUB_ID_HEADER).getValue(),
                VmApiProxyDelegate.REQUEST_STUB_ID);
        assertEquals(request.getFirstHeader(VmApiProxyDelegate.RPC_METHOD_HEADER).getValue(),
                VmApiProxyDelegate.REQUEST_STUB_METHOD);
        assertEquals(request.getFirstHeader(VmApiProxyDelegate.RPC_DEADLINE_HEADER).getValue(),
                Double.toString(timeoutMs / 1000));

        // Disable keep-alive, workaround for b/.
        // TODO(b/): revert when the underlying TCP retransmission bug is fixed.
        // assertEquals(request.getFirstHeader("Connection").getValue(), "close");

        RemoteApiPb.Request rmtRequest = new RemoteApiPb.Request();
        assertTrue(rmtRequest.parseFrom(request.getEntity().getContent()));
        assertEquals(TEST_PACKAGE_NAME, rmtRequest.getServiceName());
        assertEquals(TEST_METHOD_NAME, rmtRequest.getMethod());
        assertEquals(TICKET, rmtRequest.getRequestId());
        assertTrue(Arrays.equals(apiRequestData, rmtRequest.getRequestAsBytes()));
    }

    /*public void testCreateRequest_ForceReuseApiConnection() throws Exception {
      // TODO(b/): delete when the underlying TCP retransmission bug is fixed.
      VmApiProxyEnvironment environment = createMockEnvironment();
      when(environment.getForceReuseApiConnection()).thenReturn(true);
      assertTrue(environment.getForceReuseApiConnection());
        
      int timeoutMs = 17 * 1000;
      byte[] apiRequestData = new byte[] {1, 2, 3, 4};
      HttpPost request = VmApiProxyDelegate.createRequest(environment, TEST_PACKAGE_NAME,
    TEST_METHOD_NAME, apiRequestData, timeoutMs);
        
      assertNull(request.getFirstHeader("Connection"));
    }*/

    public void testCreateRequest_DapperHeaderForwarding() throws Exception {
        VmApiProxyEnvironment environment = createMockEnvironment();
        environment.getAttributes().put(VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.attributeKey, "abc123");
        HttpPost request = VmApiProxyDelegate.createRequest(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME,
                new byte[0], 0);
        assertEquals(request.getFirstHeader(VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.headerKey).getValue(),
                "abc123");
    }

    public void testCreateRequest_DeadlineFromEnvironment() throws Exception {
        VmApiProxyEnvironment environment = createMockEnvironment();
        final Double deadline = 10.0;
        environment.getAttributes().put(VmApiProxyDelegate.API_DEADLINE_KEY, deadline);

        HttpPost request = VmApiProxyDelegate.createRequest(environment, TEST_PACKAGE_NAME, TEST_METHOD_NAME,
                new byte[0], 0);
        assertEquals(request.getFirstHeader(VmApiProxyDelegate.RPC_DEADLINE_HEADER).getValue(),
                Double.toString(deadline));
    }

    public void testAPIExceptionWrapping() {
        VmApiProxyDelegate delegate = new VmApiProxyDelegate(createMockHttpClient());
        RuntimeException exception = delegate.constructApiException("logservice", "a");

        assertEquals(LogServiceException.class, exception.getClass());
        assertEquals("RCP Failure for API call: logservice a", exception.getMessage());
        exception = delegate.constructApiException("modules", "b");
        assertEquals(ModulesException.class, exception.getClass());
        assertEquals("RCP Failure for API call: modules b", exception.getMessage());
        exception = delegate.constructApiException("datastore_v3", "c");
        assertEquals(DatastoreFailureException.class, exception.getClass());
        assertEquals("RCP Failure for API call: datastore_v3 c", exception.getMessage());
        exception = delegate.constructApiException("barf", "d");
        assertEquals(ApiProxy.RPCFailedException.class, exception.getClass());
        assertEquals("The remote RPC to the application server failed for the call barf.d().",
                exception.getMessage());
    }
}