co.cask.cdap.internal.app.services.http.AppFabricTestBase.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.internal.app.services.http.AppFabricTestBase.java

Source

/*
 * Copyright  2014-2015 Cask Data, Inc.
 *
 * 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 co.cask.cdap.internal.app.services.http;

import co.cask.cdap.api.Config;
import co.cask.cdap.api.metrics.MetricsCollectionService;
import co.cask.cdap.api.schedule.ScheduleSpecification;
import co.cask.cdap.app.program.ManifestFields;
import co.cask.cdap.app.store.ServiceStore;
import co.cask.cdap.client.DatasetClient;
import co.cask.cdap.client.StreamClient;
import co.cask.cdap.client.StreamViewClient;
import co.cask.cdap.client.config.ClientConfig;
import co.cask.cdap.client.config.ConnectionConfig;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.discovery.EndpointStrategy;
import co.cask.cdap.common.discovery.RandomEndpointStrategy;
import co.cask.cdap.common.io.Locations;
import co.cask.cdap.common.lang.jar.BundleJarUtil;
import co.cask.cdap.common.utils.Tasks;
import co.cask.cdap.data.stream.service.StreamService;
import co.cask.cdap.data2.datafabric.dataset.service.DatasetService;
import co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetOpExecutor;
import co.cask.cdap.internal.app.services.AppFabricServer;
import co.cask.cdap.internal.guice.AppFabricTestModule;
import co.cask.cdap.internal.test.AppJarHelper;
import co.cask.cdap.internal.test.PluginJarHelper;
import co.cask.cdap.metadata.MetadataService;
import co.cask.cdap.metrics.query.MetricsQueryService;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.NamespaceMeta;
import co.cask.cdap.proto.RunRecord;
import co.cask.cdap.proto.StreamProperties;
import co.cask.cdap.proto.ViewSpecification;
import co.cask.cdap.proto.artifact.AppRequest;
import co.cask.cdap.proto.artifact.ArtifactRange;
import co.cask.tephra.TransactionManager;
import co.cask.tephra.TransactionSystemClient;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ObjectArrays;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.apache.twill.discovery.Discoverable;
import org.apache.twill.discovery.DiscoveryServiceClient;
import org.apache.twill.discovery.ServiceDiscovered;
import org.apache.twill.filesystem.Location;
import org.apache.twill.filesystem.LocationFactory;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.rules.TemporaryFolder;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.jar.Manifest;
import javax.annotation.Nullable;
import javax.ws.rs.core.MediaType;

/**
 * AppFabric HttpHandler Test classes can extend this class, this will allow the HttpService be setup before
 * running the handler tests, this also gives the ability to run individual test cases.
 */
public abstract class AppFabricTestBase {
    protected static final Gson GSON = new Gson();
    private static final String API_KEY = "SampleTestApiKey";
    private static final Header AUTH_HEADER = new BasicHeader(Constants.Gateway.API_KEY, API_KEY);

    protected static final Type MAP_STRING_STRING_TYPE = new TypeToken<Map<String, String>>() {
    }.getType();
    protected static final Type LIST_MAP_STRING_STRING_TYPE = new TypeToken<List<Map<String, String>>>() {
    }.getType();
    protected static final Type LIST_RUNRECORD_TYPE = new TypeToken<List<RunRecord>>() {
    }.getType();

    protected static final String NONEXISTENT_NAMESPACE = "12jr0j90jf3foieoi33";

    protected static final String TEST_NAMESPACE1 = "testnamespace1";
    protected static final NamespaceMeta TEST_NAMESPACE_META1 = new NamespaceMeta.Builder().setName(TEST_NAMESPACE1)
            .setDescription(TEST_NAMESPACE1).build();
    protected static final String TEST_NAMESPACE2 = "testnamespace2";
    protected static final NamespaceMeta TEST_NAMESPACE_META2 = new NamespaceMeta.Builder().setName(TEST_NAMESPACE2)
            .setDescription(TEST_NAMESPACE2).build();

