/* * 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.runtime.artifact; 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.common.Bytes; import co.cask.cdap.api.plugin.PluginClass; import co.cask.cdap.api.plugin.PluginPropertyField; import co.cask.cdap.common.ArtifactAlreadyExistsException; import co.cask.cdap.common.ArtifactNotFoundException; import co.cask.cdap.internal.AppFabricTestHelper; import co.cask.cdap.internal.app.runtime.artifact.app.inspection.InspectionApp; import co.cask.cdap.internal.app.runtime.plugin.PluginNotExistsException; import co.cask.cdap.internal.io.ReflectionSchemaGenerator; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.artifact.ArtifactRange; import co.cask.cdap.proto.id.Ids; import co.cask.cdap.proto.id.NamespaceId; import co.cask.cdap.test.SlowTests; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import org.apache.twill.filesystem.Location; import org.junit.After; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** */ // suppressing warnings for Bytes.toBytes() when we know the result is not null @SuppressWarnings("ConstantConditions") public class ArtifactStoreTest { private static ArtifactStore artifactStore; @BeforeClass public static void setup() throws Exception { artifactStore = AppFabricTestHelper.getInjector().getInstance(ArtifactStore.class); } @After public void cleanup() throws IOException { artifactStore.clear(NamespaceId.DEFAULT); } @Test public void testGetNonexistantArtifact() throws IOException { NamespaceId namespace = Ids.namespace("ns1"); // no artifacts in a namespace should return an empty collection Assert.assertTrue(artifactStore.getArtifacts(namespace).isEmpty()); // no artifacts in range should return an empty collection ArtifactRange range = new ArtifactRange( namespace.toId(), "something", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")); Assert.assertTrue(artifactStore.getArtifacts(range).isEmpty()); // no artifact by namespace and artifact name should throw an exception try { artifactStore.getArtifacts(namespace, "something"); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } // no artifact by namespace, artifact name, and version should throw an exception try { artifactStore.getArtifact(Id.Artifact.from(namespace.toId(), "something", "1.0.0")); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } } @Test public void testGetNonexistantPlugin() throws Exception { Id.Artifact parentArtifact = Id.Artifact.from(Id.Namespace.DEFAULT, "abc", "1.2.3"); // if parent doesn't exist, we expect it to throw ArtifactNotFound try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact, "sometype"); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact, "sometype", "somename"); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } // now add the parent artifact without any plugins ArtifactMeta meta = new ArtifactMeta(ArtifactClasses.builder().build()); writeArtifact(parentArtifact, meta, "jar contents"); // no plugins in a namespace should return an empty collection Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact).isEmpty()); // no plugins in namespace of a given type should return an empty map Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact, "sometype").isEmpty()); // no plugins in namespace of a given type and name should throw an exception about no plugins try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifact, "sometype", "somename"); Assert.fail(); } catch (PluginNotExistsException e) { // expected } } @Test public void testAddGetSingleArtifact() throws Exception { Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "myplugins", "1.0.0"); PluginClass plugin1 = new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()); PluginClass plugin2 = new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()); PluginClass plugin3 = new PluginClass("btype", "plugin3", "", "c.c.c.plugin3", "cfg", ImmutableMap.<String, PluginPropertyField>of()); Set<PluginClass> plugins = ImmutableSet.of(plugin1, plugin2, plugin3); ApplicationClass appClass = new ApplicationClass( InspectionApp.class.getName(), "", new ReflectionSchemaGenerator().generate(InspectionApp.AConfig.class)); ArtifactMeta artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).addApp(appClass).build()); String artifactContents = "my artifact contents"; writeArtifact(artifactId, artifactMeta, artifactContents); ArtifactDetail artifactDetail = artifactStore.getArtifact(artifactId); assertEqual(artifactId, artifactMeta, artifactContents, artifactDetail); // test that plugins in the artifact show up when getting plugins for that artifact Map<ArtifactDescriptor, Set<PluginClass>> pluginsMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, artifactId); Assert.assertEquals(1, pluginsMap.size()); Assert.assertTrue(pluginsMap.containsKey(artifactDetail.getDescriptor())); Set<PluginClass> expected = ImmutableSet.copyOf(plugins); Set<PluginClass> actual = ImmutableSet.copyOf(pluginsMap.get(artifactDetail.getDescriptor())); Assert.assertEquals(expected, actual); // test plugins for the specific type pluginsMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, artifactId, "atype"); Assert.assertEquals(1, pluginsMap.size()); Assert.assertTrue(pluginsMap.containsKey(artifactDetail.getDescriptor())); expected = ImmutableSet.of(plugin1, plugin2); actual = ImmutableSet.copyOf(pluginsMap.get(artifactDetail.getDescriptor())); Assert.assertEquals(expected, actual); // test plugins for specific type and name Map<ArtifactDescriptor, PluginClass> pluginClasses = artifactStore.getPluginClasses(NamespaceId.DEFAULT, artifactId, "btype", "plugin3"); Assert.assertEquals(1, pluginClasses.size()); Assert.assertTrue(pluginClasses.containsKey(artifactDetail.getDescriptor())); Assert.assertEquals(plugin3, pluginClasses.get(artifactDetail.getDescriptor())); } @Test public void testDelete() throws Exception { // write an artifact with an app Id.Artifact parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); ApplicationClass appClass = new ApplicationClass( InspectionApp.class.getName(), "", new ReflectionSchemaGenerator().generate(InspectionApp.AConfig.class)); ArtifactMeta artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addApp(appClass).build()); writeArtifact(parentId, artifactMeta, "parent contents"); // write a child artifact that extends the parent with some plugins Id.Artifact childId = Id.Artifact.from(Id.Namespace.DEFAULT, "myplugins", "1.0.0"); List<PluginClass> plugins = ImmutableList.of( new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()), new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); Set<ArtifactRange> parents = ImmutableSet.of(new ArtifactRange( parentId.getNamespace(), parentId.getName(), new ArtifactVersion("0.1.0"), new ArtifactVersion("2.0.0"))); artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parents); writeArtifact(childId, artifactMeta, "child contents"); // check parent has plugins from the child Assert.assertFalse(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); // delete the child artifact artifactStore.delete(childId); // shouldn't be able to get artifact detail try { artifactStore.getArtifact(childId); Assert.fail(); } catch (ArtifactNotFoundException e) { // expected } // shouldn't see it in the list List<ArtifactDetail> artifactList = artifactStore.getArtifacts(parentId.getNamespace().toEntityId()); Assert.assertEquals(1, artifactList.size()); Assert.assertEquals(parentId.getName(), artifactList.get(0).getDescriptor().getArtifactId().getName()); // shouldn't see any more plugins for parent Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); // delete parent artifactStore.delete(parentId); // nothing should be in the list Assert.assertTrue(artifactStore.getArtifacts(parentId.getNamespace().toEntityId()).isEmpty()); // shouldn't be able to see app class either Assert.assertTrue(artifactStore.getApplicationClasses(NamespaceId.DEFAULT, appClass.getClassName()).isEmpty()); } @Test(expected = ArtifactAlreadyExistsException.class) public void testImmutability() throws Exception { Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "myplugins", "1.0.0"); ArtifactMeta artifactMeta = new ArtifactMeta(ArtifactClasses.builder().build()); String artifactContents = "abc123"; // this should work try { writeArtifact(artifactId, artifactMeta, artifactContents); } catch (ArtifactAlreadyExistsException e) { Assert.fail(); } // this should throw an exception, since artifacts are immutable writeArtifact(artifactId, artifactMeta, artifactContents); } @Test public void testSnapshotMutability() throws Exception { // write parent Id.Artifact parentArtifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); ArtifactMeta parentMeta = new ArtifactMeta(ArtifactClasses.builder().build()); writeArtifact(parentArtifactId, parentMeta, "content"); ArtifactRange parentArtifacts = new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")); // write the snapshot once PluginClass plugin1 = new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()); PluginClass plugin2 = new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()); Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "myplugins", "1.0.0-SNAPSHOT"); ArtifactMeta artifactMeta = new ArtifactMeta( ArtifactClasses.builder().addPlugins(plugin1, plugin2).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactId, artifactMeta, "abc123"); // update meta and jar contents artifactMeta = new ArtifactMeta( ArtifactClasses.builder().addPlugin(plugin2).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactId, artifactMeta, "xyz321"); // check the metadata and contents got updated ArtifactDetail detail = artifactStore.getArtifact(artifactId); assertEqual(artifactId, artifactMeta, "xyz321", detail); // check that plugin1 was deleted and plugin2 remains Assert.assertEquals(ImmutableMap.of(detail.getDescriptor(), plugin2), artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, plugin2.getType(), plugin2.getName())); try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, plugin1.getType(), plugin1.getName()); Assert.fail(); } catch (PluginNotExistsException e) { // expected } } @Test public void testNamespaceIsolation() throws Exception { Id.Namespace namespace1 = Id.Namespace.from("ns1"); Id.Namespace namespace2 = Id.Namespace.from("ns2"); Id.Artifact artifact1 = Id.Artifact.from(namespace1, "myplugins", "1.0.0"); Id.Artifact artifact2 = Id.Artifact.from(namespace2, "myplugins", "1.0.0"); String contents1 = "first contents"; String contents2 = "second contents"; PluginClass plugin1 = new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()); PluginClass plugin2 = new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()); ArtifactMeta meta1 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin1).build()); ArtifactMeta meta2 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin2).build()); writeArtifact(artifact1, meta1, contents1); writeArtifact(artifact2, meta2, contents2); try { ArtifactDetail info1 = artifactStore.getArtifact(artifact1); ArtifactDetail info2 = artifactStore.getArtifact(artifact2); assertEqual(artifact1, meta1, contents1, info1); assertEqual(artifact2, meta2, contents2, info2); List<ArtifactDetail> namespace1Artifacts = artifactStore.getArtifacts(namespace1.toEntityId()); List<ArtifactDetail> namespace2Artifacts = artifactStore.getArtifacts(namespace2.toEntityId()); Assert.assertEquals(1, namespace1Artifacts.size()); assertEqual(artifact1, meta1, contents1, namespace1Artifacts.get(0)); Assert.assertEquals(1, namespace2Artifacts.size()); assertEqual(artifact2, meta2, contents2, namespace2Artifacts.get(0)); } finally { artifactStore.clear(namespace1.toEntityId()); artifactStore.clear(namespace2.toEntityId()); } } @Test public void testPluginNamespaceIsolation() throws Exception { // write some system artifact Id.Artifact systemAppArtifact = Id.Artifact.from(Id.Namespace.SYSTEM, "app", "1.0.0"); ArtifactMeta systemAppMeta = new ArtifactMeta( ArtifactClasses.builder().addApp(new ApplicationClass("co.cask.class", "desc", null)).build()); writeArtifact(systemAppArtifact, systemAppMeta, "app contents"); Set<ArtifactRange> usableBy = ImmutableSet.of( new ArtifactRange(systemAppArtifact.getNamespace(), systemAppArtifact.getName(), systemAppArtifact.getVersion(), true, systemAppArtifact.getVersion(), true)); PluginClass plugin = new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()); // write a plugins artifact in namespace1 NamespaceId namespace1 = Ids.namespace("ns1"); Id.Artifact artifact1 = Id.Artifact.from(namespace1.toId(), "plugins1", "1.0.0"); ArtifactMeta meta1 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin).build(), usableBy); String contents1 = "plugin1 contents"; writeArtifact(artifact1, meta1, contents1); // write a plugins artifact in namespace2 NamespaceId namespace2 = Ids.namespace("ns2"); Id.Artifact artifact2 = Id.Artifact.from(namespace2.toId(), "plugins2", "1.0.0"); ArtifactMeta meta2 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin).build(), usableBy); String contents2 = "plugin2 contents"; writeArtifact(artifact2, meta2, contents2); try { // this should only get plugins from artifact1 SortedMap<ArtifactDescriptor, Set<PluginClass>> plugins = artifactStore.getPluginClasses(namespace1, systemAppArtifact); Assert.assertEquals(1, plugins.size()); ArtifactDescriptor artifactDescriptor = plugins.firstKey(); Assert.assertEquals(artifact1.toArtifactId(), artifactDescriptor.getArtifactId()); assertContentsEqual(contents1, artifactDescriptor.getLocation()); // this should only get plugins from artifact2 plugins = artifactStore.getPluginClasses(namespace2, systemAppArtifact); Assert.assertEquals(1, plugins.size()); artifactDescriptor = plugins.firstKey(); Assert.assertEquals(artifact2.toArtifactId(), artifactDescriptor.getArtifactId()); assertContentsEqual(contents2, artifactDescriptor.getLocation()); } finally { artifactStore.clear(namespace1); artifactStore.clear(namespace2); artifactStore.clear(NamespaceId.SYSTEM); } } @Test public void testGetArtifacts() throws Exception { // add 1 version of another artifact1 Id.Artifact artifact1V1 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifact1", "1.0.0"); String contents1V1 = "first contents v1"; PluginClass plugin1V1 = new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()); ArtifactMeta meta1V1 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin1V1).build()); writeArtifact(artifact1V1, meta1V1, contents1V1); // add 2 versions of an artifact2 Id.Artifact artifact2V1 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifact2", "0.1.0"); Id.Artifact artifact2V2 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifact2", "0.1.1"); String contents2V1 = "second contents v1"; String contents2V2 = "second contents v2"; PluginClass plugin2V1 = new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()); PluginClass plugin2V2 = new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()); ArtifactMeta meta2V1 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin2V1).build()); ArtifactMeta meta2V2 = new ArtifactMeta(ArtifactClasses.builder().addPlugin(plugin2V2).build()); writeArtifact(artifact2V1, meta2V1, contents2V1); writeArtifact(artifact2V2, meta2V2, contents2V2); // test we get 1 version of artifact1 and 2 versions of artifact2 List<ArtifactDetail> artifact1Versions = artifactStore.getArtifacts(artifact1V1.getNamespace().toEntityId(), artifact1V1.getName()); Assert.assertEquals(1, artifact1Versions.size()); assertEqual(artifact1V1, meta1V1, contents1V1, artifact1Versions.get(0)); List<ArtifactDetail> artifact2Versions = artifactStore.getArtifacts(artifact2V1.getNamespace().toEntityId(), artifact2V1.getName()); Assert.assertEquals(2, artifact2Versions.size()); assertEqual(artifact2V1, meta2V1, contents2V1, artifact2Versions.get(0)); assertEqual(artifact2V2, meta2V2, contents2V2, artifact2Versions.get(1)); // test we get all 3 in the getArtifacts() call for the namespace List<ArtifactDetail> artifactVersions = artifactStore.getArtifacts(NamespaceId.DEFAULT); Assert.assertEquals(3, artifactVersions.size()); assertEqual(artifact1V1, meta1V1, contents1V1, artifactVersions.get(0)); assertEqual(artifact2V1, meta2V1, contents2V1, artifactVersions.get(1)); assertEqual(artifact2V2, meta2V2, contents2V2, artifactVersions.get(2)); // test get using a range // this range should get everything ArtifactRange range = new ArtifactRange( Id.Namespace.DEFAULT, "artifact2", new ArtifactVersion("0.1.0"), new ArtifactVersion("0.1.2")); artifactVersions = artifactStore.getArtifacts(range); Assert.assertEquals(2, artifactVersions.size()); assertEqual(artifact2V1, meta2V1, contents2V1, artifactVersions.get(0)); assertEqual(artifact2V2, meta2V2, contents2V2, artifactVersions.get(1)); // this range should get just v0.1.1 range = new ArtifactRange( Id.Namespace.DEFAULT, "artifact2", new ArtifactVersion("0.1.1"), new ArtifactVersion("1.0.0")); artifactVersions = artifactStore.getArtifacts(range); Assert.assertEquals(1, artifactVersions.size()); assertEqual(artifact2V2, meta2V2, contents2V2, artifactVersions.get(0)); // this range should get just v0.1.0 range = new ArtifactRange( Id.Namespace.DEFAULT, "artifact2", new ArtifactVersion("0.0.0"), new ArtifactVersion("0.1.1")); artifactVersions = artifactStore.getArtifacts(range); Assert.assertEquals(1, artifactVersions.size()); assertEqual(artifact2V1, meta2V1, contents2V1, artifactVersions.get(0)); } @Test public void testGetAppClasses() throws Exception { // create 2 versions of the same artifact with the same app class Id.Artifact app1v1Id = Id.Artifact.from(Id.Namespace.DEFAULT, "appA", "1.0.0"); ApplicationClass inspectionClass1 = new ApplicationClass( InspectionApp.class.getName(), "v1", new ReflectionSchemaGenerator().generate(InspectionApp.AConfig.class)); ArtifactMeta artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addApp(inspectionClass1).build()); writeArtifact(app1v1Id, artifactMeta, "my artifact contents"); ArtifactDetail app1v1Detail = artifactStore.getArtifact(app1v1Id); Id.Artifact app1v2Id = Id.Artifact.from(Id.Namespace.DEFAULT, "appA", "2.0.0"); ApplicationClass inspectionClass2 = new ApplicationClass( InspectionApp.class.getName(), "v2", new ReflectionSchemaGenerator().generate(InspectionApp.AConfig.class)); artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addApp(inspectionClass2).build()); writeArtifact(app1v2Id, artifactMeta, "my artifact contents"); ArtifactDetail app1v2Detail = artifactStore.getArtifact(app1v2Id); // create a different artifact with the same app class Id.Artifact app2v1Id = Id.Artifact.from(Id.Namespace.DEFAULT, "appB", "1.0.0"); artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addApp(inspectionClass1).build()); writeArtifact(app2v1Id, artifactMeta, "other contents"); ArtifactDetail app2v1Detail = artifactStore.getArtifact(app2v1Id); // create another artifact with a different app class Id.Artifact app3v1Id = Id.Artifact.from(Id.Namespace.DEFAULT, "appC", "1.0.0"); ApplicationClass wordCountClass1 = new ApplicationClass( WordCountApp.class.getName(), "v1", new ReflectionSchemaGenerator().generate(InspectionApp.AConfig.class)); artifactMeta = new ArtifactMeta(ArtifactClasses.builder().addApp(wordCountClass1).build()); writeArtifact(app3v1Id, artifactMeta, "wc contents"); ArtifactDetail app3v1Detail = artifactStore.getArtifact(app3v1Id); // test getting all app classes in the namespace Map<ArtifactDescriptor, List<ApplicationClass>> appClasses = artifactStore.getApplicationClasses(NamespaceId.DEFAULT); Map<ArtifactDescriptor, List<ApplicationClass>> expected = ImmutableMap.<ArtifactDescriptor, List<ApplicationClass>>of( app1v1Detail.getDescriptor(), ImmutableList.of(inspectionClass1), app1v2Detail.getDescriptor(), ImmutableList.of(inspectionClass2), app2v1Detail.getDescriptor(), ImmutableList.of(inspectionClass1), app3v1Detail.getDescriptor(), ImmutableList.of(wordCountClass1) ); Assert.assertEquals(expected, appClasses); // test getting all app classes by class name Map<ArtifactDescriptor, ApplicationClass> appArtifacts = artifactStore.getApplicationClasses(NamespaceId.DEFAULT, InspectionApp.class.getName()); Map<ArtifactDescriptor, ApplicationClass> expectedAppArtifacts = ImmutableMap.of( app1v1Detail.getDescriptor(), inspectionClass1, app1v2Detail.getDescriptor(), inspectionClass2, app2v1Detail.getDescriptor(), inspectionClass1 ); Assert.assertEquals(expectedAppArtifacts, appArtifacts); appArtifacts = artifactStore.getApplicationClasses(NamespaceId.DEFAULT, WordCountApp.class.getName()); expectedAppArtifacts = ImmutableMap.of(app3v1Detail.getDescriptor(), wordCountClass1); Assert.assertEquals(expectedAppArtifacts, appArtifacts); Assert.assertTrue(artifactStore.getApplicationClasses(Ids.namespace("ghost")).isEmpty()); } @Test public void testGetPlugins() throws Exception { ArtifactRange parentArtifacts = new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")); // we have 2 plugins of type A and 2 plugins of type B PluginClass pluginA1 = new PluginClass( "A", "p1", "desc", "c.p1", "cfg", ImmutableMap.of( "threshold", new PluginPropertyField("thresh", "description", "double", true), "retry", new PluginPropertyField("retries", "description", "int", false) ) ); PluginClass pluginA2 = new PluginClass( "A", "p2", "desc", "c.p2", "conf", ImmutableMap.of( "stream", new PluginPropertyField("stream", "description", "string", true) ) ); PluginClass pluginB1 = new PluginClass( "B", "p1", "desc", "c.p1", "cfg", ImmutableMap.of( "createIfNotExist", new PluginPropertyField("createIfNotExist", "desc", "boolean", false) ) ); PluginClass pluginB2 = new PluginClass( "B", "p2", "desc", "c.p2", "stuff", ImmutableMap.of( "numer", new PluginPropertyField("numerator", "description", "double", true), "denom", new PluginPropertyField("denominator", "description", "double", true) ) ); // add artifacts // not interested in artifact contents for this test, using some dummy value String contents = "0"; // write parent Id.Artifact parentArtifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); ArtifactMeta parentMeta = new ArtifactMeta(ArtifactClasses.builder().build()); writeArtifact(parentArtifactId, parentMeta, contents); // artifact artifactX-1.0.0 contains plugin A1 Id.Artifact artifactXv100 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactX", "1.0.0"); ArtifactMeta metaXv100 = new ArtifactMeta( ArtifactClasses.builder().addPlugin(pluginA1).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactXv100, metaXv100, contents); ArtifactDescriptor artifactXv100Info = artifactStore.getArtifact(artifactXv100).getDescriptor(); // artifact artifactX-1.1.0 contains plugin A1 Id.Artifact artifactXv110 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactX", "1.1.0"); ArtifactMeta metaXv110 = new ArtifactMeta( ArtifactClasses.builder().addPlugin(pluginA1).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactXv110, metaXv110, contents); ArtifactDescriptor artifactXv110Info = artifactStore.getArtifact(artifactXv110).getDescriptor(); // artifact artifactX-2.0.0 contains plugins A1 and A2 Id.Artifact artifactXv200 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactX", "2.0.0"); ArtifactMeta metaXv200 = new ArtifactMeta( ArtifactClasses.builder().addPlugins(pluginA1, pluginA2).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactXv200, metaXv200, contents); ArtifactDescriptor artifactXv200Info = artifactStore.getArtifact(artifactXv200).getDescriptor(); // artifact artifactY-1.0.0 contains plugin B1 Id.Artifact artifactYv100 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactY", "1.0.0"); ArtifactMeta metaYv100 = new ArtifactMeta( ArtifactClasses.builder().addPlugin(pluginB1).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactYv100, metaYv100, contents); ArtifactDescriptor artifactYv100Info = artifactStore.getArtifact(artifactYv100).getDescriptor(); // artifact artifactY-2.0.0 contains plugin B2 Id.Artifact artifactYv200 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactY", "2.0.0"); ArtifactMeta metaYv200 = new ArtifactMeta( ArtifactClasses.builder().addPlugin(pluginB2).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactYv200, metaYv200, contents); ArtifactDescriptor artifactYv200Info = artifactStore.getArtifact(artifactYv200).getDescriptor(); // artifact artifactZ-1.0.0 contains plugins A1 and B1 Id.Artifact artifactZv100 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactZ", "1.0.0"); ArtifactMeta metaZv100 = new ArtifactMeta( ArtifactClasses.builder().addPlugins(pluginA1, pluginB1).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactZv100, metaZv100, contents); ArtifactDescriptor artifactZv100Info = artifactStore.getArtifact(artifactZv100).getDescriptor(); // artifact artifactZ-2.0.0 contains plugins A1, A2, B1, and B2 Id.Artifact artifactZv200 = Id.Artifact.from(Id.Namespace.DEFAULT, "artifactZ", "2.0.0"); ArtifactMeta metaZv200 = new ArtifactMeta( ArtifactClasses.builder().addPlugins(pluginA1, pluginA2, pluginB1, pluginB2).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifactZv200, metaZv200, contents); ArtifactDescriptor artifactZv200Info = artifactStore.getArtifact(artifactZv200).getDescriptor(); // test getting all plugins in the namespace Map<ArtifactDescriptor, Set<PluginClass>> expected = Maps.newHashMap(); expected.put(artifactXv100Info, ImmutableSet.of(pluginA1)); expected.put(artifactXv110Info, ImmutableSet.of(pluginA1)); expected.put(artifactXv200Info, ImmutableSet.of(pluginA1, pluginA2)); expected.put(artifactYv100Info, ImmutableSet.of(pluginB1)); expected.put(artifactYv200Info, ImmutableSet.of(pluginB2)); expected.put(artifactZv100Info, ImmutableSet.of(pluginA1, pluginB1)); expected.put(artifactZv200Info, ImmutableSet.of(pluginA1, pluginA2, pluginB1, pluginB2)); Map<ArtifactDescriptor, Set<PluginClass>> actual = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId); Assert.assertEquals(expected, actual); // test getting all plugins by namespace and type // get all of type A expected = Maps.newHashMap(); expected.put(artifactXv100Info, ImmutableSet.of(pluginA1)); expected.put(artifactXv110Info, ImmutableSet.of(pluginA1)); expected.put(artifactXv200Info, ImmutableSet.of(pluginA1, pluginA2)); expected.put(artifactZv100Info, ImmutableSet.of(pluginA1)); expected.put(artifactZv200Info, ImmutableSet.of(pluginA1, pluginA2)); actual = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "A"); Assert.assertEquals(expected, actual); // get all of type B expected = Maps.newHashMap(); expected.put(artifactYv100Info, ImmutableSet.of(pluginB1)); expected.put(artifactYv200Info, ImmutableSet.of(pluginB2)); expected.put(artifactZv100Info, ImmutableSet.of(pluginB1)); expected.put(artifactZv200Info, ImmutableSet.of(pluginB1, pluginB2)); actual = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "B"); Assert.assertEquals(expected, actual); // test getting plugins by namespace, type, and name // get all of type A and name p1 Map<ArtifactDescriptor, PluginClass> expectedMap = Maps.newHashMap(); expectedMap.put(artifactXv100Info, pluginA1); expectedMap.put(artifactXv110Info, pluginA1); expectedMap.put(artifactXv200Info, pluginA1); expectedMap.put(artifactZv100Info, pluginA1); expectedMap.put(artifactZv200Info, pluginA1); Map<ArtifactDescriptor, PluginClass> actualMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "A", "p1"); Assert.assertEquals(expectedMap, actualMap); // get all of type A and name p2 expectedMap = Maps.newHashMap(); expectedMap.put(artifactXv200Info, pluginA2); expectedMap.put(artifactZv200Info, pluginA2); actualMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "A", "p2"); Assert.assertEquals(expectedMap, actualMap); // get all of type B and name p1 expectedMap = Maps.newHashMap(); expectedMap.put(artifactYv100Info, pluginB1); expectedMap.put(artifactZv100Info, pluginB1); expectedMap.put(artifactZv200Info, pluginB1); actualMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "B", "p1"); Assert.assertEquals(expectedMap, actualMap); // get all of type B and name p2 expectedMap = Maps.newHashMap(); expectedMap.put(artifactYv200Info, pluginB2); expectedMap.put(artifactZv200Info, pluginB2); actualMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "B", "p2"); Assert.assertEquals(expectedMap, actualMap); } @Test public void testSamePluginDifferentArtifacts() throws Exception { ArtifactRange parentArtifacts = new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")); // add one artifact with a couple plugins Id.Artifact artifact1 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins1", "1.0.0"); Set<PluginClass> plugins = ImmutableSet.of( new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()), new PluginClass("atype", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); ArtifactMeta meta1 = new ArtifactMeta( ArtifactClasses.builder().addPlugins(plugins).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifact1, meta1, "something"); ArtifactDescriptor artifact1Info = artifactStore.getArtifact(artifact1).getDescriptor(); // add a different artifact with the same plugins Id.Artifact artifact2 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins2", "1.0.0"); ArtifactMeta meta2 = new ArtifactMeta( ArtifactClasses.builder().addPlugins(plugins).build(), ImmutableSet.of(parentArtifacts)); writeArtifact(artifact2, meta2, "something"); ArtifactDescriptor artifact2Info = artifactStore.getArtifact(artifact2).getDescriptor(); Id.Artifact parentArtifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); writeArtifact(parentArtifactId, new ArtifactMeta(ArtifactClasses.builder().build()), "content"); Map<ArtifactDescriptor, Set<PluginClass>> expected = Maps.newHashMap(); expected.put(artifact1Info, plugins); expected.put(artifact2Info, plugins); Map<ArtifactDescriptor, Set<PluginClass>> actual = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId); Assert.assertEquals(expected, actual); } @Test public void testPluginParentInclusiveExclusiveVersions() throws Exception { // write artifacts that extend: // parent-[1.0.0,1.0.0] -- only visible by parent-1.0.0 Id.Artifact id1 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "0.0.1"); Set<ArtifactRange> parentArtifacts = ImmutableSet.of(new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), true, new ArtifactVersion("1.0.0"), true)); List<PluginClass> plugins = ImmutableList.of( new PluginClass("typeA", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); ArtifactMeta meta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parentArtifacts); writeArtifact(id1, meta, "some contents"); // parent-[2.0.0,2.0.1) -- only visible by parent-2.0.0 Id.Artifact id2 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "0.0.2"); parentArtifacts = ImmutableSet.of(new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("2.0.0"), true, new ArtifactVersion("2.0.1"), false)); plugins = ImmutableList.of( new PluginClass("typeA", "plugin2", "", "c.c.c.plugin2", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); meta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parentArtifacts); writeArtifact(id2, meta, "some contents"); // parent-(3.0.0,3.0.1] -- only visible by parent-3.0.1 Id.Artifact id3 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "0.0.3"); parentArtifacts = ImmutableSet.of(new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("3.0.0"), false, new ArtifactVersion("3.0.1"), true)); plugins = ImmutableList.of( new PluginClass("typeA", "plugin3", "", "c.c.c.plugin3", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); meta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parentArtifacts); writeArtifact(id3, meta, "some contents"); // parent-(4.0.0,4.0.2) -- only visible by parent-4.0.1 Id.Artifact id4 = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "0.0.4"); parentArtifacts = ImmutableSet.of(new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("4.0.0"), false, new ArtifactVersion("4.0.2"), false)); plugins = ImmutableList.of( new PluginClass("typeA", "plugin4", "", "c.c.c.plugin4", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); meta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parentArtifacts); writeArtifact(id4, meta, "some contents"); ArtifactMeta parentMeta = new ArtifactMeta(ArtifactClasses.builder().build()); // check parent-1.0.0 has plugin1 but parent-0.0.9 does not and 1.0.1 does not Id.Artifact parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "0.0.9"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.1"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); writeArtifact(parentId, parentMeta, "content"); Assert.assertEquals(1, artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).size()); // check parent-2.0.0 has plugin2 but parent-1.9.9 does not and 2.0.1 does not parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.9.9"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "2.0.1"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "2.0.0"); writeArtifact(parentId, parentMeta, "content"); Assert.assertEquals(1, artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).size()); // check parent-3.0.1 has plugin3 but parent-3.0.0 does not and 3.0.2 does not parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "3.0.0"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "3.0.2"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "3.0.1"); writeArtifact(parentId, parentMeta, "content"); Assert.assertEquals(1, artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).size()); // check parent-4.0.1 has plugin4 but parent-4.0.0 does not and 4.0.2 does not parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "4.0.0"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "4.0.2"); writeArtifact(parentId, parentMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).isEmpty()); parentId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "4.0.1"); writeArtifact(parentId, parentMeta, "content"); Assert.assertEquals(1, artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentId).size()); } // this test tests that when an artifact specifies a range of artifact versions it extends, // those versions are honored @Test public void testPluginParentVersions() throws Exception { // write an artifact that extends parent-[1.0.0, 2.0.0) Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "plugins", "0.1.0"); Set<ArtifactRange> parentArtifacts = ImmutableSet.of(new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0"))); Set<PluginClass> plugins = ImmutableSet.of( new PluginClass("atype", "plugin1", "", "c.c.c.plugin1", "cfg", ImmutableMap.<String, PluginPropertyField>of()) ); ArtifactMeta meta = new ArtifactMeta(ArtifactClasses.builder().addPlugins(plugins).build(), parentArtifacts); writeArtifact(artifactId, meta, "some contents"); ArtifactDescriptor artifactInfo = artifactStore.getArtifact(artifactId).getDescriptor(); // check ids that are out of range. They should not return anything List<Id.Artifact> badIds = Lists.newArrayList( // ids that are too low Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "0.9.9"), Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0-SNAPSHOT"), // ids that are too high Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "2.0.0") ); ArtifactMeta emptyMeta = new ArtifactMeta(ArtifactClasses.builder().build()); for (Id.Artifact badId : badIds) { // write the parent artifact to make sure we don't get ArtifactNotFound exceptions with later calls // we're testing range filtering, not the absence of the parent artifact writeArtifact(badId, emptyMeta, "content"); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, badId).isEmpty()); Assert.assertTrue(artifactStore.getPluginClasses(NamespaceId.DEFAULT, badId, "atype").isEmpty()); try { artifactStore.getPluginClasses(NamespaceId.DEFAULT, badId, "atype", "plugin1"); Assert.fail(); } catch (PluginNotExistsException e) { // expected } } // check ids that are in range return what we expect List<Id.Artifact> goodIds = Lists.newArrayList( // ids that are too low Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"), // ids that are too high Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.9.9"), Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.99.999"), Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "2.0.0-SNAPSHOT") ); Map<ArtifactDescriptor, Set<PluginClass>> expectedPluginsMapList = ImmutableMap.of(artifactInfo, plugins); Map<ArtifactDescriptor, PluginClass> expectedPluginsMap = ImmutableMap.of(artifactInfo, plugins.iterator().next()); for (Id.Artifact goodId : goodIds) { // make sure parent actually exists writeArtifact(goodId, emptyMeta, "content"); Assert.assertEquals(expectedPluginsMapList, artifactStore.getPluginClasses(NamespaceId.DEFAULT, goodId)); Assert.assertEquals(expectedPluginsMapList, artifactStore.getPluginClasses(NamespaceId.DEFAULT, goodId, "atype")); Assert.assertEquals(expectedPluginsMap, artifactStore.getPluginClasses(NamespaceId.DEFAULT, goodId, "atype", "plugin1")); } } @Category(SlowTests.class) @Test public void testConcurrentWrite() throws Exception { // start up a bunch of threads that will try and write the same artifact at the same time // only one of them should be able to write it int numThreads = 20; final Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "abc", "1.0.0"); final List<String> successfulWriters = Collections.synchronizedList(Lists.<String>newArrayList()); // use a barrier so they all try and write at the same time final CyclicBarrier barrier = new CyclicBarrier(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { final String writer = String.valueOf(i); executorService.execute(new Runnable() { @Override public void run() { try { barrier.await(); ArtifactMeta meta = new ArtifactMeta( ArtifactClasses.builder() .addPlugin(new PluginClass("plugin-type", "plugin" + writer, "", "classname", "cfg", ImmutableMap.<String, PluginPropertyField>of())) .build() ); writeArtifact(artifactId, meta, writer); successfulWriters.add(writer); } catch (InterruptedException | BrokenBarrierException | IOException e) { // something went wrong, fail the test throw new RuntimeException(e); } catch (ArtifactAlreadyExistsException | WriteConflictException e) { // these are ok, all but one thread should see this } finally { latch.countDown(); } } }); } // wait for all writers to finish latch.await(); // only one writer should have been able to write Assert.assertEquals(1, successfulWriters.size()); String successfulWriter = successfulWriters.get(0); // check that the contents weren't mixed between writers ArtifactDetail info = artifactStore.getArtifact(artifactId); ArtifactMeta expectedMeta = new ArtifactMeta( ArtifactClasses.builder() .addPlugin(new PluginClass("plugin-type", "plugin" + successfulWriter, "", "classname", "cfg", ImmutableMap.<String, PluginPropertyField>of())) .build() ); assertEqual(artifactId, expectedMeta, successfulWriter, info); } @Test public void testUpdateProperties() throws Exception { Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "abc", "1.2.3"); Map<String, String> properties = ImmutableMap.of("k1", "v1", "k2", "v2"); ArtifactMeta meta = new ArtifactMeta( ArtifactClasses.builder().build(), ImmutableSet.<ArtifactRange>of(), properties); writeArtifact(artifactId, meta, "some contents"); ArtifactDetail detail = artifactStore.getArtifact(artifactId); Assert.assertEquals(properties, detail.getMeta().getProperties()); // update one key and add a new key artifactStore.updateArtifactProperties(artifactId, new Function<Map<String, String>, Map<String, String>>() { @Override public Map<String, String> apply(Map<String, String> input) { return ImmutableMap.of("k3", "v3"); } }); properties = ImmutableMap.of("k3", "v3"); detail = artifactStore.getArtifact(artifactId); Assert.assertEquals(properties, detail.getMeta().getProperties()); } @Category(SlowTests.class) @Test public void testConcurrentSnapshotWrite() throws Exception { // write parent Id.Artifact parentArtifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "parent", "1.0.0"); ArtifactMeta parentMeta = new ArtifactMeta(ArtifactClasses.builder().build()); writeArtifact(parentArtifactId, parentMeta, "content"); final ArtifactRange parentArtifacts = new ArtifactRange( Id.Namespace.DEFAULT, "parent", new ArtifactVersion("1.0.0"), new ArtifactVersion("2.0.0")); // start up a bunch of threads that will try and write the same artifact at the same time // only one of them should be able to write it int numThreads = 20; final Id.Artifact artifactId = Id.Artifact.from(Id.Namespace.DEFAULT, "abc", "1.0.0-SNAPSHOT"); // use a barrier so they all try and write at the same time final CyclicBarrier barrier = new CyclicBarrier(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { final String writer = String.valueOf(i); executorService.execute(new Runnable() { @Override public void run() { try { barrier.await(); ArtifactMeta meta = new ArtifactMeta( ArtifactClasses.builder() .addPlugin(new PluginClass("plugin-type", "plugin" + writer, "", "classname", "cfg", ImmutableMap.<String, PluginPropertyField>of())) .build(), ImmutableSet.of(parentArtifacts) ); writeArtifact(artifactId, meta, writer); } catch (InterruptedException | BrokenBarrierException | ArtifactAlreadyExistsException | IOException e) { // something went wrong, fail the test throw new RuntimeException(e); } catch (WriteConflictException e) { // these are ok though unexpected (means couldn't write after a bunch of retries too) } finally { latch.countDown(); } } }); } // wait for all writers to finish latch.await(); // figure out which was the last writer by reading our data. all the writers should have been able to write, // and they should have all overwritten each other in a consistent manner ArtifactDetail detail = artifactStore.getArtifact(artifactId); // figure out the winning writer from the plugin name, which is 'plugin<writer>' String pluginName = detail.getMeta().getClasses().getPlugins().iterator().next().getName(); String winnerWriter = pluginName.substring("plugin".length()); ArtifactMeta expectedMeta = new ArtifactMeta( ArtifactClasses.builder() .addPlugin(new PluginClass("plugin-type", "plugin" + winnerWriter, "", "classname", "cfg", ImmutableMap.<String, PluginPropertyField>of())) .build(), ImmutableSet.of(parentArtifacts) ); assertEqual(artifactId, expectedMeta, winnerWriter, detail); // check only 1 plugin remains and that its the correct one Map<ArtifactDescriptor, Set<PluginClass>> pluginMap = artifactStore.getPluginClasses(NamespaceId.DEFAULT, parentArtifactId, "plugin-type"); Map<ArtifactDescriptor, Set<PluginClass>> expected = Maps.newHashMap(); expected.put(detail.getDescriptor(), ImmutableSet.<PluginClass>of( new PluginClass("plugin-type", "plugin" + winnerWriter, "", "classname", "cfg", ImmutableMap.<String, PluginPropertyField>of()))); Assert.assertEquals(expected, pluginMap); } private void assertEqual(Id.Artifact expectedId, ArtifactMeta expectedMeta, String expectedContents, ArtifactDetail actual) throws IOException { Assert.assertEquals(expectedId.getName(), actual.getDescriptor().getArtifactId().getName()); Assert.assertEquals(expectedId.getVersion(), actual.getDescriptor().getArtifactId().getVersion()); Assert.assertEquals(expectedId.getNamespace().equals(Id.Namespace.SYSTEM), actual.getDescriptor().getArtifactId().getScope().equals(ArtifactScope.SYSTEM)); Assert.assertEquals(expectedMeta, actual.getMeta()); assertContentsEqual(expectedContents, actual.getDescriptor().getLocation()); } private void assertContentsEqual(String expectedContents, Location location) throws IOException { Assert.assertEquals(expectedContents, CharStreams.toString(new InputStreamReader(location.getInputStream(), Charsets.UTF_8))); } private void writeArtifact(Id.Artifact artifactId, ArtifactMeta meta, String contents) throws ArtifactAlreadyExistsException, IOException, WriteConflictException { artifactStore.write(artifactId, meta, ByteStreams.newInputStreamSupplier(Bytes.toBytes(contents))); } }