/*
* Copyright © 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.ConfigTestApp;
import co.cask.cdap.WordCountApp;
import co.cask.cdap.api.artifact.ApplicationClass;
import co.cask.cdap.api.artifact.ArtifactClasses;
import co.cask.cdap.api.artifact.ArtifactScope;
import co.cask.cdap.api.artifact.ArtifactVersion;
import co.cask.cdap.api.data.schema.Schema;
import co.cask.cdap.api.plugin.PluginPropertyField;
import co.cask.cdap.app.program.ManifestFields;
import co.cask.cdap.client.MetadataClient;
import co.cask.cdap.client.config.ClientConfig;
import co.cask.cdap.client.config.ConnectionConfig;
import co.cask.cdap.common.NotFoundException;
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.gateway.handlers.ArtifactHttpHandler;
import co.cask.cdap.internal.app.runtime.artifact.ArtifactRepository;
import co.cask.cdap.internal.app.runtime.artifact.plugin.Plugin1;
import co.cask.cdap.internal.app.runtime.artifact.plugin.Plugin2;
import co.cask.cdap.internal.app.runtime.artifact.plugin.endpointtest.PluginEndpointContextTestPlugin;
import co.cask.cdap.internal.app.runtime.artifact.plugin.invalid.InvalidPlugin;
import co.cask.cdap.internal.app.runtime.artifact.plugin.invalid2.InvalidPluginMethodParams;
import co.cask.cdap.internal.app.runtime.artifact.plugin.invalid3.InvalidPluginMethodParamType;
import co.cask.cdap.internal.app.runtime.artifact.plugin.p3.CallablePlugin;
import co.cask.cdap.internal.app.runtime.artifact.plugin.p4.CallingPlugin;
import co.cask.cdap.internal.app.runtime.artifact.plugin.p5.PluginWithPojo;
import co.cask.cdap.internal.app.runtime.artifact.plugin.p5.TestData;
import co.cask.cdap.internal.app.services.http.AppFabricTestBase;
import co.cask.cdap.internal.io.ReflectionSchemaGenerator;
import co.cask.cdap.internal.io.SchemaTypeAdapter;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.artifact.ArtifactInfo;
import co.cask.cdap.proto.artifact.ArtifactRange;
import co.cask.cdap.proto.artifact.ArtifactSummary;
import co.cask.cdap.proto.artifact.PluginInfo;
import co.cask.cdap.proto.artifact.PluginSummary;
import co.cask.cdap.proto.id.NamespaceId;
import co.cask.cdap.proto.metadata.MetadataRecord;
import co.cask.cdap.proto.metadata.MetadataScope;
import co.cask.common.http.HttpRequest;
import co.cask.common.http.HttpRequests;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.apache.http.HttpResponse;
import org.apache.twill.discovery.Discoverable;
import org.apache.twill.discovery.DiscoveryServiceClient;
import org.apache.twill.discovery.ServiceDiscovered;
import org.apache.twill.filesystem.LocalLocationFactory;
import org.apache.twill.filesystem.LocationFactory;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.jar.Manifest;
import javax.annotation.Nullable;
import javax.ws.rs.HttpMethod;
/**
* Tests for {@link ArtifactHttpHandler}
*/
public class ArtifactHttpHandlerTest extends AppFabricTestBase {
private static final ReflectionSchemaGenerator schemaGenerator = new ReflectionSchemaGenerator(false);
private static final Type ARTIFACTS_TYPE = new TypeToken<Set<ArtifactSummary>>() { }.getType();
private static final Type PLUGIN_SUMMARIES_TYPE = new TypeToken<Set<PluginSummary>>() { }.getType();
private static final Type PLUGIN_INFOS_TYPE = new TypeToken<Set<PluginInfo>>() { }.getType();
private static final Type PLUGIN_TYPES_TYPE = new TypeToken<Set<String>>() { }.getType();
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(Schema.class, new SchemaTypeAdapter())
.create();
private static ArtifactRepository artifactRepository;
private static LocationFactory locationFactory;
private static MetadataClient metadataClient;
protected static ClientConfig clientConfig;
@BeforeClass
public static void setup() throws IOException {
artifactRepository = getInjector().getInstance(ArtifactRepository.class);
locationFactory = new LocalLocationFactory(tmpFolder.newFolder());
DiscoveryServiceClient discoveryClient = getInjector().getInstance(DiscoveryServiceClient.class);
ServiceDiscovered metadataHttpDiscovered = discoveryClient.discover(Constants.Service.METADATA_SERVICE);
EndpointStrategy endpointStrategy = new RandomEndpointStrategy(metadataHttpDiscovered);
Discoverable discoverable = endpointStrategy.pick(1, TimeUnit.SECONDS);
Assert.assertNotNull(discoverable);
String host = "127.0.0.1";
int port = discoverable.getSocketAddress().getPort();
ConnectionConfig connectionConfig = ConnectionConfig.builder().setHostname(host).setPort(port).build();
clientConfig = ClientConfig.builder().setConnectionConfig(connectionConfig).build();
metadataClient = new MetadataClient(clientConfig);
}
@After
public void wipeData() throws Exception {
artifactRepository.clear(NamespaceId.DEFAULT);
artifactRepository.clear(NamespaceId.SYSTEM);
}
// test deploying an application artifact that has a non-application as its main class
@Test
public void testAddBadApp() throws Exception {
Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Assert.assertEquals(HttpResponseStatus.BAD_REQUEST.getCode(),
addAppArtifact(artifactId, ArtifactSummary.class).getStatusLine().getStatusCode());
}
@Test
public void testNotFound() throws IOException, URISyntaxException {
Assert.assertTrue(getArtifacts(Id.Namespace.DEFAULT).isEmpty());
Assert.assertNull(getArtifacts(Id.Namespace.DEFAULT, "wordcount"));
Assert.assertNull(getArtifact(Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0")));
}
@Test
public void testAddAndGet() throws Exception {
// add 2 versions of the same app that doesn't use config
Id.Artifact wordcountId1 = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Id.Artifact wordcountId2 = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "2.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordcountId1, WordCountApp.class).getStatusLine().getStatusCode());
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordcountId2, WordCountApp.class).getStatusLine().getStatusCode());
// and 1 version of another app that uses a config
Id.Artifact configTestAppId = Id.Artifact.from(Id.Namespace.DEFAULT, "cfgtest", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(configTestAppId, ConfigTestApp.class).getStatusLine().getStatusCode());
// test get /artifacts endpoint
Set<ArtifactSummary> expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0"),
new ArtifactSummary("wordcount", "2.0.0"),
new ArtifactSummary("cfgtest", "1.0.0")
);
Set<ArtifactSummary> actualArtifacts = getArtifacts(Id.Namespace.DEFAULT);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts/wordcount endpoint
expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0"),
new ArtifactSummary("wordcount", "2.0.0")
);
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT, "wordcount");
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts/cfgtest/versions/1.0.0 endpoint
Schema appConfigSchema = schemaGenerator.generate(ConfigTestApp.ConfigClass.class);
ArtifactClasses classes = ArtifactClasses.builder()
.addApp(new ApplicationClass(ConfigTestApp.class.getName(), "", appConfigSchema))
.build();
ArtifactInfo expectedInfo = new ArtifactInfo("cfgtest", "1.0.0", ArtifactScope.USER,
classes, ImmutableMap.<String, String>of());
ArtifactInfo actualInfo = getArtifact(configTestAppId);
Assert.assertEquals(expectedInfo, actualInfo);
}
@Test
public void testDeletePropertiesAndArtifacts() throws Exception {
// add 2 versions of the same app that doesn't use config
Id.Artifact wordcountId1 = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordcountId1, WordCountApp.class).getStatusLine().getStatusCode());
// test get /artifacts endpoint
Set<ArtifactSummary> expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0")
);
Set<ArtifactSummary> actualArtifacts = getArtifacts(Id.Namespace.DEFAULT);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
addArtifactProperties(wordcountId1, ImmutableMap.of("key1", "value1", "key2", "value2", "key3", "value3"));
Assert.assertEquals(ImmutableMap.of("key1", "value1", "key2", "value2", "key3", "value3"),
getArtifactProperties(wordcountId1));
// delete a single property
deleteArtifact(wordcountId1, false, "key1", 200);
Assert.assertEquals(ImmutableMap.of("key2", "value2", "key3", "value3"), getArtifactProperties(wordcountId1));
// delete all properties
deleteArtifact(wordcountId1, false, null, 200);
Assert.assertEquals(ImmutableMap.of(), getArtifactProperties(wordcountId1));
Set<MetadataRecord> metadataRecords = metadataClient.getMetadata(wordcountId1, MetadataScope.USER);
Assert.assertEquals(1, metadataRecords.size());
Assert.assertEquals(new MetadataRecord(wordcountId1, MetadataScope.USER), metadataRecords.iterator().next());
// delete artifact
deleteArtifact(wordcountId1, true, null, 200);
try {
metadataClient.getMetadata(wordcountId1, MetadataScope.USER);
Assert.fail("Should not reach here");
} catch (NotFoundException e) {
// no-op
}
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT);
Assert.assertTrue(actualArtifacts.isEmpty());
}
protected void deleteArtifact(Id.Artifact artifact, boolean deleteArtifact,
@Nullable String property, int expectedResponseCode) throws Exception {
String path = String.format("artifacts/%s/versions/%s/", artifact.getName(), artifact.getVersion().getVersion());
if (!deleteArtifact) {
path += property == null ? "properties" : String.format("properties/%s", property);
}
HttpResponse response = doDelete(getVersionedAPIPath(path, artifact.getNamespace().getId()));
Assert.assertEquals(expectedResponseCode, response.getStatusLine().getStatusCode());
}
@Test
public void testSystemArtifacts() throws Exception {
// add the app in the default namespace
Id.Artifact defaultId = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(defaultId, WordCountApp.class).getStatusLine().getStatusCode());
// add a system artifact. currently can't do this through the rest api (by design)
// so bypass it and use the repository directly
Id.Artifact systemId = Id.Artifact.from(Id.Namespace.SYSTEM, "wordcount", "1.0.0");
File systemArtifact = buildAppArtifact(WordCountApp.class, "wordcount-1.0.0.jar");
artifactRepository.addArtifact(systemId, systemArtifact, new HashSet<ArtifactRange>());
// test get /artifacts
Set<ArtifactSummary> expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.USER),
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.SYSTEM)
);
Set<ArtifactSummary> actualArtifacts = getArtifacts(Id.Namespace.DEFAULT);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts?scope=system
expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.SYSTEM)
);
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT, ArtifactScope.SYSTEM);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts?scope=user
expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.USER)
);
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT, ArtifactScope.USER);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts/wordcount?scope=user
expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.USER)
);
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT, "wordcount", ArtifactScope.USER);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts/wordcount?scope=system
expectedArtifacts = Sets.newHashSet(
new ArtifactSummary("wordcount", "1.0.0", ArtifactScope.SYSTEM)
);
actualArtifacts = getArtifacts(Id.Namespace.DEFAULT, "wordcount", ArtifactScope.SYSTEM);
Assert.assertEquals(expectedArtifacts, actualArtifacts);
// test get /artifacts/wordcount/versions/1.0.0?scope=user
ArtifactClasses classes = ArtifactClasses.builder()
.addApp(new ApplicationClass(WordCountApp.class.getName(), "", null))
.build();
ArtifactInfo expectedInfo = new ArtifactInfo("wordcount", "1.0.0", ArtifactScope.USER,
classes, ImmutableMap.<String, String>of());
ArtifactInfo actualInfo = getArtifact(defaultId, ArtifactScope.USER);
Assert.assertEquals(expectedInfo, actualInfo);
// test get /artifacts/wordcount/versions/1.0.0?scope=system
expectedInfo = new ArtifactInfo("wordcount", "1.0.0", ArtifactScope.SYSTEM,
classes, ImmutableMap.<String, String>of());
actualInfo = getArtifact(defaultId, ArtifactScope.SYSTEM);
Assert.assertEquals(expectedInfo, actualInfo);
}
@Test
public void testPluginNamespaceIsolation() throws Exception {
// add a system artifact. currently can't do this through the rest api (by design)
// so bypass it and use the repository directly
Id.Artifact systemId = Id.Artifact.from(Id.Namespace.SYSTEM, "wordcount", "1.0.0");
File systemArtifact = buildAppArtifact(WordCountApp.class, "wordcount-1.0.0.jar");
artifactRepository.addArtifact(systemId, systemArtifact, Sets.<ArtifactRange>newHashSet());
Set<ArtifactRange> parents = Sets.newHashSet(new ArtifactRange(
systemId.getNamespace(), systemId.getName(), systemId.getVersion(), true, systemId.getVersion(), true));
Id.Namespace namespace1 = Id.Namespace.from("ns1");
Id.Namespace namespace2 = Id.Namespace.from("ns2");
createNamespace(namespace1.getId());
createNamespace(namespace2.getId());
try {
// add some plugins in namespace1. Will contain Plugin1 and Plugin2
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, Plugin1.class.getPackage().getName());
Id.Artifact pluginsId1 = Id.Artifact.from(namespace1, "plugins1", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(pluginsId1, Plugin1.class, manifest, parents)
.getStatusLine().getStatusCode());
// add some plugins in namespace2. Will contain Plugin1 and Plugin2
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, Plugin1.class.getPackage().getName());
Id.Artifact pluginsId2 = Id.Artifact.from(namespace2, "plugins2", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(pluginsId2, Plugin1.class, manifest, parents)
.getStatusLine().getStatusCode());
ArtifactSummary artifact1 =
new ArtifactSummary(pluginsId1.getName(), pluginsId1.getVersion().getVersion(), ArtifactScope.USER);
ArtifactSummary artifact2 =
new ArtifactSummary(pluginsId2.getName(), pluginsId2.getVersion().getVersion(), ArtifactScope.USER);
PluginSummary summary1Namespace1 =
new PluginSummary("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), artifact1);
PluginSummary summary2Namespace1 =
new PluginSummary("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), artifact1);
PluginSummary summary1Namespace2 =
new PluginSummary("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), artifact2);
PluginSummary summary2Namespace2 =
new PluginSummary("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), artifact2);
PluginInfo info1Namespace1 =
new PluginInfo("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), artifact1,
ImmutableMap.of(
"x", new PluginPropertyField("x", "", "int", true),
"stuff", new PluginPropertyField("stuff", "", "string", true)
), new HashSet<String>());
PluginInfo info2Namespace1 =
new PluginInfo("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), artifact1,
ImmutableMap.of(
"v", new PluginPropertyField("v", "value to return when called", "int", true)
), new HashSet<String>());
PluginInfo info1Namespace2 =
new PluginInfo("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), artifact2,
ImmutableMap.of(
"x", new PluginPropertyField("x", "", "int", true),
"stuff", new PluginPropertyField("stuff", "", "string", true)
), new HashSet<String>());
PluginInfo info2Namespace2 =
new PluginInfo("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), artifact2,
ImmutableMap.of(
"v", new PluginPropertyField("v", "value to return when called", "int", true)
), new HashSet<String>());
Id.Artifact namespace1Artifact = Id.Artifact.from(namespace1, systemId.getName(), systemId.getVersion());
Id.Artifact namespace2Artifact = Id.Artifact.from(namespace2, systemId.getName(), systemId.getVersion());
// should see same types in both namespaces
Assert.assertEquals(ImmutableSet.of("dummy", "callable"),
getPluginTypes(namespace1Artifact, ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of("dummy", "callable"),
getPluginTypes(namespace2Artifact, ArtifactScope.SYSTEM));
// should see that plugins in namespace1 come only from the namespace1 artifact
Assert.assertEquals(ImmutableSet.of(summary1Namespace1),
getPluginSummaries(namespace1Artifact, "dummy", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(summary2Namespace1),
getPluginSummaries(namespace1Artifact, "callable", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(info1Namespace1),
getPluginInfos(namespace1Artifact, "dummy", "Plugin1", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(info2Namespace1),
getPluginInfos(namespace1Artifact, "callable", "Plugin2", ArtifactScope.SYSTEM));
// should see that plugins in namespace2 come only from the namespace2 artifact
Assert.assertEquals(ImmutableSet.of(summary1Namespace2),
getPluginSummaries(namespace2Artifact, "dummy", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(summary2Namespace2),
getPluginSummaries(namespace2Artifact, "callable", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(info1Namespace2),
getPluginInfos(namespace2Artifact, "dummy", "Plugin1", ArtifactScope.SYSTEM));
Assert.assertEquals(ImmutableSet.of(info2Namespace2),
getPluginInfos(namespace2Artifact, "callable", "Plugin2", ArtifactScope.SYSTEM));
} finally {
deleteNamespace("iso1");
deleteNamespace("iso2");
}
}
@Test
public void testPluginWithEndpoints() throws Exception {
// add an app for plugins to extend
Id.Artifact wordCount1Id = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordCount1Id, WordCountApp.class).getStatusLine().getStatusCode());
// add some plugins.
// plugins-3.0.0 extends wordcount[1.0.0,2.0.0)
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, CallablePlugin.class.getPackage().getName());
Id.Artifact plugins3Id = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins3", "1.0.0");
Set<ArtifactRange> plugins3Parents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(plugins3Id, CallablePlugin.class, manifest,
plugins3Parents).getStatusLine().getStatusCode());
Set<PluginInfo> expectedInfos = Sets.newHashSet(
new PluginInfo("CallablePlugin", "interactive", "This is plugin with endpoint", CallablePlugin.class.getName(),
new ArtifactSummary("plugins3", "1.0.0"),
ImmutableMap.<String, PluginPropertyField>of(), ImmutableSet.<String>of("ping")));
Assert.assertEquals(expectedInfos, getPluginInfos(wordCount1Id, "interactive", "CallablePlugin"));
// test plugin with endpoint
Assert.assertEquals("hello",
GSON.fromJson(callPluginMethod(plugins3Id, "interactive",
"CallablePlugin", "ping", "user",
ArtifactScope.USER, 200).getResponseBodyAsString(), String.class));
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, CallingPlugin.class.getPackage().getName());
Id.Artifact plugins4Id = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins4", "1.0.0");
Set<ArtifactRange> plugins4Parents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(plugins4Id, CallingPlugin.class, manifest,
plugins4Parents).getStatusLine().getStatusCode());
// test plugin with endpoint having endpoint-context parameter
Assert.assertEquals("hi user",
GSON.fromJson(callPluginMethod(plugins4Id, "interactive", "CallingPlugin", "ping", "user",
ArtifactScope.USER, 200).getResponseBodyAsString(), String.class));
// test plugin that accepts list of data and aggregates and returns result map
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, PluginWithPojo.class.getPackage().getName());
Id.Artifact plugins5Id = Id.Artifact.from(Id.Namespace.DEFAULT, "aggregator", "1.0.0");
Set<ArtifactRange> plugins5Parents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(plugins5Id, PluginWithPojo.class, manifest,
plugins5Parents).getStatusLine().getStatusCode());
// test plugin with endpoint having endpoint-context parameter
List<TestData> data = ImmutableList.of(new TestData(1, 10), new TestData(1, 20),
new TestData(3, 15), new TestData(4, 5), new TestData(3, 15));
Map<Long, Long> expectedResult = new HashMap<>();
expectedResult.put(1L, 30L);
expectedResult.put(3L, 30L);
expectedResult.put(4L, 5L);
String response = callPluginMethod(plugins5Id, "interactive", "aggregator", "aggregate",
GSON.toJson(data),
ArtifactScope.USER, 200).getResponseBodyAsString();
Assert.assertEquals(expectedResult,
GSON.fromJson(response, new TypeToken<Map<Long, Long>>() { }.getType()));
// test calling a non-existent plugin method "bing"
callPluginMethod(plugins4Id, "interactive", "CallingPlugin", "bing", "user", ArtifactScope.USER, 404);
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, InvalidPlugin.class.getPackage().getName());
Id.Artifact invalidPluginId = Id.Artifact.from(Id.Namespace.DEFAULT, "invalid", "1.0.0");
Set<ArtifactRange> invalidPluginParents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.BAD_REQUEST.getCode(),
addPluginArtifact(invalidPluginId, InvalidPlugin.class, manifest,
invalidPluginParents).getStatusLine().getStatusCode());
// test adding plugin artifact which has endpoint method containing 3 params (invalid)
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE,
InvalidPluginMethodParams.class.getPackage().getName());
invalidPluginId = Id.Artifact.from(Id.Namespace.DEFAULT, "invalidParams", "1.0.0");
invalidPluginParents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.BAD_REQUEST.getCode(),
addPluginArtifact(invalidPluginId, InvalidPluginMethodParams.class, manifest,
invalidPluginParents).getStatusLine().getStatusCode());
// test adding plugin artifact which has endpoint method containing 2 params
// but 2nd param is not EndpointPluginContext (invalid)
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE,
InvalidPluginMethodParamType.class.getPackage().getName());
invalidPluginId = Id.Artifact.from(Id.Namespace.DEFAULT, "invalidParamType", "1.0.0");
invalidPluginParents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.BAD_REQUEST.getCode(),
addPluginArtifact(invalidPluginId, InvalidPluginMethodParamType.class, manifest,
invalidPluginParents).getStatusLine().getStatusCode());
// test adding plugin artifact which has endpoint methods containing 2 params
// but 2nd param is implementation and extensions of EndpointPluginContext, should succeed
manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE,
PluginEndpointContextTestPlugin.class.getPackage().getName());
Id.Artifact validPluginId = Id.Artifact.from(Id.Namespace.DEFAULT, "extender", "1.0.0");
Set<ArtifactRange> validPluginParents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(validPluginId, PluginEndpointContextTestPlugin.class, manifest,
validPluginParents).getStatusLine().getStatusCode());
}
@Test
public void testGetPlugins() throws Exception {
// add an app for plugins to extend
Id.Artifact wordCount1Id = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "1.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordCount1Id, WordCountApp.class).getStatusLine().getStatusCode());
Id.Artifact wordCount2Id = Id.Artifact.from(Id.Namespace.DEFAULT, "wordcount", "2.0.0");
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addAppArtifact(wordCount2Id, WordCountApp.class).getStatusLine().getStatusCode());
// add some plugins.
// plugins-1.0.0 extends wordcount[1.0.0,2.0.0)
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(ManifestFields.EXPORT_PACKAGE, Plugin1.class.getPackage().getName());
Id.Artifact pluginsId1 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "1.0.0");
Set<ArtifactRange> plugins1Parents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(pluginsId1, Plugin1.class, manifest,
plugins1Parents).getStatusLine().getStatusCode());
// plugin-2.0.0 extends wordcount[1.0.0,3.0.0)
Id.Artifact pluginsId2 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "2.0.0");
Set<ArtifactRange> plugins2Parents = Sets.newHashSet(new ArtifactRange(
Id.Namespace.DEFAULT, "wordcount", new ArtifactVersion("1.0.0"), new ArtifactVersion("3.0.0")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
addPluginArtifact(pluginsId2, Plugin1.class, manifest,
plugins2Parents).getStatusLine().getStatusCode());
ArtifactSummary plugins1Artifact = new ArtifactSummary("plugins", "1.0.0");
ArtifactSummary plugins2Artifact = new ArtifactSummary("plugins", "2.0.0");
// get plugin types, should be the same for both
Set<String> expectedTypes = Sets.newHashSet("dummy", "callable");
Set<String> actualTypes = getPluginTypes(wordCount1Id);
Assert.assertEquals(expectedTypes, actualTypes);
actualTypes = getPluginTypes(wordCount2Id);
Assert.assertEquals(expectedTypes, actualTypes);
// get plugin summaries. wordcount1 should see plugins from both plugin artifacts
Set<PluginSummary> expectedSummaries = Sets.newHashSet(
new PluginSummary("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), plugins1Artifact),
new PluginSummary("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), plugins2Artifact)
);
Set<PluginSummary> actualSummaries = getPluginSummaries(wordCount1Id, "dummy");
Assert.assertEquals(expectedSummaries, actualSummaries);
expectedSummaries = Sets.newHashSet(
new PluginSummary(
"Plugin2", "callable", "Just returns the configured integer", Plugin2.class.getName(), plugins1Artifact),
new PluginSummary(
"Plugin2", "callable", "Just returns the configured integer", Plugin2.class.getName(), plugins2Artifact)
);
actualSummaries = getPluginSummaries(wordCount1Id, "callable");
Assert.assertEquals(expectedSummaries, actualSummaries);
// wordcount2 should only see plugins from plugins2 artifact
expectedSummaries = Sets.newHashSet(
new PluginSummary("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(), plugins2Artifact)
);
actualSummaries = getPluginSummaries(wordCount2Id, "dummy");
Assert.assertEquals(expectedSummaries, actualSummaries);
expectedSummaries = Sets.newHashSet(
new PluginSummary(
"Plugin2", "callable", "Just returns the configured integer", Plugin2.class.getName(), plugins2Artifact)
);
actualSummaries = getPluginSummaries(wordCount2Id, "callable");
Assert.assertEquals(expectedSummaries, actualSummaries);
// get plugin info. Again, wordcount1 should see plugins from both artifacts
Map<String, PluginPropertyField> p1Properties = ImmutableMap.of(
"x", new PluginPropertyField("x", "", "int", true),
"stuff", new PluginPropertyField("stuff", "", "string", true)
);
Map<String, PluginPropertyField> p2Properties = ImmutableMap.of(
"v", new PluginPropertyField("v", "value to return when called", "int", true)
);
Set<PluginInfo> expectedInfos = Sets.newHashSet(
new PluginInfo("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(),
plugins1Artifact, p1Properties, new HashSet<String>()),
new PluginInfo("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(),
plugins2Artifact, p1Properties, new HashSet<String>())
);
Assert.assertEquals(expectedInfos, getPluginInfos(wordCount1Id, "dummy", "Plugin1"));
expectedInfos = Sets.newHashSet(
new PluginInfo("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), plugins1Artifact, p2Properties, new HashSet<String>()),
new PluginInfo("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), plugins2Artifact, p2Properties, new HashSet<String>())
);
Assert.assertEquals(expectedInfos, getPluginInfos(wordCount1Id, "callable", "Plugin2"));
// while wordcount2 should only see plugins from plugins2 artifact
expectedInfos = Sets.newHashSet(
new PluginInfo("Plugin1", "dummy", "This is plugin1", Plugin1.class.getName(),
plugins2Artifact, p1Properties, new HashSet<String>())
);
Assert.assertEquals(expectedInfos, getPluginInfos(wordCount2Id, "dummy", "Plugin1"));
expectedInfos = Sets.newHashSet(
new PluginInfo("Plugin2", "callable", "Just returns the configured integer",
Plugin2.class.getName(), plugins2Artifact, p2Properties, new HashSet<String>())
);
Assert.assertEquals(expectedInfos, getPluginInfos(wordCount2Id, "callable", "Plugin2"));
}
private Set<ArtifactSummary> getArtifacts(Id.Namespace namespace) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format(
"%s/namespaces/%s/artifacts", Constants.Gateway.API_VERSION_3, namespace.getId())).toURL();
return getResults(endpoint, ARTIFACTS_TYPE);
}
private Set<ArtifactSummary> getArtifacts(Id.Namespace namespace, ArtifactScope scope)
throws URISyntaxException, IOException {
if (scope == null) {
return getArtifacts(namespace);
}
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts?scope=%s",
Constants.Gateway.API_VERSION_3, namespace.getId(), scope.name())).toURL();
return getResults(endpoint, ARTIFACTS_TYPE);
}
private Map<String, String> getArtifactProperties(Id.Artifact artifact)
throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/properties",
Constants.Gateway.API_VERSION_3, artifact.getNamespace().getId(),
artifact.getName(), artifact.getVersion())).toURL();
return getResults(endpoint, MAP_STRING_STRING_TYPE);
}
private Set<ArtifactSummary> getArtifacts(Id.Namespace namespace, String name)
throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format(
"%s/namespaces/%s/artifacts/%s", Constants.Gateway.API_VERSION_3, namespace.getId(), name)).toURL();
return getResults(endpoint, ARTIFACTS_TYPE);
}
private Set<ArtifactSummary> getArtifacts(Id.Namespace namespace, String name, ArtifactScope scope)
throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s?scope=%s",
Constants.Gateway.API_VERSION_3,
namespace.getId(), name, scope.name())).toURL();
return getResults(endpoint, ARTIFACTS_TYPE);
}
// get /artifacts/{name}/versions/{version}
private ArtifactInfo getArtifact(Id.Artifact artifactId) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s",
Constants.Gateway.API_VERSION_3, artifactId.getNamespace().getId(),
artifactId.getName(), artifactId.getVersion().getVersion()))
.toURL();
return getResults(endpoint, ArtifactInfo.class);
}
// get /artifacts/{name}/versions/{version}?scope={scope}
private ArtifactInfo getArtifact(Id.Artifact artifactId, ArtifactScope scope) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s?scope=%s",
Constants.Gateway.API_VERSION_3, artifactId.getNamespace().getId(),
artifactId.getName(), artifactId.getVersion().getVersion(), scope.name()))
.toURL();
return getResults(endpoint, ArtifactInfo.class);
}
// get /artifacts/{name}/versions/{version}/extensions
private Set<String> getPluginTypes(Id.Artifact artifactId) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions",
Constants.Gateway.API_VERSION_3,
artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion()))
.toURL();
return getResults(endpoint, PLUGIN_TYPES_TYPE);
}
// get /artifacts/{name}/versions/{version}/extensions?scope={scope}
private Set<String> getPluginTypes(Id.Artifact artifactId,
ArtifactScope scope) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions?scope=%s",
Constants.Gateway.API_VERSION_3,
artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion(),
scope.name())).toURL();
return getResults(endpoint, PLUGIN_TYPES_TYPE);
}
// get /artifacts/{name}/versions/{version}/extensions/{plugin-type}
private Set<PluginSummary> getPluginSummaries(Id.Artifact artifactId,
String pluginType) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions/%s",
Constants.Gateway.API_VERSION_3,
artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion(),
pluginType))
.toURL();
return getResults(endpoint, PLUGIN_SUMMARIES_TYPE);
}
// get /artifacts/{name}/versions/{version}/extensions/{plugin-type}?scope={scope}
private Set<PluginSummary> getPluginSummaries(Id.Artifact artifactId,
String pluginType,
ArtifactScope scope) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions/%s?scope=%s",
Constants.Gateway.API_VERSION_3,
artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion(),
pluginType,
scope.name()))
.toURL();
return getResults(endpoint, PLUGIN_SUMMARIES_TYPE);
}
// get /artifacts/{name}/versions/{version}/extensions/{plugin-type}/plugins/{plugin-name}
private Set<PluginInfo> getPluginInfos(Id.Artifact artifactId,
String pluginType, String pluginName) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions/%s/plugins/%s",
Constants.Gateway.API_VERSION_3, artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion(), pluginType, pluginName))
.toURL();
return getResults(endpoint, PLUGIN_INFOS_TYPE);
}
// get /artifacts/{name}/versions/{version}/extensions/{plugin-type}/plugins/{plugin-name}?scope={scope}
private Set<PluginInfo> getPluginInfos(Id.Artifact artifactId,
String pluginType,
String pluginName,
ArtifactScope scope) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(
String.format("%s/namespaces/%s/artifacts/%s/versions/%s/extensions/%s/plugins/%s?scope=%s",
Constants.Gateway.API_VERSION_3,
artifactId.getNamespace().getId(),
artifactId.getName(),
artifactId.getVersion().getVersion(),
pluginType,
pluginName,
scope.name()))
.toURL();
return getResults(endpoint, PLUGIN_INFOS_TYPE);
}
private co.cask.common.http.HttpResponse callPluginMethod(
Id.Artifact plugins3Id, String pluginType, String pluginName, String pluginMethod,
String body, ArtifactScope scope, int expectedResponseCode) throws URISyntaxException, IOException {
URL endpoint = getEndPoint(
String.format("%s/namespaces/%s/artifacts/%s/versions/%s/plugintypes/%s/plugins/%s/methods/%s?scope=%s",
Constants.Gateway.API_VERSION_3,
plugins3Id.getNamespace().getId(),
plugins3Id.getName(),
plugins3Id.getVersion().getVersion(),
pluginType,
pluginName,
pluginMethod,
scope.name()))
.toURL();
HttpRequest request = HttpRequest.post(endpoint).withBody(body).build();
co.cask.common.http.HttpResponse response = HttpRequests.execute(request);
Assert.assertEquals(expectedResponseCode, response.getResponseCode());
return response;
}
// gets the contents from doing a get on the given url as the given type, or null if a 404 is returned
private <T> T getResults(URL endpoint, Type type) throws IOException {
HttpURLConnection urlConn = (HttpURLConnection) endpoint.openConnection();
urlConn.setRequestMethod(HttpMethod.GET);
int responseCode = urlConn.getResponseCode();
if (responseCode == HttpResponseStatus.NOT_FOUND.getCode()) {
return null;
}
Assert.assertEquals(HttpResponseStatus.OK.getCode(), responseCode);
String responseStr = new String(ByteStreams.toByteArray(urlConn.getInputStream()), Charsets.UTF_8);
urlConn.disconnect();
return GSON.fromJson(responseStr, type);
}
}