    private static final String hostname = "127.0.0.1";

    private static int port;
    private static Injector injector;

    private static TransactionManager txManager;
    private static AppFabricServer appFabricServer;
    private static MetricsQueryService metricsService;
    private static MetricsCollectionService metricsCollectionService;
    private static DatasetOpExecutor dsOpService;
    private static DatasetService datasetService;
    private static TransactionSystemClient txClient;
    private static StreamService streamService;
    private static ServiceStore serviceStore;
    private static MetadataService metadataService;
    private static LocationFactory locationFactory;
    private static StreamClient streamClient;
    private static StreamViewClient streamViewClient;
    private static DatasetClient datasetClient;

    @ClassRule
    public static TemporaryFolder tmpFolder = new TemporaryFolder();

    @BeforeClass
    public static void beforeClass() throws Throwable {
        CConfiguration conf = CConfiguration.create();

        conf.set(Constants.AppFabric.SERVER_ADDRESS, hostname);
        conf.set(Constants.CFG_LOCAL_DATA_DIR, tmpFolder.newFolder("data").getAbsolutePath());
        conf.set(Constants.AppFabric.OUTPUT_DIR, System.getProperty("java.io.tmpdir"));
        conf.set(Constants.AppFabric.TEMP_DIR, System.getProperty("java.io.tmpdir"));
        conf.setBoolean(Constants.Dangerous.UNRECOVERABLE_RESET, true);

        injector = Guice.createInjector(new AppFabricTestModule(conf));

        txManager = injector.getInstance(TransactionManager.class);
        txManager.startAndWait();
        dsOpService = injector.getInstance(DatasetOpExecutor.class);
        dsOpService.startAndWait();
        datasetService = injector.getInstance(DatasetService.class);
        datasetService.startAndWait();
        appFabricServer = injector.getInstance(AppFabricServer.class);
        appFabricServer.startAndWait();
        DiscoveryServiceClient discoveryClient = injector.getInstance(DiscoveryServiceClient.class);
        ServiceDiscovered appFabricHttpDiscovered = discoveryClient.discover(Constants.Service.APP_FABRIC_HTTP);
        EndpointStrategy endpointStrategy = new RandomEndpointStrategy(appFabricHttpDiscovered);
        port = endpointStrategy.pick(1, TimeUnit.SECONDS).getSocketAddress().getPort();
        txClient = injector.getInstance(TransactionSystemClient.class);
        metricsCollectionService = injector.getInstance(MetricsCollectionService.class);
        metricsCollectionService.startAndWait();
        metricsService = injector.getInstance(MetricsQueryService.class);
        metricsService.startAndWait();
        streamService = injector.getInstance(StreamService.class);
        streamService.startAndWait();
        serviceStore = injector.getInstance(ServiceStore.class);
        serviceStore.startAndWait();
        metadataService = injector.getInstance(MetadataService.class);
        metadataService.startAndWait();
        locationFactory = getInjector().getInstance(LocationFactory.class);
        streamClient = new StreamClient(getClientConfig(discoveryClient, Constants.Service.STREAMS));
        streamViewClient = new StreamViewClient(getClientConfig(discoveryClient, Constants.Service.STREAMS));
        datasetClient = new DatasetClient(getClientConfig(discoveryClient, Constants.Service.DATASET_MANAGER));
        createNamespaces();
    }

    @AfterClass
    public static void afterClass() throws Exception {
        deleteNamespaces();
        streamService.stopAndWait();
        appFabricServer.stopAndWait();
        metricsCollectionService.stopAndWait();
        metricsService.stopAndWait();
        datasetService.stopAndWait();
        dsOpService.stopAndWait();
        txManager.stopAndWait();
        serviceStore.stopAndWait();
        metadataService.stopAndWait();
    }

    protected static Injector getInjector() {
        return injector;
    }

    protected static TransactionSystemClient getTxClient() {
        return txClient;
    }

    protected static int getPort() {
        return port;
    }

