/*
* 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.handlers;
import co.cask.cdap.AppWithDataset;
import co.cask.cdap.AppWithDatasetDuplicate;
import co.cask.cdap.AppWithNoServices;
import co.cask.cdap.BloatedWordCountApp;
import co.cask.cdap.ConfigTestApp;
import co.cask.cdap.WordCountApp;
import co.cask.cdap.api.Config;
import co.cask.cdap.common.NamespaceNotFoundException;
import co.cask.cdap.common.NotFoundException;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.gateway.handlers.AppLifecycleHttpHandler;
import co.cask.cdap.internal.app.services.http.AppFabricTestBase;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.ProgramType;
import co.cask.cdap.proto.artifact.AppRequest;
import co.cask.cdap.proto.artifact.ArtifactSummary;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpResponse;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Tests for {@link AppLifecycleHttpHandler}
*/
public class AppLifecycleHttpHandlerTest extends AppFabricTestBase {
/**
* Tests deploying an application in a non-existing non-default namespace.
*/
@Test
public void testDeployNonExistingNamespace() throws Exception {
HttpResponse response = deploy(WordCountApp.class, Constants.Gateway.API_VERSION_3_TOKEN, "random");
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
NotFoundException nfe = new NamespaceNotFoundException(Id.Namespace.from("random"));
Assert.assertEquals(nfe.getMessage(), readResponse(response));
}
/**
* Tests deploying an application.
*/
@Test
public void testDeployValid() throws Exception {
HttpResponse response = deploy(WordCountApp.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
response = doDelete(getVersionedAPIPath("apps/", Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
}
@Test
public void testDeployWithExtraConfig() throws Exception {
Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "extraConfig", "1.0.0-SNAPSHOT");
Id.Application appId = Id.Application.from(Id.Namespace.DEFAULT, "ExtraConfigApp");
HttpResponse response = addAppArtifact(artifactId, AppWithNoServices.class);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
response = deploy(appId, new AppRequest<>(ArtifactSummary.from(artifactId), new ExtraConfig()));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
deleteApp(appId, 200);
deleteArtifact(artifactId, 200);
}
@Test
public void testAppWithConfig() throws Exception {
Id.Application appId = Id.Application.from(Id.Namespace.DEFAULT, "ConfigApp");
Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "appWithConfig", "1.0.0-SNAPSHOT");
HttpResponse response = addAppArtifact(artifactId, ConfigTestApp.class);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
ConfigTestApp.ConfigClass config = new ConfigTestApp.ConfigClass("abc", "def");
response = deploy(appId, new AppRequest<>(ArtifactSummary.from(artifactId), config));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
JsonObject appDetails = getAppDetails(Id.Namespace.DEFAULT.getId(), "ConfigApp");
Assert.assertEquals(GSON.toJson(config), appDetails.get("configuration").getAsString());
deleteApp(appId, 200);
deleteArtifact(artifactId, 200);
}
@Test
public void testDeployUsingNonexistantArtifact404() throws Exception {
Id.Application appId = Id.Application.from(Id.Namespace.DEFAULT, "badapp");
AppRequest<Config> appRequest =
new AppRequest<>(new ArtifactSummary("something", "1.0.0"), null);
HttpResponse response = deploy(appId, appRequest);
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
}
@Test
public void testDeployUsingArtifact() throws Exception {
Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "configapp", "1.0.0");
addAppArtifact(artifactId, ConfigTestApp.class);
Id.Application appId = Id.Application.from(Id.Namespace.DEFAULT, "cfgApp");
ConfigTestApp.ConfigClass config = new ConfigTestApp.ConfigClass("abc", "def");
AppRequest<ConfigTestApp.ConfigClass> request = new AppRequest<>(
new ArtifactSummary(artifactId.getName(), artifactId.getVersion().getVersion()), config);
Assert.assertEquals(200, deploy(appId, request).getStatusLine().getStatusCode());
JsonObject appDetails = getAppDetails(Id.Namespace.DEFAULT.getId(), appId.getId());
Assert.assertEquals(GSON.toJson(config), appDetails.get("configuration").getAsString());
Assert.assertEquals(200,
doDelete(getVersionedAPIPath("apps/" + appId.getId(), appId.getNamespaceId())).getStatusLine().getStatusCode());
}
/**
* Tests deploying an invalid application.
*/
@Test
public void testDeployInvalid() throws Exception {
HttpResponse response = deploy(String.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
Assert.assertNotNull(response.getEntity());
Assert.assertTrue(response.getEntity().getContentLength() > 0);
}
/**
* Tests deploying an application with dataset same name as existing dataset but a different type
*/
@Test
public void testDeployFailure() throws Exception {
HttpResponse response = deploy(AppWithDataset.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertNotNull(response.getEntity());
response = deploy(AppWithDatasetDuplicate.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
Assert.assertNotNull(response.getEntity());
}
@Test
public void testListNonExistentNamespace() throws Exception {
HttpResponse response = doGet(getVersionedAPIPath("apps/", Constants.Gateway.API_VERSION_3_TOKEN,
NONEXISTENT_NAMESPACE));
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
}
@Test
public void testListAndGet() throws Exception {
final String appName = "AppWithDatasetName";
Id.Namespace ns1 = Id.Namespace.from(TEST_NAMESPACE1);
Id.Namespace ns2 = Id.Namespace.from(TEST_NAMESPACE2);
Id.Artifact ns2ArtifactId = Id.Artifact.from(ns2, "bloatedListAndGet", "1.0.0-SNAPSHOT");
//deploy without name to testnamespace1
HttpResponse response = deploy(BloatedWordCountApp.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
//deploy with name to testnamespace2
response = addAppArtifact(ns2ArtifactId, BloatedWordCountApp.class);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Id.Application appId = Id.Application.from(ns2, appName);
response = deploy(appId, new AppRequest<Config>(ArtifactSummary.from(ns2ArtifactId)));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertNotNull(response.getEntity());
//verify testnamespace1 has 1 app
List<JsonObject> apps = getAppList(TEST_NAMESPACE1);
Assert.assertEquals(1, apps.size());
//verify testnamespace2 has 1 app
apps = getAppList(TEST_NAMESPACE2);
Assert.assertEquals(1, apps.size());
//get and verify app details in testnamespace1
JsonObject result = getAppDetails(TEST_NAMESPACE1, "WordCountApp");
Assert.assertEquals("WordCountApp", result.get("name").getAsString());
Assert.assertEquals("Application for counting words", result.get("description").getAsString());
JsonArray streams = result.get("streams").getAsJsonArray();
Assert.assertEquals(1, streams.size());
JsonObject stream = streams.get(0).getAsJsonObject();
Assert.assertEquals("text", stream.get("name").getAsString());
JsonArray datasets = result.get("datasets").getAsJsonArray();
Assert.assertEquals(1, datasets.size());
JsonObject dataset = datasets.get(0).getAsJsonObject();
Assert.assertEquals("mydataset", dataset.get("name").getAsString());
JsonArray programs = result.get("programs").getAsJsonArray();
Assert.assertEquals(6, programs.size());
JsonObject[] progs = new JsonObject[programs.size()];
for (int i = 0; i < programs.size(); i++) {
progs[i] = programs.get(i).getAsJsonObject();
}
// sort the programs by name to make this test deterministic
Arrays.sort(progs, new Comparator<JsonObject>() {
@Override
public int compare(JsonObject o1, JsonObject o2) {
return o1.get("name").getAsString().compareTo(o2.get("name").getAsString());
}
});
int i = 0;
Assert.assertEquals("Worker", progs[i].get("type").getAsString());
Assert.assertEquals("LazyGuy", progs[i].get("name").getAsString());
Assert.assertEquals("nothing to describe", progs[i].get("description").getAsString());
i++;
Assert.assertEquals("Workflow", progs[i].get("type").getAsString());
Assert.assertEquals("SingleStep", progs[i].get("name").getAsString());
Assert.assertEquals("", progs[i].get("description").getAsString());
i++;
Assert.assertEquals("Spark", progs[i].get("type").getAsString());
Assert.assertEquals("SparklingNothing", progs[i].get("name").getAsString());
Assert.assertEquals("Spark program that does nothing", progs[i].get("description").getAsString());
i++;
Assert.assertEquals("Mapreduce", progs[i].get("type").getAsString());
Assert.assertEquals("VoidMapReduceJob", progs[i].get("name").getAsString());
Assert.assertTrue(progs[i].get("description").getAsString().startsWith("Mapreduce that does nothing"));
i++;
Assert.assertEquals("Flow", progs[i].get("type").getAsString());
Assert.assertEquals("WordCountFlow", progs[i].get("name").getAsString());
Assert.assertEquals("Flow for counting words", progs[i].get("description").getAsString());
i++;
Assert.assertEquals("Service", progs[i].get("type").getAsString());
Assert.assertEquals("WordFrequencyService", progs[i].get("name").getAsString());
Assert.assertEquals("", progs[i].get("description").getAsString());
//get and verify app details in testnamespace2
result = getAppDetails(TEST_NAMESPACE2, appName);
Assert.assertEquals(appName, result.get("name").getAsString());
//delete app in testnamespace1
response = doDelete(getVersionedAPIPath("apps/", Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
//delete app in testnamespace2
response = doDelete(getVersionedAPIPath("apps/", Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE2));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
deleteArtifact(ns2ArtifactId, 200);
}
/**
* Tests deleting an application.
*/
@Test
public void testDelete() throws Exception {
// Delete an non-existing app
HttpResponse response = doDelete(getVersionedAPIPath("apps/XYZ", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE1));
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
deploy(WordCountApp.class, Constants.Gateway.API_VERSION_3_TOKEN, TEST_NAMESPACE1);
Id.Program program = Id.Program.from(TEST_NAMESPACE1, "WordCountApp", ProgramType.FLOW, "WordCountFlow");
startProgram(program);
waitState(program, "RUNNING");
// Try to delete an App while its flow is running
response = doDelete(getVersionedAPIPath("apps/WordCountApp", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE1));
Assert.assertEquals(403, response.getStatusLine().getStatusCode());
stopProgram(program);
waitState(program, "STOPPED");
// Delete the app in the wrong namespace
response = doDelete(getVersionedAPIPath("apps/WordCountApp", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE2));
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
//Delete the App after stopping the flow
response = doDelete(getVersionedAPIPath("apps/WordCountApp/", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE1));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
response = doDelete(getVersionedAPIPath("apps/WordCountApp/", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE1));
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
// deleting the app should not delete the artifact
response = doGet(getVersionedAPIPath("artifacts/WordCountApp", Constants.Gateway.API_VERSION_3_TOKEN,
TEST_NAMESPACE1));
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
List<ArtifactSummary> summaries = readResponse(response, new TypeToken<List<ArtifactSummary>>() { }.getType());
Assert.assertFalse(summaries.isEmpty());
}
private static class ExtraConfig extends Config {
@SuppressWarnings("unused")
private final int x = 5;
}
}