    protected static URI getEndPoint(String path) throws URISyntaxException {
        return new URI("http://" + hostname + ":" + port + path);
    }

    protected static HttpResponse doGet(String resource) throws Exception {
        return doGet(resource, null);
    }

    protected static HttpResponse doGet(String resource, Header[] headers) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpGet get = new HttpGet(getEndPoint(resource));

        if (headers != null) {
            get.setHeaders(ObjectArrays.concat(AUTH_HEADER, headers));
        } else {
            get.setHeader(AUTH_HEADER);
        }
        return client.execute(get);
    }

    protected static HttpResponse execute(HttpUriRequest request) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        request.setHeader(AUTH_HEADER);
        return client.execute(request);
    }

    protected static HttpPost getPost(String resource) throws Exception {
        HttpPost post = new HttpPost(getEndPoint(resource));
        post.setHeader(AUTH_HEADER);
        return post;
    }

    protected static HttpPut getPut(String resource) throws Exception {
        HttpPut put = new HttpPut(getEndPoint(resource));
        put.setHeader(AUTH_HEADER);
        return put;
    }

    protected static HttpResponse doPost(String resource) throws Exception {
        return doPost(resource, null, null);
    }

    protected static HttpResponse doPost(String resource, String body) throws Exception {
        return doPost(resource, body, null);
    }

    protected static HttpResponse doPost(String resource, String body, Header[] headers) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost(getEndPoint(resource));

        if (body != null) {
            post.setEntity(new StringEntity(body));
        }

        if (headers != null) {
            post.setHeaders(ObjectArrays.concat(AUTH_HEADER, headers));
        } else {
            post.setHeader(AUTH_HEADER);
        }
        return client.execute(post);
    }

    protected static HttpResponse doPost(HttpPost post) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        post.setHeader(AUTH_HEADER);
        return client.execute(post);
    }

    protected static HttpResponse doPut(String resource) throws Exception {
        HttpPut put = new HttpPut(getEndPoint(resource));
        put.setHeader(AUTH_HEADER);
        return doPut(resource, null);
    }

    protected static HttpResponse doPut(String resource, String body) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpPut put = new HttpPut(getEndPoint(resource));
        if (body != null) {
            put.setEntity(new StringEntity(body));
        }
        put.setHeader(AUTH_HEADER);
        return client.execute(put);
    }

    protected static HttpResponse doDelete(String resource) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpDelete delete = new HttpDelete(getEndPoint(resource));
        delete.setHeader(AUTH_HEADER);
        return client.execute(delete);
    }

    protected static String readResponse(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        return EntityUtils.toString(entity, "UTF-8");
    }

    protected static <T> T readResponse(HttpResponse response, Type type) throws IOException {
        return GSON.fromJson(readResponse(response), type);
    }

    protected static <T> T readResponse(HttpResponse response, Type type, Gson gson) throws IOException {
        return gson.fromJson(readResponse(response), type);
    }

    protected HttpResponse addAppArtifact(Id.Artifact artifactId, Class<?> cls) throws Exception {

        Location appJar = AppJarHelper.createDeploymentJar(locationFactory, cls, new Manifest());

        try (InputStream artifactInputStream = appJar.getInputStream()) {
            return addArtifact(artifactId, artifactInputStream, null);
        } finally {
            appJar.delete();
        }
    }

    protected HttpResponse addPluginArtifact(Id.Artifact artifactId, Class<?> cls, Manifest manifest,
            Set<ArtifactRange> parents) throws Exception {

        Location appJar = PluginJarHelper.createPluginJar(locationFactory, manifest, cls);

        try (InputStream artifactInputStream = appJar.getInputStream()) {
            return addArtifact(artifactId, artifactInputStream, parents);
        } finally {
            appJar.delete();
        }
    }

    // add an artifact and return the response code
    protected HttpResponse addArtifact(Id.Artifact artifactId, InputStream artifactContents,
            Set<ArtifactRange> parents) throws Exception {
        String path = getVersionedAPIPath("artifacts/" + artifactId.getName(), artifactId.getNamespace().getId());
        HttpEntityEnclosingRequestBase request = getPost(path);
        request.setHeader(Constants.Gateway.API_KEY, "api-key-example");
        request.setHeader("Artifact-Version", artifactId.getVersion().getVersion());
        if (parents != null && !parents.isEmpty()) {
            request.setHeader("Artifact-Extends", Joiner.on('/').join(parents));
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ByteStreams.copy(artifactContents, bos);
        bos.close();
        request.setEntity(new ByteArrayEntity(bos.toByteArray()));
        return execute(request);
    }

    // add artifact properties and return the response code
    protected HttpResponse addArtifactProperties(Id.Artifact artifactId, Map<String, String> properties)
            throws Exception {
        String nonNamespacePath = String.format("artifacts/%s/versions/%s/properties", artifactId.getName(),
                artifactId.getVersion());
        String path = getVersionedAPIPath(nonNamespacePath, artifactId.getNamespace().getId());
        HttpEntityEnclosingRequestBase request = getPut(path);
        request.setEntity(new ByteArrayEntity(properties.toString().getBytes()));
        return execute(request);
    }

    /**
     * Deploys an application.
     */
    protected HttpResponse deploy(Class<?> application) throws Exception {
        return deploy(application, null, null);
    }

    protected HttpResponse deploy(Class<?> application, @Nullable String apiVersion, @Nullable String namespace)
            throws Exception {
        return deploy(application, apiVersion, namespace, null, null);
    }

    protected HttpResponse deploy(Id.Application appId, AppRequest<? extends Config> appRequest) throws Exception {
        HttpEntityEnclosingRequestBase request;
        String deployPath = getVersionedAPIPath("apps/" + appId.getId(), appId.getNamespaceId());
        request = getPut(deployPath);
        request.setHeader(Constants.Gateway.API_KEY, "api-key-example");
        request.setHeader(HttpHeaders.Names.CONTENT_TYPE, MediaType.APPLICATION_JSON);
        request.setEntity(new StringEntity(GSON.toJson(appRequest)));
        return execute(request);
    }

    /**
     * Deploys an application with (optionally) a defined app name and app version
     */
    protected HttpResponse deploy(Class<?> application, @Nullable String apiVersion, @Nullable String namespace,
            @Nullable String appVersion, @Nullable Config appConfig) throws Exception {
        namespace = namespace == null ? Id.Namespace.DEFAULT.getId() : namespace;
        apiVersion = apiVersion == null ? Constants.Gateway.API_VERSION_3_TOKEN : apiVersion;
        appVersion = appVersion == null ? String.format("1.0.%d", System.currentTimeMillis()) : appVersion;

        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(ManifestFields.BUNDLE_VERSION, appVersion);

        File artifactJar = buildAppArtifact(application, application.getSimpleName(), manifest);
        File expandDir = tmpFolder.newFolder();
        BundleJarUtil.unJar(Locations.toLocation(artifactJar), expandDir);

        // Add webapp
        File webAppFile = new File(expandDir, "webapp/default/netlens/src/1.txt");
        webAppFile.getParentFile().mkdirs();
        Files.write("dummy data", webAppFile, Charsets.UTF_8);
        BundleJarUtil.createJar(expandDir, artifactJar);

        HttpEntityEnclosingRequestBase request;
        String versionedApiPath = getVersionedAPIPath("apps/", apiVersion, namespace);
        request = getPost(versionedApiPath);
        request.setHeader(Constants.Gateway.API_KEY, "api-key-example");
        request.setHeader("X-Archive-Name", String.format("%s-%s.jar", application.getSimpleName(), appVersion));
        if (appConfig != null) {
            request.setHeader("X-App-Config", GSON.toJson(appConfig));
        }
        request.setEntity(new FileEntity(artifactJar));
        return execute(request);
    }

    protected String getVersionedAPIPath(String nonVersionedApiPath, String namespace) {
        return getVersionedAPIPath(nonVersionedApiPath, Constants.Gateway.API_VERSION_3_TOKEN, namespace);
    }

    protected String getVersionedAPIPath(String nonVersionedApiPath, String version, String namespace) {
        if (!Constants.Gateway.API_VERSION_3_TOKEN.equals(version)) {
            throw new IllegalArgumentException(
                    String.format("Unsupported version '%s'. Only v3 is supported.", version));
        }
        Preconditions.checkArgument(namespace != null, "Namespace cannot be null for v3 APIs.");
        return String.format("/%s/namespaces/%s/%s", version, namespace, nonVersionedApiPath);
    }

    protected List<JsonObject> getAppList(String namespace) throws Exception {
        HttpResponse response = doGet(
                getVersionedAPIPath("apps/", Constants.Gateway.API_VERSION_3_TOKEN, namespace));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        Type typeToken = new TypeToken<List<JsonObject>>() {
        }.getType();
        return readResponse(response, typeToken);
    }

    protected JsonObject getAppDetails(String namespace, String appName) throws Exception {
        HttpResponse response = doGet(getVersionedAPIPath(String.format("apps/%s", appName),
                Constants.Gateway.API_VERSION_3_TOKEN, namespace));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        Assert.assertEquals("application/json", response.getFirstHeader(HttpHeaders.Names.CONTENT_TYPE).getValue());
        Type typeToken = new TypeToken<JsonObject>() {
        }.getType();
        return readResponse(response, typeToken);
    }

    protected void assertRunHistory(final Id.Program program, final String status, int expected, long timeout,
            TimeUnit timeoutUnit) throws Exception {
        Tasks.waitFor(expected, new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return getProgramRuns(program, status).size();
            }
        }, timeout, timeoutUnit, 100, TimeUnit.MILLISECONDS);
    }

    /**
     * Checks the given schedule states.
     */
    protected void assertSchedule(final Id.Program program, final String scheduleName, boolean scheduled,
            long timeout, TimeUnit timeoutUnit) throws Exception {
        Tasks.waitFor(scheduled, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                String statusURL = getVersionedAPIPath(
                        String.format("apps/%s/schedules/%s/status", program.getApplicationId(), scheduleName),
                        Constants.Gateway.API_VERSION_3_TOKEN, program.getNamespaceId());
                HttpResponse response = doGet(statusURL);
                Preconditions.checkState(200 == response.getStatusLine().getStatusCode());
                Map<String, String> result = GSON.fromJson(EntityUtils.toString(response.getEntity()),
                        MAP_STRING_STRING_TYPE);
                return result != null && "SCHEDULED".equals(result.get("status"));
            }
        }, timeout, timeoutUnit, 100, TimeUnit.MILLISECONDS);
    }

    protected void deleteApp(Id.Application app, int expectedResponseCode) throws Exception {
        HttpResponse response = doDelete(getVersionedAPIPath("apps/" + app.getId(), app.getNamespaceId()));
        Assert.assertEquals(expectedResponseCode, response.getStatusLine().getStatusCode());
    }

    protected void deleteApp(final Id.Application app, int expectedResponseCode, long timeout, TimeUnit timeoutUnit)
            throws Exception {
        Tasks.waitFor(expectedResponseCode, new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                HttpResponse response = doDelete(getVersionedAPIPath("apps/" + app.getId(), app.getNamespaceId()));
                return response.getStatusLine().getStatusCode();
            }
        }, timeout, timeoutUnit, 100, TimeUnit.MILLISECONDS);
    }

    protected List<JsonObject> getArtifacts(String namespace) throws Exception {
        HttpResponse response = doGet(getVersionedAPIPath("artifacts", namespace));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        Type typeToken = new TypeToken<List<JsonObject>>() {
        }.getType();
        return readResponse(response, typeToken);
    }

    protected void deleteArtifact(Id.Artifact artifact, int expectedResponseCode) throws Exception {
        String path = String.format("artifacts/%s/versions/%s", artifact.getName(),
                artifact.getVersion().getVersion());
        HttpResponse response = doDelete(getVersionedAPIPath(path, artifact.getNamespace().getId()));
        Assert.assertEquals(expectedResponseCode, response.getStatusLine().getStatusCode());
    }

    /**
     * Starts the given program.
     */
    protected void startProgram(Id.Program program) throws Exception {
        startProgram(program, 200);
    }

    /**
     * Tries to start the given program and expect the call completed with the status.
     */
    protected void startProgram(Id.Program program, int expectedStatusCode) throws Exception {
        startProgram(program, ImmutableMap.<String, String>of(), expectedStatusCode);
    }

    /**
     * Starts the given program with the given runtime arguments.
     */
    protected void startProgram(Id.Program program, Map<String, String> args) throws Exception {
        startProgram(program, args, 200);
    }

    /**
     * Tries to start the given program with the given runtime arguments and expect the call completed with the status.
     */
    protected void startProgram(Id.Program program, Map<String, String> args, int expectedStatusCode)
            throws Exception {
        String path = String.format("apps/%s/%s/%s/start", program.getApplicationId(),
                program.getType().getCategoryName(), program.getId());
        HttpResponse response = doPost(getVersionedAPIPath(path, program.getNamespaceId()), GSON.toJson(args));
        Assert.assertEquals(expectedStatusCode, response.getStatusLine().getStatusCode());
    }

    /**
     * Tries to start the given program with the given runtime arguments and expect the call completed with the status.
     */
    protected void debugProgram(Id.Program program, int expectedStatusCode) throws Exception {
        String path = String.format("apps/%s/%s/%s/debug", program.getApplicationId(),
                program.getType().getCategoryName(), program.getId());
        HttpResponse response = doPost(getVersionedAPIPath(path, program.getNamespaceId()),
                GSON.toJson(ImmutableMap.<String, String>of()));
        Assert.assertEquals(expectedStatusCode, response.getStatusLine().getStatusCode());
    }

    /**
     * Stops the given program.
     */
    protected void stopProgram(Id.Program program) throws Exception {
        stopProgram(program, 200);
    }

    /**
     * Tries to stop the given program and expect the call completed with the status.
     */
    protected void stopProgram(Id.Program program, int expectedStatusCode) throws Exception {
        stopProgram(program, null, expectedStatusCode);
    }

    protected void stopProgram(Id.Program program, String runId, int expectedStatusCode) throws Exception {
        stopProgram(program, runId, expectedStatusCode, null);
    }

    protected void stopProgram(Id.Program program, String runId, int expectedStatusCode, String expectedMessage)
            throws Exception {
        String path;
        if (runId == null) {
            path = String.format("apps/%s/%s/%s/stop", program.getApplicationId(),
                    program.getType().getCategoryName(), program.getId());
        } else {
            path = String.format("apps/%s/%s/%s/runs/%s/stop", program.getApplicationId(),
                    program.getType().getCategoryName(), program.getId(), runId);
        }
        HttpResponse response = doPost(getVersionedAPIPath(path, program.getNamespaceId()));
        Assert.assertEquals(expectedStatusCode, response.getStatusLine().getStatusCode());
        if (expectedMessage != null) {
            Assert.assertEquals(expectedMessage, EntityUtils.toString(response.getEntity()));
        }
    }

    /**
     * Waits for the given program to transit to the given state.
     */
    protected void waitState(final Id.Program programId, String state) throws Exception {
        Tasks.waitFor(state, new Callable<String>() {
            @Override
            public String call() throws Exception {
                String path = String.format("apps/%s/%s/%s/status", programId.getApplicationId(),
                        programId.getType().getCategoryName(), programId.getId());
                HttpResponse response = doGet(getVersionedAPIPath(path, programId.getNamespaceId()));
                JsonObject status = GSON.fromJson(EntityUtils.toString(response.getEntity()), JsonObject.class);
                if (status == null || !status.has("status")) {
                    return null;
                }
                return status.get("status").getAsString();
            }
        }, 60, TimeUnit.SECONDS);
    }

    private static void createNamespaces() throws Exception {

        HttpResponse response = doPut(
                String.format("%s/namespaces/%s", Constants.Gateway.API_VERSION_3, TEST_NAMESPACE1),
                GSON.toJson(TEST_NAMESPACE_META1));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());

        response = doPut(String.format("%s/namespaces/%s", Constants.Gateway.API_VERSION_3, TEST_NAMESPACE2),
                GSON.toJson(TEST_NAMESPACE_META2));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
    }

    private static void deleteNamespaces() throws Exception {
        HttpResponse response = doDelete(
                String.format("%s/unrecoverable/namespaces/%s", Constants.Gateway.API_VERSION_3, TEST_NAMESPACE1));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        response = doDelete(
                String.format("%s/unrecoverable/namespaces/%s", Constants.Gateway.API_VERSION_3, TEST_NAMESPACE2));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
    }

    protected String getProgramStatus(Id.Program program) throws Exception {
        return getStatus(programStatus(program));
    }

    protected void programStatus(Id.Program program, int expectedStatus) throws Exception {
        Assert.assertEquals(expectedStatus, programStatus(program).getStatusLine().getStatusCode());
    }

    protected HttpResponse programStatus(Id.Program program) throws Exception {
        String path = String.format("apps/%s/%s/%s/status", program.getApplicationId(),
                program.getType().getCategoryName(), program.getId());
        return doGet(getVersionedAPIPath(path, program.getNamespaceId()));
    }

    private String getStatus(HttpResponse response) throws Exception {
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        String s = EntityUtils.toString(response.getEntity());
        Map<String, String> o = GSON.fromJson(s, MAP_STRING_STRING_TYPE);
        return o.get("status");
    }

    protected int suspendSchedule(String namespace, String appName, String schedule) throws Exception {
        String scheduleSuspend = String.format("apps/%s/schedules/%s/suspend", appName, schedule);
        String versionedScheduledSuspend = getVersionedAPIPath(scheduleSuspend,
                Constants.Gateway.API_VERSION_3_TOKEN, namespace);
        HttpResponse response = doPost(versionedScheduledSuspend);
        return response.getStatusLine().getStatusCode();
    }

    protected int resumeSchedule(String namespace, String appName, String schedule) throws Exception {
        String scheduleResume = String.format("apps/%s/schedules/%s/resume", appName, schedule);
        HttpResponse response = doPost(
                getVersionedAPIPath(scheduleResume, Constants.Gateway.API_VERSION_3_TOKEN, namespace));
        return response.getStatusLine().getStatusCode();
    }

    protected List<ScheduleSpecification> getSchedules(String namespace, String appName, String workflowName)
            throws Exception {
        String schedulesUrl = String.format("apps/%s/workflows/%s/schedules", appName, workflowName);
        String versionedUrl = getVersionedAPIPath(schedulesUrl, Constants.Gateway.API_VERSION_3_TOKEN, namespace);
        HttpResponse response = doGet(versionedUrl);
        String json = EntityUtils.toString(response.getEntity());
        return GSON.fromJson(json, new TypeToken<List<ScheduleSpecification>>() {
        }.getType());
    }

    protected void verifyNoRunWithStatus(final Id.Program program, final String status) throws Exception {
        Tasks.waitFor(0, new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return getProgramRuns(program, status).size();
            }
        }, 60, TimeUnit.SECONDS);
    }

    protected void verifyProgramRuns(Id.Program program, String status) throws Exception {
        verifyProgramRuns(program, status, 0);
    }

    protected void verifyProgramRuns(final Id.Program program, final String status, final int expected)
            throws Exception {
        Tasks.waitFor(true, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return getProgramRuns(program, status).size() > expected;
            }
        }, 60, TimeUnit.SECONDS);
    }

    protected List<RunRecord> getProgramRuns(Id.Program program, String status) throws Exception {
        String path = String.format("apps/%s/%s/%s/runs?status=%s", program.getApplicationId(),
                program.getType().getCategoryName(), program.getId(), status);
        HttpResponse response = doGet(getVersionedAPIPath(path, program.getNamespaceId()));
        Assert.assertEquals(200, response.getStatusLine().getStatusCode());
        String json = EntityUtils.toString(response.getEntity());
        return GSON.fromJson(json, LIST_RUNRECORD_TYPE);
    }

    protected boolean createOrUpdateView(Id.Stream.View viewId, ViewSpecification spec) throws Exception {
        return streamViewClient.createOrUpdate(viewId, spec);
    }

    protected void deleteStream(Id.Stream stream) throws Exception {
        streamClient.delete(stream);
    }

    protected void deleteView(Id.Stream.View view) throws Exception {
        streamViewClient.delete(view);
    }

    protected void setStreamProperties(Id.Stream stream, StreamProperties props) throws Exception {
        streamClient.setStreamProperties(stream, props);
    }

    protected void updateDatasetProperties(Id.DatasetInstance dataset, Map<String, String> properties)
            throws Exception {
        datasetClient.updateExisting(dataset, properties);
    }

    protected void deleteDataset(Id.DatasetInstance datasetInstance) throws Exception {
        datasetClient.delete(datasetInstance);
    }

    protected HttpResponse createNamespace(String id) throws Exception {
        return doPut(String.format("%s/namespaces/%s", Constants.Gateway.API_VERSION_3, id));
    }

    protected HttpResponse deleteNamespace(String name) throws Exception {
        return doDelete(String.format("%s/unrecoverable/namespaces/%s", Constants.Gateway.API_VERSION_3, name));
    }

    protected HttpResponse createNamespace(String metadata, String id) throws Exception {
        return doPut(String.format("%s/namespaces/%s", Constants.Gateway.API_VERSION_3, id), metadata);
    }

    protected HttpResponse listAllNamespaces() throws Exception {
        return doGet(String.format("%s/namespaces", Constants.Gateway.API_VERSION_3));
    }

    protected HttpResponse getNamespace(String name) throws Exception {
        Preconditions.checkArgument(name != null, "namespace name cannot be null");
        return doGet(String.format("%s/namespaces/%s", Constants.Gateway.API_VERSION_3, name));
    }

    protected HttpResponse deleteNamespaceData(String name) throws Exception {
        return doDelete(
                String.format("%s/unrecoverable/namespaces/%s/datasets", Constants.Gateway.API_VERSION_3, name));
    }

    protected HttpResponse setProperties(String id, NamespaceMeta meta) throws Exception {
        return doPut(String.format("%s/namespaces/%s/properties", Constants.Gateway.API_VERSION_3, id),
                GSON.toJson(meta));
    }

    protected File buildAppArtifact(Class<?> cls, String name) throws IOException {
        return buildAppArtifact(cls, name, new Manifest());
    }

    protected File buildAppArtifact(Class<?> cls, String name, Manifest manifest) throws IOException {
        if (!name.endsWith(".jar")) {
            name += ".jar";
        }
        Location appJar = AppJarHelper.createDeploymentJar(locationFactory, cls, manifest);
        File destination = new File(tmpFolder.newFolder(), name);
        Files.copy(Locations.newInputSupplier(appJar), destination);
        return destination;
    }

    private static ClientConfig getClientConfig(DiscoveryServiceClient discoveryClient, String service) {
        EndpointStrategy endpointStrategy = new RandomEndpointStrategy(discoveryClient.discover(service));
        Discoverable discoverable = endpointStrategy.pick(1, TimeUnit.SECONDS);
        Assert.assertNotNull(discoverable);
        int port = discoverable.getSocketAddress().getPort();
        ConnectionConfig connectionConfig = ConnectionConfig.builder().setHostname(hostname).setPort(port).build();
        return ClientConfig.builder().setConnectionConfig(connectionConfig).build();
    }
}