From 97b50e4c304f0886f96a7cd503f1d634d1fbd9c4 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 26 Jun 2017 12:49:38 +0100 Subject: [PATCH 01/33] add routine to create new temp bundle --- .../brooklyn/util/core/osgi/BundleMaker.java | 27 ++++++- .../util/core/osgi/BundleMakerTest.java | 78 +++++++++++-------- 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java index b590cee356..ebcc2209bd 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -144,11 +144,15 @@ public Manifest getManifest(File f) { /** as {@link #copyAddingManifest(File, Manifest)} but taking manifest entries as a map for convenience */ public File copyAddingManifest(File f, Map attrs) { + return copyAddingManifest(f, manifestOf(attrs)); + } + + protected Manifest manifestOf(Map attrs) { Manifest mf = new Manifest(); for (Map.Entry attr: attrs.entrySet()) { mf.getMainAttributes().putValue(attr.getKey(), attr.getValue()); } - return copyAddingManifest(f, mf); + return mf; } /** create a copy of the given ZIP as a JAR with the given manifest, returning the new temp file */ @@ -350,5 +354,26 @@ private void addUrlFileToZip(ZipOutputStream zout, String root, String item, Pre Streams.closeQuietly(itemFound); zout.closeEntry(); } + + /** Creates a temporary file with the given metadata */ + public File createTempBundle(String nameHint, Manifest mf, Map files) { + File f2 = Os.newTempFile(nameHint, "zip"); + ZipOutputStream zout = null; + ZipFile zf = null; + try { + zout = new JarOutputStream(new FileOutputStream(f2), mf); + writeZipEntries(zout, files); + } catch (IOException e) { + throw Exceptions.propagate("Unable to read/write for "+nameHint, e); + } finally { + Streams.closeQuietly(zf); + Streams.closeQuietly(zout); + } + return f2; + } + + public File createTempBundle(String nameHint, Map mf, Map files) { + return createTempBundle(nameHint, manifestOf(mf), files); + } } diff --git a/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java index 515ab2c7de..f9c17370e5 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java @@ -26,8 +26,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; -import java.util.Enumeration; -import java.util.List; +import java.util.Collections; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -38,6 +37,8 @@ import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.stream.Streams; import org.osgi.framework.Bundle; @@ -50,7 +51,6 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; public class BundleMakerTest extends BrooklynMgmtUnitTestSupport { @@ -81,21 +81,21 @@ public void tearDown() throws Exception { @Test public void testCopyAdding() throws Exception { generatedJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test public void testCopyAddingToNonEmpty() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("preExisting.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("preExisting.txt", "myPreExisting", "myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("preExisting.txt", "myPreExisting", "myfile.txt", "mytext"), false); } @Test public void testCopyAddingOverwritesEntry() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -109,7 +109,7 @@ public void testCopyAddingManifest() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -121,7 +121,7 @@ public void testCopyAddingManifestByMap() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -138,7 +138,7 @@ public void testCopyAddingManifestOverwritesExisting() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest), false); } @Test @@ -147,7 +147,7 @@ public void testCopyRemovingPredicate() throws Exception { new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, Predicates.equalTo("myfile.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -156,7 +156,7 @@ public void testCopyRemovingItems() throws Exception { new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("myfile.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("myfile2.txt", "mytext2"), false); } // TODO Not supported - can't remove an entire directory like this @@ -166,14 +166,14 @@ public void testCopyRemovingItemDir() throws Exception { new ZipEntry("mydir/myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), new ZipEntry("mydir2/myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("mydir")); - assertJarContents(generatedJar, ImmutableMap.of("mydir2/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("mydir2/myfile2.txt", "mytext2"), false); } @Test public void testCopyRemovingItemsUnmatched() throws Exception { tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("wrong.txt")); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), false); } @Test @@ -198,7 +198,7 @@ public void testHasOsgiManifestWhenValidManifest() throws Exception { @Test public void testCreateJarFromClasspathDirNoManifest() throws Exception { generatedJar = bundleMaker.createJarFromClasspathDir("/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest"); - assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2"), false); } @Test @@ -209,7 +209,7 @@ public void testCreateJarFromClasspathDirWithManifest() throws Exception { "Manifest-Version: 1.2.3\r\n" + "mykey: myval\r\n" + "\r\n"; - assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest, "myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest, "myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2"), false); } @SuppressWarnings("deprecation") @@ -233,6 +233,23 @@ public void testInstallBundle() throws Exception { .get(); assertEquals(bundle2, bundle); } + + @Test + public void testCreate() throws Exception { + Map manifest = ImmutableMap.of( + Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", + Constants.BUNDLE_VERSION, "4.5.6", + Constants.BUNDLE_SYMBOLICNAME, "myname"); + + generatedJar = bundleMaker.createTempBundle("test", manifest, + ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext"), true); + + @SuppressWarnings("deprecation") + Bundle bundle = bundleMaker.installBundle(generatedJar, false); + assertEquals(bundle.getSymbolicName(), "myname"); + assertEquals(bundle.getVersion(), new Version("4.5.6")); + } private File createEmptyJarFile() throws Exception { File result = Os.newTempFile("base", "jar"); @@ -241,29 +258,28 @@ private File createEmptyJarFile() throws Exception { return result; } - private void assertJarContents(File f, Map expectedContents) throws Exception { + private void assertJarContents(File f, Map expectedContents, boolean othersAllowed) throws Exception { ZipFile zipFile = new ZipFile(f); - String zipEntriesMsg = "entries="+enumerationToList(zipFile.entries()); + expectedContents = MutableMap.copyOf(expectedContents); try { - for (Map.Entry entry : expectedContents.entrySet()) { - ZipEntry zipEntry = zipFile.getEntry(entry.getKey()); - assertNotNull(zipEntry, "No entry for "+entry.getKey()+"; "+zipEntriesMsg); - String entryContents = Streams.readFullyString(zipFile.getInputStream(zipEntry)); - assertEquals(entryContents, entry.getValue()); + for (ZipEntry zipEntry: Collections.list(zipFile.entries())) { + String expectedContent = expectedContents.remove(zipEntry.getName()); + if (expectedContent==null) { + if (!othersAllowed) { + assertNotNull(expectedContent, "Unexpected item in ZIP: "+zipEntry.getName()); + } + } else { + String entryContents = Streams.readFullyString(zipFile.getInputStream(zipEntry)); + assertEquals(entryContents, expectedContent, "Contents not eas expectd for "+zipEntry.getName()); + } + } + if (!expectedContents.isEmpty()) { + Asserts.fail("ZIP did not contain expected contents: "+expectedContents.keySet()); } - assertEquals(zipFile.size(), expectedContents.size(), zipEntriesMsg); } finally { zipFile.close(); } } - private List enumerationToList(Enumeration e) { - List result = Lists.newArrayList(); - while (e.hasMoreElements()) { - result.add(e.nextElement()); - } - return result; - - } } From d7975d1414269f0f14617d3987170eba729c9c7d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 23 Jun 2017 10:53:51 +0100 Subject: [PATCH 02/33] implicitly wrap yaml in bundles when uploading to catalog creates a new bundle when given yaml in catalog; bundle name and version now recommended in the BOM. scan-java option tweaked but in a way that is consistent with the past and sensible in the new world. auto-wrapped bundles are identified with a header so we can simplify their handling in some cases (scanning, search paths). some things clumsy and need fixed: * uploading a different BOM (or bundle) at the same version says "ignoring because identical"; osgi identicality check should look at contents * auto-wrapped bundles are added to the library search path (no need for this as the bundles are empty) * failed installations keep the bundle installed, blocking subsequent installs; probably don't install unless forced? * warn if different version declared in BOM some things clumsy but we could live with: * containing bundles are added as libraries by munging the yaml; now we have a record to that so can do a bit better than munge yaml * if bundle has no name, a random one is chosen (probably deprecated this behaviour); if you re-submit we don't know it's the same bundle so we don't detect it's a bundle update; this means: * uploading the exact same non-snapshot BOM twice will fail the second time saying the items are different (because containing bundle is different) * uploading an updated item in a snapshot BOM will correctly replace, but the old bundle will still be around (just masked; though we can fix that) --- .../api/typereg/OsgiBundleWithUrl.java | 5 + .../catalog/CatalogOsgiLibraryTest.java | 11 +- .../CatalogOsgiVersionMoreEntityTest.java | 14 +- .../catalog/CatalogOsgiYamlEntityTest.java | 12 +- .../catalog/SpecParameterParsingOsgiTest.java | 26 ++- .../internal/BasicBrooklynCatalog.java | 179 +++++++++++++++--- .../catalog/internal/CatalogBundleDto.java | 9 +- .../catalog/internal/CatalogBundleLoader.java | 8 +- .../core/mgmt/ha/OsgiArchiveInstaller.java | 10 +- .../mgmt/ha/OsgiBundleInstallationResult.java | 16 +- .../core/typereg/BasicManagedBundle.java | 2 - .../core/typereg/BasicOsgiBundleWithUrl.java | 7 + .../resources/brooklyn/default.catalog.bom | 6 +- .../brooklyn/util/osgi/VersionedName.java | 7 + 14 files changed, 247 insertions(+), 65 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java b/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java index 970fc68a27..55b30fc862 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/OsgiBundleWithUrl.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.api.typereg; +import org.apache.brooklyn.util.osgi.VersionedName; + import com.google.common.annotations.Beta; @Beta @@ -36,5 +38,8 @@ public interface OsgiBundleWithUrl { /** @return true if we have a name and version for this bundle; * false if not, e.g. if we only know the URL and we haven't loaded it yet */ public boolean isNameResolved(); + + /** @return the {@link VersionedName} for this bundle, or null if not available */ + public VersionedName getVersionedName(); } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java index aebd80d86c..87a459e2e3 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; @@ -58,7 +59,6 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; import com.google.common.io.BaseEncoding; public class CatalogOsgiLibraryTest extends AbstractYamlTest { @@ -421,7 +421,12 @@ public MyExternalConfigSupplier(ManagementContext mgmt, String name, Map item, String expectedUrl) { - CatalogBundle library = Iterables.getOnlyElement(item.getLibraries()); - assertEquals(library.getUrl(), expectedUrl); + for (CatalogBundle b: item.getLibraries()) { + if (Objects.equals(b.getUrl(), expectedUrl)) { + return; + } + } + Assert.fail("No library found with URL "+expectedUrl); } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java index 8961c6f7d1..0813d80638 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java @@ -28,12 +28,14 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher; -import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest; @@ -75,8 +77,8 @@ public void testBrooklynManagedBundleInstall() throws Exception { // bundle installed Map bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); - Asserts.assertSize(bundles.keySet(), 1); - Assert.assertEquals(br.getMetadata().getId(), Iterables.getOnlyElement( bundles.keySet() )); + Asserts.assertSize(bundles.keySet(), 1 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); + Assert.assertTrue(bundles.keySet().contains( br.getMetadata().getId() )); // types installed RegisteredType t = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); @@ -96,7 +98,7 @@ public void testMoreEntityV1() throws Exception { Assert.assertNotNull(item); Assert.assertEquals(item.getVersion(), "1.0"); Assert.assertTrue(RegisteredTypePredicates.IS_ENTITY.apply(item)); - Assert.assertEquals(item.getLibraries().size(), 1); + Assert.assertEquals(item.getLibraries().size(), 1 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]"); Entity moreEntity = Iterables.getOnlyElement(app.getChildren()); @@ -220,7 +222,7 @@ public void testMoreEntityV2AutoscanWithClasspath() throws Exception { // this refers to the java item, where the libraries are defined item = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MoreEntity"); Assert.assertEquals(item.getVersion(), "2.0.test_java"); - Assert.assertEquals(item.getLibraries().size(), 2); + Assert.assertEquals(item.getLibraries().size(), 2 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); Entity app = createAndStartApplication("services: [ { type: 'more-entity:2.0.test' } ]"); Entity moreEntity = Iterables.getOnlyElement(app.getChildren()); @@ -247,7 +249,7 @@ public void testMorePolicyV2AutoscanWithClasspath() throws Exception { // this refers to the java item, where the libraries are defined item = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MorePolicy"); Assert.assertEquals(item.getVersion(), "2.0.test_java"); - Assert.assertEquals(item.getLibraries().size(), 2); + Assert.assertEquals(item.getLibraries().size(), 2 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); Entity app = createAndStartApplication( "services: ", diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java index fe86ed0648..a1bd800c1c 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java @@ -243,9 +243,9 @@ public void testPartialBundleReferenceFails() { " - name: io.brooklyn.brooklyn-test-osgi-entities", " item:", " type: " + SIMPLE_ENTITY_TYPE); - fail(); - } catch (NullPointerException e) { - Assert.assertEquals(e.getMessage(), "both name and version are required"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "both name and version are required"); } try { addCatalogItems( @@ -257,9 +257,9 @@ public void testPartialBundleReferenceFails() { " - version: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION, " item:", " type: " + SIMPLE_ENTITY_TYPE); - fail(); - } catch (NullPointerException e) { - Assert.assertEquals(e.getMessage(), "both name and version are required"); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "both name and version are required"); } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java index 814aed7c7b..557ebc5636 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java @@ -29,6 +29,7 @@ import org.apache.brooklyn.api.objs.SpecParameter; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.objs.BasicSpecParameter; @@ -39,6 +40,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; public class SpecParameterParsingOsgiTest extends AbstractYamlTest { @@ -82,16 +84,34 @@ public void testOsgiClassScanned() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); addCatalogItems("brooklyn.catalog:", + " bundle: test-items", + " version: 2.0-test_java", " items:", " - scanJavaAnnotations: true", - " version: 2.0.test_java", + " item:", + " id: here-item", + " type: "+OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, " libraries:", " - classpath://" + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH, " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + RegisteredType hereItem = mgmt().getTypeRegistry().get("here-item"); + assertEquals(hereItem.getVersion(), "2.0-test_java"); + assertEquals(hereItem.getLibraries().size(), 3); + assertEquals(hereItem.getContainingBundle(), "test-items:2.0-test_java"); + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); - assertEquals(item.getVersion(), "2.0.test_java"); - assertEquals(item.getLibraries().size(), 2); + // since 0.12.0 items now installed with version from bundle, not inherited from the version here + assertEquals(item.getVersion(), BrooklynVersion.get()); + // since 0.12.0 library bundles (correctly) don't inherit libraries from caller + assertEquals(item.getLibraries().size(), 1); + assertEquals(Iterables.getOnlyElement(item.getLibraries()).getVersionedName().toString(), + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + + assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + + // TODO assertions above should be in separate test + AbstractBrooklynObjectSpec spec = createSpec(item); List> inputs = spec.getParameters(); if (inputs.isEmpty()) Assert.fail("no inputs (if you're in the IDE, mvn clean install may need to be run to rebuild osgi test JARs)"); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index a7877893d8..d003ce97af 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -21,12 +21,20 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import javax.annotation.Nullable; @@ -40,9 +48,13 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.core.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes; import org.apache.brooklyn.core.location.BasicLocationRegistry; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer; @@ -52,6 +64,7 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.UserFacingException; @@ -60,15 +73,18 @@ import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.javalang.LoadedClassLoader; import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yaml.Yamls.YamlExtract; +import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; @@ -86,8 +102,19 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { public static final String POLICIES_KEY = "brooklyn.policies"; public static final String ENRICHERS_KEY = "brooklyn.enrichers"; public static final String LOCATIONS_KEY = "brooklyn.locations"; - public static final String NO_VERSION = "0.0.0.SNAPSHOT"; - + public static final String NO_VERSION = "0.0.0-SNAPSHOT"; + + public static final String CATALOG_BOM = "catalog.bom"; + // should always be 1.0; see bottom of + // http://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html + // (some things talk of 2.0, but haven't investigated that) + public static final String OSGI_MANIFEST_VERSION_VALUE = "1.0"; + + /** Header on bundle indicating it is a wrapped BOM with no other resources */ + public static final String BROOKLYN_WRAPPED_BOM_BUNDLE = "Brooklyn-Wrapped-BOM"; + @VisibleForTesting + public static final boolean AUTO_WRAP_CATALOG_YAML_AS_BUNDLE = true; + private static final Logger log = LoggerFactory.getLogger(BasicBrooklynCatalog.class); public static class BrooklynLoaderTracker { @@ -415,9 +442,9 @@ private static Maybe> getFirstAsMap(Map map, String firstKey, Stri return (Maybe) getFirstAs(map, Map.class, firstKey, otherKeys); } - private List> collectCatalogItems(String yaml) { + private List> collectCatalogItems(String yaml, ManagedBundle containingBundle) { List> result = MutableList.of(); - collectCatalogItems(yaml, result, ImmutableMap.of()); + collectCatalogItems(yaml, containingBundle, result, ImmutableMap.of()); return result; } @@ -449,7 +476,7 @@ public static VersionedName getVersionedName(Map catalogMetadata, boolean r return new VersionedName(bundle, version); } - private void collectCatalogItems(String yaml, List> result, Map parentMeta) { + private void collectCatalogItems(String yaml, ManagedBundle containingBundle, List> result, Map parentMeta) { Map itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class); Map catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull(); if (catalogMetadata==null) @@ -457,7 +484,7 @@ private void collectCatalogItems(String yaml, List> catalogMetadata = MutableMap.copyOf(catalogMetadata); collectCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), - catalogMetadata, result, parentMeta); + containingBundle, catalogMetadata, result, parentMeta, 0); itemDef.remove("brooklyn.catalog"); catalogMetadata.remove("item"); @@ -472,12 +499,12 @@ private void collectCatalogItems(String yaml, List> if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match); else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, ""); } - collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata); + collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), containingBundle, rootItem, result, catalogMetadata, 1); } } @SuppressWarnings("unchecked") - private void collectCatalogItems(String sourceYaml, Map itemMetadata, List> result, Map parentMetadata) { + private void collectCatalogItems(String sourceYaml, ManagedBundle containingBundle, Map itemMetadata, List> result, Map parentMetadata, int depth) { if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata); @@ -528,13 +555,35 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< if (scanJavaAnnotations==null || !scanJavaAnnotations) { // don't scan } else { - // scan for annotations: if libraries here, scan them; if inherited libraries error; else scan classpath - if (!libraryBundlesNew.isEmpty()) { - result.addAll(scanAnnotationsFromBundles(mgmt, libraryBundlesNew, catalogMetadata)); - } else if (libraryBundles.isEmpty()) { - result.addAll(scanAnnotationsFromLocal(mgmt, catalogMetadata)); + if (isNoBundleOrSimpleWrappingBundle(containingBundle)) { + // BOMs wrapped in JARs, or without JARs, have special treatment + if (isLibrariesMoreThanJustContainingBundle(libraryBundlesNew, containingBundle)) { + // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM + log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+libraryBundlesNew+"); libraries should declare they scan themselves"); + result.addAll(scanAnnotationsFromBundles(mgmt, libraryBundlesNew, catalogMetadata)); + } else if (!isLibrariesMoreThanJustContainingBundle(libraryBundles, containingBundle)) { + // for default catalog, no libraries declared, we want to scan local classpath + // bundle should be named "brooklyn-default-catalog" + if (containingBundle!=null && !containingBundle.getSymbolicName().contains("brooklyn-default-catalog")) { + // a user uplaoded a BOM trying to tell us to do a local java scan; previously supported but becoming unsupported + log.warn("Deprecated use of scanJavaAnnotations in non-Java BOM outwith the default catalog setup"); + } else if (depth>0) { + // since 0.12.0, require this to be right next to where libraries are defined, or at root + log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); + } + result.addAll(scanAnnotationsFromLocal(mgmt, catalogMetadata)); + } else { + throw new IllegalStateException("Cannot scan for Java catalog items when libraries declared on an ancestor; scanJavaAnnotations should be specified alongside brooklyn.libraries (or ideally those libraries should specify to scan)"); + } } else { - throw new IllegalStateException("Cannot scan catalog node no local bundles, and with inherited bundles we will not scan the classpath"); + if (depth>0) { + // since 0.12.0, require this to be right next to where libraries are defined, or at root + log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); + } + // normal JAR install, only scan that bundle (the one containing the catalog.bom) + result.addAll(scanAnnotationsFromBundles(mgmt, MutableList.of(containingBundle), catalogMetadata)); + // TODO above (scanning a ZIP uploaded) won't work yet because scan routines need a URL + // TODO are libraries installed properly, such that they are now managed and their catalog.bom's are scanned ? } } @@ -546,18 +595,18 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< int count = 0; for (Object ii: checkType(items, "items", List.class)) { if (ii instanceof String) { - collectUrlReferencedCatalogItems((String) ii, result, catalogMetadata); + collectUrlReferencedCatalogItems((String) ii, containingBundle, result, catalogMetadata); } else { Map i = checkType(ii, "entry in items list", Map.class); collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), - i, result, catalogMetadata); + containingBundle, i, result, catalogMetadata, depth+1); } count++; } } if (url != null) { - collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), result, catalogMetadata); + collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), containingBundle, result, catalogMetadata); } if (item==null) return; @@ -594,7 +643,7 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< itemType = planInterpreter.getCatalogItemType(); Map itemAsMap = planInterpreter.getItem(); // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key, - // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input + // as opposed to the rawer { type: foo } map without that outer key which is valid as item input // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! // if symname not set, infer from: id, then name, then item id, then item name @@ -738,7 +787,21 @@ private void collectCatalogItems(String sourceYaml, Map itemMetadata, List< result.add(dto); } - private void collectUrlReferencedCatalogItems(String url, List> result, Map parentMeta) { + private boolean isLibrariesMoreThanJustContainingBundle(Collection library, ManagedBundle containingBundle) { + if (library==null) return false; + if (containingBundle==null) return !library.isEmpty(); + if (library.size()>1) return true; + CatalogBundle li = Iterables.getOnlyElement(library); + return !containingBundle.getVersionedName().equalsOsgi(li.getVersionedName()); + } + + private boolean isNoBundleOrSimpleWrappingBundle(ManagedBundle b) { + if (b==null) return true; + String wrapped = ((ManagementContextInternal)mgmt).getOsgiManager().get().findBundle(b).get().getHeaders().get(BROOKLYN_WRAPPED_BOM_BUNDLE); + return wrapped!=null && wrapped.equalsIgnoreCase("true"); + } + + private void collectUrlReferencedCatalogItems(String url, ManagedBundle containingBundle, List> result, Map parentMeta) { @SuppressWarnings("unchecked") List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, List.class, "brooklyn.libraries", "libraries").orNull()); Collection parentLibraries = CatalogItemDtoAbstract.parseLibraries(parentLibrariesRaw); @@ -750,7 +813,7 @@ private void collectUrlReferencedCatalogItems(String url, List item, String fieldAt return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata); } - private Collection> scanAnnotationsFromBundles(ManagementContext mgmt, Collection libraries, Map catalogMetadata) { + private Collection> scanAnnotationsFromBundles(ManagementContext mgmt, Collection libraries, Map catalogMetadata) { CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode()); List urls = MutableList.of(); - for (CatalogBundle b: libraries) { + for (OsgiBundleWithUrl b: libraries) { // TODO currently does not support pre-installed bundles identified by name:version // (ie where URL not supplied) if (Strings.isNonBlank(b.getUrl())) { urls.add(b.getUrl()); + } else { + log.warn("scanJavaAnnotations does not apply to pre-installed bundles; skipping "+b); } } @@ -1060,14 +1125,9 @@ public CatalogItem addItem(String yaml) { @Override public List> addItems(String yaml) { - return addItems(yaml, null); + return addItems(yaml, false); } - @Override - public List> addItems(String yaml, ManagedBundle bundle) { - return addItems(yaml, bundle, false); - } - @Override public CatalogItem addItem(String yaml, boolean forceUpdate) { return Iterables.getOnlyElement(addItems(yaml, forceUpdate)); @@ -1075,13 +1135,74 @@ public CatalogItem addItem(String yaml, boolean forceUpdate) { @Override public List> addItems(String yaml, boolean forceUpdate) { + Maybe osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) { + // TODO wrap in a bundle to be managed; need to get bundle and version from yaml + Map cm = BasicBrooklynCatalog.getCatalogMetadata(yaml); + VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false ); + if (vn==null) { + // for better legacy compatibiity, if id specified at root use that + String id = (String) cm.get("id"); + if (Strings.isNonBlank(id)) { + vn = VersionedName.fromString(id); + } + vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), + vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION)); + } + Manifest mf = new Manifest(); + mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName()); + mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString() ); + mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE); + mf.getMainAttributes().putValue(BROOKLYN_WRAPPED_BOM_BUNDLE, Boolean.TRUE.toString()); + + BundleMaker bm = new BundleMaker(mgmt); + File bf = bm.createTempBundle(vn.getSymbolicName(), mf, MutableMap.of( + new ZipEntry(CATALOG_BOM), (InputStream) new ByteArrayInputStream(yaml.getBytes())) ); + + OsgiBundleInstallationResult result = null; + try { + result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get(); + } catch (FileNotFoundException e) { + throw Exceptions.propagate(e); + } + bf.delete(); + if (result.getCode().isError() || result.getCode()==ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED) { + // if we're wrapping YAML then we don't allow equivalent YAML to be pasted + // TODO remove this once we have better bundle equivalence checks + throw new IllegalStateException(result.getMessage()); + } + return toItems(result.getCatalogItemsInstalled()); + + // TODO check if we've overridden all items pertaining to an older anonymous catalog.bom bundle + // we could remove references to that anonymous bundle; + // without this currently we leak bundles as bom's are replaced + // (because we persist each item as well as the bundle, and we use the item XML on rebind, + // rather than rereading the catalog.bom from the bundle, there isn't currently a risk of loading + // any of those overwritten items; however probably wise in future to require a bundle ID) + } + // fallback to non-OSGi for tests and other environments return addItems(yaml, null, forceUpdate); } + @SuppressWarnings("deprecation") + private List> toItems(Iterable itemIds) { + List> result = MutableList.of(); + for (String id: itemIds) { + // TODO prefer to use RegisteredType, but that's an API change here + result.add(CatalogUtils.getCatalogItemOptionalVersion(mgmt, id)); + } + return result; + } + + @Override + public List> addItems(String yaml, ManagedBundle bundle) { + return addItems(yaml, bundle, false); + } + private List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { log.debug("Adding manual catalog item to "+mgmt+": "+yaml); checkNotNull(yaml, "yaml"); - List> result = collectCatalogItems(yaml); + List> result = collectCatalogItems(yaml, bundle); // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially for (CatalogItemDtoAbstract item: result) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java index dbbfc760a8..2006d5b027 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java @@ -23,6 +23,7 @@ import com.google.common.base.Preconditions; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; public class CatalogBundleDto implements CatalogBundle { @@ -67,7 +68,13 @@ public String getSuppliedVersionString() { public String getOsgiVersionString() { return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); } - + + @Override + public VersionedName getVersionedName() { + if (!isNameResolved()) return null; + return new VersionedName(getSymbolicName(), getSuppliedVersionString()); + } + @Override public String getUrl() { return url; diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index 4bf88240c5..8d3179d78e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -121,6 +121,7 @@ private String readBom(URL bom) { } } + // TODO remove; now that the bundle is passed through we can add it in the catalog private String addLibraryDetails(Bundle bundle, String bomText) { @SuppressWarnings("unchecked") final Map bom = (Map) Iterables.getOnlyElement(Yamls.parseAll(bomText)); @@ -142,7 +143,12 @@ private String addLibraryDetails(Bundle bundle, String bomText) { private void addLibraryDetails(Bundle bundle, Map catalog) { if (!catalog.containsKey(CatalogBundleLoader.BROOKLYN_LIBRARIES)) { - catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, MutableList.of()); + if (catalog.containsKey("libraries")) { + // legacy name + catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, catalog.remove("libraries")); + } else { + catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, MutableList.of()); + } } final Object librarySpec = catalog.get(CatalogBundleLoader.BROOKLYN_LIBRARIES); if (!(librarySpec instanceof List)) { diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index e4ffb5490d..9ef04c83df 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -59,10 +59,6 @@ class OsgiArchiveInstaller { private static final Logger log = LoggerFactory.getLogger(OsgiArchiveInstaller.class); - // must be 1.0; see bottom of - // http://www.eclipse.org/virgo/documentation/virgo-documentation-3.7.0.M01/docs/virgo-user-guide/html/ch02s02.html - private static final String OSGI_MANIFEST_VERSION_VALUE = "1.0"; - final private OsgiManager osgiManager; private ManagedBundle suppliedKnownBundleMetadata; private InputStream zipIn; @@ -185,9 +181,9 @@ private void discoverManifestFromCatalogBom(boolean isCatalogBomRequired) { } catch (IOException e) { throw new IllegalArgumentException("Invalid ZIP/JAR archive: "+e); } - ZipEntry bom = zf.getEntry("catalog.bom"); + ZipEntry bom = zf.getEntry(BasicBrooklynCatalog.CATALOG_BOM); if (bom==null) { - bom = zf.getEntry("/catalog.bom"); + bom = zf.getEntry("/"+BasicBrooklynCatalog.CATALOG_BOM); } if (bom==null) { if (isCatalogBomRequired) { @@ -231,7 +227,7 @@ private void updateManifestFromAllSourceInformation() { throw new IllegalArgumentException("Missing bundle version in BOM or MANIFEST"); } if (discoveredManifest.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) { - discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE); + discoveredManifest.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), BasicBrooklynCatalog.OSGI_MANIFEST_VERSION_VALUE); manifestNeedsUpdating = true; } if (manifestNeedsUpdating) { diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index c3a725a6bb..9e845adfd7 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -37,11 +37,17 @@ public class OsgiBundleInstallationResult { Runnable deferredStart; public enum ResultCode { - INSTALLED_NEW_BUNDLE, - UPDATED_EXISTING_BUNDLE, - IGNORING_BUNDLE_AREADY_INSTALLED, - ERROR_PREPARING_BUNDLE, - ERROR_INSTALLING_BUNDLE + INSTALLED_NEW_BUNDLE(false), + UPDATED_EXISTING_BUNDLE(false), + // TODO if bundle installed is different to bundle supplied we should flag an error + IGNORING_BUNDLE_AREADY_INSTALLED(false), + ERROR_PREPARING_BUNDLE(true), + ERROR_INSTALLING_BUNDLE(true); + + final boolean isError; + ResultCode(boolean isError) { this.isError = isError; } + + public boolean isError() { return isError; } } final List catalogItemsInstalled = MutableList.of(); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java index 350507cd89..d480ed56a7 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java @@ -55,8 +55,6 @@ public BasicManagedBundle(String name, String version, String url) { Preconditions.checkNotNull(name, "Either a URL or both name and version are required"); Preconditions.checkNotNull(version, "Either a URL or both name and version are required"); } - Version.parseVersion(version); - this.symbolicName = name; this.version = version; this.url = url; diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java index 264b96c8c9..9001ad46ec 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicOsgiBundleWithUrl.java @@ -20,6 +20,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import com.google.common.base.MoreObjects; @@ -78,6 +79,12 @@ public String getOsgiVersionString() { return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); } + @Override + public VersionedName getVersionedName() { + if (!isNameResolved()) return null; + return new VersionedName(getSymbolicName(), getSuppliedVersionString()); + } + @Override public String getUrl() { return url; diff --git a/server-cli/src/main/resources/brooklyn/default.catalog.bom b/server-cli/src/main/resources/brooklyn/default.catalog.bom index 5a5d1b2731..d4d3797f17 100644 --- a/server-cli/src/main/resources/brooklyn/default.catalog.bom +++ b/server-cli/src/main/resources/brooklyn/default.catalog.bom @@ -3,11 +3,13 @@ # and templates to get started using Brooklyn brooklyn.catalog: + bundle: brooklyn-default-catalog version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION - items: # load everything in the classpath with a @Catalog annotation - - scanJavaAnnotations: true + scanJavaAnnotations: true + + items: - id: server itemType: entity diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java index 9ae70df6da..98d6759c5d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java @@ -95,6 +95,13 @@ public Version getOsgiVersion() { return cachedOsgiVersion; } + @Nullable + public String getOsgiVersionString() { + Version ov = getOsgiVersion(); + if (ov==null) return null; + return ov.toString(); + } + @Nullable public String getVersionString() { return v; From 2165778a354fa4221eec0e8ba03a96ef5bef92a3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Jun 2017 14:23:34 +0100 Subject: [PATCH 03/33] fix bundles set on scanning, and aborted work to scan osgi bundles scanning for OSGi can be made to work in some cases if we have the jar, but it won't work after rebind, or even in some cases if persisting (race deleting the JAR), plus there are likely classloading issues; note OSGi deliberately doesn't give us the JAR. java scanning is discouraged by OSGi; it wants us to explicitly list things e.g. in the catalog.bom, which is fine. --- ...atalogOsgiVersionMoreEntityRebindTest.java | 10 +- .../CatalogOsgiVersionMoreEntityTest.java | 2 - .../brooklyn/catalog/CatalogScanOsgiTest.java | 119 ++++++++++++++++++ .../brooklyn/catalog/CatalogScanTest.java | 2 +- .../catalog/SpecParameterParsingOsgiTest.java | 31 +---- .../brooklyn/test/lite/CampYamlLiteTest.java | 6 +- .../internal/BasicBrooklynCatalog.java | 109 ++++++++++++---- .../catalog/internal/CatalogClasspathDo.java | 4 +- .../util/core/javalang/ReflectionScanner.java | 3 + .../src/main/resources/catalog.bom | 1 - .../entities/src/main/resources/catalog.bom | 1 - .../src/main/resources/catalog.bom | 1 - .../src/main/resources/catalog.bom | 1 - .../src/main/resources/catalog.bom | 1 - ...rooklyn-test-osgi-com-example-entities.jar | Bin 22139 -> 22099 bytes .../osgi/brooklyn-test-osgi-entities.jar | Bin 22902 -> 22864 bytes ...brooklyn-test-osgi-more-entities_0.1.0.jar | Bin 16003 -> 15967 bytes ...brooklyn-test-osgi-more-entities_0.2.0.jar | Bin 16922 -> 16884 bytes ...est-osgi-more-entities_evil-twin_0.2.0.jar | Bin 14098 -> 14061 bytes 19 files changed, 222 insertions(+), 69 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java index 1d066f5ac3..b881ec71f7 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java @@ -386,5 +386,13 @@ public void testWrappedEntityClassLoaderDoesntHaveAncestorClassLoader() throws E Asserts.expectedFailureContainsIgnoreCase(e, "unable to load", BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); } } - + + @Test(groups="Broken") // AH think not going to support this; see notes in BasicBrooklynCatalog.scanAnnotationsInBundle + // it's hard to get the JAR for scanning, and doesn't fit with the OSGi way + public void testRebindJavaScanningBundleInCatalog() throws Exception { + CatalogScanOsgiTest.installJavaScanningMoreEntitiesV2(mgmt(), this); + rebind(); + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item, "Scanned item should have been available after rebind"); + } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java index 0813d80638..6fee53e40b 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java @@ -28,8 +28,6 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.policy.PolicySpec; -import org.apache.brooklyn.api.sensor.Enricher; -import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.RegisteredType; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java new file mode 100644 index 0000000000..c8d5399f00 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn.catalog; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.zip.ZipEntry; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.camp.brooklyn.test.lite.CampYamlLiteTest; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.test.support.TestResourceUnavailableException; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.osgi.OsgiTestResources; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Strings; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +public class CatalogScanOsgiTest extends AbstractYamlTest { + + @Override protected boolean disableOsgi() { return false; } + + @Test(groups="Broken") // AH think not going to support this; see notes in BasicBrooklynCatalog.scanAnnotationsInBundle + public void testScanContainingBundle() throws Exception { + installJavaScanningMoreEntitiesV2(mgmt(), this); + + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item, "Scanned item should have been loaded but wasn't"); + assertEquals(item.getVersion(), "0.2.0"); + assertEquals(item.getDisplayName(), "More Entity v2"); + assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + assertEquals(Iterables.getOnlyElement(item.getLibraries()).getVersionedName().toString(), + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); + } + + static void installJavaScanningMoreEntitiesV2(ManagementContext mgmt, Object context) throws FileNotFoundException { + // scanning bundle functionality added in 0.12.0, relatively new compared to non-osgi scanning + + TestResourceUnavailableException.throwIfResourceUnavailable(context.getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + TestResourceUnavailableException.throwIfResourceUnavailable(context.getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + + CampYamlLiteTest.installWithoutCatalogBom(mgmt, OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + BundleMaker bm = new BundleMaker(mgmt); + File f = Os.newTempFile(context.getClass(), "jar"); + Streams.copy(ResourceUtils.create(context).getResourceFromUrl(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH), new FileOutputStream(f)); + f = bm.copyRemoving(f, MutableSet.of("catalog.bom")); + f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), + new ByteArrayInputStream( Strings.lines( + "brooklyn.catalog:", + " scanJavaAnnotations: true").getBytes() ) )); + + ((ManagementContextInternal)mgmt).getOsgiManager().get().install(new FileInputStream(f)).checkNoError(); + } + + @Test + public void testScanLegacyListedLibraries() { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + addCatalogItems(bomForLegacySiblingLibraries()); + + RegisteredType hereItem = mgmt().getTypeRegistry().get("here-item"); + assertEquals(hereItem.getVersion(), "2.0-test_java"); + assertEquals(hereItem.getLibraries().size(), 3); + assertEquals(hereItem.getContainingBundle(), "test-items:2.0-test_java"); + + RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + // versions and libraries _are_ inherited in this legacy mode + assertEquals(item.getVersion(), "2.0-test_java"); + assertEquals(item.getLibraries().size(), 3); + // and the containing bundle is recorded as the + assertEquals(item.getContainingBundle(), "test-items"+":"+"2.0-test_java"); + } + + static String bomForLegacySiblingLibraries() { + return Strings.lines("brooklyn.catalog:", + " bundle: test-items", + " version: 2.0-test_java", + " items:", + " - scanJavaAnnotations: true", + " item:", + " id: here-item", + " type: "+OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, + " libraries:", + " - classpath://" + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH, + " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java index ff7a0270cd..9e11b10a9d 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java @@ -84,7 +84,7 @@ public void tearDown() throws Exception { private LocalManagementContext newManagementContext(BrooklynProperties props) { final LocalManagementContext localMgmt = LocalManagementContextForTests.builder(true) - .disableOsgi(disableOsgi()) + .setOsgiEnablementAndReuse(disableOsgi(), true) .useProperties(props) .build(); mgmts.add(localMgmt); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java index 557ebc5636..06048c3893 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterParsingOsgiTest.java @@ -29,7 +29,6 @@ import org.apache.brooklyn.api.objs.SpecParameter; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; -import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.objs.BasicSpecParameter; @@ -40,7 +39,6 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; public class SpecParameterParsingOsgiTest extends AbstractYamlTest { @@ -82,36 +80,9 @@ public void testOsgiType() { public void testOsgiClassScanned() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH); TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); - - addCatalogItems("brooklyn.catalog:", - " bundle: test-items", - " version: 2.0-test_java", - " items:", - " - scanJavaAnnotations: true", - " item:", - " id: here-item", - " type: "+OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY, - " libraries:", - " - classpath://" + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH, - " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); - - RegisteredType hereItem = mgmt().getTypeRegistry().get("here-item"); - assertEquals(hereItem.getVersion(), "2.0-test_java"); - assertEquals(hereItem.getLibraries().size(), 3); - assertEquals(hereItem.getContainingBundle(), "test-items:2.0-test_java"); + addCatalogItems(CatalogScanOsgiTest.bomForLegacySiblingLibraries()); RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); - // since 0.12.0 items now installed with version from bundle, not inherited from the version here - assertEquals(item.getVersion(), BrooklynVersion.get()); - // since 0.12.0 library bundles (correctly) don't inherit libraries from caller - assertEquals(item.getLibraries().size(), 1); - assertEquals(Iterables.getOnlyElement(item.getLibraries()).getVersionedName().toString(), - OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); - - assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0"); - - // TODO assertions above should be in separate test - AbstractBrooklynObjectSpec spec = createSpec(item); List> inputs = spec.getParameters(); if (inputs.isEmpty()) Assert.fail("no inputs (if you're in the IDE, mvn clean install may need to be run to rebuild osgi test JARs)"); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java index 12abc50f67..7e6f1760a5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -46,6 +47,7 @@ import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.TestApplication; @@ -208,11 +210,11 @@ private String prepAndGetSampleMyCatalogAppYaml(String symbolicName, String bund " type: " + MockWebPlatform.APPSERVER.getName()); } - protected void installWithoutCatalogBom(LocalManagementContext mgmt, String bundleUrl) { + public static void installWithoutCatalogBom(ManagementContext mgmt, String bundleUrl) { // install bundle for class access but without loading its catalog.bom, // since we only have mock matchers here // (if we don't do this, the default routines install it and try to process the catalog.bom, failing) - mgmt.getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null).get(); + ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null).get(); } private void assertMgmtHasSampleMyCatalogApp(String symbolicName, String bundleUrl) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index d003ce97af..c0e2e070da 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.InputStream; import java.util.Collection; import java.util.Collections; @@ -57,6 +58,7 @@ import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; import org.apache.brooklyn.util.collections.MutableList; @@ -72,7 +74,9 @@ import org.apache.brooklyn.util.javalang.AggregateClassLoader; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.javalang.LoadedClassLoader; +import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; @@ -560,7 +564,7 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund if (isLibrariesMoreThanJustContainingBundle(libraryBundlesNew, containingBundle)) { // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+libraryBundlesNew+"); libraries should declare they scan themselves"); - result.addAll(scanAnnotationsFromBundles(mgmt, libraryBundlesNew, catalogMetadata)); + result.addAll(scanAnnotationsLegacyInListOfLibraries(mgmt, libraryBundlesNew, catalogMetadata, containingBundle)); } else if (!isLibrariesMoreThanJustContainingBundle(libraryBundles, containingBundle)) { // for default catalog, no libraries declared, we want to scan local classpath // bundle should be named "brooklyn-default-catalog" @@ -571,19 +575,21 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund // since 0.12.0, require this to be right next to where libraries are defined, or at root log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); } - result.addAll(scanAnnotationsFromLocal(mgmt, catalogMetadata)); + result.addAll(scanAnnotationsFromLocalNonBundleClasspath(mgmt, catalogMetadata, containingBundle)); } else { throw new IllegalStateException("Cannot scan for Java catalog items when libraries declared on an ancestor; scanJavaAnnotations should be specified alongside brooklyn.libraries (or ideally those libraries should specify to scan)"); } } else { - if (depth>0) { - // since 0.12.0, require this to be right next to where libraries are defined, or at root - log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); - } - // normal JAR install, only scan that bundle (the one containing the catalog.bom) - result.addAll(scanAnnotationsFromBundles(mgmt, MutableList.of(containingBundle), catalogMetadata)); - // TODO above (scanning a ZIP uploaded) won't work yet because scan routines need a URL - // TODO are libraries installed properly, such that they are now managed and their catalog.bom's are scanned ? + throw new IllegalArgumentException("Scanning for Java annotations is not supported in BOMs in bundles; " + + "entries should be listed explicitly in the catalog.bom"); + // see comments on scanAnnotationsInBundle +// if (depth>0) { +// // since 0.12.0, require this to be right next to where libraries are defined, or at root +// log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); +// } +// // normal JAR install, only scan that bundle (the one containing the catalog.bom) +// // note metadata not relevant here +// result.addAll(scanAnnotationsInBundle(mgmt, containingBundle)); } } @@ -832,12 +838,12 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt return oldValue; } - private Collection> scanAnnotationsFromLocal(ManagementContext mgmt, Map catalogMetadata) { + private Collection> scanAnnotationsFromLocalNonBundleClasspath(ManagementContext mgmt, Map catalogMetadata, ManagedBundle containingBundle) { CatalogDto dto = CatalogDto.newNamedInstance("Local Scanned Catalog", "All annotated Brooklyn entities detected in the classpath", "scanning-local-classpath"); - return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata); + return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata, containingBundle); } - private Collection> scanAnnotationsFromBundles(ManagementContext mgmt, Collection libraries, Map catalogMetadata) { + private Collection> scanAnnotationsLegacyInListOfLibraries(ManagementContext mgmt, Collection libraries, Map catalogMetadata, ManagedBundle containingBundle) { CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode()); List urls = MutableList.of(); for (OsgiBundleWithUrl b: libraries) { @@ -857,22 +863,52 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt CatalogDo subCatalog = new CatalogDo(dto); subCatalog.addToClasspath(urls.toArray(new String[0])); - return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata); + return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata, containingBundle); } - private Collection> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map catalogMetadata) { - // TODO this does java-scanning only; - // the call when scanning bundles should use the CatalogItem instead and use OSGi when loading for scanning - // (or another scanning mechanism). see comments on CatalogClasspathDo.load + @SuppressWarnings("unused") // keep during 0.12.0 until we are decided we won't support this; search for this method name + // note that it breaks after rebind since we don't have the JAR -- see notes below + private Collection> scanAnnotationsInBundle(ManagementContext mgmt, ManagedBundle containingBundle) { + CatalogDto dto = CatalogDto.newNamedInstance("Bundle "+containingBundle.getVersionedName().toOsgiString()+" Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundle-"+containingBundle.getVersionedName().toOsgiString()); + CatalogDo subCatalog = new CatalogDo(dto); + // need access to a JAR to scan this + String url = null; + if (containingBundle instanceof BasicManagedBundle) { + File f = ((BasicManagedBundle)containingBundle).getTempLocalFileWhenJustUploaded(); + if (f!=null) { + url = "file:"+f.getAbsolutePath(); + } + } + // type.getSubPathName(), type, id+".jar", com.google.common.io.Files.asByteSource(f), exceptionHandler); + if (url==null) { + url = containingBundle.getUrl(); + } + if (url==null) { + // NOT available after persistence/rebind + // as shown by test in CatalogOsgiVersionMoreEntityRebindTest + throw new IllegalArgumentException("Error prepaing to scan "+containingBundle.getVersionedName()+": no URL available"); + } + // org.reflections requires the URL to be "file:" containg ".jar" + File fJar = Os.newTempFile(containingBundle.getVersionedName().toOsgiString(), ".jar"); + try { + Streams.copy(ResourceUtils.create().getResourceFromUrl(url), new FileOutputStream(fJar)); + } catch (FileNotFoundException e) { + throw Exceptions.propagate("Error extracting "+url+" to scan "+containingBundle.getVersionedName(), e); + } + subCatalog.addToClasspath(new String[] { "file:"+fJar.getAbsolutePath() }); + Collection> result = scanAnnotationsInternal(mgmt, subCatalog, MutableMap.of("version", containingBundle.getSuppliedVersionString()), containingBundle); + fJar.delete(); + return result; + } + + private Collection> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map catalogMetadata, ManagedBundle containingBundle) { subCatalog.mgmt = mgmt; subCatalog.setClasspathScanForEntities(CatalogScanningModes.ANNOTATIONS); subCatalog.load(); - // TODO apply metadata? (extract YAML from the items returned) - // also see doc .../catalog/index.md which says we might not apply metadata @SuppressWarnings({ "unchecked", "rawtypes" }) Collection> result = (Collection)Collections2.transform( (Collection>)(Collection)subCatalog.getIdCache().values(), - itemDoToDtoAddingSelectedMetadataDuringScan(catalogMetadata)); + itemDoToDtoAddingSelectedMetadataDuringScan(catalogMetadata, containingBundle)); return result; } @@ -1353,24 +1389,43 @@ public CatalogItem apply(@Nullable CatalogItemDo item) { }; } - private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final Map catalogMetadata) { + private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final Map catalogMetadata, ManagedBundle containingBundle) { return new Function, CatalogItem>() { @Override public CatalogItem apply(@Nullable CatalogItemDo item) { if (item==null) return null; CatalogItemDtoAbstract dto = (CatalogItemDtoAbstract) item.getDto(); - // when scanning we only allow version and libraries to be overwritten + // allow metadata to overwrite version and library bundles; + // however this should only be used for local classpath scanning and legacy external libraries; + // bundle scans should _not_ use this String version = getFirstAs(catalogMetadata, String.class, "version").orNull(); if (Strings.isNonBlank(version)) dto.setVersion(version); - Object librariesCombined = catalogMetadata.get("brooklyn.libraries"); - if (librariesCombined instanceof Collection) { + Collection libraryBundles = MutableSet.of(); + if (containingBundle!=null) { + libraryBundles.add(new CatalogBundleDto(containingBundle.getSymbolicName(), containingBundle.getSuppliedVersionString(), null)); + } + libraryBundles.addAll(dto.getLibraries()); + Object librariesInherited; + librariesInherited = catalogMetadata.get("brooklyn.libraries"); + if (librariesInherited instanceof Collection) { + // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case + libraryBundles.addAll(CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited)); + } + librariesInherited = catalogMetadata.get("libraries"); + if (librariesInherited instanceof Collection) { + log.warn("Legacy 'libraries' encountered; use 'brooklyn.libraries'"); // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case - Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries((Collection) librariesCombined); - dto.setLibraries(libraryBundles); + libraryBundles.addAll(CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited)); + } + dto.setLibraries(libraryBundles); + + if (containingBundle!=null && dto.getContainingBundle()==null) { + dto.setContainingBundle(containingBundle.getVersionedName()); } + // replace java type with plan yaml -- needed for libraries / catalog item to be picked up, // but probably useful to transition away from javaType altogether dto.setSymbolicName(dto.getJavaType()); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java index bd0c3c7b81..70a8374373 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogClasspathDo.java @@ -111,7 +111,9 @@ public CatalogClasspathDo(CatalogDo catalog) { /** causes all scanning-based classpaths to scan the classpaths * (but does _not_ load all JARs) */ - // TODO this does a Java scan; we also need an OSGi scan which uses the OSGi classloaders when loading for scanning and resolving dependencies + // NOTE this does a Java scan; we also need an OSGi scan which uses the OSGi classloaders when loading for scanning and resolving dependencies + // TODO AH thinks we should delete ALL this old catalog stuff and scanning and annotations; it's redundant now that we use bundles, + // and scanning doesn't fit with the OSGi way of doing things synchronized void load() { if (classpath == null || isLoaded) return; diff --git a/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java b/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java index bb46376cdb..b5ca2c9e90 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/javalang/ReflectionScanner.java @@ -72,6 +72,9 @@ public ReflectionScanner( final String optionalPrefix, final Predicate filter, final ClassLoader ...classLoaders) { + if (Reflections.log==null) { + Reflections.log = log; + } reflections = new Reflections(new ConfigurationBuilder() { { if (urlsToScan!=null) diff --git a/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom index a5fe6bd0cd..9cc84c3c46 100644 --- a/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/com-example-entities/src/main/resources/catalog.bom @@ -17,7 +17,6 @@ brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity description: For testing loading catalog.bom with catalog scan displayName: I Haz Catalog diff --git a/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom index e491a23a30..8a768f1f77 100644 --- a/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/entities/src/main/resources/catalog.bom @@ -17,7 +17,6 @@ brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity description: For testing loading catalog.bom with catalog scan displayName: I Haz Catalog diff --git a/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom index c3e97dc5c6..2426d45446 100644 --- a/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v1/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity items: - id: org.apache.brooklyn.test.osgi.entities.more.MoreEntity diff --git a/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom index c3e97dc5c6..2426d45446 100644 --- a/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v2-evil-twin/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION itemType: entity items: - id: org.apache.brooklyn.test.osgi.entities.more.MoreEntity diff --git a/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom b/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom index 630750898c..0da6334a47 100644 --- a/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom +++ b/utils/common/dependencies/osgi/more-entities-v2/src/main/resources/catalog.bom @@ -16,7 +16,6 @@ # under the License. brooklyn.catalog: - version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION items: - id: org.apache.brooklyn.test.osgi.entities.more.MorePolicy itemType: policy diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-com-example-entities.jar index 51cf119bc4b73d263592e50b096d8ac8048d51b9..44f5273a77acfde2ff00915c9a6c5c7cec6311c1 100644 GIT binary patch delta 7090 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 K1}h0U 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 delta 8200 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 SR)rfehVdFx-ji0lMgI?>rrZbs diff --git a/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar b/utils/common/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar index 84fc378d05018b0f586eb4a7afb4c86d776873f9..6763cc2ccd7bfc4f0951cd0e73fb102105a7e921 100644 GIT binary patch delta 5131 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 delta 5127 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 E2X|84*8l(j delta 5359 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 U;*PYTh(Hfu=oE}%M&i)(f8X0Lwg3PC From e7dc875af19138261f95994c5da08a2187ee64c7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Jun 2017 15:38:15 +0100 Subject: [PATCH 04/33] Add checksum to managed bundle so we can tell if it changes and fail accordingly This permits us to allow the same BOM YAML to be added multiple times (whether or not bundles are declared), except where it's a non-snapshot BOM that doesn't declare a bundle, because the containing bundle will be different there. This also fixes a bug where containingBundle wasn't persisted; not a big problem as we re-read bundle items, but cleaner to have it working. --- .../rebind/mementos/CatalogItemMemento.java | 2 ++ .../brooklyn/api/typereg/ManagedBundle.java | 6 +++- ...atalogOsgiVersionMoreEntityRebindTest.java | 32 +++++++++++++---- .../catalog/CatalogOsgiYamlEntityTest.java | 13 ++++--- .../internal/BasicBrooklynCatalog.java | 6 ++-- .../core/mgmt/ha/OsgiArchiveInstaller.java | 8 +++++ .../mgmt/ha/OsgiBundleInstallationResult.java | 1 - .../brooklyn/core/mgmt/ha/OsgiManager.java | 2 ++ .../rebind/BasicCatalogItemRebindSupport.java | 1 + .../rebind/dto/BasicCatalogItemMemento.java | 14 ++++++++ .../mgmt/rebind/dto/MementosGenerators.java | 2 ++ .../core/typereg/BasicManagedBundle.java | 16 ++++++++- .../main/resources/brooklyn/empty.catalog.bom | 4 ++- .../apache/brooklyn/util/stream/Streams.java | 21 +++++++++++ .../brooklyn/util/stream/StreamsTest.java | 36 +++++++++++++++++++ .../TestResourceUnavailableException.java | 6 ++-- 16 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java index 0ab7bc852c..11b301acfd 100644 --- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java +++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/CatalogItemMemento.java @@ -30,6 +30,8 @@ public interface CatalogItemMemento extends Memento { String getSymbolicName(); + String getContainingBundle(); + String getIconUrl(); String getVersion(); diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java b/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java index 66ca59237e..49f5d7f7a8 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/ManagedBundle.java @@ -27,9 +27,13 @@ public interface ManagedBundle extends BrooklynObject, Rebindable, OsgiBundleWit /** A URL-like thing that we can register with the OSGi framework * to uniquely identify this bundle-instance. - * This typically includes the unique {@link #getId()} of this item. */ + * This typically includes the unique {@link #getId()} of this item. + * This will not normally be a URL that can be loaded. */ String getOsgiUniqueUrl(); VersionedName getVersionedName(); + /** MD5 checksum of the contents of bundle as installed to OSGi */ + String getChecksum(); + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java index b881ec71f7..54c1efae9b 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java @@ -50,6 +50,7 @@ import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.osgi.OsgiTestResources; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; import org.slf4j.Logger; @@ -72,24 +73,41 @@ public class CatalogOsgiVersionMoreEntityRebindTest extends AbstractYamlRebindTe protected boolean useOsgi() { return true; } - + @Test - public void testRebindAppIncludingBundle() throws Exception { - TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_COM_EXAMPLE_PATH); + public void testRebindAppIncludingBundleAllWorksAndPreservesChecksum() throws Exception { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V1_PATH); ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V1_URL) ); + RegisteredType item = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item); + Assert.assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0"); + + ManagedBundle mb = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(VersionedName.fromString(item.getContainingBundle())); + Assert.assertNotNull(mb); + String c1 = mb.getChecksum(); + Assert.assertTrue(Strings.isNonBlank(c1), "Missing checksum for bundle"); + + Map bundles1 = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); + createAndStartApplication("services: [ { type: "+BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY+" } ]"); StartableApplication newApp = rebind(); // bundles installed Map bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); - Asserts.assertSize(bundles.keySet(), 1); + Asserts.assertEquals(bundles, bundles1); + + //item installed + item = mgmt().getTypeRegistry().get(BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); + Assert.assertNotNull(item); + Assert.assertEquals(item.getContainingBundle(), OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0"); - // types installed - RegisteredType t = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MoreEntity"); - Assert.assertNotNull(t); + // containing bundle set, matches, and checksum matches + mb = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(VersionedName.fromString(item.getContainingBundle())); + Assert.assertEquals(mb, bundles.get(mb.getId())); + Assert.assertEquals(mb.getChecksum(), c1, "checksums should be the same after rebinding"); Assert.assertNotNull(newApp); } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java index a1bd800c1c..6d7df19e60 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlEntityTest.java @@ -236,7 +236,7 @@ public void testPartialBundleReferenceFails() { try { addCatalogItems( "brooklyn.catalog:", - " id: my.catalog.app.id.non_existing.ref", + " id: my.catalog.app.id.non_existing.ref.1", " version: " + TEST_VERSION, " itemType: entity", " libraries:", @@ -250,7 +250,7 @@ public void testPartialBundleReferenceFails() { try { addCatalogItems( "brooklyn.catalog:", - " id: my.catalog.app.id.non_existing.ref", + " id: my.catalog.app.id.non_existing.ref.2", " version: " + TEST_VERSION, " itemType: entity", " libraries:", @@ -323,13 +323,18 @@ public void testUpdatingItemAllowedIfSame() { addCatalogOSGiEntity(id); } - @Test(expectedExceptions = IllegalStateException.class) + @Test public void testUpdatingItemFailsIfDifferent() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); String id = "my.catalog.app.id.duplicate"; addCatalogOSGiEntity(id); - addCatalogOSGiEntity(id, SIMPLE_ENTITY_TYPE, true); + try { + addCatalogOSGiEntity(id, SIMPLE_ENTITY_TYPE, true); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, id, "already installed", "cannot install a different bundle at a same non-snapshot version"); + } } @Test diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index c0e2e070da..9d40e2f31d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1173,7 +1173,7 @@ public CatalogItem addItem(String yaml, boolean forceUpdate) { public List> addItems(String yaml, boolean forceUpdate) { Maybe osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager(); if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) { - // TODO wrap in a bundle to be managed; need to get bundle and version from yaml + // wrap in a bundle to be managed; need to get bundle and version from yaml Map cm = BasicBrooklynCatalog.getCatalogMetadata(yaml); VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false ); if (vn==null) { @@ -1202,9 +1202,7 @@ public List> addItems(String yaml, boolean forceUpdat throw Exceptions.propagate(e); } bf.delete(); - if (result.getCode().isError() || result.getCode()==ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED) { - // if we're wrapping YAML then we don't allow equivalent YAML to be pasted - // TODO remove this once we have better bundle equivalence checks + if (result.getCode().isError()) { throw new IllegalStateException(result.getMessage()); } return toItems(result.getCatalogItemsInstalled()); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 9ef04c83df..d15a036371 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -271,6 +271,8 @@ public ReferenceWithError install() { updateManifestFromAllSourceInformation(); if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result); assert inferredMetadata.isNameResolved() : "Should have resolved "+inferredMetadata; + assert inferredMetadata instanceof BasicManagedBundle : "Only BasicManagedBundles supported"; + ((BasicManagedBundle)inferredMetadata).setChecksum(Streams.getMd5Checksum(new FileInputStream(zipFile))); final boolean updating; result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName()); @@ -296,6 +298,12 @@ public ReferenceWithError install() { } updating = true; } else { + if (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null) { + log.warn("Missing bundle checksum data for "+result+"; assuming bundle replacement is permitted"); + } else if (!Objects.equal(result.getMetadata().getChecksum(), inferredMetadata.getChecksum())) { + throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; " + + "cannot install a different bundle at a same non-snapshot version"); + } result.setIgnoringAlreadyInstalled(); return ReferenceWithError.newInstanceWithoutError(result); } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index 9e845adfd7..e0e7456731 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -39,7 +39,6 @@ public class OsgiBundleInstallationResult { public enum ResultCode { INSTALLED_NEW_BUNDLE(false), UPDATED_EXISTING_BUNDLE(false), - // TODO if bundle installed is different to bundle supplied we should flag an error IGNORING_BUNDLE_AREADY_INSTALLED(false), ERROR_PREPARING_BUNDLE(true), ERROR_INSTALLING_BUNDLE(true); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index dcabe00144..34a5d0b92d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -251,10 +251,12 @@ public Boolean call() { framework = null; } + /** Map of bundles by UID */ public Map getManagedBundles() { return managedBundlesRecord.getManagedBundles(); } + /** Gets UID given a name, or null */ public String getManagedBundleId(VersionedName vn) { return managedBundlesRecord.getManagedBundleId(vn); } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java index 614b08554c..8cea7b21f5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/BasicCatalogItemRebindSupport.java @@ -43,6 +43,7 @@ public void reconstruct(RebindContext rebindContext, CatalogItemMemento memento) super.reconstruct(rebindContext, memento); FlagUtils.setFieldsFromFlags(MutableMap.builder() .put("symbolicName", memento.getSymbolicName()) + .put("containingBundle", memento.getContainingBundle()) .put("javaType", memento.getJavaType()) .put("displayName", memento.getDisplayName()) .put("description", memento.getDescription()) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java index 33406a4a12..72447a7117 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/BasicCatalogItemMemento.java @@ -47,6 +47,7 @@ public static Builder builder() { public static class Builder extends AbstractMemento.Builder { protected String description; protected String symbolicName; + protected String containingBundle; protected String iconUrl; protected String javaType; protected String version; @@ -69,6 +70,11 @@ public Builder symbolicName(String symbolicName) { return self(); } + public Builder containingBundle(String containingBundle) { + this.containingBundle = containingBundle; + return self(); + } + public Builder iconUrl(String iconUrl) { this.iconUrl = iconUrl; return self(); @@ -128,6 +134,7 @@ public Builder from(CatalogItemMemento other) { super.from(other); description = other.getDescription(); symbolicName = other.getSymbolicName(); + containingBundle = other.getContainingBundle(); iconUrl = other.getIconUrl(); javaType = other.getJavaType(); version = other.getVersion(); @@ -149,6 +156,7 @@ public BasicCatalogItemMemento build() { private String description; private String symbolicName; + private String containingBundle; private String iconUrl; private String javaType; private String version; @@ -168,6 +176,7 @@ protected BasicCatalogItemMemento(Builder builder) { super(builder); this.description = builder.description; this.symbolicName = builder.symbolicName; + this.containingBundle = builder.containingBundle; this.iconUrl = builder.iconUrl; this.version = builder.version; this.planYaml = builder.planYaml; @@ -196,6 +205,11 @@ public String getSymbolicName() { return symbolicName; } + @Override + public String getContainingBundle() { + return containingBundle; + } + @Override public String getIconUrl() { return iconUrl; diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java index 20179bc3b3..e3969cbdd6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/dto/MementosGenerators.java @@ -323,6 +323,7 @@ public static FeedMemento newFeedMemento(Feed feed) { return builder.build(); } + @SuppressWarnings("deprecation") private static CatalogItemMemento newCatalogItemMemento(CatalogItem catalogItem) { if (catalogItem instanceof CatalogItemDo) { catalogItem = ((CatalogItemDo)catalogItem).getDto(); @@ -331,6 +332,7 @@ private static CatalogItemMemento newCatalogItemMemento(CatalogItem catalo populateBrooklynObjectMementoBuilder(catalogItem, builder); builder.catalogItemJavaType(catalogItem.getCatalogItemJavaType()) .catalogItemType(catalogItem.getCatalogItemType()) + .containingBundle(catalogItem.getContainingBundle()) .description(catalogItem.getDescription()) .iconUrl(catalogItem.getIconUrl()) .javaType(catalogItem.getJavaType()) diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java index d480ed56a7..1cf4e8482c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java @@ -31,7 +31,6 @@ import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; -import org.osgi.framework.Version; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; @@ -42,6 +41,7 @@ public class BasicManagedBundle extends AbstractBrooklynObject implements Manage private String symbolicName; private String version; + private String checksum; private String url; private transient File localFileWhenJustUploaded; @@ -129,6 +129,7 @@ public String toString() { @Override public int hashCode() { + // checksum deliberately omitted here to match with OsgiBundleWithUrl return Objects.hashCode(symbolicName, version, url); } @@ -141,6 +142,11 @@ public boolean equals(Object obj) { if (!Objects.equal(symbolicName, other.getSymbolicName())) return false; if (!Objects.equal(getOsgiVersionString(), other.getOsgiVersionString())) return false; if (!Objects.equal(url, other.getUrl())) return false; + if (other instanceof BasicManagedBundle) { + // make equality symetricm, OsgiBunde equals this iff this equals OsgiBundle; + // checksum compared if available, but not required + if (!Objects.equal(checksum, ((BasicManagedBundle)other).getChecksum())) return false; + } return true; } @@ -180,6 +186,14 @@ public SubscriptionSupportInternal subscriptions() { public void setDisplayName(String newName) { throw new UnsupportedOperationException(); } + + @Override + public String getChecksum() { + return checksum; + } + public void setChecksum(String md5Checksum) { + this.checksum = md5Checksum; + } @Override protected BrooklynObjectInternal configure(Map flags) { diff --git a/core/src/main/resources/brooklyn/empty.catalog.bom b/core/src/main/resources/brooklyn/empty.catalog.bom index 58cfb1bba9..a256754352 100644 --- a/core/src/main/resources/brooklyn/empty.catalog.bom +++ b/core/src/main/resources/brooklyn/empty.catalog.bom @@ -15,4 +15,6 @@ # specific language governing permissions and limitations # under the License. # -brooklyn.catalog: {} \ No newline at end of file +brooklyn.catalog: + bundle: brooklyn-empty-catalog + version: 0.0.0-SNAPSHOT diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java index 0f343573e5..a703dba0eb 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/stream/Streams.java @@ -30,6 +30,9 @@ import java.io.StringReader; import java.io.Writer; import java.nio.charset.Charset; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import org.apache.brooklyn.util.exceptions.Exceptions; import org.slf4j.Logger; @@ -39,6 +42,7 @@ import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; @@ -215,4 +219,21 @@ public static boolean logStreamTail(Logger log, String message, ByteArrayOutputS return false; } + public static String getMd5Checksum(InputStream in) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw Exceptions.propagate(e); + } + DigestInputStream dis = new DigestInputStream(in, md); + readFullyAndClose(dis); + byte[] digest = md.digest(); + StringBuilder result = new StringBuilder(); + for (byte b: digest) { + result.append(Strings.padStart(Integer.toHexString((256+b)%256), 2, '0')); + } + return result.toString().toUpperCase(); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java new file mode 100644 index 0000000000..37340f47bd --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/stream/StreamsTest.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.util.stream; + +import java.io.ByteArrayInputStream; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StreamsTest { + + @Test + public void testChecksum() { + Assert.assertEquals(Streams.getMd5Checksum(new ByteArrayInputStream("hello world".getBytes())), + // generated from 3rd party tool + "5EB63BBBE01EEED093CB22BB8F5ACDC3" + ); + + } +} diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java index cb4205fd3d..34c3de3b9b 100644 --- a/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/support/TestResourceUnavailableException.java @@ -53,7 +53,7 @@ public class TestResourceUnavailableException extends SkipException { * * Note that this will use the same classloader that was used to load this class. * - * @param resourceName the classpath resource name, e.g. + * @param resourceName the classpath resource name, as a path, e.g. * /brooklyn/osgi/brooklyn-test-osgi-entities.jar */ public static void throwIfResourceUnavailable(Class relativeToClass, String resourceName) { @@ -61,8 +61,10 @@ public static void throwIfResourceUnavailable(Class relativeToClass, String r checkNotNull(resourceName, "resourceName"); checkArgument(!resourceName.isEmpty(), "resourceName must not be empty"); InputStream resource = relativeToClass.getResourceAsStream(resourceName); - if (resource == null) + if (resource == null) { + // would be nice to support URLs but ResourceUtils isn't available here throw new TestResourceUnavailableException(resourceName); + } // just make sure we clean up the resource try { From f16bda9310469ec35f4de8f3a084746abfe08a44 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Jun 2017 16:28:33 +0100 Subject: [PATCH 05/33] support force when wrapping a BOM new batch of tests around catalog versioning with OSGi --- .../brooklyn/api/catalog/BrooklynCatalog.java | 4 ++ .../CatalogOsgiYamlVersioningTest.java | 61 +++++++++++++++++++ .../catalog/CatalogYamlVersioningTest.java | 59 ++++++++++++++++-- .../internal/BasicBrooklynCatalog.java | 3 +- .../catalog/internal/CatalogBundleLoader.java | 6 +- .../core/mgmt/ha/OsgiArchiveInstaller.java | 2 +- .../brooklyn/core/mgmt/ha/OsgiManager.java | 11 +++- 7 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index 75fe634376..3e78843caf 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.api.catalog; import java.util.Collection; +import java.util.List; import java.util.NoSuchElementException; import javax.annotation.Nullable; @@ -137,6 +138,9 @@ public interface BrooklynCatalog { */ Iterable> addItems(String yaml, boolean forceUpdate); + /** As {@link #addItems(String, ManagedBundle)} but exposing forcing option as per {@link #addItem(String, boolean)}. */ + Iterable> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate); + /** * adds an item to the 'manual' catalog; * this does not update the classpath or have a record to the java Class diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java new file mode 100644 index 0000000000..aab99204f1 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn.catalog; + +import org.apache.brooklyn.test.Asserts; +import org.testng.annotations.Test; + +/** As parent tests, but using OSGi, and some of the additions are stricter / different */ +public class CatalogOsgiYamlVersioningTest extends CatalogYamlVersioningTest { + + @Override + protected boolean disableOsgi() { + return false; + } + + @Override + @Test + public void testAddSameVersionWithoutBundle() { + try { + // parent test should fail in OSGi + super.testAddSameVersionWithoutBundle(); + Asserts.shouldHaveFailedPreviously("Expected to fail because containing bundle will be different when using OSGi"); + } catch (Exception e) { + assertExpectedFailureSaysUpdatingExistingItemForbidden(e); + assertExpectedFailureIncludesSampleId(e); + } + } + + @Test + public void testAddSameVersionWithoutBundleWorksIfForced() { + String symbolicName = "sampleId"; + String version = "0.1.0"; + addCatalogEntityWithoutBundle(symbolicName, version); + forceCatalogUpdate(); + addCatalogEntityWithoutBundle(symbolicName, version); + } + + + @Override + protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, + "cannot install a different bundle at a same non-snapshot version"); + assertExpectedFailureIncludesSampleId(e); + } +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java index bebda6f867..cd4525a426 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java @@ -21,7 +21,6 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; @@ -33,6 +32,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -50,7 +50,7 @@ public void setUp() throws Exception { super.setUp(); types = mgmt().getTypeRegistry(); } - + @Test public void testAddItem() { String symbolicName = "sampleId"; @@ -67,19 +67,43 @@ public void testAddUnversionedItem() { } @Test - public void testAddSameVersionFailsWhenIconIsDifferent() { + public void testAddSameVersionWorksIfSame() { String symbolicName = "sampleId"; String version = "0.1.0"; addCatalogEntity(symbolicName, version); + // works if no different + addCatalogEntity(symbolicName, version); + } + + @Test + public void testAddSameVersionFailsWhenIconIsDifferent() { + String symbolicName = "sampleId"; + String version = "0.1.0"; addCatalogEntity(symbolicName, version); try { addCatalogEntity(symbolicName, version, BasicEntity.class.getName(), "classpath:/another/icon.png"); - fail("Expected to fail"); - } catch (IllegalStateException e) { - assertEquals(e.getMessage(), "Updating existing catalog entries is forbidden: " + symbolicName + ":" + version + ". Use forceUpdate argument to override."); + Asserts.shouldHaveFailedPreviously("Expected to fail"); + } catch (Exception e) { + checkAddSameVersionFailsWhenIconIsDifferent(e); } } + protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { + assertExpectedFailureSaysUpdatingExistingItemForbidden(e); + assertExpectedFailureIncludesSampleId(e); + } + + protected void assertExpectedFailureIncludesSampleId(Exception e) { + String symbolicName = "sampleId"; + String version = "0.1.0"; + Asserts.expectedFailureContainsIgnoreCase(e, + symbolicName + ":" + version); + } + protected void assertExpectedFailureSaysUpdatingExistingItemForbidden(Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, + "Updating existing catalog entries is forbidden"); + } + @Test public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { String symbolicName = "sampleId"; @@ -92,6 +116,15 @@ public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { assertTrue(item.getIconUrl().equals(icon), "Unexpected iconUrl: " + item.getIconUrl()); } + @Test + public void testAddSameVersionWithoutBundle() { + String symbolicName = "sampleId"; + String version = "0.1.0"; + addCatalogEntityWithoutBundle(symbolicName, version); + // allowed when not OSGi + addCatalogEntityWithoutBundle(symbolicName, version); + } + @Test public void testAddSameVersionForce() { String symbolicName = "sampleId"; @@ -281,4 +314,18 @@ private void addCatalogEntity(String symbolicName, String version, String type, " type: " + type); } + protected void addCatalogEntityWithoutBundle(String symbolicName, String version) { + addCatalogItems( + "brooklyn.catalog:", + " items:", + " - id: " + symbolicName, + (version != null ? " version: " + version : ""), + " itemType: entity", + " name: My Catalog App", + " description: My description", + " icon_url: "+"classpath://path/to/myicon.jpg", + " item:", + " type: " + BasicEntity.class.getName()); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 9d40e2f31d..8d36b689c7 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1233,7 +1233,8 @@ private List> toItems(Iterable itemIds) { return addItems(yaml, bundle, false); } - private List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { + @Override + public List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { log.debug("Adding manual catalog item to "+mgmt+": "+yaml); checkNotNull(yaml, "yaml"); List> result = collectCatalogItems(yaml, bundle); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index 8d3179d78e..cce1258c93 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -72,6 +72,10 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo * @throws RuntimeException if the catalog items failed to be added to the catalog */ public Iterable> scanForCatalog(Bundle bundle) { + return scanForCatalog(bundle, false); + } + + public Iterable> scanForCatalog(Bundle bundle, boolean force) { ManagedBundle mb = ((ManagementContextInternal)managementContext).getOsgiManager().get().getManagedBundle( new VersionedName(bundle)); @@ -82,7 +86,7 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); String bomWithLibraryPath = addLibraryDetails(bundle, bomText); - catalogItems = this.managementContext.getCatalog().addItems(bomWithLibraryPath, mb); + catalogItems = this.managementContext.getCatalog().addItems(bomWithLibraryPath, mb, force); for (CatalogItem item : catalogItems) { LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index d15a036371..50f8752687 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -375,7 +375,7 @@ public void run() { osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() ); // (ideally removal and addition would be atomic) } - for (CatalogItem ci: osgiManager.loadCatalogBom(result.bundle)) { + for (CatalogItem ci: osgiManager.loadCatalogBom(result.bundle, force)) { result.catalogItemsInstalled.add(ci.getId()); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index 34a5d0b92d..77d2a80464 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -375,10 +375,15 @@ public synchronized Bundle registerBundle(CatalogBundle bundleMetadata) { // possibly remove that other capability, so that bundles with BOMs _have_ to be installed via this method. // (load order gets confusing with auto-scanning...) public List> loadCatalogBom(Bundle bundle) { - return MutableList.copyOf(loadCatalogBom(mgmt, bundle)); + return loadCatalogBom(bundle, false); } - private static Iterable> loadCatalogBom(ManagementContext mgmt, Bundle bundle) { + @Beta // as above + public List> loadCatalogBom(Bundle bundle, boolean force) { + return MutableList.copyOf(loadCatalogBom(mgmt, bundle, force)); + } + + private static Iterable> loadCatalogBom(ManagementContext mgmt, Bundle bundle, boolean force) { Iterable> catalogItems = MutableList.of(); if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM)) { // if the above feature is not enabled, let's do it manually (as a contract of this method) @@ -388,7 +393,7 @@ public List> loadCatalogBom(Bundle bundle) { // here to get back the predicate from it. final Predicate applicationsPermitted = Predicates.alwaysTrue(); - catalogItems = new CatalogBundleLoader(applicationsPermitted, mgmt).scanForCatalog(bundle); + catalogItems = new CatalogBundleLoader(applicationsPermitted, mgmt).scanForCatalog(bundle, force); } catch (RuntimeException ex) { // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; // caller needs to upgrade, or uninstall then reinstall From 8385f133273116ec5df628325a7d71ad29d4dcb8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Jun 2017 09:12:19 +0100 Subject: [PATCH 06/33] repair tests where semantics changed, new tests including explicit test for new load order problems: * during rebind osgi bundles can have BOM loaded in any order, but they expect their deps to be available two remaining problems: * wrapper library is added (changing assertions about numbers) * added containing library changes YAML (changing assertions about comments) --- .../brooklyn/api/catalog/BrooklynCatalog.java | 1 - .../camp/brooklyn/AbstractYamlRebindTest.java | 2 ++ .../brooklyn/ConfigParametersYamlTest.java | 10 ++++---- ...atalogOsgiVersionMoreEntityRebindTest.java | 11 +++++---- .../brooklyn/catalog/CatalogScanTest.java | 9 +++++++- .../catalog/CatalogYamlEntityTest.java | 6 +++-- .../catalog/CatalogYamlRebindTest.java | 23 +++++++++++++++++++ .../apache/brooklyn/core/BrooklynVersion.java | 3 ++- .../mgmt/ha/OsgiBundleInstallationResult.java | 8 ++++++- .../core/mgmt/rebind/RebindTestFixture.java | 7 +++--- 10 files changed, 62 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index 3e78843caf..5bb9e1e04a 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -19,7 +19,6 @@ package org.apache.brooklyn.api.catalog; import java.util.Collection; -import java.util.List; import java.util.NoSuchElementException; import javax.annotation.Nullable; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java index 1248110ad6..2d4424d970 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java @@ -73,6 +73,8 @@ protected LocalManagementContext newMgmtContext() { }; launcher.launch(); platform = launcher.getCampPlatform(); + + forceUpdate = false; } @Override diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java index 53193aa7be..6e62bae725 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.location.PortRange; import org.apache.brooklyn.camp.brooklyn.catalog.SpecParameterUnwrappingTest; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.config.BasicConfigInheritance; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConfigPredicates; @@ -908,14 +909,13 @@ public void testManuallyAddWithParentFromCatalog() throws Exception { LOG.info("E1 keys: "+entity1.getEntityType().getConfigKeys()); LOG.info("E2 keys: "+entity2.getEntityType().getConfigKeys()); Assert.assertEquals(entity2.getEntityType().getConfigKeys(), entity1.getEntityType().getConfigKeys()); - Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); // TODO currently the child has item ID set from CatalogUtils.setCatalogItemIdOnAddition // that should set a search path instead of setting the actual item // (ideally we'd assert null here) - Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); } - @Test public void testManuallyAddInTaskOfOtherEntity() throws Exception { @@ -947,12 +947,12 @@ public TestEntity call() { LOG.info("E1 keys: "+entity1.getEntityType().getConfigKeys()); LOG.info("E2 keys: "+entity2.getEntityType().getConfigKeys()); Assert.assertEquals(entity2.getEntityType().getConfigKeys(), entity1.getEntityType().getConfigKeys()); - Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity1.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); // TODO currently the child has item ID set from context in constructor of AbstractBrooklynObject; // that should set a search path instead of setting the actual item // (ideally we'd assert null here) - Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:0.0.0.SNAPSHOT"); + Assert.assertEquals(entity2.getCatalogItemId(), "test-entity:"+BasicBrooklynCatalog.NO_VERSION); } @Test diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java index 54c1efae9b..0bea66c990 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityRebindTest.java @@ -48,6 +48,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.osgi.VersionedName; @@ -277,7 +278,7 @@ public void testClassAccessAfterUninstall() throws Exception { } } - @Test + // @Test public void testClassAccessAfterUpgrade() throws Exception { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), BROOKLYN_TEST_OSGI_MORE_ENTITIES_0_1_0_PATH); @@ -303,9 +304,11 @@ public void testClassAccessAfterUpgrade() throws Exception { "HI BOB FROM V2"); // unforced upgrade should report already installed - Assert.assertEquals( ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( - new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL) ).get().getCode(), - OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED); + ReferenceWithError installEvilTwin = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install( + new ResourceUtils(getClass()).getResourceFromUrl(BROOKLYN_TEST_MORE_ENTITIES_V2_EVIL_TWIN_URL) ); + Assert.assertTrue(installEvilTwin.hasError()); + Assert.assertEquals(installEvilTwin.getWithoutError().getCode(), + OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE); // force upgrade OsgiBundleInstallationResult b2b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().install(b2a.getMetadata(), diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java index 9e11b10a9d..07cc23d7d8 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanTest.java @@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicates; @@ -48,7 +49,7 @@ public class CatalogScanTest { - // TODO setUp/tearDown copied from AbstractYamlTest + // TODO tearDown could be merged with AbstractYamlTest // Moved from brooklyn-core. When we deleted support for catalog.xml, the scanned catalog // was then only stored in camp format, which meant we needed the camp parser. @@ -60,6 +61,12 @@ public class CatalogScanTest { private List mgmts = Lists.newCopyOnWriteArrayList(); private List launchers = Lists.newCopyOnWriteArrayList(); + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception { + defaultCatalog = null; + annotsCatalog = null; + } + /** Override to enable OSGi in the management context for all tests in the class. */ protected boolean disableOsgi() { return true; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java index 0f378ffe50..08e2432c57 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.test.entity.TestEntity; @@ -124,8 +125,9 @@ public void testAddCatalogItemUsingNameInsteadOfIdWithoutVersion() throws Except " item:", " type: "+ BasicEntity.class.getName()); RegisteredType catalogItem = mgmt().getTypeRegistry().get(id, BrooklynCatalog.DEFAULT_VERSION); - assertEquals(catalogItem.getVersion(), "0.0.0.SNAPSHOT"); - mgmt().getCatalog().deleteCatalogItem(id, "0.0.0.SNAPSHOT"); + assertEquals(catalogItem.getVersion(), BasicBrooklynCatalog.NO_VERSION); + mgmt().getCatalog().deleteCatalogItem(id, BasicBrooklynCatalog.NO_VERSION); + Assert.assertNull(mgmt().getTypeRegistry().get(id)); } // Legacy / backwards compatibility: should use id diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index 127a110fdd..a962dbee61 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -412,4 +412,27 @@ public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mo } } } + + @Test + public void testLongReferenceSequence() throws Exception { + // adds a0, a1 extending a0, a2 extending a1, ... a9 extending a8 + // osgi rebind of types can fail because bundles are restored in any order + // and dependencies might not yet be installed; + // ensure items are added first without validation, then validating + for (int i = 0; i<10; i++) { + addCatalogItems( + "brooklyn.catalog:", + " id: a" + i, + " version: 1", + " itemType: entity", + " item:", + " type: " + (i==0 ? BasicEntity.class.getName() : "a" + (i-1))); + } + origApp = (StartableApplication) createAndStartApplication("services: [ { type: a9 } ]"); + rebind(); + Entity child = Iterables.getOnlyElement( newApp.getChildren() ); + Asserts.assertTrue(child instanceof BasicEntity); + Asserts.assertEquals(child.getCatalogItemId(), "a9:1"); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java index f28d3fbde2..4cc13529f6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynVersion.java @@ -36,6 +36,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.classloading.OsgiBrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; @@ -127,7 +128,7 @@ public String getVersionFromClasspath() { if (Strings.isNonBlank(v)) return v; v = getVersionFromOsgiManifest(); if (Strings.isNonBlank(v)) return v; - return "0.0.0-SNAPSHOT"; + return BasicBrooklynCatalog.NO_VERSION; } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index e0e7456731..947de1c3a6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -39,8 +39,14 @@ public class OsgiBundleInstallationResult { public enum ResultCode { INSTALLED_NEW_BUNDLE(false), UPDATED_EXISTING_BUNDLE(false), - IGNORING_BUNDLE_AREADY_INSTALLED(false), + /** Bundle is already installed at exact same version and same contents; safely ignoring + * (safe in that behaviour won't be different or dangerous; + * could potentially be surprising, but ability to idempotently install things is nicer) */ + IGNORING_BUNDLE_AREADY_INSTALLED(false), + /** bundle could not be made insto a state where it could be installed; bundle is not installed, even if forced */ ERROR_PREPARING_BUNDLE(true), + /** bundle could be installed as OSGi but there was an item later, such as catalog items loading; + * bundle may be installed */ // TODO behaviour TBC ERROR_INSTALLING_BUNDLE(true); final boolean isError; diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java index 614d6303b7..c94b933f36 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java @@ -46,7 +46,6 @@ import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore; import org.apache.brooklyn.core.mgmt.persist.PersistMode; -import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.util.core.task.BasicExecutionManager; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.repeat.Repeater; @@ -83,6 +82,10 @@ public void setUp() throws Exception { File mementoDirParent = mementoDir.getParentFile(); mementoDirBackup = new File(mementoDirParent, mementoDir.getName()+"."+Identifiers.makeRandomId(4)+".bak"); + origApp = null; + newApp = null; + newManagementContext = null; + origManagementContext = createOrigManagementContext(); origApp = createApp(); @@ -99,8 +102,6 @@ protected BrooklynProperties createBrooklynProperties() { /** @return A started management context */ protected LocalManagementContext createOrigManagementContext() { - LocalManagementContextForTests.newInstance(); - return RebindTestUtils.managementContextBuilder(mementoDir, classLoader) .persistPeriodMillis(getPersistPeriodMillis()) .haMode(getHaMode()) From e32d74d15359152c9b1900db2ee0a7958b15c29b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Jun 2017 09:28:13 +0100 Subject: [PATCH 07/33] comments on BOM scanning --- .../catalog/internal/CatalogBomScanner.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java index 1f528c8756..90efaa231d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java @@ -35,8 +35,13 @@ import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; -/** Scans bundles being added, filtered by a whitelist and blacklist, and adds catalog.bom files to the catalog. +/** Scans bundles being added to add their catalog.bom to Brooklyn, + * filtering which bundles are allowed add _applications_ through a whitelist and blacklist. + * (All bundles are allowed to add other types.) * See karaf blueprint.xml for configuration, and tests in dist project. */ +// TODO AH: i wonder if we can remove this or generalize it; disallowing just app/templates seems a little weird +// (makes sense as those appear more prominently in some displays, but feels like there'd be a better way +// to customize; intercepting at this particular point is surprising) @Beta public class CatalogBomScanner { @@ -44,8 +49,9 @@ public class CatalogBomScanner { private static final Logger LOG = LoggerFactory.getLogger(CatalogBomScanner.class); - private List whiteList = ImmutableList.of(ACCEPT_ALL_BY_DEFAULT); - private List blackList = ImmutableList.of(); + // configured by `brooklyn.catalog.osgi.application.{white,black}list` + private List bundlesAllowedToAddAppsWhitelist = ImmutableList.of(ACCEPT_ALL_BY_DEFAULT); + private List bundlesAllowedToAddAppsBlacklist = ImmutableList.of(); private CatalogBundleTracker catalogBundleTracker; @@ -57,7 +63,7 @@ public void bind(ServiceReference mgmtContextReference) throw final BundleContext bundleContext = mgmtContextReference.getBundle().getBundleContext(); ManagementContext mgmt = bundleContext.getService(mgmtContextReference); - CatalogBundleLoader bundleLoader = new CatalogBundleLoader(new SymbolicNameAccessControl(), mgmt); + CatalogBundleLoader bundleLoader = new CatalogBundleLoader(new BundlesAllowedToAddAddsFilter(), mgmt); catalogBundleTracker = new CatalogBundleTracker(bundleContext, bundleLoader); catalogBundleTracker.open(); @@ -81,32 +87,32 @@ private boolean isEnabled() { } public List getWhiteList() { - return whiteList; + return bundlesAllowedToAddAppsWhitelist; } public void setWhiteList(List whiteList) { - this.whiteList = whiteList; + this.bundlesAllowedToAddAppsWhitelist = whiteList; } public void setWhiteList(String whiteListText) { LOG.debug("Setting whiteList to ", whiteListText); - this.whiteList = Strings.parseCsv(whiteListText); + this.bundlesAllowedToAddAppsWhitelist = Strings.parseCsv(whiteListText); } public List getBlackList() { - return blackList; + return bundlesAllowedToAddAppsBlacklist; } public void setBlackList(List blackList) { - this.blackList = blackList; + this.bundlesAllowedToAddAppsBlacklist = blackList; } public void setBlackList(String blackListText) { LOG.debug("Setting blackList to ", blackListText); - this.blackList = Strings.parseCsv(blackListText); + this.bundlesAllowedToAddAppsBlacklist = Strings.parseCsv(blackListText); } - public class SymbolicNameAccessControl implements Predicate { + public class BundlesAllowedToAddAddsFilter implements Predicate { @Override public boolean apply(@Nullable Bundle input) { return passesWhiteAndBlacklists(input); From dc43f03fe2fa269e49a7865eafb491430f4e7e32 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Jun 2017 09:46:13 +0100 Subject: [PATCH 08/33] add library bundles directly, without munging yaml, and only if not wrapper --- .../internal/BasicBrooklynCatalog.java | 36 ++++++---- .../catalog/internal/CatalogBundleLoader.java | 68 +------------------ 2 files changed, 24 insertions(+), 80 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 8d36b689c7..5a8c31a806 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -54,7 +54,6 @@ import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes; import org.apache.brooklyn.core.location.BasicLocationRegistry; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; -import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; @@ -542,18 +541,25 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund // brooklyn.libraries we treat specially, to append the list, with the child's list preferred in classloading order // `libraries` is supported in some places as a legacy syntax; it should always be `brooklyn.libraries` for new apps // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" on its own - List librariesNew = MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, "brooklyn.libraries", "libraries").orNull()); - Collection libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew); - - List librariesCombined = MutableList.copyOf(librariesNew) - .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull()); - if (!librariesCombined.isEmpty()) - catalogMetadata.put("brooklyn.libraries", librariesCombined); - Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombined); + List librariesAddedHereNames = MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, "brooklyn.libraries", "libraries").orNull()); + Collection librariesAddedHereBundles = CatalogItemDtoAbstract.parseLibraries(librariesAddedHereNames); + + MutableSet librariesCombinedNames = MutableSet.of(); + if (!isNoBundleOrSimpleWrappingBundle(containingBundle)) { + // ensure containing bundle is declared, first, for search purposes + librariesCombinedNames.add(containingBundle.getVersionedName().toOsgiString()); + } + librariesCombinedNames.putAll(librariesAddedHereNames); + librariesCombinedNames.putAll(getFirstAs(parentMetadata, Collection.class, "brooklyn.libraries", "libraries").orNull()); + if (!librariesCombinedNames.isEmpty()) { + catalogMetadata.put("brooklyn.libraries", librariesCombinedNames); + } + Collection libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombinedNames); - // TODO as this may take a while if downloading, the REST call should be async - // (this load is required for the scan below and I think also for yaml resolution) - CatalogUtils.installLibraries(mgmt, libraryBundlesNew); + // TODO this may take a while if downloading; ideally the REST call would be async + // but this load is required for resolving YAML in this BOM (and if java-scanning); + // need to think through how we expect dependencies to be installed + CatalogUtils.installLibraries(mgmt, librariesAddedHereBundles); Boolean scanJavaAnnotations = getFirstAs(itemMetadataWithoutItemDef, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull(); if (scanJavaAnnotations==null || !scanJavaAnnotations) { @@ -561,10 +567,10 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund } else { if (isNoBundleOrSimpleWrappingBundle(containingBundle)) { // BOMs wrapped in JARs, or without JARs, have special treatment - if (isLibrariesMoreThanJustContainingBundle(libraryBundlesNew, containingBundle)) { + if (isLibrariesMoreThanJustContainingBundle(librariesAddedHereBundles, containingBundle)) { // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM - log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+libraryBundlesNew+"); libraries should declare they scan themselves"); - result.addAll(scanAnnotationsLegacyInListOfLibraries(mgmt, libraryBundlesNew, catalogMetadata, containingBundle)); + log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+librariesAddedHereBundles+"); libraries should declare they scan themselves"); + result.addAll(scanAnnotationsLegacyInListOfLibraries(mgmt, librariesAddedHereBundles, catalogMetadata, containingBundle)); } else if (!isLibrariesMoreThanJustContainingBundle(libraryBundles, containingBundle)) { // for default catalog, no libraries declared, we want to scan local classpath // bundle should be named "brooklyn-default-catalog" diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index cce1258c93..3af87b9143 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -25,7 +25,6 @@ import java.io.InputStream; import java.net.URL; import java.util.List; -import java.util.Map; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; @@ -36,25 +35,18 @@ import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yaml.Yamls; import org.osgi.framework.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; import com.google.common.annotations.Beta; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; @Beta public class CatalogBundleLoader { private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleLoader.class); private static final String CATALOG_BOM_URL = "catalog.bom"; - private static final String BROOKLYN_CATALOG = "brooklyn.catalog"; - private static final String BROOKLYN_LIBRARIES = "brooklyn.libraries"; private Predicate applicationsPermitted; private ManagementContext managementContext; @@ -85,8 +77,7 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo if (null != bom) { LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); - String bomWithLibraryPath = addLibraryDetails(bundle, bomText); - catalogItems = this.managementContext.getCatalog().addItems(bomWithLibraryPath, mb, force); + catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); for (CatalogItem item : catalogItems) { LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); } @@ -95,7 +86,7 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo } if (!applicationsPermitted.apply(bundle)) { - catalogItems = removeAnyApplications(catalogItems); + catalogItems = removeApplications(catalogItems); } return catalogItems; @@ -125,60 +116,7 @@ private String readBom(URL bom) { } } - // TODO remove; now that the bundle is passed through we can add it in the catalog - private String addLibraryDetails(Bundle bundle, String bomText) { - @SuppressWarnings("unchecked") - final Map bom = (Map) Iterables.getOnlyElement(Yamls.parseAll(bomText)); - final Object catalog = bom.get(CatalogBundleLoader.BROOKLYN_CATALOG); - if (null != catalog) { - if (catalog instanceof Map) { - @SuppressWarnings("unchecked") - Map catalogMap = (Map) catalog; - addLibraryDetails(bundle, catalogMap); - } else { - LOG.warn("Unexpected syntax for {} (expected Map, but got {}), ignoring", - CatalogBundleLoader.BROOKLYN_CATALOG, catalog.getClass().getName()); - } - } - final String updatedBom = backToYaml(bom); - LOG.trace("Updated catalog bom:\n{}", updatedBom); - return updatedBom; - } - - private void addLibraryDetails(Bundle bundle, Map catalog) { - if (!catalog.containsKey(CatalogBundleLoader.BROOKLYN_LIBRARIES)) { - if (catalog.containsKey("libraries")) { - // legacy name - catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, catalog.remove("libraries")); - } else { - catalog.put(CatalogBundleLoader.BROOKLYN_LIBRARIES, MutableList.of()); - } - } - final Object librarySpec = catalog.get(CatalogBundleLoader.BROOKLYN_LIBRARIES); - if (!(librarySpec instanceof List)) { - throw new RuntimeException("expected " + CatalogBundleLoader.BROOKLYN_LIBRARIES + " to be a list, but got " - + (librarySpec == null ? "null" : librarySpec.getClass().getName())); - } - @SuppressWarnings("unchecked") - List> libraries = (List>) librarySpec; - if (bundle.getSymbolicName() == null || bundle.getVersion() == null) { - throw new IllegalStateException("Cannot scan " + bundle + " for catalog files: name or version is null"); - } - libraries.add(ImmutableMap.of( - "name", bundle.getSymbolicName(), - "version", bundle.getVersion().toString())); - LOG.debug("library spec is {}", librarySpec); - } - - private String backToYaml(Map bom) { - final DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - options.setPrettyFlow(true); - return new Yaml(options).dump(bom); - } - - private Iterable> removeAnyApplications( - Iterable> catalogItems) { + private Iterable> removeApplications(Iterable> catalogItems) { List> result = MutableList.of(); From 480a0c76ae434a7d05455ec0de63e33b5c05aebc Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Jun 2017 10:43:17 +0100 Subject: [PATCH 09/33] Delete wrapper bundles who have no types installed. There is no point to them (they aren't OSGi). Fixes bug where uploading the same BOM multiple times, could cause bundle leakage. And tidy logging, more tests. --- .../CatalogOsgiYamlVersioningTest.java | 46 ++++++++++++++++++- .../internal/BasicBrooklynCatalog.java | 22 +++++++++ .../core/mgmt/ha/OsgiArchiveInstaller.java | 24 ++++++++-- .../brooklyn/core/mgmt/ha/OsgiManager.java | 6 ++- .../brooklyn/util/osgi/VersionedName.java | 7 ++- 5 files changed, 97 insertions(+), 8 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java index aab99204f1..9a047d8940 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java @@ -18,7 +18,12 @@ */ package org.apache.brooklyn.camp.brooklyn.catalog; +import java.util.Collection; + +import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.test.Asserts; +import org.testng.Assert; import org.testng.annotations.Test; /** As parent tests, but using OSGi, and some of the additions are stricter / different */ @@ -33,7 +38,8 @@ protected boolean disableOsgi() { @Test public void testAddSameVersionWithoutBundle() { try { - // parent test should fail in OSGi + // parent test should fail in OSGi - anonymous bundle is snapshot so updating is attempted + // but item version is not snapshot and containing bundle is different, so ultimately fails super.testAddSameVersionWithoutBundle(); Asserts.shouldHaveFailedPreviously("Expected to fail because containing bundle will be different when using OSGi"); } catch (Exception e) { @@ -42,6 +48,16 @@ public void testAddSameVersionWithoutBundle() { } } + @Test + public void testAddSameVersionWithoutBundleWorksIfItemIsSnapshot() { + String symbolicName = "sampleId"; + String version = "0.1.0-SNAPSHOT"; + addCatalogEntityWithoutBundle(symbolicName, version); + // allowed because item is snapshot + addCatalogEntityWithoutBundle(symbolicName, version); + assertJustOneBundle(); + } + @Test public void testAddSameVersionWithoutBundleWorksIfForced() { String symbolicName = "sampleId"; @@ -50,7 +66,6 @@ public void testAddSameVersionWithoutBundleWorksIfForced() { forceCatalogUpdate(); addCatalogEntityWithoutBundle(symbolicName, version); } - @Override protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { @@ -58,4 +73,31 @@ protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { "cannot install a different bundle at a same non-snapshot version"); assertExpectedFailureIncludesSampleId(e); } + + @Test + public void testEmptyCatalogBundleIsRemoved() { + Collection bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values(); + Assert.assertTrue(bundles.isEmpty(), "Expected no bundles before starting; but had: "+bundles); + } + + @Override + @Test + public void testAddSameVersionWorksIfSame() { + // in OSGi, assert additionally that we aren't leaking bundles + super.testAddSameVersionWorksIfSame(); + assertJustOneBundle(); + } + + protected void assertJustOneBundle() { + Collection bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values(); + Assert.assertTrue(bundles.size()==1, "Expected one bundle after installing the same; but had: "+bundles); + } + + @Override + @Test + public void testAddSameSnapshotVersionSucceedsWhenIconIsDifferent() { + super.testAddSameSnapshotVersionSucceedsWhenIconIsDifferent(); + assertJustOneBundle(); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 5a8c31a806..dacdb90626 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -50,6 +50,7 @@ import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes; import org.apache.brooklyn.core.location.BasicLocationRegistry; @@ -1191,6 +1192,7 @@ public List> addItems(String yaml, boolean forceUpdat vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION)); } + log.debug("Wrapping supplied BOM as "+vn); Manifest mf = new Manifest(); mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName()); mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString() ); @@ -1208,6 +1210,7 @@ public List> addItems(String yaml, boolean forceUpdat throw Exceptions.propagate(e); } bf.delete(); + uninstallEmptyWrapperBundles(); if (result.getCode().isError()) { throw new IllegalStateException(result.getMessage()); } @@ -1477,4 +1480,23 @@ public void addSpec(String itemId, AbstractBrooklynObjectSpec spec) { cache.put(itemId, spec); } } + + private Object uninstallingEmptyLock = new Object(); + public void uninstallEmptyWrapperBundles() { + log.debug("uninstalling empty wrapper bundles"); + synchronized (uninstallingEmptyLock) { + Maybe osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgi.isAbsent()) return; + for (ManagedBundle b: osgi.get().getManagedBundles().values()) { + if (isNoBundleOrSimpleWrappingBundle(b)) { + Iterable typesInBundle = osgi.get().getTypesFromBundle(b.getVersionedName()); + if (Iterables.isEmpty(typesInBundle)) { + log.debug("uninstalling empty wrapper bundle "+b); + osgi.get().uninstallUploadedBundle(b); + } + } + } + } + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 50f8752687..1497b42266 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -23,6 +23,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; @@ -34,6 +35,7 @@ import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.osgi.Osgis; @@ -53,6 +55,7 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Objects; +import com.google.common.collect.Iterables; // package-private so we can move this one if/when we move OsgiManager class OsgiArchiveInstaller { @@ -340,14 +343,14 @@ public ReferenceWithError install() { if (!updating) { osgiManager.managedBundlesRecord.addManagedBundle(result); result.code = OsgiBundleInstallationResult.ResultCode.INSTALLED_NEW_BUNDLE; - result.message = "Installed "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId(); + result.message = "Installed Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" with ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; mgmt().getRebindManager().getChangeListener().onManaged(result.getMetadata()); } else { result.code = OsgiBundleInstallationResult.ResultCode.UPDATED_EXISTING_BUNDLE; - result.message = "Updated "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId(); + result.message = "Updated Brooklyn catalog bundle "+result.getMetadata().getVersionedName()+" as existing ID "+result.getMetadata().getId()+" ["+result.bundle.getBundleId()+"]"; mgmt().getRebindManager().getChangeListener().onChanged(result.getMetadata()); } - log.info(result.message); + log.debug(result.message + " (in osgi container)"); // setting the above before the code below means if there is a problem starting or loading catalog items // a user has to remove then add again, or forcibly reinstall; @@ -364,6 +367,7 @@ public ReferenceWithError install() { public void run() { if (start) { try { + log.debug("Starting bundle "+result.getVersionedName()); result.bundle.start(); } catch (BundleException e) { throw Exceptions.propagate(e); @@ -375,7 +379,9 @@ public void run() { osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() ); // (ideally removal and addition would be atomic) } - for (CatalogItem ci: osgiManager.loadCatalogBom(result.bundle, force)) { + List> items = osgiManager.loadCatalogBom(result.bundle, force); + log.debug("Adding items from bundle "+result.getVersionedName()+": "+items); + for (CatalogItem ci: items) { result.catalogItemsInstalled.add(ci.getId()); } } @@ -383,8 +389,18 @@ public void run() { }; if (deferredStart) { result.deferredStart = startRunnable; + log.debug(result.message+" (Brooklyn load deferred)"); } else { startRunnable.run(); + if (!result.catalogItemsInstalled.isEmpty()) { + // show fewer info messages, only for 'interesting' and non-deferred installations + // (rebind is deferred, as are tests, but REST is not) + MutableList firstFive = MutableList.copyOf(Iterables.limit(result.catalogItemsInstalled, 5)); + log.info(result.message+", items: "+firstFive+ + (result.catalogItemsInstalled.size() > 5 ? " (and others, "+result.catalogItemsInstalled.size()+" total)" : "") ); + } else { + log.debug(result.message+" (into Brooklyn), with no catalog items"); + } } return ReferenceWithError.newInstanceWithoutError(result); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index 77d2a80464..a73c0165e3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -288,6 +288,8 @@ public ReferenceWithError installDeferredStart(@Nu public ReferenceWithError install(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn, boolean start, boolean loadCatalogBom, boolean forceUpdateOfNonSnapshots) { + log.debug("Installing bundle from stream - known details: "+knownBundleMetadata); + OsgiArchiveInstaller installer = new OsgiArchiveInstaller(this, knownBundleMetadata, zipIn); installer.setStart(start); installer.setLoadCatalogBom(loadCatalogBom); @@ -335,12 +337,14 @@ public void uninstallUploadedBundle(ManagedBundle bundleMetadata) { @Beta public void uninstallCatalogItemsFromBundle(VersionedName bundle) { List thingsFromHere = ImmutableList.copyOf(getTypesFromBundle( bundle )); + log.debug("Uninstalling items from bundle "+bundle+": "+thingsFromHere); for (RegisteredType t: thingsFromHere) { mgmt.getCatalog().deleteCatalogItem(t.getSymbolicName(), t.getVersion()); } } - protected Iterable getTypesFromBundle(final VersionedName vn) { + @Beta + public Iterable getTypesFromBundle(final VersionedName vn) { final String bundleId = vn.toString(); return mgmt.getTypeRegistry().getMatching(new Predicate() { @Override diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java index 98d6759c5d..dc35334ed6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java @@ -126,8 +126,13 @@ public boolean equals(Object other) { return Objects.equal(name, o.name) && Objects.equal(v, o.v); } - /** As {@link #equals(Object)} but accepting the argument as equal if versions are identical when injected to OSGi-valid versions */ + /** As {@link #equals(Object)} but accepting the argument as equal + * if versions are identical when injected to OSGi-valid versions, + * and accepting strings as the other */ public boolean equalsOsgi(Object other) { + if (other instanceof String) { + other = VersionedName.fromString((String)other); + } if (!(other instanceof VersionedName)) { return false; } From 21e93d27bed7efd3d8f115c8fc2234b39360ee3a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Jun 2017 11:28:24 +0100 Subject: [PATCH 10/33] restore test bundle counts, and resolve library URLs on install --- .../CatalogOsgiVersionMoreEntityTest.java | 19 +++++++--- .../internal/BasicBrooklynCatalog.java | 35 +++++++++++++------ .../catalog/internal/CatalogBundleDto.java | 13 +++++++ .../apache/brooklyn/util/core/osgi/Osgis.java | 1 + 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java index 6fee53e40b..10f2454e26 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java @@ -20,6 +20,7 @@ import static org.testng.Assert.assertTrue; +import java.util.List; import java.util.Map; import org.apache.brooklyn.api.entity.Entity; @@ -30,10 +31,10 @@ import org.apache.brooklyn.api.policy.PolicySpec; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher; -import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiVersionMoreEntityTest; @@ -42,6 +43,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.text.Strings; @@ -75,7 +77,7 @@ public void testBrooklynManagedBundleInstall() throws Exception { // bundle installed Map bundles = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); - Asserts.assertSize(bundles.keySet(), 1 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); + Asserts.assertSize(bundles.keySet(), 1); Assert.assertTrue(bundles.keySet().contains( br.getMetadata().getId() )); // types installed @@ -96,7 +98,7 @@ public void testMoreEntityV1() throws Exception { Assert.assertNotNull(item); Assert.assertEquals(item.getVersion(), "1.0"); Assert.assertTrue(RegisteredTypePredicates.IS_ENTITY.apply(item)); - Assert.assertEquals(item.getLibraries().size(), 1 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); + Assert.assertEquals(item.getLibraries().size(), 1); Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]"); Entity moreEntity = Iterables.getOnlyElement(app.getChildren()); @@ -220,7 +222,7 @@ public void testMoreEntityV2AutoscanWithClasspath() throws Exception { // this refers to the java item, where the libraries are defined item = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MoreEntity"); Assert.assertEquals(item.getVersion(), "2.0.test_java"); - Assert.assertEquals(item.getLibraries().size(), 2 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); + Assert.assertEquals(item.getLibraries().size(), 2); Entity app = createAndStartApplication("services: [ { type: 'more-entity:2.0.test' } ]"); Entity moreEntity = Iterables.getOnlyElement(app.getChildren()); @@ -247,7 +249,14 @@ public void testMorePolicyV2AutoscanWithClasspath() throws Exception { // this refers to the java item, where the libraries are defined item = mgmt().getTypeRegistry().get("org.apache.brooklyn.test.osgi.entities.more.MorePolicy"); Assert.assertEquals(item.getVersion(), "2.0.test_java"); - Assert.assertEquals(item.getLibraries().size(), 2 + (BasicBrooklynCatalog.AUTO_WRAP_CATALOG_YAML_AS_BUNDLE ? 1 : 0)); + // check the libraries are as expected and fully resolved + List libStr = MutableList.of(); + for (OsgiBundleWithUrl ob: item.getLibraries()) { + libStr.add(ob.getVersionedName()==null ? ob.getUrl() : ob.getVersionedName().toString()); + } + Assert.assertEquals(libStr, MutableList.of( + BROOKLYN_TEST_MORE_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.2.0", + BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL+":"+"0.1.0")); Entity app = createAndStartApplication( "services: ", diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index dacdb90626..116113ddd6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -67,6 +67,7 @@ import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.core.osgi.Osgis; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.UserFacingException; @@ -546,7 +547,7 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund Collection librariesAddedHereBundles = CatalogItemDtoAbstract.parseLibraries(librariesAddedHereNames); MutableSet librariesCombinedNames = MutableSet.of(); - if (!isNoBundleOrSimpleWrappingBundle(containingBundle)) { + if (!isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { // ensure containing bundle is declared, first, for search purposes librariesCombinedNames.add(containingBundle.getVersionedName().toOsgiString()); } @@ -561,12 +562,16 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund // but this load is required for resolving YAML in this BOM (and if java-scanning); // need to think through how we expect dependencies to be installed CatalogUtils.installLibraries(mgmt, librariesAddedHereBundles); + + // use resolved bundles + librariesAddedHereBundles = resolveWherePossible(mgmt, librariesAddedHereBundles); + libraryBundles = resolveWherePossible(mgmt, libraryBundles); Boolean scanJavaAnnotations = getFirstAs(itemMetadataWithoutItemDef, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull(); if (scanJavaAnnotations==null || !scanJavaAnnotations) { // don't scan } else { - if (isNoBundleOrSimpleWrappingBundle(containingBundle)) { + if (isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { // BOMs wrapped in JARs, or without JARs, have special treatment if (isLibrariesMoreThanJustContainingBundle(librariesAddedHereBundles, containingBundle)) { // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM @@ -800,7 +805,15 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund result.add(dto); } - private boolean isLibrariesMoreThanJustContainingBundle(Collection library, ManagedBundle containingBundle) { + protected static Collection resolveWherePossible(ManagementContext mgmt, Collection libraryBundles) { + Collection libraryBundlesResolved = MutableSet.of(); + for (CatalogBundle b: libraryBundles) { + libraryBundlesResolved.add(CatalogBundleDto.resolve(mgmt, b).or(b)); + } + return libraryBundlesResolved; + } + + private static boolean isLibrariesMoreThanJustContainingBundle(Collection library, ManagedBundle containingBundle) { if (library==null) return false; if (containingBundle==null) return !library.isEmpty(); if (library.size()>1) return true; @@ -808,7 +821,7 @@ private boolean isLibrariesMoreThanJustContainingBundle(Collection item, String fieldAt @SuppressWarnings({ "unchecked", "rawtypes" }) Collection> result = (Collection)Collections2.transform( (Collection>)(Collection)subCatalog.getIdCache().values(), - itemDoToDtoAddingSelectedMetadataDuringScan(catalogMetadata, containingBundle)); + itemDoToDtoAddingSelectedMetadataDuringScan(mgmt, catalogMetadata, containingBundle)); return result; } @@ -1397,7 +1410,7 @@ public CatalogItem apply(@Nullable CatalogItemDo item) { }; } - private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final Map catalogMetadata, ManagedBundle containingBundle) { + private static Function, CatalogItem> itemDoToDtoAddingSelectedMetadataDuringScan(final ManagementContext mgmt, final Map catalogMetadata, ManagedBundle containingBundle) { return new Function, CatalogItem>() { @Override public CatalogItem apply(@Nullable CatalogItemDo item) { @@ -1412,21 +1425,21 @@ public CatalogItem apply(@Nullable CatalogItemDo item) { if (Strings.isNonBlank(version)) dto.setVersion(version); Collection libraryBundles = MutableSet.of(); - if (containingBundle!=null) { + if (!isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { libraryBundles.add(new CatalogBundleDto(containingBundle.getSymbolicName(), containingBundle.getSuppliedVersionString(), null)); } libraryBundles.addAll(dto.getLibraries()); Object librariesInherited; librariesInherited = catalogMetadata.get("brooklyn.libraries"); if (librariesInherited instanceof Collection) { - // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case - libraryBundles.addAll(CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited)); + // will be set by scan -- slightly longwinded way to retrieve, but scanning java should be deprecated I think (AH) + libraryBundles.addAll(resolveWherePossible(mgmt, CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited))); } librariesInherited = catalogMetadata.get("libraries"); if (librariesInherited instanceof Collection) { log.warn("Legacy 'libraries' encountered; use 'brooklyn.libraries'"); // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case - libraryBundles.addAll(CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited)); + libraryBundles.addAll(resolveWherePossible(mgmt, CatalogItemDtoAbstract.parseLibraries((Collection) librariesInherited))); } dto.setLibraries(libraryBundles); @@ -1488,7 +1501,7 @@ public void uninstallEmptyWrapperBundles() { Maybe osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); if (osgi.isAbsent()) return; for (ManagedBundle b: osgi.get().getManagedBundles().values()) { - if (isNoBundleOrSimpleWrappingBundle(b)) { + if (isNoBundleOrSimpleWrappingBundle(mgmt, b)) { Iterable typesInBundle = osgi.get().getTypesFromBundle(b.getVersionedName()); if (Iterables.isEmpty(typesInBundle)) { log.debug("uninstalling empty wrapper bundle "+b); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java index 2006d5b027..1653fccfeb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java @@ -23,8 +23,13 @@ import com.google.common.base.Preconditions; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; +import org.osgi.framework.Bundle; public class CatalogBundleDto implements CatalogBundle { private String symbolicName; @@ -106,5 +111,13 @@ public boolean equals(Object obj) { return true; } + public static Maybe resolve(ManagementContext mgmt, CatalogBundle b) { + if (b.isNameResolved()) return Maybe.of(b); + OsgiManager osgi = ((ManagementContextInternal)mgmt).getOsgiManager().orNull(); + if (osgi==null) return Maybe.absent("No OSGi manager"); + Maybe b2 = osgi.findBundle(b); + if (b2.isAbsent()) return Maybe.absent("Nothing installed for "+b); + return Maybe.of(new CatalogBundleDto(b2.get().getSymbolicName(), b2.get().getVersion().toString(), b.getUrl())); + } } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java index c1a40f88d0..235f9dd7f2 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/Osgis.java @@ -428,4 +428,5 @@ public static Optional getBundleOf(Class clazz) { Bundle bundle = org.osgi.framework.FrameworkUtil.getBundle(clazz); return Optional.fromNullable(bundle); } + } From 9885cff87b47cefe78c26755cf7ed8e82a1dbd4d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Jun 2017 12:31:03 +0100 Subject: [PATCH 11/33] catalog.bom parsing can result in RegisteredType instances instead of CatalogItem added to non-persisting BasicBrooklynTypeRegistry, currently without validation; with quite a few tests, and previous failing cobundle-reference tests now uncommented. deletion support added as beta to TypeRegistry, works alongside Catalog deletion. not done, lots of tests failing still, but lots working, and shape of code pretty good. (even if the two-path code in BasicBrooklynCatalog is ugly, it's private and we'll be able to simplify a lot if/when we cut the old catalog code) need to next: perform some validation on the added registered types, as a secondary phase so we can add all types first, and populate supertypes then, which should allow all tests to pass, --- .../brooklyn/api/catalog/BrooklynCatalog.java | 24 ++ .../camp/brooklyn/AbstractYamlTest.java | 10 +- .../camp/brooklyn/ReferencedYamlTest.java | 6 +- ...CatalogYamlEntityOsgiTypeRegistryTest.java | 88 +++++++ .../catalog/CatalogYamlEntityTest.java | 4 +- .../internal/BasicBrooklynCatalog.java | 249 +++++++++++++----- .../catalog/internal/CatalogBundleLoader.java | 2 + .../brooklyn/core/mgmt/ha/OsgiManager.java | 9 +- .../typereg/BasicBrooklynTypeRegistry.java | 22 +- .../typereg/RegisteredTypePredicates.java | 19 ++ .../core/typereg/RegisteredTypes.java | 27 +- .../brooklyn/util/core/osgi/BundleMaker.java | 6 +- 12 files changed, 372 insertions(+), 94 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index 5bb9e1e04a..d2cd6d5961 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -19,13 +19,17 @@ package org.apache.brooklyn.api.catalog; import java.util.Collection; +import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import javax.annotation.Nullable; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; +import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; @@ -92,6 +96,26 @@ public interface BrooklynCatalog { */ AbstractBrooklynObjectSpec peekSpec(CatalogItem item); + /** Adds the given registered types defined in a bundle's catalog BOM; + * no validation performed, so caller should do that subsequently after + * loading all potential inter-dependent types. + *

+ * Nothing is returned but caller can retrieve the results by searching the + * type registry for types with the same containing bundle. + */ + @Beta // method may move elsewhere + public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean forceUpdate); + + /** Performs YAML validation on the given set of types, returning a map whose keys are + * those types where validation failed, mapped to a collection of errors. + * An empty map result indicates no validation errors in the types passed in. + *

+ * Validation may be side-effecting in that it sets metadata and refines supertypes + * for the given registered type. + */ + @Beta + public Map> validateTypes(Iterable typesToValidate); + /** * Adds an item (represented in yaml) to the catalog. * Fails if the same version exists in catalog. diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java index c69f48101a..00b9d4efdd 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java @@ -97,7 +97,7 @@ protected LocalManagementContext newTestManagementContext() { } return builder.build(); } - + /** Override to enable OSGi in the management context for all tests in the class. */ protected boolean disableOsgi() { return true; @@ -254,7 +254,15 @@ protected int countCatalogItemsMatching(Predicate filter return Iterables.size(mgmt().getTypeRegistry().getMatching(filter)); } + /** forcibly update items when adding to catalog (default is not to do this) */ public void forceCatalogUpdate() { forceUpdate = true; } + + /** whether when adding to catalog to forcibly update */ + public final boolean isForceUpdate() { + return forceUpdate; + } + + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java index 3480368e21..e22a935df8 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java @@ -169,7 +169,7 @@ public void testYamlReferencingEarlierItemShortForm() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") // references to earlier items only work with short form syntax + @Test // long form discouraged but references should now still work public void testYamlReferencingEarlierItemLongFormEntity() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -222,7 +222,7 @@ public void testYamlReferencingEarlierItemLongFormTemplate() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") //Not able to use caller provided catalog items when referencing entity specs (as opposed to catalog meta) + @Test // references to co-bundled items work even in nested url yaml public void testYamlReferencingEarlierItemInUrl() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -245,7 +245,7 @@ public void testYamlReferencingEarlierItemInUrl() throws Exception { checkChildEntitySpec(app, entityName); } - @Test(groups="WIP") //Not able to use caller provided catalog items when referencing entity specs (as opposed to catalog meta) + @Test // reference to co-bundled items work also in nested url yaml as a type public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { addCatalogItems( "brooklyn.catalog:", diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java new file mode 100644 index 0000000000..82a609540c --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn.catalog; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.util.zip.ZipEntry; + +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; +import org.apache.brooklyn.util.osgi.VersionedName; +import org.testng.annotations.Test; + +import com.google.common.collect.Iterables; + +/** Variant of parent tests using OSGi, bundles, and type registry, instead of lightweight non-osgi catalog */ +@Test +public class CatalogYamlEntityOsgiTypeRegistryTest extends CatalogYamlEntityTest { + + // use OSGi here + @Override protected boolean disableOsgi() { return false; } + + // use type registry appraoch + @Override + protected void addCatalogItems(String catalogYaml) { + try { + BundleMaker bundleMaker = new BundleMaker(mgmt()); + File bf = bundleMaker.createTempZip("test", MutableMap.of( + new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); + ReferenceWithError b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installDeferredStart( + new BasicManagedBundle(bundleName(), bundleVersion(), null), + new FileInputStream(bf)); + // bundle not started (no need), and BOM not installed above; do it explicitly below + // testing the type registry approach instead + mgmt().getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), isForceUpdate()); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + protected void deleteCatalogEntity(String catalogItem) { + mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION); + } + + protected String bundleName() { return "sample-bundle"; } + protected String bundleVersion() { return BasicBrooklynCatalog.NO_VERSION; } + + @Test // basic test that this approach to adding types works + public void testAddTypes() throws Exception { + String symbolicName = "my.catalog.app.id.load"; + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + + Iterable itemsInstalled = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(new VersionedName(bundleName(), bundleVersion()))); + Asserts.assertSize(itemsInstalled, 1); + RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION); + Asserts.assertEquals(item, Iterables.getOnlyElement(itemsInstalled), "Wrong item; installed: "+itemsInstalled); + } + + // many other tests from super now run, with the type registry approach instead of catalog item / catalog approach + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java index 08e2432c57..bcd3dc2105 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java @@ -694,11 +694,11 @@ public IdAndVersion(String id, String version) { } } - private void addCatalogEntity(String symbolicName, String entityType) { + protected void addCatalogEntity(String symbolicName, String entityType) { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), entityType); } - private void addCatalogEntity(IdAndVersion idAndVersion, String serviceType) { + protected void addCatalogEntity(IdAndVersion idAndVersion, String serviceType) { addCatalogItems( "brooklyn.catalog:", " id: " + idAndVersion.id, diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index c027268309..c311664a77 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -48,6 +48,7 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -58,16 +59,19 @@ import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.core.typereg.BasicRegisteredType; +import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; +import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.core.osgi.BundleMaker; -import org.apache.brooklyn.util.core.osgi.Osgis; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.UserFacingException; @@ -89,6 +93,7 @@ import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; +import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; @@ -281,8 +286,22 @@ public CatalogItem getCatalogItem(String symbolicName, String version) { return itemDo.getDto(); } + private static ThreadLocal deletingCatalogItem = new ThreadLocal<>(); @Override public void deleteCatalogItem(String symbolicName, String version) { + if (!Boolean.TRUE.equals(deletingCatalogItem.get())) { + // while we switch from catalog to type registry, make sure deletion covers both; + // thread local lets us call to other once then he calls us and we do other code path + deletingCatalogItem.set(true); + try { + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).delete( + mgmt.getTypeRegistry().get(symbolicName, version) ); + return; + } finally { + deletingCatalogItem.remove(); + } + } + log.debug("Deleting manual catalog item from "+mgmt+": "+symbolicName + ":" + version); checkNotNull(symbolicName, "id"); checkNotNull(version, "version"); @@ -447,12 +466,6 @@ private static Maybe> getFirstAsMap(Map map, String firstKey, Stri return (Maybe) getFirstAs(map, Map.class, firstKey, otherKeys); } - private List> collectCatalogItems(String yaml, ManagedBundle containingBundle) { - List> result = MutableList.of(); - collectCatalogItems(yaml, containingBundle, result, ImmutableMap.of()); - return result; - } - public static Map getCatalogMetadata(String yaml) { Map itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class); return getFirstAsMap(itemDef, "brooklyn.catalog").orNull(); @@ -481,15 +494,17 @@ public static VersionedName getVersionedName(Map catalogMetadata, boolean r return new VersionedName(bundle, version); } - private void collectCatalogItems(String yaml, ManagedBundle containingBundle, List> result, Map parentMeta) { + /** See comments on {@link #collectCatalogItemsFromItemMetadataBlock(String, ManagedBundle, Map, List, boolean, Map, int, boolean)}; + * this is a shell around that that parses the `brooklyn.catalog` header on the BOM YAML file */ + private void collectCatalogItemsFromCatalogBomRoot(String yaml, ManagedBundle containingBundle, List> result, boolean requireValidation, Map parentMeta, int depth, boolean force) { Map itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class); Map catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull(); if (catalogMetadata==null) log.warn("No `brooklyn.catalog` supplied in catalog request; using legacy mode for "+itemDef); catalogMetadata = MutableMap.copyOf(catalogMetadata); - collectCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), - containingBundle, catalogMetadata, result, parentMeta, 0); + collectCatalogItemsFromItemMetadataBlock(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), + containingBundle, catalogMetadata, result, requireValidation, parentMeta, 0, force); itemDef.remove("brooklyn.catalog"); catalogMetadata.remove("item"); @@ -504,12 +519,46 @@ private void collectCatalogItems(String yaml, ManagedBundle containingBundle, Li if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match); else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, ""); } - collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), containingBundle, rootItem, result, catalogMetadata, 1); + collectCatalogItemsFromItemMetadataBlock("item:\n"+makeAsIndentedObject(rootItemYaml), containingBundle, rootItem, result, requireValidation, catalogMetadata, 1, force); } } + /** + * Expects item metadata, containing an `item` containing the definition, + * and/or `items` containing a list of item metadata (recursing with depth). + * + * Supports two modes: + * + * * CatalogItems validated and returned, but not added to catalog here; + * caller does that, and CI instances are persisted and loaded directly after rebind + * + * * RegisteredTypes added to (unpersisted) type registry; + * caller than validates, optionally removes broken ones, + * given the ability to add multiple interdependent BOMs/bundles and then validate; + * bundles with BOMs are persisted instead of catalog items + * + * I (Alex) think the first one should be considered legacy, and removed once we do + * everything with bundles. (At that point we can kill nearly ALL the code in this package, + * needing just a subset of the parsing and validation routines in this class which can + * be tidied up a lot.) + * + * Parameters suggest other combinations besides the above, but they aren't guaranteed to work. + * This is a private method and expect to clean it up a lot as per above. + * + * @param sourceYaml - metadata source for reference + * @param containingBundle - bundle where this is being loaded, or null + * @param itemMetadata - map of this item metadata reap + * @param result - list where items should be added, or add to type registry if null + * @param requireValidation - whether to require items to be validated; if false items might not be valid, + * and/or their catalog item types might not be set correctly yet; caller should normally validate later + * (useful if we have to load a bunch of things before they can all be validated) + * @param parentMetadata - inherited metadata + * @param depth - depth this is running in + * @param force - whether to force the catalog addition (only applies if result is null) + */ @SuppressWarnings("unchecked") - private void collectCatalogItems(String sourceYaml, ManagedBundle containingBundle, Map itemMetadata, List> result, Map parentMetadata, int depth) { + private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, ManagedBundle containingBundle, Map itemMetadata, List> result, boolean requireValidation, + Map parentMetadata, int depth, boolean force) { if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata); @@ -613,18 +662,18 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund int count = 0; for (Object ii: checkType(items, "items", List.class)) { if (ii instanceof String) { - collectUrlReferencedCatalogItems((String) ii, containingBundle, result, catalogMetadata); + collectUrlReferencedCatalogItems((String) ii, containingBundle, result, requireValidation, catalogMetadata, depth+1, force); } else { Map i = checkType(ii, "entry in items list", Map.class); - collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), - containingBundle, i, result, catalogMetadata, depth+1); + collectCatalogItemsFromItemMetadataBlock(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), + containingBundle, i, result, requireValidation, catalogMetadata, depth+1, force); } count++; } } if (url != null) { - collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), containingBundle, result, catalogMetadata); + collectUrlReferencedCatalogItems(checkType(url, "include in catalog meta", String.class), containingBundle, result, requireValidation, catalogMetadata, depth+1, force); } if (item==null) return; @@ -649,8 +698,10 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund } PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, libraryBundles, result).reconstruct(); + Exception resolutionError = null; if (!planInterpreter.isResolved()) { - throw Exceptions.create("Could not resolve definition of item" + // don't throw yet, we may be able to add it in an unresolved state + resolutionError = Exceptions.create("Could not resolve definition of item" + (Strings.isNonBlank(id) ? " '"+id+"'" : Strings.isNonBlank(symbolicName) ? " '"+symbolicName+"'" : Strings.isNonBlank(name) ? " '"+name+"'" : "") // better not to show yaml, takes up lots of space, and with multiple plan transformers there might be multiple errors; // some of the errors themselves may reproduce it @@ -658,7 +709,9 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund // + ":\n"+sourceYaml , planInterpreter.getErrors()); } + // now allowed to be null here itemType = planInterpreter.getCatalogItemType(); + Map itemAsMap = planInterpreter.getItem(); // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key, // as opposed to the rawer { type: foo } map without that outer key which is valid as item input @@ -785,24 +838,59 @@ private void collectCatalogItems(String sourceYaml, ManagedBundle containingBund final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull(); final Boolean catalogDeprecated = Boolean.valueOf(deprecated); - // run again now that we know the ID + // run again now that we know the ID to catch recursive definitions and possibly other mistakes (itemType inconsistency?) planInterpreter = new PlanInterpreterGuessingType(id, item, sourceYaml, itemType, libraryBundles, result).reconstruct(); - if (!planInterpreter.isResolved()) { - throw new IllegalStateException("Could not resolve plan once id and itemType are known (recursive reference?): "+sourceYaml); + if (resolutionError==null && !planInterpreter.isResolved()) { + resolutionError = new IllegalStateException("Plan resolution breaks after id and itemType are set; is there a recursive reference or other type inconsistency?\n"+sourceYaml); } String sourcePlanYaml = planInterpreter.getPlanYaml(); - CatalogItemDtoAbstract dto = createItemBuilder(itemType, symbolicName, version) - .libraries(libraryBundles) - .displayName(displayName) - .description(description) - .deprecated(catalogDeprecated) - .iconUrl(catalogIconUrl) - .plan(sourcePlanYaml) - .build(); - - dto.setManagementContext((ManagementContextInternal) mgmt); - result.add(dto); + if (result==null) { + // horrible API but basically if `result` is null then add to local unpersisted registry instead, + // without forcing resolution and ignoring errors; this lets us deal with forward references, but + // we'll have to do a validation step subsequently. (already we let bundles deal with persistence, + // just need TODO to make sure we delete previously-persisted things which now come through this path.) + // NB: when everything is a bundle and we've removed all scanning then this can be the _only_ path + // and code can be massively simpler + + if (resolutionError!=null) { + if (requireValidation) { + throw Exceptions.propagate(resolutionError); + } + // warn? add as "unresolved" ? just do nothing? + } + String format = null; // could support specifying format + // TODO bean? or spec?? + // TODO learn supertypes, or find later + Class javaType = null; + List> superTypes = MutableList.>of().appendIfNotNull(javaType); + BasicRegisteredType type = (BasicRegisteredType) RegisteredTypes.newInstance( + RegisteredTypeKind.SPEC, + symbolicName, version, new BasicTypeImplementationPlan(format, sourcePlanYaml), + superTypes, containingBundle, libraryBundles, + displayName, description, catalogIconUrl, catalogDeprecated); + // TODO tags ? + + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, force); + + } else { + if (resolutionError!=null) { + // if there was an error, throw it here + throw Exceptions.propagate(resolutionError); + } + + CatalogItemDtoAbstract dto = createItemBuilder(itemType, symbolicName, version) + .libraries(libraryBundles) + .displayName(displayName) + .description(description) + .deprecated(catalogDeprecated) + .iconUrl(catalogIconUrl) + .plan(sourcePlanYaml) + .build(); + + dto.setManagementContext((ManagementContextInternal) mgmt); + result.add(dto); + } } protected static Collection resolveWherePossible(ManagementContext mgmt, Collection libraryBundles) { @@ -827,7 +915,7 @@ private static boolean isNoBundleOrSimpleWrappingBundle(ManagementContext mgmt, return wrapped!=null && wrapped.equalsIgnoreCase("true"); } - private void collectUrlReferencedCatalogItems(String url, ManagedBundle containingBundle, List> result, Map parentMeta) { + private void collectUrlReferencedCatalogItems(String url, ManagedBundle containingBundle, List> result, boolean requireValidation, Map parentMeta, int depth, boolean force) { @SuppressWarnings("unchecked") List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, List.class, "brooklyn.libraries", "libraries").orNull()); Collection parentLibraries = CatalogItemDtoAbstract.parseLibraries(parentLibrariesRaw); @@ -839,7 +927,7 @@ private void collectUrlReferencedCatalogItems(String url, ManagedBundle containi Exceptions.propagateIfFatal(e); throw new IllegalStateException("Remote catalog url " + url + " can't be fetched.", e); } - collectCatalogItems(yaml, containingBundle, result, parentMeta); + collectCatalogItemsFromCatalogBomRoot(yaml, containingBundle, result, requireValidation, parentMeta, depth, force); } @SuppressWarnings("unchecked") @@ -946,7 +1034,7 @@ private class PlanInterpreterGuessingType { List errors = MutableList.of(); List entityErrors = MutableList.of(); - public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, + public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, Collection libraryBundles, List> itemsDefinedSoFar) { // ID is useful to prevent recursive references (possibly only supported for entities?) this.id = id; @@ -1016,52 +1104,54 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { else candidateYaml = key + ":\n" + makeAsIndentedList(itemYaml); } - // first look in collected items, if a key is given String type = (String) item.get("type"); - - if (type!=null && key!=null) { - for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { - if (candidateCiType == candidate.getCatalogItemType() && - (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { - // matched - exit - catalogItemType = candidateCiType; - planYaml = candidateYaml; - resolved = true; - return true; - } - } - } - { - // legacy routine; should be the same as above code added in 0.12 because: - // if type is symbolic_name, the type will match above, and version will be null so any version allowed to match - // if type is symbolic_name:version, the id will match, and the version will also have to match - // SHOULD NEVER NEED THIS - remove during or after 0.13 - String typeWithId = type; - String version = null; - if (CatalogUtils.looksLikeVersionedId(type)) { - version = CatalogUtils.getVersionFromVersionedId(type); - type = CatalogUtils.getSymbolicNameFromVersionedId(type); - } + if (itemsDefinedSoFar!=null) { + // first look in collected items, if a key is given + if (type!=null && key!=null) { for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { if (candidateCiType == candidate.getCatalogItemType() && (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { - if (version==null || version.equals(candidate.getVersion())) { - log.error("Lookup of '"+type+"' version '"+version+"' only worked using legacy routines; please advise Brooklyn community so they understand why"); - // matched - exit - catalogItemType = candidateCiType; - planYaml = candidateYaml; - resolved = true; - return true; + // matched - exit + catalogItemType = candidateCiType; + planYaml = candidateYaml; + resolved = true; + return true; + } + } + } + { + // legacy routine; should be the same as above code added in 0.12 because: + // if type is symbolic_name, the type will match above, and version will be null so any version allowed to match + // if type is symbolic_name:version, the id will match, and the version will also have to match + // SHOULD NEVER NEED THIS - remove during or after 0.13 + String typeWithId = type; + String version = null; + if (CatalogUtils.looksLikeVersionedId(type)) { + version = CatalogUtils.getVersionFromVersionedId(type); + type = CatalogUtils.getSymbolicNameFromVersionedId(type); + } + if (type!=null && key!=null) { + for (CatalogItemDtoAbstract candidate: itemsDefinedSoFar) { + if (candidateCiType == candidate.getCatalogItemType() && + (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) { + if (version==null || version.equals(candidate.getVersion())) { + log.error("Lookup of '"+type+"' version '"+version+"' only worked using legacy routines; please advise Brooklyn community so they understand why"); + // matched - exit + catalogItemType = candidateCiType; + planYaml = candidateYaml; + resolved = true; + return true; + } } } } + + type = typeWithId; + // above line is a change to behaviour; previously we proceeded below with the version dropped in code above; + // but that seems like a bug as the code below will have ignored version. + // likely this means we are now stricter about loading things that reference new versions, but correctly so. } - - type = typeWithId; - // above line is a change to behaviour; previously we proceeded below with the version dropped in code above; - // but that seems like a bug as the code below will have ignored version. - // likely this means we are now stricter about loading things that reference new versions, but correctly so. } // then try parsing plan - this will use loader @@ -1257,9 +1347,10 @@ private List> toItems(Iterable itemIds) { @Override public List> addItems(String yaml, ManagedBundle bundle, boolean forceUpdate) { - log.debug("Adding manual catalog item to "+mgmt+": "+yaml); + log.debug("Adding catalog item to "+mgmt+": "+yaml); checkNotNull(yaml, "yaml"); - List> result = collectCatalogItems(yaml, bundle); + List> result = MutableList.of(); + collectCatalogItemsFromCatalogBomRoot(yaml, bundle, result, true, ImmutableMap.of(), 0, false); // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially for (CatalogItemDtoAbstract item: result) { @@ -1271,6 +1362,18 @@ public List> addItems(String yaml, ManagedBundle bund return result; } + @Override @Beta + public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean forceUpdate) { + log.debug("Adding catalog item to "+mgmt+": "+yaml); + checkNotNull(yaml, "yaml"); + collectCatalogItemsFromCatalogBomRoot(yaml, bundle, null, false, MutableMap.of(), 0, forceUpdate); + } + + @Override @Beta + public Map> validateTypes(Iterable typesToValidate) { + return MutableMap.of(); + } + private CatalogItem addItemDto(CatalogItemDtoAbstract itemDto, boolean forceUpdate) { CatalogItem existingDto = checkItemAllowedAndIfSoReturnAnyDuplicate(itemDto, true, forceUpdate); if (existingDto!=null) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index 3af87b9143..f42dac12a6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -77,6 +77,8 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo if (null != bom) { LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); + // TODO use addTypesFromBundleBom; but when should we do validation? after all bundles are loaded? + // OR maybe deprecate/remove this experiment in favour of explicitly installed and managed bundles? catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); for (CatalogItem item : catalogItems) { LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index a73c0165e3..6fe1068ffd 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -46,6 +46,7 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -345,13 +346,7 @@ public void uninstallCatalogItemsFromBundle(VersionedName bundle) { @Beta public Iterable getTypesFromBundle(final VersionedName vn) { - final String bundleId = vn.toString(); - return mgmt.getTypeRegistry().getMatching(new Predicate() { - @Override - public boolean apply(RegisteredType input) { - return bundleId.equals(input.getContainingBundle()); - } - }); + return mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(vn)); } /** @deprecated since 0.12.0 use {@link #install(ManagedBundle, InputStream, boolean, boolean)} */ diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index 468664b842..bcb24ad2e5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -32,7 +32,6 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; -import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; @@ -41,6 +40,8 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Identifiers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +50,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; public class BasicBrooklynTypeRegistry implements BrooklynTypeRegistry { @@ -57,7 +57,6 @@ public class BasicBrooklynTypeRegistry implements BrooklynTypeRegistry { private static final Logger log = LoggerFactory.getLogger(BasicBrooklynTypeRegistry.class); private ManagementContext mgmt; - private Map uploadedBundles = MutableMap.of(); private Map localRegisteredTypes = MutableMap.of(); public BasicBrooklynTypeRegistry(ManagementContext mgmt) { @@ -326,5 +325,22 @@ public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canFo throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present"); } } + + @Beta // API stabilising + public void delete(RegisteredType type) { + if (localRegisteredTypes.remove(type.getId()) != null) { + return ; + } + mgmt.getCatalog().deleteCatalogItem(type.getSymbolicName(), type.getVersion()); + } + + @Beta // API stabilising + public void delete(String id) { + if (localRegisteredTypes.remove(id) != null) { + return ; + } + VersionedName vn = VersionedName.fromString(id); + mgmt.getCatalog().deleteCatalogItem(vn.getSymbolicName(), vn.getVersionString()); + } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java index e480cd1d95..1d67638beb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java @@ -30,6 +30,7 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.osgi.VersionedName; import com.google.common.base.Function; import com.google.common.base.Predicate; @@ -256,4 +257,22 @@ public boolean apply(@Nullable RegisteredType item) { } } + public static Predicate containingBundle(VersionedName versionedName) { + return new ContainingBundle(versionedName); + } + public static Predicate containingBundle(String versionedName) { + return new ContainingBundle(VersionedName.fromString(versionedName)); + } + private static class ContainingBundle implements Predicate { + private final VersionedName bundle; + + public ContainingBundle(VersionedName bundle) { + this.bundle = bundle; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + return bundle.equalsOsgi(item.getContainingBundle()); + } + } + } \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index 72feeddedd..e3a8540e9f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -19,8 +19,10 @@ package org.apache.brooklyn.core.typereg; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -28,6 +30,7 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.objs.BrooklynObject; @@ -125,21 +128,35 @@ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String } /** Convenience for {@link #bean(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan, @Nonnull Class superType) { - if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); + if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); return addSuperType(bean(symbolicName, version, plan), superType); } /** Preferred mechanism for defining a spec {@link RegisteredType}. * Callers should also {@link #addSuperTypes(RegisteredType, Iterable)} on the result.*/ public static RegisteredType spec(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan) { - if (symbolicName==null || version==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+plan)); + if (symbolicName==null || version==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+plan)); return new BasicRegisteredType(RegisteredTypeKind.SPEC, symbolicName, version, plan); } - /** Convenience for {@link #cpec(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ + /** Convenience for {@link #spec(String, String, TypeImplementationPlan)} when there is a single known java signature/super type */ public static RegisteredType spec(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan, @Nonnull Class superType) { - if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null name/version", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); + if (superType==null) log.warn("Deprecated use of RegisteredTypes API passing null supertype", new Exception("Location of deprecated use, wrt "+symbolicName+":"+version+" "+plan)); return addSuperType(spec(symbolicName, version, plan), superType); } + public static RegisteredType newInstance(@Nonnull RegisteredTypeKind kind, @Nonnull String symbolicName, @Nonnull String version, + @Nonnull TypeImplementationPlan plan, @Nonnull List> superTypes, + ManagedBundle containingBundle, Collection libraryBundles, + String displayName, String description, String catalogIconUrl, boolean catalogDeprecated) { + BasicRegisteredType result = new BasicRegisteredType(kind, symbolicName, version, plan); + addSuperTypes(result, superTypes); + result.containingBundle = containingBundle.getVersionedName().toString(); + result.bundles.addAll(libraryBundles); + result.displayName = displayName; + result.description = description; + result.iconUrl = catalogIconUrl; + result.deprecated = catalogDeprecated; + return result; + } /** Creates an anonymous {@link RegisteredType} for plan-instantiation-only use. */ @Beta @@ -306,6 +323,7 @@ public static boolean isSubtypeOf(RegisteredType type, Class superType) { * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) * to see whether any inherit from the given {@link Class} */ public static boolean isAnyTypeSubtypeOf(Set candidateTypes, Class superType) { + if (superType == Object.class) return true; return isAnyTypeOrSuperSatisfying(candidateTypes, Predicates.assignableFrom(superType)); } @@ -480,4 +498,5 @@ public static String getIconUrl(BrooklynObject object) { if (item==null) return null; return item.getIconUrl(); } + } diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java index ebcc2209bd..37aa5a11df 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -361,7 +361,7 @@ public File createTempBundle(String nameHint, Manifest mf, Map mf, Map files) { + return createTempBundle(nameHint, (Manifest)null, files); + } + } From d2e6a536c51382e7008eafd54298d6ecd5eada32 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Jun 2017 21:07:06 +0100 Subject: [PATCH 12/33] big work in catalog to add unresolved items to type registry with support for resolving subsequently, solving problems with references; also shifts most things to being in _unpersisted_ type registry, repopulated on rebind, instead of the clunky catalog which persists items as XML additional todo items: * make tests pass * have all OSGi routines use the new `catalog.addTypes...` instead of `.addItems` * fix clash in removing empty bundles --- .../brooklyn/api/catalog/BrooklynCatalog.java | 18 +- .../brooklyn/api/objs/BrooklynObjectType.java | 33 ++- .../api/typereg/BrooklynTypeRegistry.java | 7 +- .../brooklyn/api/typereg/RegisteredType.java | 9 + .../BrooklynComponentTemplateResolver.java | 6 +- .../brooklyn/spi/creation/CampResolver.java | 4 + .../spi/creation/CampTypePlanTransformer.java | 7 +- ...CatalogYamlEntityOsgiTypeRegistryTest.java | 7 + .../catalog/CatalogYamlEntityTest.java | 21 +- .../catalog/CatalogYamlRebindTest.java | 54 ++-- .../internal/BasicBrooklynCatalog.java | 249 ++++++++++++++++-- .../internal/CatalogItemDtoAbstract.java | 7 +- .../typereg/AbstractTypePlanTransformer.java | 4 +- .../typereg/BasicBrooklynTypeRegistry.java | 53 ++-- .../core/typereg/BasicRegisteredType.java | 52 ++++ .../typereg/BasicTypeImplementationPlan.java | 23 ++ .../ReferencedUnresolvedTypeException.java | 44 ++++ .../typereg/RegisteredTypeKindVisitor.java | 3 + .../typereg/RegisteredTypePredicates.java | 6 +- .../core/typereg/RegisteredTypes.java | 44 +++- .../typereg/UnsupportedTypePlanException.java | 2 + .../BasicBrooklynTypeRegistryTest.java | 11 +- .../brooklyn/util/exceptions/Exceptions.java | 17 +- 23 files changed, 563 insertions(+), 118 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index d2cd6d5961..6599f6dd12 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; import javax.annotation.Nullable; @@ -106,16 +105,23 @@ public interface BrooklynCatalog { @Beta // method may move elsewhere public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean forceUpdate); - /** Performs YAML validation on the given set of types, returning a map whose keys are - * those types where validation failed, mapped to a collection of errors. + /** As {@link #validateType(RegisteredType)} but taking a set of types, returning a map whose keys are + * those types where validation failed, mapped to the collection of errors validating that type. * An empty map result indicates no validation errors in the types passed in. + */ + @Beta // method may move elsewhere + public Map> validateTypes(Iterable typesToValidate); + + /** Performs YAML validation on the given type, returning a collection of errors. + * An empty result indicates no validation errors in the type passed in. *

* Validation may be side-effecting in that it sets metadata and refines supertypes * for the given registered type. */ - @Beta - public Map> validateTypes(Iterable typesToValidate); - + @Beta // method may move elsewhere + Collection validateType(RegisteredType typeToValidate); + + /** * Adds an item (represented in yaml) to the catalog. * Fails if the same version exists in catalog. diff --git a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java index 60021f5bd0..eaf1754ed4 100644 --- a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java +++ b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.api.objs; import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; @@ -72,10 +73,36 @@ public Class getInterfaceType() { } public static BrooklynObjectType of(BrooklynObject instance) { - for (BrooklynObjectType t: values()) { - if (t.getInterfaceType()!=null && t.getInterfaceType().isInstance(instance)) - return t; + if (instance!=null) { + for (BrooklynObjectType t: values()) { + if (t.getInterfaceType()!=null && t.getInterfaceType().isInstance(instance)) + return t; + } } return UNKNOWN; } + + public static BrooklynObjectType of(Class objectTypeOrSpecType) { + if (objectTypeOrSpecType!=null) { + for (BrooklynObjectType t: values()) { + if (t.getInterfaceType()!=null && t.getInterfaceType().isAssignableFrom(objectTypeOrSpecType)) + return t; + if (t.getSpecType()!=null && t.getSpecType().isAssignableFrom(objectTypeOrSpecType)) + return t; + } + } + return UNKNOWN; + } + + public static BrooklynObjectType of(CatalogItemType t) { + switch (t) { + case ENRICHER: return BrooklynObjectType.ENRICHER; + case ENTITY: return BrooklynObjectType.ENTITY; + case LOCATION: return BrooklynObjectType.LOCATION; + case POLICY: return BrooklynObjectType.POLICY; + case TEMPLATE: return BrooklynObjectType.ENTITY; + default: return BrooklynObjectType.UNKNOWN; + } + } + } \ No newline at end of file diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java index 5b15c75e95..33c6733e1a 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.api.typereg; +import java.util.Collection; + import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; @@ -36,7 +38,10 @@ public enum RegisteredTypeKind { * for the type registered (e.g. the {@link Entity} instance) */ SPEC, /** a registered type which will create the java type described */ - BEAN + BEAN, + /** a partially registered type which requires subsequent validation and changing the kind; + * until then, an item of this kind cannot be instantiated */ + UNRESOLVED // note: additional kinds should have the visitor in core/RegisteredTypeKindVisitor updated // to flush out all places which want to implement support for all kinds } diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java index c987473695..b7158e29aa 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredType.java @@ -26,6 +26,7 @@ import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.Identifiable; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.util.osgi.VersionedName; import com.google.common.annotations.Beta; @@ -37,6 +38,9 @@ public interface RegisteredType extends Identifiable { String getSymbolicName(); String getVersion(); + + VersionedName getVersionedName(); + /** Bundle in symbolicname:id format where this type is defined */ // TODO would prefer this to be VersionedName if/when everything comes from OSGi bundles // unrevert 7260bf9cf3f3ebaaa790693e1b7217a81bef78a7 to start that, and adjust serialization @@ -111,6 +115,11 @@ public interface TypeImplementationPlan { String getPlanFormat(); /** data for the implementation; may be more specific */ Object getPlanData(); + + @Override boolean equals(Object obj); + @Override int hashCode(); } + @Override boolean equals(Object obj); + @Override int hashCode(); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java index abc2e8ea87..47b48016fd 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java @@ -152,18 +152,18 @@ public boolean canResolve() { public EntitySpec resolveSpec(Set encounteredRegisteredTypeSymbolicNames) { if (alreadyBuilt.getAndSet(true)) - throw new IllegalStateException("Spec can only be used once: "+this); + throw new IllegalStateException("Spec resolver can only be used once: "+this); EntitySpec spec = serviceSpecResolver.resolve(type, loader, encounteredRegisteredTypeSymbolicNames); if (spec == null) { // Try to provide some troubleshooting details final String msgDetails; - RegisteredType item = mgmt.getTypeRegistry().get(Strings.removeFromStart(type, "catalog:")); + RegisteredType item = mgmt.getTypeRegistry().get(Strings.removeAllFromStart(type, "catalog:", "brooklyn:")); String proto = Urls.getProtocol(type); if (item != null && encounteredRegisteredTypeSymbolicNames.contains(item.getSymbolicName())) { msgDetails = "Cycle between catalog items detected, starting from " + type + - ". Other catalog items being resolved up the stack are " + encounteredRegisteredTypeSymbolicNames + + ". Other catalog items being resolved recursively up the stack are " + encounteredRegisteredTypeSymbolicNames + ". Tried loading it as a Java class instead but failed."; } else if (proto != null) { if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(proto)) { diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java index 55853d796e..f0116cf192 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; @@ -155,6 +156,9 @@ private static EntitySpec createEntitySpecFromServicesBlock(String plan, Broo return appSpec; } else { + if (at.getPlatformComponentTemplates()==null || at.getPlatformComponentTemplates().isEmpty()) { + throw new UnsupportedTypePlanException("No 'services' declared"); + } throw new IllegalStateException("Unable to instantiate YAML; invalid type or parameters in plan:\n"+plan); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java index d8ec7fbd33..0901632726 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java @@ -25,6 +25,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; @@ -47,6 +48,8 @@ public CampTypePlanTransformer() { @Override protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + if (type!=null && type.getKind()!=RegisteredTypeKind.SPEC) return 0; + Maybe> plan = RegisteredTypes.getAsYamlMap(planData); if (plan.isAbsent()) return 0; if (plan.get().containsKey("services")) return 0.8; @@ -60,6 +63,8 @@ protected double scoreForNullFormat(Object planData, RegisteredType type, Regist @Override protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + if (type!=null && type.getKind()!=RegisteredTypeKind.SPEC) return 0; + if (FORMATS.contains(planFormat.toLowerCase())) return 0.9; return 0; } @@ -73,7 +78,7 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { // beans not supported by this? - return null; + throw new IllegalStateException("beans not supported here"); } @Override diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java index 82a609540c..78df657330 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java @@ -21,6 +21,8 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; +import java.util.Collection; +import java.util.Map; import java.util.zip.ZipEntry; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -60,6 +62,11 @@ protected void addCatalogItems(String catalogYaml) { // bundle not started (no need), and BOM not installed above; do it explicitly below // testing the type registry approach instead mgmt().getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), isForceUpdate()); + Map> validation = mgmt().getCatalog().validateTypes( mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b.get().getVersionedName())) ); + if (!validation.isEmpty()) { + throw Exceptions.propagate("Brooklyn failed to load types: "+validation.keySet(), + Iterables.concat(validation.values())); + } } catch (Exception e) { throw Exceptions.propagate(e); } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java index bcd3dc2105..d39a97e370 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java @@ -338,11 +338,16 @@ public void testUpdatingItemAllowedIfSame() { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); } - @Test(expectedExceptions = IllegalStateException.class) + @Test public void testUpdatingItemFailsIfDifferent() { String symbolicName = "my.catalog.app.id.duplicate"; - addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); - addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + try { + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), TestEntity.class.getName()); + addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION), BasicEntity.class.getName()); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "different", symbolicName, TEST_VERSION, "already present"); + } } @Test @@ -375,7 +380,7 @@ public void testMissingTypeDoesNotRecurse() { try { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION + "-update"), symbolicName); Asserts.shouldHaveFailedPreviously("Catalog addition expected to fail due to recursive reference to " + symbolicName); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive", symbolicName); } } @@ -391,7 +396,7 @@ public void testVersionedTypeDoesNotRecurse() throws Exception { try { addCatalogEntity(IdAndVersion.of(symbolicName, TEST_VERSION + "-update"), versionedId); Asserts.shouldHaveFailedPreviously("Catalog addition expected to fail due to recursive reference to " + versionedId); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive", symbolicName, versionedId); } } @@ -410,7 +415,7 @@ public void testIndirectRecursionFails() throws Exception { try { addCatalogEntity(IdAndVersion.of(callerSymbolicName, TEST_VERSION), calleeSymbolicName); Asserts.shouldHaveFailedPreviously(); - } catch (IllegalStateException e) { + } catch (Exception e) { Asserts.expectedFailureContains(e, "recursive"); } } @@ -441,8 +446,8 @@ public void testChildItemsDoNotRecurse() throws Exception { " brooklyn.children:", " - type: " + calleeSymbolicName); Asserts.shouldHaveFailedPreviously(); - } catch (IllegalStateException e) { - Asserts.expectedFailureContains(e, "recursive"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "recursive", callerSymbolicName, calleeSymbolicName); } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index a962dbee61..d92c2b8813 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -118,33 +118,33 @@ protected boolean useOsgi() { public Object[][] dataProvider() { return new Object[][] { {RebindWithCatalogTestMode.NO_OP, OsgiMode.NONE}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, - - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, - - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, - - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, - - // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. - // Deletes the catalog item before rebind, but the referenced types are still on the - // default classpath. Will fallback to loading from classpath. - // - // Does not work for OSGi, because our bundle will no longer be available. - {RebindWithCatalogTestMode.DELETE_CATALOG, OsgiMode.NONE}, - - // Upgrades the catalog item before rebind, deleting the old version. - // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, +// {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, +// {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, +// +// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, +// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, +// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, +// +// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, +// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, +// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, +// +// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, +// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, +// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, +// +// // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. +// // Deletes the catalog item before rebind, but the referenced types are still on the +// // default classpath. Will fallback to loading from classpath. +// // +// // Does not work for OSGi, because our bundle will no longer be available. +// {RebindWithCatalogTestMode.DELETE_CATALOG, OsgiMode.NONE}, +// +// // Upgrades the catalog item before rebind, deleting the old version. +// // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" +// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, +// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, +// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, }; } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index c311664a77..4fc78a0934 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -48,6 +48,8 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.api.objs.BrooklynObjectType; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; @@ -73,7 +75,9 @@ import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.CompoundRuntimeException; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.exceptions.UserFacingException; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.AggregateClassLoader; @@ -88,6 +92,7 @@ import org.apache.brooklyn.util.time.Time; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yaml.Yamls.YamlExtract; +import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,6 +101,7 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; +import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -860,16 +866,32 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed // warn? add as "unresolved" ? just do nothing? } String format = null; // could support specifying format - // TODO bean? or spec?? - // TODO learn supertypes, or find later - Class javaType = null; - List> superTypes = MutableList.>of().appendIfNotNull(javaType); + // TODO if kind and supertype is known, set those here + Class javaType = null; + List superTypes = MutableList.of().appendIfNotNull(javaType); + + // TODO allow these to be set in catalog.bom ? + List aliases = MutableList.of(); + List tags = MutableList.of(); + Boolean catalogDisabled = null; + + if (version==null) { + // use this as default version when nothing specified + version = BasicBrooklynCatalog.NO_VERSION; + } + + if (sourcePlanYaml==null) { + // happens if unresolved and not valid yaml, replace with item yaml + // which normally has "type: " prefixed + sourcePlanYaml = planInterpreter.itemYaml; + } + BasicRegisteredType type = (BasicRegisteredType) RegisteredTypes.newInstance( - RegisteredTypeKind.SPEC, + RegisteredTypeKind.UNRESOLVED, symbolicName, version, new BasicTypeImplementationPlan(format, sourcePlanYaml), - superTypes, containingBundle, libraryBundles, - displayName, description, catalogIconUrl, catalogDeprecated); - // TODO tags ? + superTypes, aliases, tags, containingBundle==null ? null : containingBundle.getVersionedName().toString(), + MutableList.copyOf(libraryBundles), + displayName, description, catalogIconUrl, catalogDeprecated, catalogDisabled); ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, force); @@ -911,7 +933,18 @@ private static boolean isLibrariesMoreThanJustContainingBundle(Collection osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgi.isAbsent()) { + // odd, shouldn't happen, installing bundle but not using osgi + throw new IllegalStateException("OSGi not being used but installing a bundle"); + } + Maybe bb = osgi.get().findBundle(b); + if (bb.isAbsent()) { + // odd, shouldn't happen, bundle not managed + // (originally seen during a race where the empty-remover ran while we were installing) + throw new IllegalStateException("Loading from a bundle which is not installed"); + } + String wrapped = bb.get().getHeaders().get(BROOKLYN_WRAPPED_BOM_BUNDLE); return wrapped!=null && wrapped.equalsIgnoreCase("true"); } @@ -1022,7 +1055,7 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt private class PlanInterpreterGuessingType { - final String id; + final String idAsSymbolicNameWithoutVersion; final Map item; final String itemYaml; final Collection libraryBundles; @@ -1034,18 +1067,20 @@ private class PlanInterpreterGuessingType { List errors = MutableList.of(); List entityErrors = MutableList.of(); - public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, + public PlanInterpreterGuessingType(@Nullable String idAsSymbolicNameWithoutVersion, Object itemDefinitionParsedToStringOrMap, String itemYaml, @Nullable CatalogItemType optionalCiType, Collection libraryBundles, List> itemsDefinedSoFar) { // ID is useful to prevent recursive references (possibly only supported for entities?) - this.id = id; + this.idAsSymbolicNameWithoutVersion = idAsSymbolicNameWithoutVersion; - if (item instanceof String) { + if (itemDefinitionParsedToStringOrMap instanceof String) { // if just a string supplied, wrap as map - this.item = MutableMap.of("type", item); + this.item = MutableMap.of("type", itemDefinitionParsedToStringOrMap); this.itemYaml = "type:\n"+makeAsIndentedObject(itemYaml); - } else { - this.item = (Map)item; + } else if (itemDefinitionParsedToStringOrMap instanceof Map) { + this.item = (Map)itemDefinitionParsedToStringOrMap; this.itemYaml = itemYaml; + } else { + throw new IllegalArgumentException("Item definition should be a string or map to use the guesser"); } this.catalogItemType = optionalCiType; this.libraryBundles = libraryBundles; @@ -1060,7 +1095,12 @@ public PlanInterpreterGuessingType reconstruct() { } else { attemptType(null, CatalogItemType.ENTITY); + List oldEntityErrors = MutableList.copyOf(entityErrors); attemptType("services", CatalogItemType.ENTITY); + entityErrors.removeAll(oldEntityErrors); + entityErrors.addAll(oldEntityErrors); + // prefer errors when wrapped in services block + attemptType(POLICIES_KEY, CatalogItemType.POLICY); attemptType(ENRICHERS_KEY, CatalogItemType.ENRICHER); attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION); @@ -1185,7 +1225,7 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { if (candidateCiType==CatalogItemType.ENTITY) { entityErrors.add(e); } - if (log.isTraceEnabled()) + if (log.isTraceEnabled()) log.trace("Guessing type of plan, it looks like it isn't "+candidateCiType+"/"+key+": "+e); } } @@ -1219,7 +1259,7 @@ private boolean attemptType(String key, CatalogItemType candidateCiType) { } private String getIdWithRandomDefault() { - return id != null ? id : Strings.makeRandomId(10); + return idAsSymbolicNameWithoutVersion != null ? idAsSymbolicNameWithoutVersion : Strings.makeRandomId(10); } public Map getItem() { return item; @@ -1370,10 +1410,179 @@ public void addTypesFromBundleBom(String yaml, ManagedBundle bundle, boolean for } @Override @Beta - public Map> validateTypes(Iterable typesToValidate) { - return MutableMap.of(); + public Map> validateTypes(Iterable typesToValidate) { + List typesRemainingToValidate = MutableList.copyOf(typesToValidate); + while (true) { + Map> result = MutableMap.of(); + for (RegisteredType t: typesToValidate) { + Collection tr = validateType(t); + if (!tr.isEmpty()) { + result.put(t, tr); + } + } + if (result.isEmpty() || result.size()==typesRemainingToValidate.size()) { + return result; + } + // recurse wherever there were problems so long as we are reducing the number of problem types + // (this lets us solve complex reference problems without needing a complex dependency tree, + // in max O(N^2) time) + typesRemainingToValidate = MutableList.copyOf(result.keySet()); + } + } + + @Override @Beta + public Collection validateType(RegisteredType typeToValidate) { + ReferenceWithError result = resolve(typeToValidate); + if (result.hasError()) { + if (result.getError() instanceof CompoundRuntimeException) { + return ((CompoundRuntimeException)result.getError()).getAllCauses(); + } + return Collections.singleton(result.getError()); + } + // replace what's in catalog with resolved+validated version + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(result.get(), true); + return Collections.emptySet(); } + /** + * Resolves the given object with respect to the catalog. Returns any errors found while trying to resolve. + * The argument may be changed (e.g. its kind set, supertypes set), and normal usage is to add + * a type in an "unresolved" state if things may need to reference it, then call resolve here, + * then replace what was added with the argument given here. */ + @Beta + public ReferenceWithError resolve(RegisteredType typeToValidate) { + Throwable inconsistentSuperTypesError=null, specError=null, beanError=null; + List guesserErrors = MutableList.of(); + + // collect supertype spec / most specific java + Set supers = typeToValidate.getSuperTypes(); + BrooklynObjectType boType = null; + for (Object superI: supers) { + BrooklynObjectType boTypeI = null; + if (superI instanceof BrooklynObject) boTypeI = BrooklynObjectType.of((BrooklynObject)superI); + else if (superI instanceof Class) boTypeI = BrooklynObjectType.of((Class)superI); + if (boTypeI!=null && boTypeI!=BrooklynObjectType.UNKNOWN) { + if (boType==null) boType = boTypeI; + else { + if (boTypeI!=boType) { + inconsistentSuperTypesError = new IllegalStateException("Inconsistent supertypes for "+typeToValidate+"; indicates "+boType+" and "+boTypeI); + } + } + } + } + Class superJ = null; + for (Object superI: supers) { + if (superI instanceof Class) { + if (superJ==null) superJ = (Class) superI; + else if (superJ.isAssignableFrom((Class)superI)) superJ = (Class) superI; + } + } + + // TODO filter what we try based on kind, + // and set things based on declared itemType; + // also support itemType spec (generic) and bean to help filter + + // TODO support "template" (never instantiable) in registry + + RegisteredType resultT = null; + + Object resultO = null; + if (resultO==null && boType!=null) try { + // try spec instantiation if we know the BO Type (no point otherwise) + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate); + try { + resultO = ((BasicBrooklynTypeRegistry)mgmt.getTypeRegistry()).createSpec(resultT, null, boType.getSpecType()); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + specError = e; + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // ignore if we couldn't resolve as spec + } + + if (resultO==null) try { + // try it as a bean + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.BEAN, typeToValidate); + try { + resultO = ((BasicBrooklynTypeRegistry)mgmt.getTypeRegistry()).createBean(resultT, null, superJ); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + beanError = e; + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // ignore if we couldn't resolve as spec + } + + if (resultO==null) try { + // try the legacy PlanInterpreterGuessingType + // (this is the only place where we will guess specs, so it handles + // most of our traditional catalog items in BOMs) + String yaml = RegisteredTypes.getImplementationDataStringForSpec(typeToValidate); + PlanInterpreterGuessingType guesser = new PlanInterpreterGuessingType(typeToValidate.getSymbolicName(), Iterables.getOnlyElement( Yamls.parseAll(yaml) ), + yaml, null, CatalogItemDtoAbstract.parseLibraries( typeToValidate.getLibraries() ), null); + guesser.reconstruct(); + guesserErrors.addAll(guesser.getErrors()); + if (guesser.isResolved()) { + CatalogItemType ciType = guesser.getCatalogItemType(); + if (ciType==CatalogItemType.TEMPLATE) { + // TODO templates in registry + throw new IllegalStateException("Templates not yet supported in registry"); + + } else if (boType==null) { + boType = BrooklynObjectType.of(ciType); + if (boType!=null) { + supers = MutableSet.copyOf(supers); + supers.add(boType.getInterfaceType()); + // didn't know type before, retry now that we know the type + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate); + RegisteredTypes.addSuperTypes(resultT, supers); + RegisteredTypes.changePlan(resultT, + new BasicTypeImplementationPlan(null /* CampTypePlanTransformer.FORMAT */, guesser.getPlanYaml())); + return resolve(resultT); + } + } else if (Objects.equal(boType, BrooklynObjectType.of(ciType))) { + if (specError==null) { + throw new IllegalStateException("Guesser resolved but TypeRegistry couldn't create"); + } else { + // do nothing; type was already known, prefer the spec error + } + } else { + throw new IllegalStateException("Guesser resolved as "+ciType+" but we expected "+boType); + } + } else { + throw new IllegalStateException("Guesser could not resolve"); + } + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + guesserErrors.add(e); + } + + if (resultO!=null) { + if (resultO instanceof BrooklynObject) { + // if it was a bean that points at a BO then switch it to a spec and try to re-validate + return resolve(RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate)); + } + RegisteredTypes.cacheActualJavaType(resultT, resultO.getClass()); + + supers = MutableSet.copyOf(supers); + supers.add(resultO.getClass()); + supers.add(BrooklynObjectType.of(resultO.getClass()).getInterfaceType()); + RegisteredTypes.addSuperTypes(resultT, supers); + + return ReferenceWithError.newInstanceWithoutError(resultT); + } + + List errors = MutableList.of() + .appendIfNotNull(inconsistentSuperTypesError) + .appendAll(guesserErrors) + .appendIfNotNull(beanError) + .appendIfNotNull(specError); + return ReferenceWithError.newInstanceThrowingError(null, Exceptions.create("Could not resolve "+typeToValidate, errors)); + } + private CatalogItem addItemDto(CatalogItemDtoAbstract itemDto, boolean forceUpdate) { CatalogItem existingDto = checkItemAllowedAndIfSoReturnAnyDuplicate(itemDto, true, forceUpdate); if (existingDto!=null) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java index 9047fcb543..0557a8c490 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java @@ -29,6 +29,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.mgmt.rebind.BasicCatalogItemRebindSupport; import org.apache.brooklyn.core.objs.AbstractBrooklynObject; @@ -386,7 +387,7 @@ protected void setTags(Set tags) { /** * Parses an instance of CatalogLibrariesDto from the given List. Expects the list entries - * to be either Strings or Maps of String -> String. Will skip items that are not. + * to be either Strings or Maps of String -> String or bundles. Will skip items that are not. *

* If a string is supplied, this tries heuristically to identify whether a reference is a bundle or a URL, as follows: * - if the string contains a slash, it is treated as a URL (or classpath reference), e.g. /file.txt; @@ -445,6 +446,10 @@ public static Collection parseLibraries(Collection possibleLib } dto.add(new CatalogBundleDto(name, version, url)); + } else if (object instanceof OsgiBundleWithUrl) { + dto.add(new CatalogBundleDto( ((OsgiBundleWithUrl)object).getSymbolicName(), ((OsgiBundleWithUrl)object).getSuppliedVersionString(), ((OsgiBundleWithUrl)object).getUrl() )); + } else if (object instanceof VersionedName) { + dto.add(new CatalogBundleDto( ((VersionedName)object).getSymbolicName(), ((VersionedName)object).getVersionString(), null )); } else { LOG.debug("Unexpected entry in libraries list neither string nor map: " + object); } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java index 46b41d6e6b..d0970c869e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java @@ -112,7 +112,9 @@ public Object create(final RegisteredType type, final RegisteredTypeLoadingConte return createBean(type, context); } catch (Exception e) { throw Exceptions.propagate(e); } } - + @Override protected Object visitUnresolved() { + throw new IllegalStateException(type+" is not yet resolved"); + } }.visit(type.getKind()), type, context).get(); } catch (Exception e) { Exceptions.propagateIfFatal(e); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index bcb24ad2e5..1d07a5a424 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -41,7 +41,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.osgi.VersionedName; -import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Identifiers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -175,7 +174,8 @@ public RegisteredType get(String symbolicNameWithOptionalVersion) { public > SpecT createSpec(RegisteredType type, @Nullable RegisteredTypeLoadingContext constraint, Class specSuperType) { Preconditions.checkNotNull(type, "type"); if (type.getKind()!=RegisteredTypeKind.SPEC) { - throw new IllegalStateException("Cannot create spec from type "+type+" (kind "+type.getKind()+")"); + if (type.getKind()==RegisteredTypeKind.UNRESOLVED) throw new ReferencedUnresolvedTypeException(type); + else throw new UnsupportedTypePlanException("Cannot create spec from type "+type+" (kind "+type.getKind()+")"); } return createSpec(type, type.getPlan(), type.getSymbolicName(), type.getVersion(), type.getSuperTypes(), constraint, specSuperType); } @@ -253,11 +253,12 @@ private > SpecT createSpec( public T createBean(RegisteredType type, RegisteredTypeLoadingContext constraint, Class optionalResultSuperType) { Preconditions.checkNotNull(type, "type"); if (type.getKind()!=RegisteredTypeKind.BEAN) { - throw new IllegalStateException("Cannot create bean from type "+type+" (kind "+type.getKind()+")"); + if (type.getKind()==RegisteredTypeKind.UNRESOLVED) throw new ReferencedUnresolvedTypeException(type); + else throw new UnsupportedTypePlanException("Cannot create bean from type "+type+" (kind "+type.getKind()+")"); } if (constraint!=null) { if (constraint.getExpectedKind()!=null && constraint.getExpectedKind()!=RegisteredTypeKind.SPEC) { - throw new IllegalStateException("Cannot create spec with constraint "+constraint); + throw new IllegalStateException("Cannot create bean with constraint "+constraint); } if (constraint.getAlreadyEncounteredTypes().contains(type.getSymbolicName())) { // avoid recursive cycle @@ -281,15 +282,12 @@ public T createBeanFromPlan(String planFormat, Object planData, RegisteredTy @Override public T create(RegisteredType type, RegisteredTypeLoadingContext constraint, Class optionalResultSuperType) { Preconditions.checkNotNull(type, "type"); - if (type.getKind()==RegisteredTypeKind.BEAN) { - return createBean(type, constraint, optionalResultSuperType); - } - if (type.getKind()==RegisteredTypeKind.SPEC) { + return new RegisteredTypeKindVisitor() { + @Override protected T visitBean() { return createBean(type, constraint, optionalResultSuperType); } @SuppressWarnings({ "unchecked", "rawtypes" }) - T result = (T) createSpec(type, constraint, (Class)optionalResultSuperType); - return result; - } - throw new IllegalArgumentException("Kind-agnostic create method can only be used when the registered type declares its kind, which "+type+" does not"); + @Override protected T visitSpec() { return (T) createSpec(type, constraint, (Class)optionalResultSuperType); } + @Override protected T visitUnresolved() { throw new IllegalArgumentException("Kind-agnostic create method can only be used when the registered type declares its kind, which "+type+" does not"); } + }.visit(type.getKind()); } @Override @@ -317,30 +315,39 @@ public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canFo log.debug("Inserting "+type+" into "+this); localRegisteredTypes.put(type.getId(), type); } else { - if (oldType == type) { - // ignore if same instance - // (equals not yet implemented, so would be the same, but misleading) + if (sameTypeAndPlan(oldType, type)) { + // ignore if same type and plan; other things can be changed while we sort out replacements etc return; } throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present"); } } + private boolean sameTypeAndPlan(RegisteredType oldType, RegisteredType type) { + if (!oldType.getVersionedName().equals(type.getVersionedName())) return false; + if (!oldType.getPlan().equals(type.getPlan())) return false; + return true; + } + @Beta // API stabilising - public void delete(RegisteredType type) { - if (localRegisteredTypes.remove(type.getId()) != null) { + public void delete(VersionedName type) { + if (localRegisteredTypes.remove(type.toString()) != null) { return ; } - mgmt.getCatalog().deleteCatalogItem(type.getSymbolicName(), type.getVersion()); + // TODO may need to support version-less here? + + // legacy deletion (may call back to us, but max once) + mgmt.getCatalog().deleteCatalogItem(type.getSymbolicName(), type.getVersionString()); + // if nothing deleted, throw NoSuchElement + } + + public void delete(RegisteredType type) { + delete(type.getVersionedName()); } @Beta // API stabilising public void delete(String id) { - if (localRegisteredTypes.remove(id) != null) { - return ; - } - VersionedName vn = VersionedName.fromString(id); - mgmt.getCatalog().deleteCatalogItem(vn.getSymbolicName(), vn.getVersionString()); + delete(VersionedName.fromString(id)); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java index f345ec9625..2edd49fa92 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java @@ -29,8 +29,10 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.osgi.VersionedName; import com.google.common.annotations.Beta; +import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; /** Instances are usually created by methods in {@link RegisteredTypes}. */ @@ -84,6 +86,11 @@ public String getVersion() { return version; } + @Override + public VersionedName getVersionedName() { + return new VersionedName(getSymbolicName(), getVersion()); + } + @Override public String getContainingBundle() { return containingBundle; @@ -153,4 +160,49 @@ public String toString() { (getPlan()!=null ? ";"+getPlan().getPlanFormat() : "")+ "]"; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((aliases == null) ? 0 : aliases.hashCode()); + result = prime * result + ((bundles == null) ? 0 : bundles.hashCode()); + result = prime * result + ((containingBundle == null) ? 0 : containingBundle.hashCode()); + result = prime * result + (deprecated ? 1231 : 1237); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + (disabled ? 1231 : 1237); + result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); + result = prime * result + ((iconUrl == null) ? 0 : iconUrl.hashCode()); + result = prime * result + ((implementationPlan == null) ? 0 : implementationPlan.hashCode()); + result = prime * result + ((kind == null) ? 0 : kind.hashCode()); + result = prime * result + ((superTypes == null) ? 0 : superTypes.hashCode()); + result = prime * result + ((symbolicName == null) ? 0 : symbolicName.hashCode()); + result = prime * result + ((tags == null) ? 0 : tags.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + BasicRegisteredType other = (BasicRegisteredType) obj; + if (!Objects.equal(aliases, other.aliases)) return false; + if (!Objects.equal(bundles, other.bundles)) return false; + if (!Objects.equal(containingBundle, other.containingBundle)) return false; + if (!Objects.equal(deprecated, other.deprecated)) return false; + if (!Objects.equal(disabled, other.disabled)) return false; + if (!Objects.equal(iconUrl, other.iconUrl)) return false; + if (!Objects.equal(implementationPlan, other.implementationPlan)) return false; + if (!Objects.equal(kind, other.kind)) return false; + if (!Objects.equal(superTypes, other.superTypes)) return false; + if (!Objects.equal(symbolicName, other.symbolicName)) return false; + if (!Objects.equal(tags, other.tags)) return false; + if (!Objects.equal(version, other.version)) return false; + + return true; + } + + } \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java index 7647323945..206698fb5c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicTypeImplementationPlan.java @@ -20,6 +20,8 @@ import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; +import com.google.common.base.Objects; + public class BasicTypeImplementationPlan implements TypeImplementationPlan { final String format; final Object data; @@ -38,4 +40,25 @@ public String getPlanFormat() { public Object getPlanData() { return data; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + result = prime * result + ((format == null) ? 0 : format.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + BasicTypeImplementationPlan other = (BasicTypeImplementationPlan) obj; + if (!Objects.equal(format, other.format)) return false; + if (!Objects.equal(data, other.data)) return false; + return true; + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java b/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java new file mode 100644 index 0000000000..c82ed9b064 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/ReferencedUnresolvedTypeException.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.core.typereg; + +import org.apache.brooklyn.api.typereg.RegisteredType; + +/** Indicates a type has requested to resolve another type which is not resolved or not resolvable */ +public class ReferencedUnresolvedTypeException extends UnsupportedTypePlanException { + + private static final long serialVersionUID = -5590108442839125317L; + + public ReferencedUnresolvedTypeException(String message, Throwable cause) { + super(message, cause); + } + + public ReferencedUnresolvedTypeException(String message) { + super(message); + } + + public ReferencedUnresolvedTypeException(RegisteredType t) { + this("Reference to "+t.getVersionedName()+" in plan but that type cannot be resolved (recursive plan or premature evaluation?)"); + } + + public ReferencedUnresolvedTypeException(Throwable cause) { + super(cause); + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java index 6f781fabd7..457828f700 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeKindVisitor.java @@ -31,6 +31,7 @@ public T visit(RegisteredTypeKind kind) { switch (kind) { case SPEC: return visitSpec(); case BEAN: return visitBean(); + case UNRESOLVED: return visitUnresolved(); default: throw new IllegalStateException("Unexpected registered type kind: "+kind); } @@ -42,4 +43,6 @@ protected T visitNull() { protected abstract T visitSpec(); protected abstract T visitBean(); + protected abstract T visitUnresolved(); + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java index 1d67638beb..ce54fef10b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java @@ -26,6 +26,7 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; @@ -260,8 +261,11 @@ public boolean apply(@Nullable RegisteredType item) { public static Predicate containingBundle(VersionedName versionedName) { return new ContainingBundle(versionedName); } + public static Predicate containingBundle(OsgiBundleWithUrl bundle) { + return containingBundle(bundle.getVersionedName()); + } public static Predicate containingBundle(String versionedName) { - return new ContainingBundle(VersionedName.fromString(versionedName)); + return containingBundle(VersionedName.fromString(versionedName)); } private static class ContainingBundle implements Predicate { private final VersionedName bundle; diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index e3a8540e9f..2f205eb4da 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -19,10 +19,8 @@ package org.apache.brooklyn.core.typereg; import java.lang.reflect.Method; -import java.util.Collection; import java.util.Comparator; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Set; @@ -30,13 +28,13 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; @@ -62,6 +60,7 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.reflect.TypeToken; @@ -144,19 +143,36 @@ public static RegisteredType spec(@Nonnull String symbolicName, @Nonnull String return addSuperType(spec(symbolicName, version, plan), superType); } public static RegisteredType newInstance(@Nonnull RegisteredTypeKind kind, @Nonnull String symbolicName, @Nonnull String version, - @Nonnull TypeImplementationPlan plan, @Nonnull List> superTypes, - ManagedBundle containingBundle, Collection libraryBundles, - String displayName, String description, String catalogIconUrl, boolean catalogDeprecated) { + @Nonnull TypeImplementationPlan plan, @Nonnull Iterable superTypes, + Iterable aliases, Iterable tags, + String containingBundle, Iterable libraryBundles, + String displayName, String description, String catalogIconUrl, + Boolean catalogDeprecated, Boolean catalogDisabled) { BasicRegisteredType result = new BasicRegisteredType(kind, symbolicName, version, plan); addSuperTypes(result, superTypes); - result.containingBundle = containingBundle.getVersionedName().toString(); - result.bundles.addAll(libraryBundles); + addAliases(result, aliases); + addTags(result, tags); + result.containingBundle = containingBundle; + Iterables.addAll(result.bundles, libraryBundles); result.displayName = displayName; result.description = description; result.iconUrl = catalogIconUrl; - result.deprecated = catalogDeprecated; + if (catalogDeprecated!=null) result.deprecated = catalogDeprecated; + if (catalogDisabled!=null) result.disabled = catalogDisabled; return result; } + public static RegisteredType copy(RegisteredType t) { + return copyResolved(t.getKind(), t); + } + @Beta + public static RegisteredType copyResolved(RegisteredTypeKind kind, RegisteredType t) { + if (t.getKind()!=null && t.getKind()!=RegisteredTypeKind.UNRESOLVED && t.getKind()!=kind) { + throw new IllegalStateException("Cannot copy resolve "+t+" ("+t.getKind()+") as "+kind); + } + return newInstance(kind, t.getSymbolicName(), t.getVersion(), t.getPlan(), + t.getSuperTypes(), t.getAliases(), t.getTags(), t.getContainingBundle(), t.getLibraries(), + t.getDisplayName(), t.getDescription(), t.getIconUrl(), t.isDeprecated(), t.isDisabled()); + } /** Creates an anonymous {@link RegisteredType} for plan-instantiation-only use. */ @Beta @@ -263,6 +279,7 @@ public static RegisteredType addTags(RegisteredType type, Iterable tags) { public static String getImplementationDataStringForSpec(RegisteredType item) { if (item==null || item.getPlan()==null) return null; Object data = item.getPlan().getPlanData(); + if (data==null) throw new IllegalStateException("No plan data for "+item); if (!(data instanceof String)) throw new IllegalStateException("Expected plan data for "+item+" to be a string"); return (String)data; } @@ -419,6 +436,10 @@ protected Maybe visitSpec() { protected Maybe visitBean() { return tryValidateBean(object, type, context); } + + protected Maybe visitUnresolved() { + return Maybe.absent(object+" is not yet resolved"); + } }.visit(kind); } @@ -499,4 +520,9 @@ public static String getIconUrl(BrooklynObject object) { return item.getIconUrl(); } + public static RegisteredType changePlan(RegisteredType type, TypeImplementationPlan plan) { + ((BasicRegisteredType)type).implementationPlan = plan; + return type; + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java b/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java index 98cbd7a8c6..696a5c2dbc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/UnsupportedTypePlanException.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.typereg; +/** Used by {@link BrooklynTypePlanTransformer} for a transformer to indicate an error resolving a plan. */ +// TODO add another hierarchy RecognizedTypePlanException or a score to indicate priority of errors for reporting? public class UnsupportedTypePlanException extends RuntimeException { private static final long serialVersionUID = -5590108442839125317L; diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java b/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java index 6f2f573dc0..1c78684b86 100644 --- a/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistryTest.java @@ -63,15 +63,16 @@ public void testAddAndGet() { @Test public void testCantAddSameIdUnlessSameInstanceOrForced() { add(SAMPLE_TYPE); - RegisteredType sampleTypeClone = RegisteredTypes.bean("item.A", "1", new BasicTypeImplementationPlan("ignore", null), String.class); - add(sampleTypeClone, true); - Assert.assertNotEquals( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); + RegisteredType sampleTypeDifferent = RegisteredTypes.bean("item.A", "1", new BasicTypeImplementationPlan("ignore2", null), String.class); + add(sampleTypeDifferent, true); + Assert.assertSame( registry().get(SAMPLE_TYPE.getId()), sampleTypeDifferent ); + Assert.assertNotSame( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); add(SAMPLE_TYPE, true); - Assert.assertEquals( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); + Assert.assertSame( registry().get(SAMPLE_TYPE.getId()), SAMPLE_TYPE ); try { - add(sampleTypeClone); + add(sampleTypeDifferent); Asserts.shouldHaveFailedPreviously(); } catch (Exception e) { Asserts.expectedFailureContains(e, SAMPLE_TYPE.getSymbolicName()); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 3b41cf6587..54870722ea 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -448,30 +447,30 @@ public String get() { public Throwable getOriginal() { return cause; } } - public static RuntimeException propagate(Collection exceptions) { + public static RuntimeException propagate(Iterable exceptions) { throw propagate(create(exceptions)); } - public static RuntimeException propagate(String prefix, Collection exceptions) { + public static RuntimeException propagate(String prefix, Iterable exceptions) { throw propagate(create(prefix, exceptions)); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ - public static Throwable create(Collection exceptions) { + public static Throwable create(Iterable exceptions) { return create(null, exceptions); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ - public static RuntimeException create(@Nullable String prefix, Collection exceptions) { - if (exceptions.size()==1) { + public static RuntimeException create(@Nullable String prefix, Iterable exceptions) { + if (Iterables.size(exceptions)==1) { Throwable e = exceptions.iterator().next(); if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e); return new PropagatedRuntimeException(prefix + ": " + Exceptions.collapseText(e), e); } - if (exceptions.isEmpty()) { + if (Iterables.isEmpty(exceptions)) { if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions); return new CompoundRuntimeException(prefix, exceptions); } - if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); - return new CompoundRuntimeException(prefix+"; "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); + if (Strings.isBlank(prefix)) return new CompoundRuntimeException(Iterables.size(exceptions)+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); + return new CompoundRuntimeException(prefix+"; "+Iterables.size(exceptions)+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); } /** Some throwables require a prefix for the message to make sense, From bf31100c2969a848a45d24452baee78d15a7e37a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Jun 2017 01:14:32 +0100 Subject: [PATCH 13/33] roughly working to validate types in a separate pass, fixing the big problem quite a few holes to fill, but confirmed working in a few places. also makes sure not to detect and delete empty wrapper bundles until we've finished adding them. --- .../mementos/BrooklynMementoPersister.java | 1 + ...CatalogYamlEntityOsgiTypeRegistryTest.java | 7 +- .../catalog/CatalogYamlRebindTest.java | 54 ++++++++-------- .../brooklyn/test/lite/CampYamlLiteTest.java | 2 +- .../internal/BasicBrooklynCatalog.java | 20 +++--- .../catalog/internal/CatalogBundleLoader.java | 62 +++++++++++++++--- .../internal/CatalogBundleTracker.java | 25 +++----- .../core/catalog/internal/CatalogUtils.java | 2 +- .../core/mgmt/ha/OsgiArchiveInstaller.java | 20 ++++-- .../mgmt/ha/OsgiBundleInstallationResult.java | 7 +- .../brooklyn/core/mgmt/ha/OsgiManager.java | 49 ++++++++++++-- .../core/mgmt/rebind/RebindContextImpl.java | 7 +- .../rebind/RebindContextLookupContext.java | 7 +- .../core/mgmt/rebind/RebindIteration.java | 64 ++++++++++++++++--- .../RegisteredTypeLoadingContexts.java | 3 +- 15 files changed, 230 insertions(+), 100 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java index e3522004ff..4bd013e438 100644 --- a/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java +++ b/api/src/main/java/org/apache/brooklyn/api/mgmt/rebind/mementos/BrooklynMementoPersister.java @@ -38,6 +38,7 @@ import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.Feed; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.util.time.Duration; import com.google.common.annotations.Beta; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java index 78df657330..cde51598bb 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java @@ -58,9 +58,10 @@ protected void addCatalogItems(String catalogYaml) { new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); ReferenceWithError b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installDeferredStart( new BasicManagedBundle(bundleName(), bundleVersion(), null), - new FileInputStream(bf)); - // bundle not started (no need), and BOM not installed above; do it explicitly below - // testing the type registry approach instead + new FileInputStream(bf), + false); + // bundle not started (no need), and BOM not installed nor validated above; + // do BOM install and validation below manually to test the type registry approach mgmt().getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), isForceUpdate()); Map> validation = mgmt().getCatalog().validateTypes( mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b.get().getVersionedName())) ); if (!validation.isEmpty()) { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index d92c2b8813..a962dbee61 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -118,33 +118,33 @@ protected boolean useOsgi() { public Object[][] dataProvider() { return new Object[][] { {RebindWithCatalogTestMode.NO_OP, OsgiMode.NONE}, -// {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, -// {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, -// -// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, -// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, -// {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, -// -// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, -// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, -// {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, -// -// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, -// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, -// {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, -// -// // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. -// // Deletes the catalog item before rebind, but the referenced types are still on the -// // default classpath. Will fallback to loading from classpath. -// // -// // Does not work for OSGi, because our bundle will no longer be available. -// {RebindWithCatalogTestMode.DELETE_CATALOG, OsgiMode.NONE}, -// -// // Upgrades the catalog item before rebind, deleting the old version. -// // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" -// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, -// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, -// {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, + {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, + {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, + + {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, + {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, + {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, + + {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, + {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, + {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, + + {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, + {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, + {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, + + // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. + // Deletes the catalog item before rebind, but the referenced types are still on the + // default classpath. Will fallback to loading from classpath. + // + // Does not work for OSGi, because our bundle will no longer be available. + {RebindWithCatalogTestMode.DELETE_CATALOG, OsgiMode.NONE}, + + // Upgrades the catalog item before rebind, deleting the old version. + // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" + {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, + {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, + {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, }; } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java index 7e6f1760a5..07a80984fa 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java @@ -214,7 +214,7 @@ public static void installWithoutCatalogBom(ManagementContext mgmt, String bundl // install bundle for class access but without loading its catalog.bom, // since we only have mock matchers here // (if we don't do this, the default routines install it and try to process the catalog.bom, failing) - ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null).get(); + ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(new BasicManagedBundle(null, null, bundleUrl), null, false).get(); } private void assertMgmtHasSampleMyCatalogApp(String symbolicName, String bundleUrl) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 4fc78a0934..323c70fac8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -140,7 +140,7 @@ public static void setLoader(BrooklynClassLoadingContext val) { loader.set(val); } - // TODO Stack, for recursive calls? + // If needed, could use stack; see ClassLoaderFromStack... public static void unsetLoader(BrooklynClassLoadingContext val) { loader.set(null); } @@ -931,7 +931,8 @@ private static boolean isLibrariesMoreThanJustContainingBundle(Collection osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); if (osgi.isAbsent()) { @@ -988,7 +989,7 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode()); List urls = MutableList.of(); for (OsgiBundleWithUrl b: libraries) { - // TODO currently does not support pre-installed bundles identified by name:version + // does not support pre-installed bundles identified by name:version // (ie where URL not supplied) if (Strings.isNonBlank(b.getUrl())) { urls.add(b.getUrl()); @@ -1358,13 +1359,9 @@ public List> addItems(String yaml, boolean forceUpdat throw new IllegalStateException(result.getMessage()); } return toItems(result.getCatalogItemsInstalled()); - - // TODO check if we've overridden all items pertaining to an older anonymous catalog.bom bundle - // we could remove references to that anonymous bundle; - // without this currently we leak bundles as bom's are replaced - // (because we persist each item as well as the bundle, and we use the item XML on rebind, - // rather than rereading the catalog.bom from the bundle, there isn't currently a risk of loading - // any of those overwritten items; however probably wise in future to require a bundle ID) + + // if all items pertaining to an older anonymous catalog.bom bundle have been overridden + // we delete those later; see list of wrapper bundles kept in OsgiManager } // fallback to non-OSGi for tests and other environments return addItems(yaml, null, forceUpdate); @@ -1374,7 +1371,6 @@ public List> addItems(String yaml, boolean forceUpdat private List> toItems(Iterable itemIds) { List> result = MutableList.of(); for (String id: itemIds) { - // TODO prefer to use RegisteredType, but that's an API change here result.add(CatalogUtils.getCatalogItemOptionalVersion(mgmt, id)); } return result; @@ -1812,7 +1808,7 @@ public void uninstallEmptyWrapperBundles() { synchronized (uninstallingEmptyLock) { Maybe osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); if (osgi.isAbsent()) return; - for (ManagedBundle b: osgi.get().getManagedBundles().values()) { + for (ManagedBundle b: osgi.get().getInstalledWrapperBundles()) { if (isNoBundleOrSimpleWrappingBundle(mgmt, b)) { Iterable typesInBundle = osgi.get().getTypesFromBundle(b.getVersionedName()); if (Iterables.isEmpty(typesInBundle)) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index f42dac12a6..bd9c7fae70 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -24,12 +24,16 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Collection; import java.util.List; +import java.util.Map; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.osgi.VersionedName; @@ -41,6 +45,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; @Beta public class CatalogBundleLoader { @@ -56,6 +61,10 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo this.managementContext = managementContext; } + public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { + scanForCatalogInternal(bundle, force, validate, false); + } + /** * Scan the given bundle for a catalog.bom and adds it to the catalog. * @@ -63,11 +72,15 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo * @return A list of items added to the catalog * @throws RuntimeException if the catalog items failed to be added to the catalog */ - public Iterable> scanForCatalog(Bundle bundle) { - return scanForCatalog(bundle, false); + public Iterable> scanForCatalogLegacy(Bundle bundle) { + return scanForCatalogLegacy(bundle, false); + } + + public Iterable> scanForCatalogLegacy(Bundle bundle, boolean force) { + return scanForCatalogInternal(bundle, force, true, true); } - public Iterable> scanForCatalog(Bundle bundle, boolean force) { + private Iterable> scanForCatalogInternal(Bundle bundle, boolean force, boolean validate, boolean legacy) { ManagedBundle mb = ((ManagementContextInternal)managementContext).getOsgiManager().get().getManagedBundle( new VersionedName(bundle)); @@ -77,23 +90,50 @@ public CatalogBundleLoader(Predicate applicationsPermitted, ManagementCo if (null != bom) { LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); - // TODO use addTypesFromBundleBom; but when should we do validation? after all bundles are loaded? - // OR maybe deprecate/remove this experiment in favour of explicitly installed and managed bundles? - catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); - for (CatalogItem item : catalogItems) { - LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); + if (legacy) { + catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); + for (CatalogItem item : catalogItems) { + LOG.debug("Added to catalog: {}, {}", item.getSymbolicName(), item.getVersion()); + } + } else { + this.managementContext.getCatalog().addTypesFromBundleBom(bomText, mb, force); + if (validate) { + Map> validationErrors = this.managementContext.getCatalog().validateTypes( + this.managementContext.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(mb.getVersionedName())) ); + if (!validationErrors.isEmpty()) { + throw Exceptions.propagate("Failed to install "+mb.getVersionedName()+", types "+validationErrors.keySet()+" gave errors", + Iterables.concat(validationErrors.values())); + } + } + } + + if (BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(managementContext, mb)) { + ((ManagementContextInternal)managementContext).getOsgiManager().get().addInstalledWrapperBundle(mb); } } else { LOG.debug("No BOM found in {} {} {}", CatalogUtils.bundleIds(bundle)); } if (!applicationsPermitted.apply(bundle)) { - catalogItems = removeApplications(catalogItems); + if (legacy) { + catalogItems = removeApplications(catalogItems); + } else { + removeApplications(mb); + } } return catalogItems; } + private void removeApplications(ManagedBundle mb) { + for (RegisteredType t: managementContext.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(mb.getVersionedName()))) { + // TODO support templates, and remove them here +// if (t.getKind() == RegisteredTypeKind.TEMPLATE) { +// ((BasicBrooklynTypeRegistry) managementContext.getTypeRegistry()).delete(t); +// } + } + } + /** * Remove the given items from the catalog. * @@ -109,6 +149,10 @@ public void removeFromCatalog(CatalogItem item) { }, " "), e); } } + + public void removeFromCatalog(VersionedName n) { + ((ManagementContextInternal)managementContext).getOsgiManager().get().uninstallCatalogItemsFromBundle(n); + } private String readBom(URL bom) { try (final InputStream ins = bom.openStream()) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java index 074d7f7352..1ec4cd2dfc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java @@ -19,7 +19,7 @@ package org.apache.brooklyn.core.catalog.internal; -import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.util.osgi.VersionedName; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; @@ -30,7 +30,7 @@ import com.google.common.annotations.Beta; @Beta -public class CatalogBundleTracker extends BundleTracker>> { +public class CatalogBundleTracker extends BundleTracker { private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleTracker.class); @@ -46,13 +46,13 @@ public CatalogBundleTracker(BundleContext bundleContext, CatalogBundleLoader cat * * @param bundle The bundle being added to the bundle context. * @param bundleEvent The event of the addition. - * @return The items added to the catalog; these will be tracked by the {@link BundleTracker} mechanism - * and supplied to the {@link #removedBundle(Bundle, BundleEvent, Iterable)} method. + * @return null * @throws RuntimeException if the catalog items failed to be added to the catalog */ @Override - public Iterable> addingBundle(Bundle bundle, BundleEvent bundleEvent) { - return catalogBundleLoader.scanForCatalog(bundle); + public Object addingBundle(Bundle bundle, BundleEvent bundleEvent) { + catalogBundleLoader.scanForCatalog(bundle, false, true); + return null; } /** @@ -60,19 +60,12 @@ public CatalogBundleTracker(BundleContext bundleContext, CatalogBundleLoader cat * * @param bundle The bundle being removed to the bundle context. * @param bundleEvent The event of the removal. - * @param items The items being removed + * @param callback Ignored * @throws RuntimeException if the catalog items failed to be added to the catalog */ @Override - public void removedBundle(Bundle bundle, BundleEvent bundleEvent, Iterable> items) { - if (!items.iterator().hasNext()) { - return; - } + public void removedBundle(Bundle bundle, BundleEvent bundleEvent, Object callback) { LOG.debug("Unloading catalog BOM entries from {} {} {}", CatalogUtils.bundleIds(bundle)); - for (CatalogItem item : items) { - LOG.debug("Unloading {} {} from catalog", item.getSymbolicName(), item.getVersion()); - - catalogBundleLoader.removeFromCatalog(item); - } + catalogBundleLoader.removeFromCatalog(new VersionedName(bundle)); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index bc744a0980..473fa6b6c8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -175,7 +175,7 @@ public static void installLibraries(ManagementContext managementContext, @Nullab Stopwatch timer = Stopwatch.createStarted(); List results = MutableList.of(); for (CatalogBundle bundleUrl : libraries) { - OsgiBundleInstallationResult result = osgi.get().installDeferredStart(BasicManagedBundle.of(bundleUrl), null).get(); + OsgiBundleInstallationResult result = osgi.get().installDeferredStart(BasicManagedBundle.of(bundleUrl), null, true).get(); if (log.isDebugEnabled()) { logDebugOrTraceIfRebinding(log, "Installation of library "+bundleUrl+": "+result); } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 1497b42266..a634230373 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -23,18 +23,18 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.BasicManagedBundle; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.osgi.BundleMaker; @@ -70,6 +70,7 @@ class OsgiArchiveInstaller { private boolean loadCatalogBom = true; private boolean force = false; private boolean deferredStart = false; + private boolean validateTypes = true; private File zipFile; private Manifest discoveredManifest; @@ -100,7 +101,11 @@ public void setForce(boolean force) { public void setDeferredStart(boolean deferredStart) { this.deferredStart = deferredStart; - } + } + + public void setValidateTypes(boolean validateTypes) { + this.validateTypes = validateTypes; + } private ManagementContextInternal mgmt() { return (ManagementContextInternal) osgiManager.mgmt; @@ -318,7 +323,7 @@ public ReferenceWithError install() { // if it's non-brooklyn installed then fail // (e.g. someone trying to install brooklyn or guice through this mechanism!) result.bundle = b.get(); - result.code = OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE; + result.code = OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_BUNDLE; throw new IllegalStateException("Bundle "+result.getMetadata().getVersionedName()+" already installed in framework but not managed by Brooklyn; cannot install or update through Brooklyn"); } // normal install @@ -379,9 +384,10 @@ public void run() { osgiManager.uninstallCatalogItemsFromBundle( result.getVersionedName() ); // (ideally removal and addition would be atomic) } - List> items = osgiManager.loadCatalogBom(result.bundle, force); + osgiManager.loadCatalogBom(result.bundle, force, validateTypes); + Iterable items = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(result.getMetadata())); log.debug("Adding items from bundle "+result.getVersionedName()+": "+items); - for (CatalogItem ci: items) { + for (RegisteredType ci: items) { result.catalogItemsInstalled.add(ci.getId()); } } @@ -407,7 +413,7 @@ public void run() { } catch (Exception e) { Exceptions.propagateIfFatal(e); - result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_INSTALLING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE; + result.code = startedInstallation ? OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_BUNDLE : OsgiBundleInstallationResult.ResultCode.ERROR_PREPARING_BUNDLE; result.message = "Bundle "+inferredMetadata+" failed "+ (startedInstallation ? "installation" : "preparation") + ": " + Exceptions.collapseText(e); return ReferenceWithError.newInstanceThrowingError(result, new IllegalStateException(result.message, e)); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java index 947de1c3a6..c611553241 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java @@ -45,9 +45,10 @@ public enum ResultCode { IGNORING_BUNDLE_AREADY_INSTALLED(false), /** bundle could not be made insto a state where it could be installed; bundle is not installed, even if forced */ ERROR_PREPARING_BUNDLE(true), - /** bundle could be installed as OSGi but there was an item later, such as catalog items loading; - * bundle may be installed */ // TODO behaviour TBC - ERROR_INSTALLING_BUNDLE(true); + /** bundle successfully installed to OSGi container but there was an error launching it, + * either the OSGi bundle start, catalog items load, or (most commonly) validating the catalog items; + * bundle may be installed (currently it is in most/all places, but behaviour TBC) so caller may have to uninstall it */ + ERROR_LAUNCHING_BUNDLE(true); final boolean isError; ResultCode(boolean isError) { this.isError = isError; } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index 6fe1068ffd..9b92a8cb47 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.URL; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; @@ -44,6 +45,7 @@ import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.catalog.internal.CatalogBundleLoader; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; @@ -104,6 +106,7 @@ public class OsgiManager { private Set bundlesAtStartup; private File osgiCacheDir; final ManagedBundlesRecord managedBundlesRecord = new ManagedBundlesRecord(); + final Map wrapperBundles = MutableMap.of(); static class ManagedBundlesRecord { private Map managedBundlesByUid = MutableMap.of(); @@ -277,9 +280,10 @@ public ReferenceWithError install(InputStream zipI } /** See {@link OsgiArchiveInstaller#install()}, but deferring the start and catalog load */ - public ReferenceWithError installDeferredStart(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn) { + public ReferenceWithError installDeferredStart(@Nullable ManagedBundle knownBundleMetadata, @Nullable InputStream zipIn, boolean validateTypes) { OsgiArchiveInstaller installer = new OsgiArchiveInstaller(this, knownBundleMetadata, zipIn); installer.setDeferredStart(true); + installer.setValidateTypes(validateTypes); return installer.install(); } @@ -318,6 +322,7 @@ public void uninstallUploadedBundle(ManagedBundle bundleMetadata) { } managedBundlesRecord.managedBundlesUidByVersionedName.remove(bundleMetadata.getVersionedName()); managedBundlesRecord.managedBundlesUidByUrl.remove(bundleMetadata.getUrl()); + removeInstalledWrapperBundle(bundleMetadata); } mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata); @@ -373,16 +378,22 @@ public synchronized Bundle registerBundle(CatalogBundle bundleMetadata) { // it probably works even if that is true, but we should consider what to do; // possibly remove that other capability, so that bundles with BOMs _have_ to be installed via this method. // (load order gets confusing with auto-scanning...) - public List> loadCatalogBom(Bundle bundle) { - return loadCatalogBom(bundle, false); + public List> loadCatalogBomLegacy(Bundle bundle) { + return loadCatalogBomLegacy(bundle, false); } @Beta // as above - public List> loadCatalogBom(Bundle bundle, boolean force) { - return MutableList.copyOf(loadCatalogBom(mgmt, bundle, force)); + public List> loadCatalogBomLegacy(Bundle bundle, boolean force) { + return MutableList.copyOf(loadCatalogBomInternal(mgmt, bundle, force, true, true)); } - private static Iterable> loadCatalogBom(ManagementContext mgmt, Bundle bundle, boolean force) { + // since 0.12.0 no longer returns items; it installs non-persisted RegisteredTypes to the type registry instead + @Beta + public void loadCatalogBom(Bundle bundle, boolean force, boolean validate) { + loadCatalogBomInternal(mgmt, bundle, force, validate, false); + } + + private static Iterable> loadCatalogBomInternal(ManagementContext mgmt, Bundle bundle, boolean force, boolean validate, boolean legacy) { Iterable> catalogItems = MutableList.of(); if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM)) { // if the above feature is not enabled, let's do it manually (as a contract of this method) @@ -392,7 +403,13 @@ public List> loadCatalogBom(Bundle bundle, boolean fo // here to get back the predicate from it. final Predicate applicationsPermitted = Predicates.alwaysTrue(); - catalogItems = new CatalogBundleLoader(applicationsPermitted, mgmt).scanForCatalog(bundle, force); + CatalogBundleLoader cl = new CatalogBundleLoader(applicationsPermitted, mgmt); + if (legacy) { + catalogItems = cl.scanForCatalogLegacy(bundle, force); + } else { + cl.scanForCatalog(bundle, force, validate); + catalogItems = null; + } } catch (RuntimeException ex) { // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; // caller needs to upgrade, or uninstall then reinstall @@ -603,4 +620,22 @@ public Iterable getResources(String name, Iterable getInstalledWrapperBundles() { + synchronized (wrapperBundles) { + return MutableSet.copyOf(wrapperBundles.values()); + } + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java index 6bea476c38..17f0349d70 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextImpl.java @@ -91,10 +91,11 @@ public void registerCatalogItem(String id, CatalogItem catalogItem) { catalogItems.put(id, catalogItem); } - // we don't track register/unregister of bundles; it isn't needed as it happens so early - // but we do need to know which ones to start subsequently + /** install the bundles into brooklyn and osgi, but do not start nor validate; + * caller (rebind) will do that manually, doing each step across all bundles before proceeding + * to prevent reference errors */ public OsgiBundleInstallationResult installBundle(ManagedBundle bundle, InputStream zipInput) { - return ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(bundle, zipInput).get(); + return ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart(bundle, zipInput, false).get(); } public void startBundle(OsgiBundleInstallationResult br) throws BundleException { if (br.getDeferredStart()!=null) { diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java index f463047226..c1ebc5eafb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindContextLookupContext.java @@ -116,14 +116,17 @@ public RebindContextLookupContext(ManagementContext managementContext, RebindCon @SuppressWarnings("deprecation") @Override + // only used for persisted xml catalog items; not used for registered types public CatalogItem lookupCatalogItem(String id) { CatalogItem result = rebindContext.getCatalogItem(id); if (result == null) { - // TODO-type-registry -// result = managementContext.getTypeRegistry().get(id, null, null); result = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id); } if (result == null) { + if (managementContext.getTypeRegistry().get(id)!=null) { + // don't treat as dangling; caller should now recognise null as meaning it's known in the type registry + return null; + } result = exceptionHandler.onDanglingCatalogItemRef(id); } return result; diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java index a80b14a00e..6ee3dc74f4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java @@ -100,13 +100,16 @@ import org.apache.brooklyn.core.policy.AbstractPolicy; import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; @@ -117,6 +120,7 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -337,13 +341,40 @@ protected void installBundlesAndRebuildCatalog() { } } // Start them all after we've installed them + Set installedTypes = MutableSet.of(); for (OsgiBundleInstallationResult br: installs) { try { rebindContext.startBundle(br); + Iterables.addAll(installedTypes, managementContext.getTypeRegistry().getMatching( + RegisteredTypePredicates.containingBundle(br.getVersionedName()))); } catch (Exception e) { exceptionHandler.onCreateFailed(BrooklynObjectType.MANAGED_BUNDLE, br.getMetadata().getId(), br.getMetadata().getSymbolicName(), e); } } + // Now validate all types + Map> validationErrors = this.managementContext.getCatalog().validateTypes( installedTypes ); + if (!validationErrors.isEmpty()) { + Map>> errorsByBundle = MutableMap.of(); + for (RegisteredType t: validationErrors.keySet()) { + VersionedName vn = VersionedName.fromString(t.getContainingBundle()); + Map> errorsInBundle = errorsByBundle.get(vn); + if (errorsInBundle==null) { + errorsInBundle = MutableMap.of(); + errorsByBundle.put(vn, errorsInBundle); + } + errorsInBundle.put(t, validationErrors.get(t)); + } + for (VersionedName vn: errorsByBundle.keySet()) { + Map> errorsInBundle = errorsByBundle.get(vn); + ManagedBundle b = managementContext.getOsgiManager().get().getManagedBundle(vn); + exceptionHandler.onCreateFailed(BrooklynObjectType.MANAGED_BUNDLE, + b!=null ? b.getId() : /* just in case it was uninstalled concurrently somehow */ vn.toString(), + vn.getSymbolicName(), + Exceptions.create("Failed to install "+vn+", types "+errorsInBundle.keySet()+" gave errors", + Iterables.concat(errorsInBundle.values()))); + } + } + } else { logRebindingDebug("Not rebinding bundles; feature disabled: {}", mementoManifest.getBundleIds()); } @@ -1021,20 +1052,37 @@ protected LoadedClass load(Class bTyp List reboundSearchPath = MutableList.of(); if (searchPath != null && !searchPath.isEmpty()) { for (String searchItemId : searchPath) { - CatalogItem searchItem = findCatalogItemInReboundCatalog(bType, searchItemId, contextSuchAsId); - if (searchItem != null) { - reboundSearchPath.add(searchItem.getCatalogItemId()); + String fixedSearchItemId = null; + RegisteredType t1 = managementContext.getTypeRegistry().get(searchItemId); + if (t1!=null) fixedSearchItemId = t1.getId(); + if (fixedSearchItemId==null) { + CatalogItem ci = findCatalogItemInReboundCatalog(bType, searchItemId, contextSuchAsId); + if (ci!=null) fixedSearchItemId = ci.getCatalogItemId(); + } + if (fixedSearchItemId != null) { + reboundSearchPath.add(fixedSearchItemId); } else { LOG.warn("Unable to load catalog item "+ searchItemId - +" for "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting load nevertheless"); + +" for search path of "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting load nevertheless"); } } } if (catalogItemId != null) { - CatalogItem catalogItem = findCatalogItemInReboundCatalog(bType, catalogItemId, contextSuchAsId); - if (catalogItem != null) { - String transformedCatalogItemId = catalogItem.getCatalogItemId(); + String transformedCatalogItemId = null; + + Maybe registeredType = managementContext.getTypeRegistry().getMaybe(catalogItemId, + // ignore bType; catalog item ID gives us the search path, but doesn't need to be of the requested type + null ); + if (registeredType.isPresent()) { + transformedCatalogItemId = registeredType.get().getId(); + } else { + CatalogItem catalogItem = findCatalogItemInReboundCatalog(bType, catalogItemId, contextSuchAsId); + if (catalogItem != null) { + transformedCatalogItemId = catalogItem.getCatalogItemId(); + } + } + if (transformedCatalogItemId!=null) { try { BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(managementContext); @@ -1046,7 +1094,7 @@ protected LoadedClass load(Class bTyp LOG.warn("Unable to load "+jType+" using loader; will try reflections"); } } else { - LOG.warn("Unable to load catalog item "+catalogItemId+" for " + contextSuchAsId + + LOG.warn("Unable to load catalog item "+catalogItemId+" ("+bType+") for " + contextSuchAsId + " ("+bType.getSimpleName()+"); will try reflection"); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java index 0368ba3c96..f6ee347afe 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java @@ -128,7 +128,8 @@ public static RegisteredTypeLoadingContext bean(Class javaSuperType) { public static RegisteredTypeLoadingContext spec(Class javaSuperType) { return of(RegisteredTypeKind.SPEC, javaSuperType); } - + + /** Adds the required supertype for beans, or supertype of the spec target (e.g. Entity not EntitySpec) for specs */ public static RegisteredTypeLoadingContext withBeanSuperType(@Nullable RegisteredTypeLoadingContext source, @Nullable Class beanSuperType) { Class superType = beanSuperType; BasicRegisteredTypeLoadingContext constraint = new BasicRegisteredTypeLoadingContext(source); From 02cfe50a2075f0049e4d6c8b526bed1ca5bef4e4 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Jun 2017 02:59:39 +0100 Subject: [PATCH 14/33] misc fixes related to type-reg / osgi, making the tests work some failing tests still: * still need to address TEMPLATEs in OSGi * deprecation / catalog.xml persistence --- .../camp/brooklyn/AbstractYamlTest.java | 39 ++++++++++- .../camp/brooklyn/ReferencedOsgiYamlTest.java | 9 +-- .../camp/brooklyn/ReferencedYamlOsgiTest.java | 54 +++++++++++++++ .../camp/brooklyn/ReferencedYamlTest.java | 10 +-- .../catalog/CatalogOsgiLibraryTest.java | 26 ++++++-- ...CatalogYamlEntityOsgiTypeRegistryTest.java | 46 +++---------- .../catalog/CatalogYamlEntityTest.java | 1 + .../core/BrooklynFeatureEnablement.java | 3 +- .../internal/BasicBrooklynCatalog.java | 27 +++++--- .../catalog/internal/CatalogBundleLoader.java | 7 +- .../core/catalog/internal/CatalogUtils.java | 65 ++++++++++++++----- .../typereg/BasicBrooklynTypeRegistry.java | 3 +- .../core/typereg/RegisteredTypes.java | 12 ++++ .../brooklyn/util/core/ClassLoaderUtils.java | 40 ++++++++---- .../brooklyn/util/core/LoaderDispatcher.java | 3 +- .../util/text/BrooklynVersionSyntax.java | 7 ++ .../brooklyn/util/text/VersionComparator.java | 6 +- 17 files changed, 257 insertions(+), 101 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java index 00b9d4efdd..0300fc0bea 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java @@ -18,9 +18,14 @@ */ package org.apache.brooklyn.camp.brooklyn; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.Reader; +import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.zip.ZipEntry; import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.entity.Application; @@ -30,19 +35,28 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; +import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests.Builder; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.stream.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -218,8 +232,31 @@ protected void addCatalogItems(String catalogYaml) { mgmt().getCatalog().addItems(catalogYaml, forceUpdate); } + public static void addCatalogItemsAsOsgi(ManagementContext mgmt, String catalogYaml, VersionedName bundleName, boolean force) { + try { + BundleMaker bundleMaker = new BundleMaker(mgmt); + File bf = bundleMaker.createTempZip("test", MutableMap.of( + new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); + ReferenceWithError b = ((ManagementContextInternal)mgmt).getOsgiManager().get().installDeferredStart( + new BasicManagedBundle(bundleName.getSymbolicName(), bundleName.getVersionString(), null), + new FileInputStream(bf), + false); + // bundle not started (no need), and BOM not installed nor validated above; + // do BOM install and validation below manually to test the type registry approach + mgmt.getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), force); + Map> validation = mgmt.getCatalog().validateTypes( + mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b.get().getVersionedName())) ); + if (!validation.isEmpty()) { + throw Exceptions.propagate("Brooklyn failed to load types: "+validation.keySet(), + Iterables.concat(validation.values())); + } + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + protected void deleteCatalogEntity(String catalogItem) { - mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION); + ((BasicBrooklynTypeRegistry) mgmt().getTypeRegistry()).delete(new VersionedName(catalogItem, TEST_VERSION)); } protected Logger getLogger() { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java index 4fc647d957..640e1c1cf7 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedOsgiYamlTest.java @@ -22,6 +22,7 @@ import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.entity.stock.BasicEntity; import org.apache.brooklyn.test.support.TestResourceUnavailableException; @@ -193,10 +194,10 @@ public void testCatalogReferenceMixesMetaAndUrl() throws Exception { " item:", " type: " + OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY); - BrooklynCatalog catalog = mgmt().getCatalog(); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.nested", BrooklynCatalog.DEFAULT_VERSION)); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.simple", BrooklynCatalog.DEFAULT_VERSION)); - Assert.assertNotNull(catalog.getCatalogItem("yaml.nested.catalog.more", BrooklynCatalog.DEFAULT_VERSION)); + BrooklynTypeRegistry catalog = mgmt().getTypeRegistry(); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.nested", BrooklynCatalog.DEFAULT_VERSION)); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.simple", BrooklynCatalog.DEFAULT_VERSION)); + Assert.assertNotNull(catalog.get("yaml.nested.catalog.more", BrooklynCatalog.DEFAULT_VERSION)); } protected void assertCatalogReference() throws Exception { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java new file mode 100644 index 0000000000..e46a34f229 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlOsgiTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn; + +import org.apache.brooklyn.util.osgi.VersionedName; +import org.testng.annotations.Test; + +/** like superclass, but with OSGi enabled, complex references now work */ +public class ReferencedYamlOsgiTest extends ReferencedYamlTest { + + @Override + protected boolean disableOsgi() { + return false; + } + + @Override + protected void addCatalogItems(String catalogYaml) { + addCatalogItemsAsOsgi(mgmt(), catalogYaml, VersionedName.fromString("sample-bundle:0-SNAPSHOT"), isForceUpdate()); + } + + // these are not broken with OSGi + + @Test + public void testYamlReferencingEarlierItemLongFormEntity() throws Exception { + super.testYamlReferencingEarlierItemLongFormEntity(); + } + + @Test + public void testYamlReferencingEarlierItemInUrl() throws Exception { + super.testYamlReferencingEarlierItemInUrl(); + } + + @Test + public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { + super.testYamlReferencingEarlierItemInUrlAsType(); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java index e22a935df8..2626106cef 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ReferencedYamlTest.java @@ -169,7 +169,7 @@ public void testYamlReferencingEarlierItemShortForm() throws Exception { checkChildEntitySpec(app, entityName); } - @Test // long form discouraged but references should now still work + @Test(groups="Broken") // long form discouraged but references should still work (but only in OSGi subclass) public void testYamlReferencingEarlierItemLongFormEntity() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -222,7 +222,7 @@ public void testYamlReferencingEarlierItemLongFormTemplate() throws Exception { checkChildEntitySpec(app, entityName); } - @Test // references to co-bundled items work even in nested url yaml + @Test(groups="Broken") // references to co-bundled items work even in nested url yaml (but only in OSGi subclass) public void testYamlReferencingEarlierItemInUrl() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -231,7 +231,7 @@ public void testYamlReferencingEarlierItemInUrl() throws Exception { " - id: yaml.basic", " version: " + TEST_VERSION, " item:", - " type: org.apache.brooklyn.entity.stock.BasicApplication", + " type: org.apache.brooklyn.entity.stock.BasicEntity", " - id: yaml.reference", " version: " + TEST_VERSION, " item: classpath://yaml-ref-catalog.yaml"); // this references yaml.basic above @@ -245,7 +245,7 @@ public void testYamlReferencingEarlierItemInUrl() throws Exception { checkChildEntitySpec(app, entityName); } - @Test // reference to co-bundled items work also in nested url yaml as a type + @Test(groups="Broken") // reference to co-bundled items work also in nested url yaml as a type (but only in OSGi subclass) public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { addCatalogItems( "brooklyn.catalog:", @@ -254,7 +254,7 @@ public void testYamlReferencingEarlierItemInUrlAsType() throws Exception { " - id: yaml.basic", " version: " + TEST_VERSION, " item:", - " type: org.apache.brooklyn.entity.stock.BasicApplication", + " type: org.apache.brooklyn.entity.stock.BasicEntity", " - id: yaml.reference", " version: " + TEST_VERSION, " item:", diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java index 87a459e2e3..bd0707505e 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java @@ -268,6 +268,15 @@ protected void assertCanFindMessages(Entity entity) { Assert.fail("Expected to find 'messages.txt'"); } } + + protected void assertCannotFindMessages(Entity entity) { + ResourceUtils ru = ResourceUtils.create(entity); + Iterable files = ru.getResources("org/apache/brooklyn/test/osgi/resources/message.txt"); + if (files.iterator().hasNext()) { + Entities.dumpInfo(entity); + Assert.fail("Expected NOT to find 'messages.txt'"); + } + } @Test public void testLibraryIsUsedByChildInCatalogItem() throws Exception { @@ -295,16 +304,19 @@ public void testLibraryIsUsedByChildInCatalogItem() throws Exception { // the spec has no catalog item ID except at the root Application Entity app = createAndStartApplication("services: [ { type: item-from-library } ]"); - Entity entity = app.getChildren().iterator().next(); - entity = entity.getChildren().iterator().next(); - Entities.dumpInfo(entity); + Entity entity1 = app.getChildren().iterator().next(); + + Entities.dumpInfo(app); - // TODO re-enable when we've converted to a search path; + Assert.assertEquals(entity1.getCatalogItemId(), "item-from-library:1.0"); + assertCanFindMessages( entity1 ); + + // TODO enable when we've converted to a search path; // currently this test method passes because of CatalogUtils.setCatalogItemIdOnAddition // but we don't want to be doing that, we only want the search path - //Assert.assertNull(entity.getCatalogItemId(), "Entity had a catalog item ID, even though it was stockj"); - - assertCanFindMessages( entity ); + //Entity entity2 = entity1.getChildren().iterator().next(); + //Assert.assertNull(entity2.getCatalogItemId(), "Entity had a catalog item ID, even though it was stockj"); + //assertCannotFindMessages( entity2 ); } @Test diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java index cde51598bb..ac11898513 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java @@ -18,25 +18,11 @@ */ package org.apache.brooklyn.camp.brooklyn.catalog; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.util.Collection; -import java.util.Map; -import java.util.zip.ZipEntry; - import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; -import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; -import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; -import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.entity.stock.BasicEntity; import org.apache.brooklyn.test.Asserts; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.core.osgi.BundleMaker; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.osgi.VersionedName; import org.testng.annotations.Test; @@ -52,29 +38,7 @@ public class CatalogYamlEntityOsgiTypeRegistryTest extends CatalogYamlEntityTest // use type registry appraoch @Override protected void addCatalogItems(String catalogYaml) { - try { - BundleMaker bundleMaker = new BundleMaker(mgmt()); - File bf = bundleMaker.createTempZip("test", MutableMap.of( - new ZipEntry(BasicBrooklynCatalog.CATALOG_BOM), new ByteArrayInputStream(catalogYaml.getBytes()))); - ReferenceWithError b = ((ManagementContextInternal)mgmt()).getOsgiManager().get().installDeferredStart( - new BasicManagedBundle(bundleName(), bundleVersion(), null), - new FileInputStream(bf), - false); - // bundle not started (no need), and BOM not installed nor validated above; - // do BOM install and validation below manually to test the type registry approach - mgmt().getCatalog().addTypesFromBundleBom(catalogYaml, b.get().getMetadata(), isForceUpdate()); - Map> validation = mgmt().getCatalog().validateTypes( mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b.get().getVersionedName())) ); - if (!validation.isEmpty()) { - throw Exceptions.propagate("Brooklyn failed to load types: "+validation.keySet(), - Iterables.concat(validation.values())); - } - } catch (Exception e) { - throw Exceptions.propagate(e); - } - } - - protected void deleteCatalogEntity(String catalogItem) { - mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION); + addCatalogItemsAsOsgi(mgmt(), catalogYaml, new VersionedName(bundleName(), bundleVersion()), isForceUpdate()); } protected String bundleName() { return "sample-bundle"; } @@ -91,6 +55,12 @@ public void testAddTypes() throws Exception { Asserts.assertEquals(item, Iterables.getOnlyElement(itemsInstalled), "Wrong item; installed: "+itemsInstalled); } - // many other tests from super now run, with the type registry approach instead of catalog item / catalog approach + @Test // test broken in super works here + // TODO" comment at https://issues.apache.org/jira/browse/BROOKLYN-343 + public void testSameCatalogReferences() { + super.testSameCatalogReferences(); + } + + // also runs many other tests from super, here using the osgi/type-registry appraoch } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java index d39a97e370..9d907d9083 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java @@ -552,6 +552,7 @@ public void testHardcodedCatalog() throws Exception { @Test(groups = "Broken") // See https://issues.apache.org/jira/browse/BROOKLYN-343 + // Fixed in OSGi subclass public void testSameCatalogReferences() { addCatalogItems( "brooklyn.catalog:", diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java index fe3609a901..2776d9b69d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java @@ -22,7 +22,6 @@ import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; import org.apache.brooklyn.core.internal.BrooklynProperties; -import org.apache.brooklyn.core.internal.storage.BrooklynStorage; import org.apache.brooklyn.util.core.internal.ssh.ShellTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,6 +120,8 @@ public class BrooklynFeatureEnablement { * The functionality loads catalog items regardless of the persistence state so best used with persistence disabled. * If a bundle is uploaded its BOM is scanned regardless of this property (this only applies to bundles * installed through a non-brooklyn method, eg karaf.) + * + * This installs legacy items and so should be deprecated in favour of uploading BOMs which Brooklyn manages. */ public static final String FEATURE_LOAD_BUNDLE_CATALOG_BOM = FEATURE_PROPERTY_PREFIX+".osgi.catalog_bom"; diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 323c70fac8..8beb3be888 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -627,11 +627,12 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed // don't scan } else { if (isNoBundleOrSimpleWrappingBundle(mgmt, containingBundle)) { + Collection> scanResult; // BOMs wrapped in JARs, or without JARs, have special treatment if (isLibrariesMoreThanJustContainingBundle(librariesAddedHereBundles, containingBundle)) { // legacy mode, since 0.12.0, scan libraries referenced in a legacy non-bundle BOM log.warn("Deprecated use of scanJavaAnnotations to scan other libraries ("+librariesAddedHereBundles+"); libraries should declare they scan themselves"); - result.addAll(scanAnnotationsLegacyInListOfLibraries(mgmt, librariesAddedHereBundles, catalogMetadata, containingBundle)); + scanResult = scanAnnotationsLegacyInListOfLibraries(mgmt, librariesAddedHereBundles, catalogMetadata, containingBundle); } else if (!isLibrariesMoreThanJustContainingBundle(libraryBundles, containingBundle)) { // for default catalog, no libraries declared, we want to scan local classpath // bundle should be named "brooklyn-default-catalog" @@ -642,10 +643,20 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed // since 0.12.0, require this to be right next to where libraries are defined, or at root log.warn("Deprecated use of scanJavaAnnotations declared in item; should be declared at the top level of the BOM"); } - result.addAll(scanAnnotationsFromLocalNonBundleClasspath(mgmt, catalogMetadata, containingBundle)); + scanResult = scanAnnotationsFromLocalNonBundleClasspath(mgmt, catalogMetadata, containingBundle); } else { throw new IllegalStateException("Cannot scan for Java catalog items when libraries declared on an ancestor; scanJavaAnnotations should be specified alongside brooklyn.libraries (or ideally those libraries should specify to scan)"); } + if (scanResult!=null && !scanResult.isEmpty()) { + if (result!=null) { + result.addAll( scanResult ); + } else { + // not returning a result; we need to add here + for (CatalogItem item: scanResult) { + mgmt.getCatalog().addItem(item); + } + } + } } else { throw new IllegalArgumentException("Scanning for Java annotations is not supported in BOMs in bundles; " + "entries should be listed explicitly in the catalog.bom"); @@ -847,7 +858,7 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed // run again now that we know the ID to catch recursive definitions and possibly other mistakes (itemType inconsistency?) planInterpreter = new PlanInterpreterGuessingType(id, item, sourceYaml, itemType, libraryBundles, result).reconstruct(); if (resolutionError==null && !planInterpreter.isResolved()) { - resolutionError = new IllegalStateException("Plan resolution breaks after id and itemType are set; is there a recursive reference or other type inconsistency?\n"+sourceYaml); + resolutionError = new IllegalStateException("Plan resolution for "+id+" breaks after id and itemType are set; is there a recursive reference or other type inconsistency?\n"+sourceYaml); } String sourcePlanYaml = planInterpreter.getPlanYaml(); @@ -951,7 +962,7 @@ public static boolean isNoBundleOrSimpleWrappingBundle(ManagementContext mgmt, M private void collectUrlReferencedCatalogItems(String url, ManagedBundle containingBundle, List> result, boolean requireValidation, Map parentMeta, int depth, boolean force) { @SuppressWarnings("unchecked") - List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, List.class, "brooklyn.libraries", "libraries").orNull()); + List parentLibrariesRaw = MutableList.copyOf(getFirstAs(parentMeta, Iterable.class, "brooklyn.libraries", "libraries").orNull()); Collection parentLibraries = CatalogItemDtoAbstract.parseLibraries(parentLibrariesRaw); BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, ":0.0.0", parentLibraries); String yaml; @@ -1626,11 +1637,11 @@ private void onAdditionUpdateOtherRegistries(CatalogItemDtoAbstract itemDt CatalogItem existingDto = existingItem.getDto(); if (existingDto.equals(itemDto)) { if (allowDuplicates) return existingItem; - throw new IllegalStateException("Updating existing catalog entries, even with the same content, is forbidden: " + - itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override."); + throw new IllegalStateException("Not allowed to update existing catalog entries, even with the same content: " + + itemDto.getSymbolicName() + ":" + itemDto.getVersion()); } else { - throw new IllegalStateException("Updating existing catalog entries is forbidden: " + - itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override."); + throw new IllegalStateException("Cannot add " + itemDto.getSymbolicName() + ":" + itemDto.getVersion() + + " to catalog; a different definition is already present"); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index bd9c7fae70..90bc628142 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.ManagedBundle; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; @@ -90,6 +91,10 @@ public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { if (null != bom) { LOG.debug("Found catalog BOM in {} {} {}", CatalogUtils.bundleIds(bundle)); String bomText = readBom(bom); + if (mb==null) { + LOG.warn("Bundle "+bundle+" containing BOM is not managed by Brooklyn; using legacy item installation"); + legacy = true; + } if (legacy) { catalogItems = this.managementContext.getCatalog().addItems(bomText, mb, force); for (CatalogItem item : catalogItems) { @@ -107,7 +112,7 @@ public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { } } - if (BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(managementContext, mb)) { + if (!legacy && BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(managementContext, mb)) { ((ManagementContextInternal)managementContext).getOsgiManager().get().addInstalledWrapperBundle(mb); } } else { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index 473fa6b6c8..81cbbcf8dc 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.List; +import java.util.NoSuchElementException; import javax.annotation.Nullable; @@ -31,6 +32,7 @@ import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.BrooklynLogging; @@ -50,7 +52,9 @@ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Time; import org.osgi.framework.Bundle; @@ -97,11 +101,11 @@ public static BrooklynClassLoadingContext getClassLoadingContext(Entity entity) log.warn("Cannot load "+catId+" to get classloader for "+entity+"; will try with standard loader, but might fail subsequently"); return JavaBrooklynClassLoadingContext.create(mgmt); } - return newClassLoadingContext(mgmt, cat.get()); + return newClassLoadingContext(mgmt, cat.get(), JavaBrooklynClassLoadingContext.create(mgmt)); } public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection libraries) { - return newClassLoadingContext(mgmt, catalogItemId, libraries, null); + return newClassLoadingContext(mgmt, catalogItemId, libraries, JavaBrooklynClassLoadingContext.create(mgmt)); } @Deprecated /** @deprecated since 0.9.0; becoming private because we should now always have a registered type callers can pass instead of the catalog item id */ @@ -146,12 +150,12 @@ public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable Manag } public static BrooklynClassLoadingContext newClassLoadingContextForCatalogItems( - ManagementContext managementContext, String catalogItemId, List searchPath) { + ManagementContext managementContext, String primaryItemId, List searchPath) { BrooklynClassLoadingContextSequential seqLoader = new BrooklynClassLoadingContextSequential(managementContext); - addCatalogItemContext(managementContext, seqLoader, catalogItemId); + addSearchItem(managementContext, seqLoader, primaryItemId); for (String searchId : searchPath) { - addCatalogItemContext(managementContext, seqLoader, searchId); + addSearchItem(managementContext, seqLoader, searchId); } return seqLoader; } @@ -341,29 +345,58 @@ public static void setDisabled(ManagementContext mgmt, String symbolicNameAndOpt @Deprecated public static void setDeprecated(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem item = mgmt.getCatalog().getCatalogItem(symbolicName, version); - Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); - item.setDeprecated(newValue); - mgmt.getCatalog().persist(item); + if (item!=null) { + item.setDeprecated(newValue); + mgmt.getCatalog().persist(item); + } else { + RegisteredType type = mgmt.getTypeRegistry().get(symbolicName, version); + if (type!=null) { + RegisteredTypes.setDeprecated(type, newValue); + } else { + throw new NoSuchElementException(symbolicName+":"+version); + } + } } /** @deprecated since it was introduced in 0.9.0; TBD where this should live */ @Deprecated public static void setDisabled(ManagementContext mgmt, String symbolicName, String version, boolean newValue) { CatalogItem item = mgmt.getCatalog().getCatalogItem(symbolicName, version); - Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version); - item.setDisabled(newValue); - mgmt.getCatalog().persist(item); + if (item!=null) { + item.setDisabled(newValue); + mgmt.getCatalog().persist(item); + } else { + RegisteredType type = mgmt.getTypeRegistry().get(symbolicName, version); + if (type!=null) { + RegisteredTypes.setDisabled(type, newValue); + } else { + throw new NoSuchElementException(symbolicName+":"+version); + } + } } - private static void addCatalogItemContext(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String catalogItemId) { - RegisteredType item = managementContext.getTypeRegistry().get(catalogItemId); - + private static void addSearchItem(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String itemId) { + OsgiManager osgi = ((ManagementContextInternal)managementContext).getOsgiManager().orNull(); + boolean didSomething = false; + if (osgi!=null) { + ManagedBundle bundle = osgi.getManagedBundle(VersionedName.fromString(itemId)); + if (bundle!=null) { + loader.add( newClassLoadingContext(managementContext, itemId, MutableSet.of(bundle)) ); + didSomething = true; + // but also load entities, if name is same as a bundle and libraries are set on entity + } + } + + RegisteredType item = managementContext.getTypeRegistry().get(itemId); if (item != null) { BrooklynClassLoadingContext itemLoader = newClassLoadingContext(managementContext, item); loader.add(itemLoader); - } else { + didSomething = true; + } + + if (!didSomething) { // TODO review what to do here - log.warn("Can't find catalog item " + catalogItemId); + log.warn("Can't find catalog item " + itemId); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index 1d07a5a424..5095f682ca 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -41,6 +41,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.osgi.VersionedName; +import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Identifiers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -311,7 +312,7 @@ public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canFo Asserts.fail("Registered type "+type+" has ID / symname mismatch"); RegisteredType oldType = mgmt.getTypeRegistry().get(type.getId()); - if (oldType==null || canForce) { + if (oldType==null || canForce || BrooklynVersionSyntax.isSnapshot(oldType.getVersion())) { log.debug("Inserting "+type+" into "+this); localRegisteredTypes.put(type.getId(), type); } else { diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index 2f205eb4da..06014c65d6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -209,6 +209,18 @@ public static RegisteredType setContainingBundle(RegisteredType type, @Nullable return type; } + @Beta + public static RegisteredType setDeprecated(RegisteredType type, boolean deprecated) { + ((BasicRegisteredType)type).deprecated = deprecated; + return type; + } + + @Beta + public static RegisteredType setDisabled(RegisteredType type, boolean disabled) { + ((BasicRegisteredType)type).disabled = disabled; + return type; + } + @Beta public static RegisteredType addSuperType(RegisteredType type, @Nullable Class superType) { if (superType!=null) { diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java index 8d5cf0f5fd..c3e75be3ee 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/ClassLoaderUtils.java @@ -27,20 +27,21 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.LoaderDispatcher.ClassLoaderDispatcher; import org.apache.brooklyn.util.core.LoaderDispatcher.MultipleResourceLoaderDispatcher; import org.apache.brooklyn.util.core.LoaderDispatcher.ResourceLoaderDispatcher; import org.apache.brooklyn.util.core.osgi.Osgis; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.osgi.OsgiUtils; -import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -253,18 +254,31 @@ private Maybe loadClass(String name, LoaderDispatcher dispatcher, Stri Maybe cls; if (entity != null && mgmt != null) { String catalogItemId = entity.getCatalogItemId(); + if (catalogItemId != null) { -// RegisteredType type = mgmt.getTypeRegistry().get(catalogItemId); -// if (type != null) { -// BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); -// loader.add(newClassLoadingContextForCatalogItems(mgmt, type.getId(), -// type.getContainingBundle() + type.getLibraries() ?); -// cls = dispatcher.tryLoadFrom(loader, className); -// if (cls.isPresent()) { -// return cls; -// } - // TODO prefer above to below, but need to reconcile item.searchPath with RegisteredType? - // or use entity search path ? + { + BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); + loader.add( newClassLoadingContextForCatalogItems(mgmt, catalogItemId, + entity.getCatalogItemIdSearchPath()) ); + cls = dispatcher.tryLoadFrom(loader, className); + if (cls.isPresent()) { + return cls; + } + } + // the above (entity) should be sufficient? + + RegisteredType type = mgmt.getTypeRegistry().get(catalogItemId); + if (type != null) { + BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); + List libs = MutableList.of(); + for (OsgiBundleWithUrl o: type.getLibraries()) libs.add(o.getVersionedName().toString()); + loader.add( newClassLoadingContextForCatalogItems(mgmt, type.getContainingBundle(), libs) ); + cls = dispatcher.tryLoadFrom(loader, className); + if (cls.isPresent()) { + return cls; + } + } + CatalogItem item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogItemId); if (item != null) { BrooklynClassLoadingContextSequential loader = new BrooklynClassLoadingContextSequential(mgmt); diff --git a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java index 8fffa9d556..4d13aa888a 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java @@ -54,7 +54,8 @@ public Maybe> tryLoadFrom(Bundle bundle, String className) { @Override public Maybe> tryLoadFrom(BrooklynClassLoadingContext loader, String className) { try { - return Maybe.>of(loader.loadClass(className)); + // return Maybe.>of(loader.loadClass(className)); + return loader.tryLoadClass(className); } catch (IllegalStateException e) { propagateIfCauseNotClassNotFound(e); return Maybe.absent("Failed to load class " + className + " from loader " + loader, e); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java index 887ca6b756..1e0e698fb8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/BrooklynVersionSyntax.java @@ -31,6 +31,8 @@ */ public class BrooklynVersionSyntax { + private static final String SNAPSHOT = "SNAPSHOT"; + public static final String USABLE_REGEX = "[^:\\s/\\\\]+"; public static final String DOT = "\\."; @@ -192,4 +194,9 @@ public static boolean equalAsOsgiVersions(String v1, String v2) { return toValidOsgiVersion(v1).equals(toValidOsgiVersion(v2)); } + public static boolean isSnapshot(String version) { + if (version==null) return false; + return version.toUpperCase().contains(SNAPSHOT); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java index 120a210b69..449d0e5d86 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java @@ -44,8 +44,6 @@ */ public class VersionComparator implements Comparator { - private static final String SNAPSHOT = "SNAPSHOT"; - public static final VersionComparator INSTANCE = new VersionComparator(); public static VersionComparator getInstance() { @@ -53,10 +51,8 @@ public static VersionComparator getInstance() { } public static boolean isSnapshot(String version) { - if (version==null) return false; - return version.toUpperCase().contains(SNAPSHOT); + return BrooklynVersionSyntax.isSnapshot(version); } - @SuppressWarnings("unused") private static class TwoBooleans { From 580c00da07e6956b911ec48e2626540805702f83 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Jun 2017 09:54:11 +0100 Subject: [PATCH 15/33] remove osgi deprecation, disable, and transform tests these are no longer supported; need to think through how to address --- .../catalog/CatalogYamlRebindTest.java | 93 ++++--------------- 1 file changed, 16 insertions(+), 77 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index a962dbee61..d8cd514df5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -51,7 +51,6 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.osgi.OsgiTestResources; import org.apache.brooklyn.util.text.Strings; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -70,12 +69,10 @@ public class CatalogYamlRebindTest extends AbstractYamlRebindTest { // - config/attribute cannot be instantiated (e.g. because class no longer on classpath) // - entity file corrupt - private static final String OSGI_BUNDLE_SYMBOLID_NAME_FULL = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL; - private static final String OSGI_BUNDLE_URL = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; - private static final String OSGI_SIMPLE_ENTITY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY; - private static final String OSGI_SIMPLE_POLICY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_POLICY; - private static final String OSGI_SIMPLE_EFFECTOR_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_EFFECTOR; - + // Since 0.12.0 OSGi reads from bundles so many of the things this used to test are no longer supported; + // deprecation and disablement will have to be done on a bundle-wide basis + // and transforms will have to be done by forcibly replacing a bundle or another "upgrade" mechanism + enum RebindWithCatalogTestMode { NO_OP, STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, @@ -87,8 +84,6 @@ enum RebindWithCatalogTestMode { enum OsgiMode { NONE, - LIBRARY, - PREFIX } private Boolean defaultEnablementOfFeatureAutoFixatalogRefOnRebind; @@ -111,27 +106,19 @@ public void tearDown() throws Exception { @Override protected boolean useOsgi() { - return true; + return false; } @DataProvider public Object[][] dataProvider() { return new Object[][] { {RebindWithCatalogTestMode.NO_OP, OsgiMode.NONE}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.NO_OP, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.NONE}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.STRIP_DEPRECATION_AND_ENABLEMENT_FROM_CATALOG_ITEM, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DEPRECATE_CATALOG, OsgiMode.PREFIX}, {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.NONE}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.DISABLE_CATALOG, OsgiMode.PREFIX}, // For DELETE_CATALOG, see https://issues.apache.org/jira/browse/BROOKLYN-149. // Deletes the catalog item before rebind, but the referenced types are still on the @@ -143,8 +130,6 @@ public Object[][] dataProvider() { // Upgrades the catalog item before rebind, deleting the old version. // Will automatically upgrade. Test will enable "FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND" {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.NONE}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.LIBRARY}, - {RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION, OsgiMode.PREFIX}, }; } @@ -216,63 +201,17 @@ public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mo String appSymbolicName = "my.catalog.app.id.load"; String appVersion = "0.1.0"; - String appCatalogFormat; - if (osgiMode == OsgiMode.LIBRARY) { - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " libraries:", - " - url: " + OSGI_BUNDLE_URL, - " item:", - " type: " + OSGI_SIMPLE_ENTITY_TYPE, - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + OSGI_SIMPLE_POLICY_TYPE, - " brooklyn.initializers:", - " - type: " + OSGI_SIMPLE_EFFECTOR_TYPE); - } else if (osgiMode == OsgiMode.PREFIX) { - // This catalog item is just meant to load the bundle in the OSGi environment. Its content is irrelevant. - String libraryItem = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: dummy", - " version: %s", - " itemType: entity", - " libraries:", - " - url: " + OSGI_BUNDLE_URL, - " item: " + BasicEntity.class.getName()); - addCatalogItems(String.format(libraryItem, appVersion)); - - // Use bundle prefixes here, pointing to the bundle already loaded above - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " item:", - " type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_ENTITY_TYPE, - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_POLICY_TYPE, - " brooklyn.initializers:", - " - type: " + OSGI_BUNDLE_SYMBOLID_NAME_FULL + ":" + OSGI_SIMPLE_EFFECTOR_TYPE); - } else { - appCatalogFormat = Joiner.on("\n").join( - "brooklyn.catalog:", - " id: " + appSymbolicName, - " version: %s", - " itemType: entity", - " item:", - " type: " + BasicEntity.class.getName(), - " brooklyn.enrichers:", - " - type: " + TestEnricher.class.getName(), - " brooklyn.policies:", - " - type: " + TestPolicy.class.getName()); - } - + String appCatalogFormat = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + appSymbolicName, + " version: %s", + " itemType: entity", + " item:", + " type: " + BasicEntity.class.getName(), + " brooklyn.enrichers:", + " - type: " + TestEnricher.class.getName(), + " brooklyn.policies:", + " - type: " + TestPolicy.class.getName()); String locSymbolicName = "my.catalog.loc.id.load"; String locVersion = "1.0.0"; From 520fb6a602702202fcb768842101ddd04cd56e2b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 3 Jul 2017 12:10:15 +0100 Subject: [PATCH 16/33] tidy tests, add more OSGi-variants of existing tests, weaken same-blueprint check now permit uploading the same BOM twice, forgive different containing bundle in this case (some cleverness to allow that, but preserves compatibility there; code needed changing anyway to allow adding unresovled items then replacing as resolved items) --- .../brooklyn/api/catalog/BrooklynCatalog.java | 1 + .../brooklyn/ApplicationsYamlOsgiTest.java | 31 ++++++ .../camp/brooklyn/ApplicationsYamlTest.java | 29 +++-- .../CatalogOsgiYamlVersioningTest.java | 30 ++--- .../brooklyn/catalog/CatalogScanOsgiTest.java | 5 +- .../catalog/CatalogYamlAppOsgiTest.java | 31 ++++++ .../brooklyn/catalog/CatalogYamlAppTest.java | 15 +-- .../catalog/CatalogYamlVersioningTest.java | 6 +- .../brooklyn/test/lite/CampYamlLiteTest.java | 35 +++--- .../internal/BasicBrooklynCatalog.java | 23 +++- .../typereg/BasicBrooklynTypeRegistry.java | 104 ++++++++++++++++-- .../core/typereg/RegisteredTypes.java | 47 ++++++++ .../rest/resources/CatalogResource.java | 6 +- 13 files changed, 291 insertions(+), 72 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java index 6599f6dd12..65c2b88699 100644 --- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java +++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java @@ -60,6 +60,7 @@ public interface BrooklynCatalog { Iterable> getCatalogItems(); /** convenience for filtering items in the catalog; see CatalogPredicates for useful filters */ +// XXX Iterable> getCatalogItems(Predicate> filter); /** persists the catalog item to the object store, if persistence is enabled */ diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java new file mode 100644 index 0000000000..a93ee64868 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlOsgiTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn; + +import org.testng.annotations.Test; + +@Test +public class ApplicationsYamlOsgiTest extends ApplicationsYamlTest { + + @Override + protected boolean disableOsgi() { + return false; + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java index bcda335b82..27f7927343 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ApplicationsYamlTest.java @@ -24,21 +24,24 @@ import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.TestSensorAndEffectorInitializer.TestConfigurableInitializer; import org.apache.brooklyn.camp.brooklyn.catalog.CatalogYamlLocationTest; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.EntityManagementUtils; -import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; -import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.policy.TestEnricher; import org.apache.brooklyn.core.test.policy.TestPolicy; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.test.Asserts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.Assert; import org.testng.annotations.Test; import com.google.common.base.Joiner; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -46,12 +49,6 @@ public class ApplicationsYamlTest extends AbstractYamlTest { private static final Logger log = LoggerFactory.getLogger(ApplicationsYamlTest.class); - @Override - protected LocalManagementContext newTestManagementContext() { - // Don't need osgi - return LocalManagementContextForTests.newInstance(); - } - @Test public void testWrapsEntity() throws Exception { Entity app = createAndStartApplication( @@ -403,6 +400,22 @@ public void testNameWithinItem() throws Exception { assertDoesNotWrap(app1, BasicApplication.class, "My name within item"); } + @Test + /** Tests catalog.bom format where service is defined alongside brooklyn.catalog, IE latter has no item/items */ + public void testItemFromServicesSectionInCatalog() { + addCatalogItems( + "brooklyn.catalog:", + " id: simple-test", + " version: "+TEST_VERSION, + "services:", + "- type: org.apache.brooklyn.entity.stock.BasicEntity"); + + Iterable retrievedItems = mgmt().getTypeRegistry() + .getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo("simple-test"))); + Asserts.assertSize(retrievedItems, 1); + Assert.assertEquals(Iterables.getOnlyElement(retrievedItems).getVersion(), TEST_VERSION); + } + @Override protected Logger getLogger() { return log; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java index 9a047d8940..672c722460 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlVersioningTest.java @@ -34,19 +34,23 @@ protected boolean disableOsgi() { return false; } - @Override - @Test - public void testAddSameVersionWithoutBundle() { - try { - // parent test should fail in OSGi - anonymous bundle is snapshot so updating is attempted - // but item version is not snapshot and containing bundle is different, so ultimately fails - super.testAddSameVersionWithoutBundle(); - Asserts.shouldHaveFailedPreviously("Expected to fail because containing bundle will be different when using OSGi"); - } catch (Exception e) { - assertExpectedFailureSaysUpdatingExistingItemForbidden(e); - assertExpectedFailureIncludesSampleId(e); - } - } + // now parent version of this test passes also in OSGi; + // the "sameEnough" check in BasicBrooklynTypeRegistry.addToLocalUnpersisted + // does _not_ look at containing bundle + // TODO delete this when we're happy with this behaviour +// @Override +// @Test +// public void testAddSameVersionWithoutBundle() { +// try { +// // parent test should fail in OSGi - anonymous bundle is snapshot so updating is attempted +// // but item version is not snapshot and containing bundle is different, so ultimately fails +// super.testAddSameVersionWithoutBundle(); +// Asserts.shouldHaveFailedPreviously("Expected to fail because containing bundle will be different when using OSGi"); +// } catch (Exception e) { +// assertExpectedFailureSaysDifferentIsBad(e); +// assertExpectedFailureIncludesSampleId(e); +// } +// } @Test public void testAddSameVersionWithoutBundleWorksIfItemIsSnapshot() { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java index c8d5399f00..4d1dfd5e00 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.test.lite.CampYamlLiteTest; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -91,13 +92,13 @@ public void testScanLegacyListedLibraries() { RegisteredType hereItem = mgmt().getTypeRegistry().get("here-item"); assertEquals(hereItem.getVersion(), "2.0-test_java"); - assertEquals(hereItem.getLibraries().size(), 3); + Asserts.assertSize(hereItem.getLibraries(), 2); assertEquals(hereItem.getContainingBundle(), "test-items:2.0-test_java"); RegisteredType item = mgmt().getTypeRegistry().get(OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_MORE_ENTITY); // versions and libraries _are_ inherited in this legacy mode assertEquals(item.getVersion(), "2.0-test_java"); - assertEquals(item.getLibraries().size(), 3); + Asserts.assertSize(hereItem.getLibraries(), 2); // and the containing bundle is recorded as the assertEquals(item.getContainingBundle(), "test-items"+":"+"2.0-test_java"); } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java new file mode 100644 index 0000000000..90133c71ee --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppOsgiTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.camp.brooklyn.catalog; + +import org.testng.annotations.Test; + +// OSGi variant of parent +@Test +public class CatalogYamlAppOsgiTest extends CatalogYamlAppTest { + + @Override + protected boolean disableOsgi() { + return false; + } +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java index cc9066c403..e4a9bf9e84 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlAppTest.java @@ -18,15 +18,15 @@ */ package org.apache.brooklyn.camp.brooklyn.catalog; -import org.testng.annotations.Test; - import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.ApplicationsYamlTest; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.testng.annotations.Test; /** - * Also see related tests in {@link ApplicationsYamlTest}. - * TODO Not clear what the difference is between these two test classes! + * Tests a few obscure circular references. + * See primary tests in {@link ApplicationsYamlTest}. + * Also see OSGi subclass. */ public class CatalogYamlAppTest extends AbstractYamlTest { @@ -40,12 +40,8 @@ public class CatalogYamlAppTest extends AbstractYamlTest { * "org.apache.brooklyn.entity.stock.BasicEntity", but that has been defined in the * catalog as this new blueprint (which overrides the previous value of it * being a reference to the Java class). - * - * We need to use an id that matches something else already on the classpath. - * Otherwise we'd get an error telling us "could not resolve item ..." when - * attempting to add the initial catalog item. */ - @Test(groups="WIP") // TODO Fix this! + @Test // already fixed prior to type-registry shift in 0.12.0, but also works with OSGi public void testAddCatalogItemWithCircularReference() throws Exception { // Add a catalog item with a circular reference to its own id. addCatalogItems( @@ -107,4 +103,5 @@ public void testAddCatalogItemWithMemberSpecCircularReference() throws Exception deleteCatalogEntity("org.apache.brooklyn.entity.stock.BasicApplication"); } } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java index cd4525a426..29428281e1 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java @@ -89,7 +89,7 @@ public void testAddSameVersionFailsWhenIconIsDifferent() { } protected void checkAddSameVersionFailsWhenIconIsDifferent(Exception e) { - assertExpectedFailureSaysUpdatingExistingItemForbidden(e); + assertExpectedFailureSaysDifferentIsBad(e); assertExpectedFailureIncludesSampleId(e); } @@ -99,9 +99,9 @@ protected void assertExpectedFailureIncludesSampleId(Exception e) { Asserts.expectedFailureContainsIgnoreCase(e, symbolicName + ":" + version); } - protected void assertExpectedFailureSaysUpdatingExistingItemForbidden(Exception e) { + protected void assertExpectedFailureSaysDifferentIsBad(Exception e) { Asserts.expectedFailureContainsIgnoreCase(e, - "Updating existing catalog entries is forbidden"); + "cannot add", "different"); } @Test diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java index 07a80984fa..92b053ebb0 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java @@ -29,7 +29,6 @@ import java.util.Map; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; @@ -40,7 +39,6 @@ import org.apache.brooklyn.camp.spi.AssemblyTemplate; import org.apache.brooklyn.camp.spi.pdp.PdpYamlTest; import org.apache.brooklyn.camp.test.mock.web.MockWebPlatform; -import org.apache.brooklyn.core.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.AddChildrenEffector; @@ -54,7 +52,9 @@ import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; +import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; @@ -155,29 +155,28 @@ public void testAddChildrenEffector() throws Exception { } @Test + /** Tests catalog.bom format where service is defined alongside brooklyn.catalog, IE latter has no item/items */ public void testYamlServiceForCatalog() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); installWithoutCatalogBom(mgmt, OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL); - CatalogItem realItem = Iterables.getOnlyElement(mgmt.getCatalog().addItems(Streams.readFullyStringAndClose(getClass().getResourceAsStream("test-app-service-blueprint.yaml")))); - Iterable> retrievedItems = mgmt.getCatalog() - .getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo("catalog-name"))); + Iterable> itemsInstalled = mgmt.getCatalog().addItems(Streams.readFullyStringAndClose(getClass().getResourceAsStream("test-app-service-blueprint.yaml"))); + Asserts.assertSize(itemsInstalled, 1); + CatalogItem itemInstalled = Iterables.getOnlyElement(itemsInstalled); + Assert.assertEquals(itemInstalled.getSymbolicName(), "catalog-name"); + Assert.assertEquals(itemInstalled.getVersion(), "0.9"); - Assert.assertEquals(Iterables.size(retrievedItems), 1, "Wrong retrieved items: "+retrievedItems); - CatalogItem retrievedItem = Iterables.getOnlyElement(retrievedItems); - Assert.assertEquals(retrievedItem, realItem); - - Collection bundles = retrievedItem.getLibraries(); - Assert.assertEquals(bundles.size(), 1); - CatalogBundle bundle = Iterables.getOnlyElement(bundles); + Iterable retrievedItems = mgmt.getTypeRegistry() + .getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo("catalog-name"))); + Asserts.assertSize(retrievedItems, 1); + RegisteredType retrievedItem = Iterables.getOnlyElement(retrievedItems); + Assert.assertEquals(retrievedItem.getVersion(), "0.9"); + + Collection bundles = retrievedItem.getLibraries(); + Asserts.assertSize(bundles, 1); + OsgiBundleWithUrl bundle = Iterables.getOnlyElement(bundles); Assert.assertEquals(bundle.getUrl(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL); Assert.assertEquals(bundle.getSuppliedVersionString(), "0.1.0"); - - EntitySpec spec1 = (EntitySpec) mgmt.getCatalog().peekSpec(retrievedItem); - assertNotNull(spec1); - Assert.assertEquals(spec1.getConfig().get(TestEntity.CONF_NAME), "sample"); - - // TODO other assertions, about children } @Test diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 8beb3be888..0d858e0b82 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -516,7 +516,12 @@ private void collectCatalogItemsFromCatalogBomRoot(String yaml, ManagedBundle co catalogMetadata.remove("item"); catalogMetadata.remove("items"); if (!itemDef.isEmpty()) { - log.debug("Reading brooklyn.catalog peer keys as item ('top-level syntax')"); + // AH - i forgot we even supported this. probably no point anymore, + // now that catalog defs can reference an item yaml and things can be bundled together? + log.warn("Reading catalog item from sibling keys of `brooklyn.catalog` section, " + + "instead of the more common appraoch of putting inside an `item` within it. " + + "This behavior is not deprecated yet but it is being considered. " + + "If you find it useful please inform the community."); Map rootItem = MutableMap.of("item", itemDef); String rootItemYaml = yaml; YamlExtract yamlExtract = Yamls.getTextOfYamlAtPath(rootItemYaml, "brooklyn.catalog"); @@ -849,7 +854,7 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed String description = getFirstAs(catalogMetadata, String.class, "description").orNull(); description = setFromItemIfUnset(description, itemAsMap, "description"); - // icon.url is discouraged, but kept for legacy compatibility; should deprecate this + // icon.url is discouraged (using '.'), but kept for legacy compatibility; should deprecate this final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "iconUrl", "icon_url", "icon.url").orNull(); final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull(); @@ -935,7 +940,7 @@ protected static Collection resolveWherePossible(ManagementContex } private static boolean isLibrariesMoreThanJustContainingBundle(Collection library, ManagedBundle containingBundle) { - if (library==null) return false; + if (library==null || library.isEmpty()) return false; if (containingBundle==null) return !library.isEmpty(); if (library.size()>1) return true; CatalogBundle li = Iterables.getOnlyElement(library); @@ -1369,7 +1374,7 @@ public List> addItems(String yaml, boolean forceUpdat if (result.getCode().isError()) { throw new IllegalStateException(result.getMessage()); } - return toItems(result.getCatalogItemsInstalled()); + return toLegacyCatalogItems(result.getCatalogItemsInstalled()); // if all items pertaining to an older anonymous catalog.bom bundle have been overridden // we delete those later; see list of wrapper bundles kept in OsgiManager @@ -1379,10 +1384,16 @@ public List> addItems(String yaml, boolean forceUpdat } @SuppressWarnings("deprecation") - private List> toItems(Iterable itemIds) { + private List> toLegacyCatalogItems(Iterable itemIds) { List> result = MutableList.of(); for (String id: itemIds) { - result.add(CatalogUtils.getCatalogItemOptionalVersion(mgmt, id)); + CatalogItem item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id); + if (item==null) { + // using new Type Registry (OSGi addition); + result.add(RegisteredTypes.toPartialCatalogItem( mgmt.getTypeRegistry().get(id) )); + } else { + result.add(item); + } } return result; } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index 5095f682ca..2e1a1fafc9 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.core.typereg; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.annotation.Nullable; @@ -35,6 +36,8 @@ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -316,18 +319,101 @@ public void addToLocalUnpersistedTypeRegistry(RegisteredType type, boolean canFo log.debug("Inserting "+type+" into "+this); localRegisteredTypes.put(type.getId(), type); } else { - if (sameTypeAndPlan(oldType, type)) { - // ignore if same type and plan; other things can be changed while we sort out replacements etc - return; - } - throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present"); + assertSameEnoughToAllowReplacing(oldType, type); } } - private boolean sameTypeAndPlan(RegisteredType oldType, RegisteredType type) { - if (!oldType.getVersionedName().equals(type.getVersionedName())) return false; - if (!oldType.getPlan().equals(type.getPlan())) return false; - return true; + /** + * Allow replacing even of non-SNAPSHOT versions if plans are "similar enough"; + * ie, forgiving some metadata changes. + *

+ * This is needed when replacing an unresolved item with a resolved one or vice versa; + * the {@link RegisteredType#equals(Object)} check is too strict. + */ + private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, RegisteredType type) { + /* The bundle checksum check prevents swapping a different bundle at the same name+version. + * For SNAPSHOT and forced-updates, this method doesn't apply, so we can assume here that + * either the bundle checksums are the same, + * or it is a different bundle declaring an item which is already installed. + *

+ * Thus if the containing bundle is the same, the items are necessarily the same, + * except for metadata we've mucked with (e.g. kind = unresolved). + *

+ * If the containing bundle is different, it's possible someone is doing something sneaky. + * If bundles aren't anonymous wrappers, then we should disallow -- + * e.g. a bundle BAR is declaring and item already declared by a bundle FOO. + * + * + * It is the latter case we have to check. + * + * In the latter case we want to fail unless the old item comes from a wrapper bundle. + * + * the only time this method + * applies where there might be differences in the item is if installing a bundle BAR which + * declares an item with same name and version as an item already installed by a bundle FOO. + * + * * uploading a bundle + * * uploading a BOM (no bundle) where the item it is defining is the same as one already defined + * + * (with the same non-SNAPSHOT version) bundle. So any changes to icons, plans, etc, should have already been caught; + * this only applies if the BOM/bundle is identical, and in that case the only field where the two types here + * could be different is the containing bundle metadata, viz. someone has uploaded an anonymous BOM twice. + */ + + if (!oldType.getVersionedName().equals(type.getVersionedName())) { + // different name - shouldn't even come here + throw new IllegalStateException("Cannot add "+type+" to catalog; different "+oldType+" is already present"); + } + if (Objects.equals(oldType.getContainingBundle(), type.getContainingBundle())) { + // if named bundles equal then contents must be the same (due to bundle checksum); bail out early + if (!oldType.getPlan().equals(type.getPlan())) { + // shouldn't come here, but check anyway (or maybe if item added twice in the same catalog?) + throw new IllegalStateException("Cannot add "+type+" to catalog; different plan in "+oldType+" from same bundle is already present"); + } + if (oldType.getKind()!=RegisteredTypeKind.UNRESOLVED && type.getKind()!=RegisteredTypeKind.UNRESOLVED && + !Objects.equals(oldType.getKind(), type.getKind())) { + throw new IllegalStateException("Cannot add "+type+" to catalog; different kind in "+oldType+" from same bundle is already present"); + } + return true; + } + + // different bundles, either anonymous or same item in two named bundles + if (!oldType.getPlan().equals(type.getPlan())) { + // if plan is different, fail + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; different plan in "+oldType+" from bundle "+ + oldType.getContainingBundle()+" is already present"); + } + if (oldType.getKind()!=RegisteredTypeKind.UNRESOLVED && type.getKind()!=RegisteredTypeKind.UNRESOLVED && + !Objects.equals(oldType.getKind(), type.getKind())) { + // if kind is different and both resolved, fail + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; different kind in "+oldType+" from bundle "+ + oldType.getContainingBundle()+" is already present"); + } + + // now if old is a wrapper bundle (or old, no bundle), allow it -- metadata may be different here + // but we'll allow that, probably the user is updating their catalog to the new format. + // icons might change, maybe a few other things (but any such errors can be worked around), + // and more useful to be able to upload the same BOM or replace an anonymous BOM with a named bundle. + // crucially if old is a wrapper bundle the containing bundle won't actually be needed for anything, + // so this is safe in terms of search paths etc. + if (oldType.getContainingBundle()==null) { + // if old type wasn't from a bundle, let it be replaced by a bundle + return true; + } + // bundle is changing; was old bundle a wrapper? + OsgiManager osgi = ((ManagementContextInternal)mgmt).getOsgiManager().orNull(); + if (osgi==null) { + // shouldn't happen, as we got a containing bundle, but just in case + return true; + } + if (BasicBrooklynCatalog.isNoBundleOrSimpleWrappingBundle(mgmt, + osgi.getManagedBundle(VersionedName.fromString(oldType.getContainingBundle())))) { + // old was a wrapper bundle; allow it + return true; + } + + throw new IllegalStateException("Cannot add "+type+" in "+type.getContainingBundle()+" to catalog; " + + "item is already present in different bundle "+oldType.getContainingBundle()); } @Beta // API stabilising diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index 06014c65d6..eb761ad34d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -19,8 +19,10 @@ package org.apache.brooklyn.core.typereg; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -30,6 +32,8 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; +import org.apache.brooklyn.api.mgmt.rebind.mementos.CatalogItemMemento; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; @@ -119,6 +123,49 @@ public static RegisteredType of(CatalogItem item) { return type; } + /** @deprecated since introduced in 0.12.0; for backwards compatibility only, may be removed at any point. + * Returns a partially-populated CatalogItem. Many methods throw {@link UnsupportedOperationException} + * but the basic ones work. */ + @Deprecated + public static CatalogItem toPartialCatalogItem(RegisteredType t) { + return new CatalogItemFromRegisteredType(t); + } + private static class CatalogItemFromRegisteredType implements CatalogItem { + private final RegisteredType type; + public CatalogItemFromRegisteredType(RegisteredType type) { this.type = type; } + @Override public String getDisplayName() { return type.getDisplayName(); } + @Override public String getCatalogItemId() { return type.getVersionedName().toString(); } + @Override public String getId() { return type.getId(); } + @Override public String getName() { return type.getSymbolicName(); } + @Override public String getSymbolicName() { return type.getSymbolicName(); } + @Override public String getRegisteredTypeName() { return type.getSymbolicName(); } + @Override public String getDescription() { return type.getDescription(); } + @Override public String getIconUrl() { return type.getIconUrl(); } + @Override public String getContainingBundle() { return type.getContainingBundle(); } + @Override public String getVersion() { return type.getVersion(); } + + @Override public List getCatalogItemIdSearchPath() { throw new UnsupportedOperationException(); } + @Override public TagSupport tags() { throw new UnsupportedOperationException(); } + @Override public RelationSupport relations() { throw new UnsupportedOperationException(); } + @Override public T setConfig(ConfigKey key, T val) { throw new UnsupportedOperationException(); } + @Override public T getConfig(ConfigKey key) { throw new UnsupportedOperationException(); } + @Override public ConfigurationSupport config() { throw new UnsupportedOperationException(); } + @Override public SubscriptionSupport subscriptions() { throw new UnsupportedOperationException(); } + @Override public org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType getCatalogItemType() { throw new UnsupportedOperationException(); } + @Override public Class getCatalogItemJavaType() { throw new UnsupportedOperationException(); } + @Override public Class getSpecType() { throw new UnsupportedOperationException(); } + @Override public String getJavaType() { throw new UnsupportedOperationException(); } + @Override public Collection getLibraries() { + throw new UnsupportedOperationException(); + } + @Override public String getPlanYaml() { throw new UnsupportedOperationException(); } + @Override public RebindSupport getRebindSupport() { throw new UnsupportedOperationException(); } + @Override public void setDeprecated(boolean deprecated) { throw new UnsupportedOperationException(); } + @Override public void setDisabled(boolean disabled) { throw new UnsupportedOperationException(); } + @Override public boolean isDeprecated() { throw new UnsupportedOperationException(); } + @Override public boolean isDisabled() { throw new UnsupportedOperationException(); } + } + /** Preferred mechanism for defining a bean {@link RegisteredType}. * Callers should also {@link #addSuperTypes(RegisteredType, Iterable)} on the result.*/ public static RegisteredType bean(@Nonnull String symbolicName, @Nonnull String version, @Nonnull TypeImplementationPlan plan) { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java index ae22c79f5e..6983e5d587 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java @@ -201,10 +201,8 @@ public Response createFromArchive(byte[] zipInput, boolean detail, boolean force if (OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED.equals(result.getWithoutError().getCode())) { result = ReferenceWithError.newInstanceThrowingError(result.getWithoutError(), new IllegalStateException( - "Updating existing catalog entries is forbidden: " + - result.getWithoutError().getMetadata().getSymbolicName() + ":" + - result.getWithoutError().getMetadata().getSuppliedVersionString() + - ". Use forceUpdate argument to override.")); + "Cannot add bundle" + result.getWithoutError().getMetadata().getVersionedName() + + "; different bundle with same name already installed")); } if (result.hasError()) { From b016e67e9bef300677344b0d7eedcaee6e1940c0 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 3 Jul 2017 13:13:18 +0100 Subject: [PATCH 17/33] fix more tests, pointing at type registry instead of catalog note items added via OSGi will no longer show up in calls to search the catalog; callers should use type registry instead. at this point most things are done except: * REST calls to catalog won't show things added via bundles (as they are in TypeRegistry) * TEMPLATE items aren't supported * LOCATION items aren't put/removed from BasicLocationRegistry (but I think that is no longer needed?) need to fix those then can declare victory on this! --- .../api/typereg/BrooklynTypeRegistry.java | 4 +- .../brooklyn/ConfigParametersYamlTest.java | 3 +- .../catalog/CatalogOsgiLibraryTest.java | 17 ++++--- .../catalog/SpecParameterUnwrappingTest.java | 42 ++++++++--------- .../typereg/BasicBrooklynTypeRegistry.java | 5 +- .../mgmt/rebind/RebindCatalogItemTest.java | 47 ++++++++++++++++++- .../core/mgmt/rebind/RebindTestFixture.java | 33 ------------- 7 files changed, 79 insertions(+), 72 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java index 33c6733e1a..96b0bc60f3 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java @@ -18,8 +18,6 @@ */ package org.apache.brooklyn.api.typereg; -import java.util.Collection; - import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; @@ -52,7 +50,7 @@ public enum RegisteredTypeKind { /** @return The item matching the given given * {@link RegisteredType#getSymbolicName() symbolicName} * and optionally {@link RegisteredType#getVersion()}, - * taking the best version if the version is null or a default marker, + * taking the best version if the version is null, blank, or a default marker, * returning null if no matches are found. */ RegisteredType get(String symbolicName, String version); /** as {@link #get(String, String)} but the given string here diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java index 6e62bae725..98ccb0bddf 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigParametersYamlTest.java @@ -1229,7 +1229,8 @@ public void testConfigDefaultIsNotInheritedWith_LocalDefaultResolvesWithAncestor // check on spec - AbstractBrooklynObjectSpec spec = mgmt().getCatalog().peekSpec(mgmt().getCatalog().getCatalogItem("wrapper-entity", null)); + AbstractBrooklynObjectSpec spec = mgmt().getTypeRegistry().createSpec( + mgmt().getTypeRegistry().get("wrapper-entity", ""), null, null); Assert.assertEquals(spec.getParameters().size(), SpecParameterUnwrappingTest.NUM_ENTITY_DEFAULT_CONFIG_KEYS + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, "params: "+spec.getParameters()); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java index bd0707505e..7f7cbe7127 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiLibraryTest.java @@ -27,11 +27,10 @@ import java.util.Map; import java.util.Objects; -import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier; @@ -124,7 +123,7 @@ public void testLibraryStringWithClasspathUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -141,7 +140,7 @@ public void testLibraryMapWithClasspathUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -158,7 +157,7 @@ public void testLibraryHttpUrl() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, jarUrl.toString()); } @@ -220,7 +219,7 @@ public void testLibraryUrlUsingExternalizedConfig() throws Exception { " services:", " - type: org.apache.brooklyn.test.osgi.entities.SimpleApplication"); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, classpathUrl); } @@ -415,7 +414,7 @@ protected void runLibraryUrlUsingExternalizedConfigForCredentials(boolean includ } String expectedUrl = "http://" + escapedUsername + ":" + escapedPassword+ "@" + jarUrl.getHost() + ":" + jarUrl.getPort() + jarUrl.getPath(); - CatalogItem item = mgmt().getCatalog().getCatalogItem("simple-osgi-library", "1.0"); + RegisteredType item = mgmt().getTypeRegistry().get("simple-osgi-library", "1.0"); assertCatalogLibraryUrl(item, expectedUrl); } @@ -432,8 +431,8 @@ public MyExternalConfigSupplier(ManagementContext mgmt, String name, Map item, String expectedUrl) { - for (CatalogBundle b: item.getLibraries()) { + protected void assertCatalogLibraryUrl(RegisteredType item, String expectedUrl) { + for (OsgiBundleWithUrl b: item.getLibraries()) { if (Objects.equals(b.getUrl(), expectedUrl)) { return; } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java index 1e20f5eefa..e5a69c70e5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/SpecParameterUnwrappingTest.java @@ -119,8 +119,7 @@ public void testParameters(Class testClass) { ConfigKey SIMPLE_CONFIG = ConfigKeys.newStringConfigKey("simple"); SpecParameter SIMPLE_PARAM = new BasicSpecParameter<>("simple", true, SIMPLE_CONFIG); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); assertTrue(Iterables.tryFind(spec.getParameters(), Predicates.>equalTo(SIMPLE_PARAM)).isPresent()); } @@ -134,8 +133,7 @@ public void testDefaultParameters(Class testClass) { " item:", " type: "+ testClass.getName()); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); assertEquals(ImmutableSet.copyOf(spec.getParameters()), ImmutableSet.copyOf(BasicSpecParameter.fromClass(mgmt(),testClass))); } @@ -153,8 +151,7 @@ public void testRootParametersUnwrapped() { " - simple"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); assertTrue(Iterables.tryFind(params, nameEqualTo(SHARED_CONFIG.getName())).isPresent()); @@ -181,8 +178,7 @@ public void testDependantCatalogsInheritParameters(Class item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have simple in parent yaml type and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -223,8 +219,7 @@ public void testDependantCatalogsExtendsParameters(Class item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have another locally, simple in parent yaml type, and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -254,8 +249,7 @@ public void testDependantCatalogMergesParameters(Class " label: override"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); // should have simple locally (and in parent yaml type) and sample from parent java type assertEquals(params.size(), getNumDefaultConfigKeysFor(type.getSimpleName()) + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT, @@ -286,8 +280,7 @@ public void testDependantCatalogConfigOverridesParameters() { " - name: simple", " default: rabbits"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); Optional> config = Iterables.tryFind(spec.getConfig().keySet(), ConfigPredicates.nameEqualTo("simple")); @@ -312,8 +305,7 @@ public void testCatalogConfigOverridesParameters() { " simple: value"); final int NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + ConfigEntityForTest.NUM_CONFIG_KEYS_DEFINED_HERE + NUM_CONFIG_KEYS_FROM_TEST_BLUEPRINT); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); @@ -323,6 +315,15 @@ public void testCatalogConfigOverridesParameters() { assertEquals(value, "value"); } + private AbstractBrooklynObjectSpec peekSpec() { + return peekSpec(SYMBOLIC_NAME, TEST_VERSION); + } + + private AbstractBrooklynObjectSpec peekSpec(String name, String version) { + return mgmt().getTypeRegistry().createSpec( + mgmt().getTypeRegistry().get(name, version), null, null); + } + @Test public void testDependantCatalogConfigReplacesParameters() { addCatalogItems( @@ -341,8 +342,7 @@ public void testDependantCatalogConfigReplacesParameters() { " - name: simple", " default: rabbits"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(); List> params = spec.getParameters(); assertTrue(Iterables.tryFind(params, nameEqualTo("simple")).isPresent()); Optional> config = Iterables.tryFind(spec.getConfig().keySet(), ConfigPredicates.nameEqualTo("simple")); @@ -365,8 +365,7 @@ public void testChildEntitiyHasParameters() { " brooklyn.parameters:", " - simple"); - CatalogItem item = catalog.getCatalogItem(SYMBOLIC_NAME, TEST_VERSION); - EntitySpec parentSpec = (EntitySpec) catalog.peekSpec(item); + EntitySpec parentSpec = (EntitySpec) peekSpec(); EntitySpec spec = parentSpec.getChildren().get(0); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + 2, "params="+params); @@ -550,8 +549,7 @@ public void testParameterDefaultsUsedInConfig() throws Exception { " test: $brooklyn:config(\"num\")"); final int NUM_CONFIG_KEYS_FROM_WITH_PARAMS_TEST_BLUEPRINT = 1; - CatalogItem item = catalog.getCatalogItem(ConfigEntityForTest.class.getSimpleName() + "WithParams", TEST_VERSION); - AbstractBrooklynObjectSpec spec = catalog.peekSpec(item); + AbstractBrooklynObjectSpec spec = peekSpec(ConfigEntityForTest.class.getSimpleName() + "WithParams", TEST_VERSION); List> params = spec.getParameters(); assertEquals(params.size(), NUM_ENTITY_DEFAULT_CONFIG_KEYS + ConfigEntityForTest.NUM_CONFIG_KEYS_DEFINED_HERE + NUM_CONFIG_KEYS_FROM_WITH_PARAMS_TEST_BLUEPRINT, "params="+params); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index 2e1a1fafc9..f7f71e5673 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -46,6 +46,7 @@ import org.apache.brooklyn.util.osgi.VersionedName; import org.apache.brooklyn.util.text.BrooklynVersionSyntax; import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,7 +98,7 @@ private Maybe getSingle(String symbolicNameOrAliasIfNoVersion, f RegisteredTypeLoadingContext context = contextFinal; if (context==null) context = RegisteredTypeLoadingContexts.any(); String version = versionFinal; - if (version==null) version = BrooklynCatalog.DEFAULT_VERSION; + if (Strings.isBlank(version)) version = BrooklynCatalog.DEFAULT_VERSION; if (!BrooklynCatalog.DEFAULT_VERSION.equals(version)) { // normal code path when version is supplied @@ -139,7 +140,7 @@ private Maybe getSingle(String symbolicNameOrAliasIfNoVersion, f return Maybe.of( RegisteredTypes.CI_TO_RT.apply( item ) ); return Maybe.absent("No matches for "+symbolicNameOrAliasIfNoVersion+ - (versionFinal!=null ? ":"+versionFinal : "")+ + (Strings.isNonBlank(versionFinal) ? ":"+versionFinal : "")+ (contextFinal!=null ? " ("+contextFinal+")" : "") ); } diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java index 2418a3343d..77d8919832 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindCatalogItemTest.java @@ -26,7 +26,9 @@ import java.io.File; import java.util.Map; +import java.util.Set; +import org.apache.brooklyn.api.catalog.BrooklynCatalog; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.entity.EntitySpec; @@ -40,6 +42,8 @@ import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; import org.apache.brooklyn.core.catalog.internal.CatalogLocationItemDto; import org.apache.brooklyn.core.catalog.internal.CatalogPolicyItemDto; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.entity.EntityFunctions; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; import org.apache.brooklyn.core.policy.AbstractPolicy; @@ -58,7 +62,9 @@ import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; public class RebindCatalogItemTest extends RebindTestFixtureWithApp { @@ -274,7 +280,7 @@ public void testDeletedCatalogItemIsNotPersisted() { assertFalse(itemPostRebind.isPresent(), "item="+itemPostRebind); } - @Test(invocationCount = 3) + @Test(invocationCount = 3) // why three times? public void testCanTagCatalogItemAfterRebind() { String symbolicName = "rebind-yaml-catalog-item-test"; String yaml = Joiner.on("\n").join( @@ -292,9 +298,13 @@ public void testCanTagCatalogItemAfterRebind() { .build(); origManagementContext.getCatalog().addItem(item); LOG.info("Added item to catalog: {}, id={}", item, item.getId()); - + + assertEquals(Iterables.size(origManagementContext.getTypeRegistry().getAll()), 1); assertEquals(Iterables.size(origManagementContext.getCatalog().getCatalogItems()), 1); + // in OSGi/type registry you cannot edit tags directly (they won't be persisted as they come from BOM) + // RegisteredType origItem = origManagementContext.getTypeRegistry().get(symbolicName, TEST_VERSION); CatalogItem origItem = origManagementContext.getCatalog().getCatalogItem(symbolicName, TEST_VERSION); + assertNotNull(origItem, "Item not in catalog (might be using new type registry where adding tags to items isn't supported as items come from BOM)"); final String tag = "tag1"; origItem.tags().addTag(tag); assertTrue(origItem.tags().containsTag(tag)); @@ -374,4 +384,37 @@ private void rebindAndAssertCatalogsAreEqual() { } assertCatalogsEqual(newManagementContext.getCatalog(), origManagementContext.getCatalog()); } + + protected void assertCatalogsEqual(BrooklynCatalog actual, BrooklynCatalog expected) { + Set actualIds = getCatalogItemIds(actual.getCatalogItems()); + Set expectedIds = getCatalogItemIds(expected.getCatalogItems()); + assertEquals(actualIds.size(), Iterables.size(actual.getCatalogItems()), "id keyset size != size of catalog. Are there duplicates in the catalog?"); + assertEquals(actualIds, expectedIds); + for (String versionedId : actualIds) { + String id = CatalogUtils.getSymbolicNameFromVersionedId(versionedId); + String version = CatalogUtils.getVersionFromVersionedId(versionedId); + assertCatalogItemsEqual(actual.getCatalogItem(id, version), expected.getCatalogItem(id, version)); + } + } + + private Set getCatalogItemIds(Iterable> catalogItems) { + return FluentIterable.from(catalogItems) + .transform(EntityFunctions.id()) + .copyInto(Sets.newHashSet()); + } + + protected void assertCatalogItemsEqual(CatalogItem actual, CatalogItem expected) { + assertEquals(actual.getClass(), expected.getClass()); + assertEquals(actual.getId(), expected.getId()); + assertEquals(actual.getDisplayName(), expected.getDisplayName()); + assertEquals(actual.getVersion(), expected.getVersion()); + assertEquals(actual.getDescription(), expected.getDescription()); + assertEquals(actual.getIconUrl(), expected.getIconUrl()); + assertEquals(actual.getVersion(), expected.getVersion()); + assertEquals(actual.getCatalogItemJavaType(), expected.getCatalogItemJavaType()); + assertEquals(actual.getCatalogItemType(), expected.getCatalogItemType()); + assertEquals(actual.getSpecType(), expected.getSpecType()); + assertEquals(actual.getSymbolicName(), expected.getSymbolicName()); + assertEquals(actual.getLibraries(), expected.getLibraries()); + } } diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java index c94b933f36..c7d97a96c2 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java @@ -361,39 +361,6 @@ protected BrooklynMementoManifest loadMementoManifest() throws Exception { persister.stop(false); return mementoManifest; } - - protected void assertCatalogsEqual(BrooklynCatalog actual, BrooklynCatalog expected) { - Set actualIds = getCatalogItemIds(actual.getCatalogItems()); - Set expectedIds = getCatalogItemIds(expected.getCatalogItems()); - assertEquals(actualIds.size(), Iterables.size(actual.getCatalogItems()), "id keyset size != size of catalog. Are there duplicates in the catalog?"); - assertEquals(actualIds, expectedIds); - for (String versionedId : actualIds) { - String id = CatalogUtils.getSymbolicNameFromVersionedId(versionedId); - String version = CatalogUtils.getVersionFromVersionedId(versionedId); - assertCatalogItemsEqual(actual.getCatalogItem(id, version), expected.getCatalogItem(id, version)); - } - } - - private Set getCatalogItemIds(Iterable> catalogItems) { - return FluentIterable.from(catalogItems) - .transform(EntityFunctions.id()) - .copyInto(Sets.newHashSet()); - } - - protected void assertCatalogItemsEqual(CatalogItem actual, CatalogItem expected) { - assertEquals(actual.getClass(), expected.getClass()); - assertEquals(actual.getId(), expected.getId()); - assertEquals(actual.getDisplayName(), expected.getDisplayName()); - assertEquals(actual.getVersion(), expected.getVersion()); - assertEquals(actual.getDescription(), expected.getDescription()); - assertEquals(actual.getIconUrl(), expected.getIconUrl()); - assertEquals(actual.getVersion(), expected.getVersion()); - assertEquals(actual.getCatalogItemJavaType(), expected.getCatalogItemJavaType()); - assertEquals(actual.getCatalogItemType(), expected.getCatalogItemType()); - assertEquals(actual.getSpecType(), expected.getSpecType()); - assertEquals(actual.getSymbolicName(), expected.getSymbolicName()); - assertEquals(actual.getLibraries(), expected.getLibraries()); - } // protected void assertCatalogContains(BrooklynCatalog catalog, CatalogItem item) { // CatalogItem found = catalog.getCatalogItem(item.getSymbolicName(), item.getVersion()); From c5af5495549829462f8658468897a5d064398838 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 3 Jul 2017 14:03:29 +0100 Subject: [PATCH 18/33] make LocationRegistry be a facade to TypeRegistry for most things instead of the messy old way where catalog added/removed in the location registry; means it works for things added directly to TypeRegistry now (and those tests pass) one minor change to backwards compatibility, the internal LocationDefinition ID now contains the version. it isn't used and in other cases is a random string anyway. this will fix bugs where mulitple versions of the same location would result in just one being returned. --- .../api/location/LocationRegistry.java | 20 ++- .../catalog/CatalogOsgiYamlLocationTest.java | 10 +- .../catalog/CatalogYamlLocationTest.java | 21 ++- .../internal/BasicBrooklynCatalog.java | 11 +- .../core/location/BasicLocationRegistry.java | 168 ++++++++++-------- .../typereg/BasicBrooklynTypeRegistry.java | 14 +- .../core/location/LocationManagementTest.java | 10 +- .../core/location/LocationRegistryTest.java | 13 +- .../command/support/CloudExplorerSupport.java | 2 +- .../rest/resources/LocationResource.java | 2 +- .../rest/resources/LocationResourceTest.java | 4 +- 11 files changed, 154 insertions(+), 121 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java b/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java index 7804afc082..bbf6f52fc3 100644 --- a/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/location/LocationRegistry.java @@ -27,6 +27,7 @@ import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.LocationManager; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.util.guava.Maybe; /** @@ -37,19 +38,32 @@ public interface LocationRegistry { /** map of ID (possibly randomly generated) to the definition (spec, name, id, and props; - * where spec is the spec as defined, for instance possibly another named:xxx location) */ + * where spec is the spec as defined, for instance possibly another named:xxx location). + * optionally will also return all things from other sources where this can look up. */ + public Map getDefinedLocations(boolean includeThingsWeAreFacadeFor); + + /** @deprecated since 0.12.0 use {@link #getDefinedLocations(boolean)} passing true. + * some clients might only want the things actually defined here, which is cheaper. */ + @Deprecated public Map getDefinedLocations(); /** returns a LocationDefinition given its ID (usually a random string), or null if none */ public LocationDefinition getDefinedLocationById(String id); /** returns a LocationDefinition given its name (e.g. for named locations, supply the bit after the "named:" prefix), - * or null if none */ + * looking inthe {@link BrooklynTypeRegistry} for registered items, then in this list, or null if none */ public LocationDefinition getDefinedLocationByName(String name); - /** adds or updates the given defined location */ + /** as {@link #updateDefinedLocationNonPersisted(LocationDefinition)} + * @deprecated since 0.12.0 use {@link #updateDefinedLocationNonPersisted(LocationDefinition)}; + * it's exactly the same, just the name makes it clear */ + @Deprecated public void updateDefinedLocation(LocationDefinition l); + /** adds or updates the given defined location in this registry; note it is not persisted; + * callers should consider adding to the {@link BrooklynTypeRegistry} instead */ + public void updateDefinedLocationNonPersisted(LocationDefinition l); + /** removes the defined location from the registry (applications running there are unaffected) */ public void removeDefinedLocation(String id); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java index 94b2c50242..d986a51ec3 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlLocationTest.java @@ -75,6 +75,14 @@ public void testAddCatalogItemOsgi() throws Exception { assertAdded(symbolicName, SIMPLE_LOCATION_TYPE); assertOsgi(symbolicName); removeAndAssert(symbolicName); + + // and do it again; the OSGi registry doesn't add it; + // it only works because the location registry initializes itself on first call + // by reading from the catalog + symbolicName = "my.catalog.location.id.load.2"; + addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries()); + assertAdded(symbolicName, SIMPLE_LOCATION_TYPE); + assertOsgi(symbolicName); } @Test @@ -103,7 +111,7 @@ private void assertAdded(String symbolicName, String expectedJavaType) { // Item added to catalog should automatically be available in location registry LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName); - assertEquals(def.getId(), symbolicName); + assertEquals(def.getId(), symbolicName+":"+TEST_VERSION); assertEquals(def.getName(), symbolicName); LocationSpec spec = mgmt().getTypeRegistry().createSpec(item, null, LocationSpec.class); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java index 026b2e8ce1..80db3f348e 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java @@ -95,7 +95,6 @@ private void assertAdded(String symbolicName, String expectedJavaType) { // Item added to catalog should automatically be available in location registry LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName); - assertEquals(def.getId(), symbolicName); assertEquals(def.getName(), symbolicName); LocationSpec spec = mgmt().getTypeRegistry().createSpec(item, null, LocationSpec.class); @@ -171,9 +170,9 @@ public void testTypeInheritance() throws Exception { addCatalogItems(yaml); - Map defs = mgmt().getLocationRegistry().getDefinedLocations(); - LocationDefinition def1 = checkNotNull(defs.get("loc1"), "loc1 missing; has %s", defs.keySet()); - LocationDefinition def2 = checkNotNull(defs.get("loc2"), "loc2 missing; has %s", defs.keySet()); + Map defs = mgmt().getLocationRegistry().getDefinedLocations(true); + LocationDefinition def1 = checkNotNull(defs.get("loc1:0.1.2"), "loc1 missing; has %s", defs.keySet()); + LocationDefinition def2 = checkNotNull(defs.get("loc2:0.1.2"), "loc2 missing; has %s", defs.keySet()); LocationSpec spec1 = mgmt().getLocationRegistry().getLocationSpec(def1).get(); LocationSpec spec2 = mgmt().getLocationRegistry().getLocationSpec(def2).get(); @@ -234,15 +233,15 @@ public void testNamePrecedence() throws Exception { addCatalogItems(yaml2); addCatalogItems(yaml3); - LocationDefinition def1 = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def1 = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec1 = mgmt().getLocationRegistry().getLocationSpec(def1).get(); assertEquals(spec1.getDisplayName(), "My name in item metadata"); - LocationDefinition def2 = mgmt().getLocationRegistry().getDefinedLocations().get("loc2"); + LocationDefinition def2 = mgmt().getLocationRegistry().getDefinedLocationByName("loc2"); LocationSpec spec2 = mgmt().getLocationRegistry().getLocationSpec(def2).get(); assertEquals(spec2.getDisplayName(), "My name in top-level metadata"); - LocationDefinition def3b = mgmt().getLocationRegistry().getDefinedLocations().get("loc3b"); + LocationDefinition def3b = mgmt().getLocationRegistry().getDefinedLocationByName("loc3b"); LocationSpec spec3b = mgmt().getLocationRegistry().getLocationSpec(def3b).get(); assertEquals(spec3b.getDisplayName(), "My name within item 3b"); } @@ -261,7 +260,7 @@ public void testNameInCatalogMetadata() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name in top-level"); } @@ -280,7 +279,7 @@ public void testNameInItemMetadata() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name in item metadata"); } @@ -299,7 +298,7 @@ public void testNameWithinItem() throws Exception { addCatalogItems(yaml); - LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocations().get("loc1"); + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName("loc1"); LocationSpec spec = mgmt().getLocationRegistry().getLocationSpec(def).get(); assertEquals(spec.getDisplayName(), "My name within item"); } @@ -403,7 +402,7 @@ public void testManagedLocationsCreateAndCleanup() { } private void assertLocationRegistryCount(int size) { - Asserts.assertThat(mgmt().getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(size)); + Asserts.assertThat(mgmt().getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(size)); } private void assertLocationManagerInstancesCount(int size) { Asserts.assertThat(mgmt().getLocationManager().getLocations(), CollectionFunctionals.sizeEquals(size)); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 0d858e0b82..40655bc832 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -330,11 +330,6 @@ public void deleteCatalogItem(String symbolicName, String version) { if (log.isTraceEnabled()) { log.trace("Scheduling item for persistence removal: {}", itemDto.getId()); } - if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { - @SuppressWarnings("unchecked") - CatalogItem> locationItem = (CatalogItem>) itemDto; - ((BasicLocationRegistry)mgmt.getLocationRegistry()).removeDefinedLocation(locationItem); - } mgmt.getRebindManager().getChangeListener().onUnmanaged(itemDto); } @@ -1629,11 +1624,7 @@ private CatalogItem addItemDto(CatalogItemDtoAbstract itemDto, boolea } private void onAdditionUpdateOtherRegistries(CatalogItemDtoAbstract itemDto) { - if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { - @SuppressWarnings("unchecked") - CatalogItem> locationItem = (CatalogItem>) itemDto; - ((BasicLocationRegistry)mgmt.getLocationRegistry()).updateDefinedLocation(locationItem); - } + // nothing needed (previously updated BasicLocationRegistry but now that is a facade) } /** returns item DTO if item is an allowed duplicate, or null if it should be added (there is no duplicate), diff --git a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java index a7615133b9..cbfd140fe0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java @@ -47,6 +47,7 @@ import org.apache.brooklyn.core.location.internal.LocationInternal; import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.location.localhost.LocalhostLocationResolver; import org.apache.brooklyn.util.collections.MutableList; @@ -71,74 +72,46 @@ /** * See {@link LocationRegistry} for general description. *

- * TODO The relationship between the catalog and the location registry is a bit messy. - * For all existing code, the location registry is the definitive way to resolve - * locations. + * This implementation unfortunately has to deal with location definitions coming from three places: + * + *

  • brooklyn.properties + *
  • {@link BrooklynCatalog} + *
  • {@link BrooklynTypeRegistry} + *

    + * The first is read on init in the call to {@link #updateDefinedLocations()}. + * The latter two are not loaded here but are treated as lookups to {@link BrooklynTypeRegistry}. *

    - * Any location item added to the catalog must therefore be registered here. - * Whenever an item is added to the catalog, it will automatically call - * {@link #updateDefinedLocation(RegisteredType)}. Similarly, when a location - * is deleted from the catalog it will call {@link #removeDefinedLocation(RegisteredType)}. + * (Prior to 0.12.0 items in {@link BrooklynCatalog} were scanned on init here, and then + * it updated this on additions and removals there. That is no longer done with type registry.) *

    - * However, the location item in the catalog has an unparsed blob of YAML, which contains - * important things like the type and the config of the location. This is only parsed when - * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore jump through - * some hoops to wire together the catalog and the registry. + * We should consider deprecating this as a place where locations can be defined (switch to type registry only). *

    - * To add a location to the catalog, and then to resolve a location that is in the catalog, - * it goes through the following steps: - * - *

      - *
    1. Call {@link BrooklynCatalog#addItems(String)} - *
        - *
      1. This automatically calls {@link #updateDefinedLocation(RegisteredType)} - *
      2. A LocationDefinition is creating, using as its id the {@link RegisteredType#getSymbolicName()}. - * The definition's spec is {@code brooklyn.catalog::}, - *
      - *
    2. A blueprint can reference the catalog item using its symbolic name, - * such as the YAML {@code location: my-new-location}. - * (this feels similar to the "named locations"). - *
        - *
      1. This automatically calls {@link #getLocationManaged(String)}. - *
      2. The LocationDefinition is found by lookig up this name. - *
      3. The {@link LocationDefiniton.getSpec()} is retrieved; the right {@link LocationResolver} is - * found for it. - *
      4. This uses the {@link CatalogLocationResolver}, because the spec starts with {@code brooklyn.catalog:}. - *
      5. This resolver extracts from the spec the :, and looks up the - * location item using the {@link BrooklynTypeRegistry}. - *
      6. It then creates a {@link LocationSpec} by calling {@link BrooklynTypeRegistry#createSpec(RegisteredType)}. - *
          - *
        1. This first tries to use the type (that is in the YAML) as a simple Java class. - *
        2. If that fails, it will resolve the type using {@link #resolve(String, Boolean, Map)}, which - * returns an actual location object. - *
        3. It extracts from that location object the appropriate metadata to create a {@link LocationSpec}, - * returns the spec and discards the location object. - *
        - *
      7. The resolver creates the {@link Location} from the {@link LocationSpec} - *
      - *
    - * - * TODO we should change the registry to be a pass-through facade on top of the catalog, - * and shift to preferring catalog access mechanisms. - * this brings it in line with how we do other things; - * also this does not understand versions. - * to do this we will need to: - *
  • update the catalog on addition, setting a plan (ensuring serialization); - * in case of a definition CHANGED in brooklyn.properties give an error if it means the plan has changed - * (user could then remove from brooklyn.properties, if persistence on, or apply the update in the catalog; - * ie similar semantics to defining an initial catalog via the CLI) - *
  • find and return the RegisteredType from the type-registry/catalog here + * However this is used for items from brooklyn.properties -- we could create a bundle to store those + * to help with migration, and then deprecate/block changes to those after the bundle is created. + * However properties are the only way currently to set default data used for various clouds; + * either we'd need to deprecate that also, or find a way to support that in a bundled world. + *

    + * This is also used in a few other places, such as clocker and the server pool entity. + * Things here are never persisted though, so it is those callers' responsibility to recreate. + * Alternatively (better) would be for them to add items to the catalog instead of here. + *

    + * Or we keep this lying around for now. Probably not worth the pain above. *

    - * Once done, update the UI use /v1/catalog/locations instead of /v1/locations - * (currently the latter is the only way to list locations known in the LocationRegistry - * ie those from brookln.properties.) + * Also note the location item in the catalog has an unparsed blob of YAML, which contains + * important things like the type and the config of the location. This is only parsed when + * {@link BrooklynTypeRegistry#createSpec(RegisteredType)} is called, so we do that for + * lookup possibly more often than one might expect. (If a time hole this could be cached.) + * We also need to make sure some of the resolvers here can be used to identify types, so + * when loading types, the spec creation routines are able to call this to + * {@link #resolve(String, Boolean, Map)} (that returns a {@link Location} not a spec, + * but the {@link Location} instance is discarded). + *

    + * Finally note /v1/catalog/locations does not list items stored here; + * only /v1/locations does that. */ @SuppressWarnings({"rawtypes","unchecked"}) public class BasicLocationRegistry implements LocationRegistry { - // TODO save / serialize - // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry) - public static final Logger log = LoggerFactory.getLogger(BasicLocationRegistry.class); /** @@ -194,29 +167,68 @@ public boolean registerResolver(LocationResolver r) { } @Override - public Map getDefinedLocations() { + public Map getDefinedLocations(boolean includeThingsWeAreFacadeFor) { + Map result = MutableMap.of(); synchronized (definedLocations) { - return ImmutableMap.copyOf(definedLocations); + result.putAll(definedLocations); + } + for (RegisteredType rt: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) { + result.put(rt.getId(), newDefinedLocation(rt)); } + return result; + } + + @Override @Deprecated + public Map getDefinedLocations() { + return getDefinedLocations(true); } @Override public LocationDefinition getDefinedLocationById(String id) { - return definedLocations.get(id); + return getDefinedLocation(id, true); } @Override public LocationDefinition getDefinedLocationByName(String name) { + return getDefinedLocation(name, false); + } + + private LocationDefinition getDefinedLocation(String nameOrId, boolean isId) { + RegisteredType lt = mgmt.getTypeRegistry().get(nameOrId, RegisteredTypeLoadingContexts.withSpecSuperType(null, LocationSpec.class)); + if (lt!=null) { + return newDefinedLocation(lt); + } + synchronized (definedLocations) { - for (LocationDefinition l: definedLocations.values()) { - if (l.getName().equals(name)) return l; + if (isId) { + LocationDefinition ld = definedLocations.get(nameOrId); + if (ld!=null) { + return ld; + } + } else { + for (LocationDefinition l: definedLocations.values()) { + if (l.getName().equals(nameOrId)) return l; + } } - return null; } + + // fall back to ignoring supertypes, in case they weren't set + lt = mgmt.getTypeRegistry().get(nameOrId); + if (lt!=null) { + log.warn("Location registry only found "+nameOrId+" when ignoring supertypes; check it is correctly resolved."); + return newDefinedLocation(lt); + } + + return null; } - @Override + @Override @Deprecated public void updateDefinedLocation(LocationDefinition l) { + updateDefinedLocationNonPersisted(l); + } + + @Override + public void updateDefinedLocationNonPersisted(LocationDefinition l) { synchronized (definedLocations) { definedLocations.put(l.getId(), l); } @@ -226,7 +238,9 @@ public void updateDefinedLocation(LocationDefinition l) { * Converts the given item from the catalog into a LocationDefinition, and adds it * to the registry (overwriting anything already registered with the id * {@link CatalogItem#getCatalogItemId()}. + * @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void updateDefinedLocation(CatalogItem> item) { String id = item.getCatalogItemId(); String symbolicName = item.getSymbolicName(); @@ -241,17 +255,22 @@ public void updateDefinedLocation(CatalogItem> item) { * Converts the given item from the catalog into a LocationDefinition, and adds it * to the registry (overwriting anything already registered with the id * {@link RegisteredType#getId()}. + * @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void updateDefinedLocation(RegisteredType item) { + BasicLocationDefinition locDefinition = newDefinedLocation(item); + updateDefinedLocation(locDefinition); + } + + protected BasicLocationDefinition newDefinedLocation(RegisteredType item) { String id = item.getId(); - String symbolicName = item.getSymbolicName(); String spec = CatalogLocationResolver.createLegacyWrappedReference(id); - Map config = ImmutableMap.of(); - BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config); - - updateDefinedLocation(locDefinition); + return new BasicLocationDefinition(id, item.getSymbolicName(), spec, ImmutableMap.of()); } + /** @deprecated since 0.12.0 this class is a facade so method no longer wanted */ + @Deprecated // not used public void removeDefinedLocation(CatalogItem> item) { removeDefinedLocation(item.getSymbolicName()); } @@ -290,11 +309,6 @@ public void updateDefinedLocations() { } if (log.isDebugEnabled()) log.debug("Found "+count+" defined locations from properties (*.named.* syntax): "+definedLocations.values()); - - for (RegisteredType item: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) { - updateDefinedLocation(item); - count++; - } } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index f7f71e5673..f1a3e662d2 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.core.typereg; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; @@ -28,6 +29,7 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -35,7 +37,9 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder; +import org.apache.brooklyn.core.catalog.internal.CatalogItemDtoAbstract; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.location.BasicLocationRegistry; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.test.Asserts; @@ -419,14 +423,18 @@ private boolean assertSameEnoughToAllowReplacing(RegisteredType oldType, Registe @Beta // API stabilising public void delete(VersionedName type) { - if (localRegisteredTypes.remove(type.toString()) != null) { + RegisteredType registeredTypeRemoved = localRegisteredTypes.remove(type.toString()); + if (registeredTypeRemoved != null) { return ; } - // TODO may need to support version-less here? // legacy deletion (may call back to us, but max once) mgmt.getCatalog().deleteCatalogItem(type.getSymbolicName(), type.getVersionString()); - // if nothing deleted, throw NoSuchElement + // currently the above will succeed or throw; but if we delete that, we need to enable the code below +// if (Strings.isBlank(type.getVersionString()) || BrooklynCatalog.DEFAULT_VERSION.equals(type.getVersionString())) { +// throw new IllegalStateException("Deleting items with unspecified version (argument DEFAULT_VERSION) not supported."); +// } +// throw new NoSuchElementException("No catalog item found with id "+type); } public void delete(RegisteredType type) { diff --git a/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java b/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java index ed6758a73a..da85a9ee2f 100644 --- a/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/location/LocationManagementTest.java @@ -106,23 +106,23 @@ public void testManagedLocationsSimpleCreateAndCleanup() { @Test public void testManagedLocationsNamedCreateAndCleanup() { - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(0)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(0)); Asserts.assertThat(mgmt.getCatalog().getCatalogItems(), CollectionFunctionals.sizeEquals(0)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); - mgmt.getLocationRegistry().updateDefinedLocation( new BasicLocationDefinition("lh1", "localhost", null) ); + mgmt.getLocationRegistry().updateDefinedLocationNonPersisted( new BasicLocationDefinition("lh1", "localhost", null) ); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); // currently such defined locations do NOT appear in catalog -- see CatalogYamlLocationTest Asserts.assertThat(mgmt.getCatalog().getCatalogItems(), CollectionFunctionals.sizeEquals(0)); Location loc = mgmt.getLocationRegistry().getLocationManaged("lh1"); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(1)); mgmt.getLocationManager().unmanage(loc); - Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations().keySet(), CollectionFunctionals.sizeEquals(1)); + Asserts.assertThat(mgmt.getLocationRegistry().getDefinedLocations(true).keySet(), CollectionFunctionals.sizeEquals(1)); Asserts.assertThat(locationManager.getLocations(), CollectionFunctionals.sizeEquals(0)); } diff --git a/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java b/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java index bd27f814ab..b9c8d5ac95 100644 --- a/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/location/LocationRegistryTest.java @@ -53,9 +53,9 @@ public void testNamedLocationsPropertyDefinedLocations() { properties.put("brooklyn.location.named.foo", "byon:(hosts=\"root@192.168.1.{1,2,3,4}\")"); properties.put("brooklyn.location.named.foo.privateKeyFile", "~/.ssh/foo.id_rsa"); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("foo properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("foo properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo"); - Assert.assertNotNull(locdef, "Expected 'foo' location; but had "+mgmt.getLocationRegistry().getDefinedLocations()); + Assert.assertNotNull(locdef, "Expected 'foo' location; but had "+mgmt.getLocationRegistry().getDefinedLocations(true)); Assert.assertEquals(locdef.getConfig().get("privateKeyFile"), "~/.ssh/foo.id_rsa"); } @@ -67,7 +67,7 @@ public void testResolvesByNamedAndId() { mgmt = LocalManagementContextForTests.newInstance(properties); locdef = mgmt.getLocationRegistry().getDefinedLocationByName("foo"); - log.info("testResovlesBy has defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("testResovlesBy has defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); LocationSpec ls = mgmt.getLocationRegistry().getLocationSpec("named:foo").get(); Location l = mgmt.getLocationManager().createLocation(ls); @@ -127,7 +127,7 @@ public void testCircularReference() { BrooklynProperties properties = BrooklynProperties.Factory.newEmpty(); properties.put("brooklyn.location.named.bar", "named:bar"); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("bar properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("bar properties gave defined locations: "+mgmt.getLocationRegistry().getDefinedLocations(true)); try { mgmt.getLocationRegistry().getLocationSpec("bar").get(); Asserts.shouldHaveFailedPreviously("Circular reference gave a location"); @@ -137,7 +137,7 @@ public void testCircularReference() { } protected boolean findLocationMatching(String regex) { - for (LocationDefinition d: mgmt.getLocationRegistry().getDefinedLocations().values()) { + for (LocationDefinition d: mgmt.getLocationRegistry().getDefinedLocations(true).values()) { if (d.getName()!=null && d.getName().matches(regex)) return true; } return false; @@ -159,8 +159,7 @@ public void testLocalhostNotPresentByDefault() { properties.put(LocalhostLocationResolver.LOCALHOST_ENABLED.getName(), false); mgmt = LocalManagementContextForTests.newInstance(properties); - log.info("RESOLVERS: "+mgmt.getLocationRegistry().getDefinedLocations()); - log.info("DEFINED LOCATIONS: "+mgmt.getLocationRegistry().getDefinedLocations()); + log.info("DEFINED LOCATIONS: "+mgmt.getLocationRegistry().getDefinedLocations(true)); Assert.assertFalse( findLocationMatching("localhost") ); } diff --git a/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java b/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java index dc4000bced..e35517d3d8 100644 --- a/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java +++ b/launcher-common/src/main/java/org/apache/brooklyn/launcher/command/support/CloudExplorerSupport.java @@ -104,7 +104,7 @@ public Void call() throws Exception { locs.add(loc); } else if (allLocations) { // Find all named locations that point at different target clouds - Map definedLocations = managementContext.getLocationRegistry().getDefinedLocations(); + Map definedLocations = managementContext.getLocationRegistry().getDefinedLocations(true); for (LocationDefinition locationDef : definedLocations.values()) { Location loc = managementContext.getLocationManager().createLocation( diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java index 55159facaf..052114a67c 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java @@ -83,7 +83,7 @@ public LocationSummary apply(LocationDefinition l) { } } }; - return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations().values()) + return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations(true).values()) .transform(transformer) .filter(LocationSummary.class) .toSortedList(nameOrSpecComparator()); diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java index c61c3955bd..d4a484d929 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java @@ -180,7 +180,7 @@ public void testGetLocationConfig() { @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" }) @Deprecated public void testDeleteLocation() { - final int size = getLocationRegistry().getDefinedLocations().size(); + final int size = getLocationRegistry().getDefinedLocations(true).size(); URI expectedLocationUri = URI.create("/locations/"+legacyLocationName); Response response = client().path(expectedLocationUri).delete(); @@ -188,7 +188,7 @@ public void testDeleteLocation() { Asserts.succeedsEventually(new Runnable() { @Override public void run() { - assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1); + assertEquals(getLocationRegistry().getDefinedLocations(true).size(), size - 1); } }); } From 7a42cf87da84483277e1fdbb0d158e89c07af877 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 3 Jul 2017 17:09:16 +0100 Subject: [PATCH 19/33] support templates for RegisteredType instances, using tags --- .../brooklyn/api/objs/BrooklynObjectType.java | 1 + .../api/typereg/BrooklynTypeRegistry.java | 4 +- .../internal/BasicBrooklynCatalog.java | 98 +++++++++++++------ .../brooklyn/core/mgmt/BrooklynTags.java | 3 + .../core/typereg/RegisteredTypes.java | 5 + 5 files changed, 78 insertions(+), 33 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java index eaf1754ed4..f4a175648c 100644 --- a/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java +++ b/api/src/main/java/org/apache/brooklyn/api/objs/BrooklynObjectType.java @@ -95,6 +95,7 @@ public static BrooklynObjectType of(Class objectTypeOrSpecType) { } public static BrooklynObjectType of(CatalogItemType t) { + if (t==null) return null; switch (t) { case ENRICHER: return BrooklynObjectType.ENRICHER; case ENTITY: return BrooklynObjectType.ENTITY; diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java index 96b0bc60f3..4d5bbcae2e 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/BrooklynTypeRegistry.java @@ -38,7 +38,9 @@ public enum RegisteredTypeKind { /** a registered type which will create the java type described */ BEAN, /** a partially registered type which requires subsequent validation and changing the kind; - * until then, an item of this kind cannot be instantiated */ + * until then, an item of this kind cannot be instantiated. + * items registered as templates (using a tag) may remain in this state + * if they do not resolve. */ UNRESOLVED // note: additional kinds should have the visitor in core/RegisteredTypeKindVisitor updated // to flush out all places which want to implement support for all kinds diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 40655bc832..f718180640 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -43,9 +43,8 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; +import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.objs.BrooklynObject; @@ -56,7 +55,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.CatalogPredicates; import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes; -import org.apache.brooklyn.core.location.BasicLocationRegistry; +import org.apache.brooklyn.core.mgmt.BrooklynTags; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.mgmt.internal.CampYamlParser; @@ -869,22 +868,34 @@ private void collectCatalogItemsFromItemMetadataBlock(String sourceYaml, Managed // just need TODO to make sure we delete previously-persisted things which now come through this path.) // NB: when everything is a bundle and we've removed all scanning then this can be the _only_ path // and code can be massively simpler + // TODO allow these to be set in catalog.bom ? + List aliases = MutableList.of(); + List tags = MutableList.of(); + Boolean catalogDisabled = null; + + MutableList superTypes = MutableList.of(); + + if (itemType==CatalogItemType.TEMPLATE) { + tags.add(BrooklynTags.CATALOG_TEMPLATE); + itemType = CatalogItemType.ENTITY; + superTypes.add(Application.class); + } if (resolutionError!=null) { - if (requireValidation) { - throw Exceptions.propagate(resolutionError); + if (!tags.contains(BrooklynTags.CATALOG_TEMPLATE)) { + if (requireValidation) { + throw Exceptions.propagate(resolutionError); + } + // warn? add as "unresolved" ? just do nothing? } - // warn? add as "unresolved" ? just do nothing? } - String format = null; // could support specifying format - // TODO if kind and supertype is known, set those here - Class javaType = null; - List superTypes = MutableList.of().appendIfNotNull(javaType); + String format = null; // could support specifying format? - // TODO allow these to be set in catalog.bom ? - List aliases = MutableList.of(); - List tags = MutableList.of(); - Boolean catalogDisabled = null; + if (itemType!=null) { + // if supertype is known, set it here; + // we don't set kind (spec) because that is inferred from the supertype type + superTypes.appendIfNotNull(BrooklynObjectType.of(itemType).getInterfaceType()); + } if (version==null) { // use this as default version when nothing specified @@ -1106,12 +1117,15 @@ public PlanInterpreterGuessingType reconstruct() { } else { attemptType(null, CatalogItemType.ENTITY); - + List oldEntityErrors = MutableList.copyOf(entityErrors); + // try with services key attemptType("services", CatalogItemType.ENTITY); entityErrors.removeAll(oldEntityErrors); entityErrors.addAll(oldEntityErrors); - // prefer errors when wrapped in services block + // errors when wrapped in services block are better currently + // as we parse using CAMP and need that + // so prefer those for now (may change with YOML) attemptType(POLICIES_KEY, CatalogItemType.POLICY); attemptType(ENRICHERS_KEY, CatalogItemType.ENRICHER); @@ -1447,6 +1461,10 @@ public Map> validateTypes(Iterable validateType(RegisteredType typeToValidate) { ReferenceWithError result = resolve(typeToValidate); if (result.hasError()) { + if (RegisteredTypes.isTemplate(typeToValidate)) { + // ignore for templates + return Collections.emptySet(); + } if (result.getError() instanceof CompoundRuntimeException) { return ((CompoundRuntimeException)result.getError()).getAllCauses(); } @@ -1456,7 +1474,7 @@ public Collection validateType(RegisteredType typeToValidate) { ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(result.get(), true); return Collections.emptySet(); } - + /** * Resolves the given object with respect to the catalog. Returns any errors found while trying to resolve. * The argument may be changed (e.g. its kind set, supertypes set), and normal usage is to add @@ -1491,11 +1509,8 @@ public ReferenceWithError resolve(RegisteredType typeToValidate) } } - // TODO filter what we try based on kind, - // and set things based on declared itemType; - // also support itemType spec (generic) and bean to help filter - - // TODO support "template" (never instantiable) in registry + // could filter what we try based on kind; also could support itemType spec (generic) and + // more importantly bean to allow arbitrary types to be added to catalog RegisteredType resultT = null; @@ -1508,6 +1523,7 @@ public ReferenceWithError resolve(RegisteredType typeToValidate) } catch (Exception e) { Exceptions.propagateIfFatal(e); specError = e; + resultT = null; } } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -1522,6 +1538,7 @@ public ReferenceWithError resolve(RegisteredType typeToValidate) } catch (Exception e) { Exceptions.propagateIfFatal(e); beanError = e; + resultT = null; } } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -1537,24 +1554,40 @@ public ReferenceWithError resolve(RegisteredType typeToValidate) yaml, null, CatalogItemDtoAbstract.parseLibraries( typeToValidate.getLibraries() ), null); guesser.reconstruct(); guesserErrors.addAll(guesser.getErrors()); + if (guesser.isResolved()) { + // guesser resolved, but we couldn't create; did guesser change something? + CatalogItemType ciType = guesser.getCatalogItemType(); - if (ciType==CatalogItemType.TEMPLATE) { - // TODO templates in registry - throw new IllegalStateException("Templates not yet supported in registry"); - - } else if (boType==null) { + // try this even for templates; errors in them will be ignored by validator + // but might be interesting to someone calling resolve directly + + boolean changedSomething = false; + // reset resultT and change things as needed based on guesser + resultT = typeToValidate; + if (boType==null) { + // guesser inferred a type boType = BrooklynObjectType.of(ciType); if (boType!=null) { supers = MutableSet.copyOf(supers); supers.add(boType.getInterfaceType()); // didn't know type before, retry now that we know the type - resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate); + resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, resultT); RegisteredTypes.addSuperTypes(resultT, supers); - RegisteredTypes.changePlan(resultT, - new BasicTypeImplementationPlan(null /* CampTypePlanTransformer.FORMAT */, guesser.getPlanYaml())); - return resolve(resultT); + changedSomething = true; } + } + + if (!Objects.equal(guesser.getPlanYaml(), yaml)) { + RegisteredTypes.changePlan(resultT, + new BasicTypeImplementationPlan(null /* CampTypePlanTransformer.FORMAT */, guesser.getPlanYaml())); + changedSomething = true; + } + + if (changedSomething) { + // try again with new plan or supertype info + return resolve(resultT); + } else if (Objects.equal(boType, BrooklynObjectType.of(ciType))) { if (specError==null) { throw new IllegalStateException("Guesser resolved but TypeRegistry couldn't create"); @@ -1571,9 +1604,10 @@ public ReferenceWithError resolve(RegisteredType typeToValidate) } catch (Exception e) { Exceptions.propagateIfFatal(e); guesserErrors.add(e); + resultT = null; } - if (resultO!=null) { + if (resultO!=null && resultT!=null) { if (resultO instanceof BrooklynObject) { // if it was a bean that points at a BO then switch it to a spec and try to re-validate return resolve(RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate)); diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java index c8248f12d8..001d8193f0 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java @@ -39,6 +39,9 @@ public class BrooklynTags { public static final String NOTES_KIND = "notes"; public static final String OWNER_ENTITY_ID = "owner_entity_id"; public static final String ICON_URL = "icon_url"; + /** tag on a registered type indicating that an item is intended to be used as a template, + * and does not have to resolve */ + public static final Object CATALOG_TEMPLATE = "catalog_template"; public static class NamedStringTag implements Serializable { private static final long serialVersionUID = 7932098757009051348L; diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index eb761ad34d..d0edcb12af 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -584,4 +584,9 @@ public static RegisteredType changePlan(RegisteredType type, TypeImplementationP return type; } + public static boolean isTemplate(RegisteredType type) { + if (type==null) return false; + return type.getTags().contains(BrooklynTags.CATALOG_TEMPLATE); + } + } From 694ac1dc258e226f9f6d5ff3e13909becec58cb3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 4 Jul 2017 18:04:41 +0100 Subject: [PATCH 20/33] use more forgiving checksum routine, ignoring dates and order in zip file --- .../core/mgmt/ha/OsgiArchiveInstaller.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index a634230373..1b06463c8f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -23,6 +23,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; @@ -36,6 +39,7 @@ import org.apache.brooklyn.core.typereg.BasicManagedBundle; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.osgi.BundleMaker; import org.apache.brooklyn.util.core.osgi.Osgis; @@ -280,7 +284,7 @@ public ReferenceWithError install() { if (result.code!=null) return ReferenceWithError.newInstanceWithoutError(result); assert inferredMetadata.isNameResolved() : "Should have resolved "+inferredMetadata; assert inferredMetadata instanceof BasicManagedBundle : "Only BasicManagedBundles supported"; - ((BasicManagedBundle)inferredMetadata).setChecksum(Streams.getMd5Checksum(new FileInputStream(zipFile))); + ((BasicManagedBundle)inferredMetadata).setChecksum(getChecksum(new ZipFile(zipFile))); final boolean updating; result.metadata = osgiManager.getManagedBundle(inferredMetadata.getVersionedName()); @@ -310,7 +314,7 @@ public ReferenceWithError install() { log.warn("Missing bundle checksum data for "+result+"; assuming bundle replacement is permitted"); } else if (!Objects.equal(result.getMetadata().getChecksum(), inferredMetadata.getChecksum())) { throw new IllegalArgumentException("Bundle "+result.getMetadata().getVersionedName()+" already installed; " - + "cannot install a different bundle at a same non-snapshot version"); + + "cannot install a different bundle at a same non-snapshot version"); } result.setIgnoringAlreadyInstalled(); return ReferenceWithError.newInstanceWithoutError(result); @@ -422,6 +426,21 @@ public void run() { } } + private static String getChecksum(ZipFile zf) { + // checksum should ignore time/date stamps on files - just look at entries and contents. also ignore order. + // (tests fail without time/date is one reason, but really if a person rebuilds a ZIP that is the same + // files we should treat it as identical) + try { + Map entriesToChecksum = MutableMap.of(); + for (ZipEntry ze: Collections.list(zf.entries())) { + entriesToChecksum.put(ze.getName(), Streams.getMd5Checksum(zf.getInputStream(ze))); + } + return Streams.getMd5Checksum(Streams.newInputStreamWithContents(new TreeMap<>(entriesToChecksum).toString())); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + private boolean canUpdate() { // only update if forced, or it's a snapshot for which a byte stream is supplied // (IE don't update a snapshot verison every time its URL is referenced in a 'libraries' section) From d7ebb6bb8c97cd5c5024bdd22d342577ebd7e3fd Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 4 Jul 2017 18:09:36 +0100 Subject: [PATCH 21/33] switch REST API to use TypeRegistry methods (but not yet changing API) we could have a much nicer API around bundles and type registry -- but to keep this PR minimal we've just done backwards compatibility here. (we have to go through type registry however as we are no longer adding things to the "catalog"; it gets added to "type registry".) --- .../typereg/RegisteredTypePredicates.java | 55 +++++- .../core/typereg/RegisteredTypes.java | 30 ++- .../apache/brooklyn/rest/api/CatalogApi.java | 3 +- .../rest/resources/CatalogResource.java | 116 +++++------ .../rest/transform/CatalogTransformer.java | 180 +++++++++++++++++- .../rest/transform/LocationTransformer.java | 9 +- .../rest/util/BrooklynRestResourceUtils.java | 5 + .../resources/ApplicationResourceTest.java | 28 +-- .../rest/resources/CatalogResourceTest.java | 57 +++++- .../rest/resources/LocationResourceTest.java | 6 +- 10 files changed, 376 insertions(+), 113 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java index ce54fef10b..0bf3490fb6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.typereg; +import static com.google.common.base.Preconditions.checkNotNull; + import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Application; @@ -31,18 +33,23 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.util.collections.CollectionFunctionals; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.osgi.VersionedName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; public class RegisteredTypePredicates { + private static final Logger log = LoggerFactory.getLogger(RegisteredTypePredicates.class); + public static Predicate deprecated(final boolean deprecated) { return new DeprecatedEqualTo(deprecated); } - private static class DeprecatedEqualTo implements Predicate { private final boolean deprecated; @@ -58,7 +65,6 @@ public boolean apply(@Nullable RegisteredType item) { public static Predicate disabled(boolean disabled) { return new DisabledEqualTo(disabled); } - private static class DisabledEqualTo implements Predicate { private final boolean disabled; @@ -70,6 +76,21 @@ public boolean apply(@Nullable RegisteredType item) { return (item != null) && item.isDisabled() == disabled; } } + + public static Predicate template(final boolean template) { + return new TemplateTagPresent(template); + } + private static class TemplateTagPresent implements Predicate { + private final boolean present; + + public TemplateTagPresent(boolean present) { + this.present = present; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + return (item != null) && RegisteredTypes.isTemplate(item) == present; + } + } public static final Function ID_OF_ITEM_TRANSFORMER = new IdOfItemTransformer(); @@ -279,4 +300,34 @@ public boolean apply(@Nullable RegisteredType item) { } } + @Beta // expensive way to compare everything; API likely to change to be clearer + public static Predicate stringRepresentationMatches(Predicate filter) { + return new StringRepresentationMatches<>(checkNotNull(filter, "filter")); + } + private static class StringRepresentationMatches implements Predicate { + private final Predicate filter; + StringRepresentationMatches(final Predicate filter) { + this.filter = filter; + } + @Override + public boolean apply(@Nullable RegisteredType item) { + try { + String thingToCompare = + item.getVersionedName().toString()+"\n"+ + item.getVersionedName().toOsgiString()+"\n"+ + item.getTags()+"\n"+ + item.getDisplayName()+"\n"+ + item.getAliases()+"\n"+ + item.getDescription()+"\n"+ + RegisteredTypes.getImplementationDataStringForSpec(item); + return filter.apply(thingToCompare); + } catch (Exception e) { + // If we propagated exceptions, then we'd risk aborting the checks for other catalog items. + // Play it safe, in case there's something messed up with just one catalog item. + Exceptions.propagateIfFatal(e); + log.warn("Problem producing string representation of "+item+"; assuming no match, and continuing", e); + return false; + } + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java index d0edcb12af..6519edfa76 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java @@ -30,6 +30,7 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.rebind.RebindSupport; @@ -118,6 +119,9 @@ public static RegisteredType of(CatalogItem item) { if (item.getLibraries()!=null) type.bundles.addAll(item.getLibraries()); // aliases aren't on item if (item.tags()!=null) type.tags.addAll(item.tags().getTags()); + if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) { + type.tags.add(BrooklynTags.CATALOG_TEMPLATE); + } // these things from item we ignore: javaType, specType, registeredTypeName ... return type; @@ -456,23 +460,39 @@ private static boolean isSubtypeOf(Class candidate, RegisteredType type) { public static RegisteredType getBestVersion(Iterable types) { if (types==null || !types.iterator().hasNext()) return null; - return Ordering.from(RegisteredTypeComparator.INSTANCE).max(types); + return Ordering.from(RegisteredTypeNameThenBestFirstComparator.INSTANCE).min(types); } - public static class RegisteredTypeComparator implements Comparator { - public static Comparator INSTANCE = new RegisteredTypeComparator(); - private RegisteredTypeComparator() {} + /** by name, then with disabled, deprecated first, then by increasing version */ + public static class RegisteredTypeNameThenWorstFirstComparator implements Comparator { + public static Comparator INSTANCE = new RegisteredTypeNameThenWorstFirstComparator(); + private RegisteredTypeNameThenWorstFirstComparator() {} @Override public int compare(RegisteredType o1, RegisteredType o2) { return ComparisonChain.start() + .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) .compareTrueFirst(o1.isDisabled(), o2.isDisabled()) .compareTrueFirst(o1.isDeprecated(), o2.isDeprecated()) - .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) .compare(o1.getVersion(), o2.getVersion(), VersionComparator.INSTANCE) .result(); } } + /** by name, then with disabled, deprecated first, then by increasing version */ + public static class RegisteredTypeNameThenBestFirstComparator implements Comparator { + public static Comparator INSTANCE = new RegisteredTypeNameThenBestFirstComparator(); + private RegisteredTypeNameThenBestFirstComparator() {} + @Override + public int compare(RegisteredType o1, RegisteredType o2) { + return ComparisonChain.start() + .compare(o1.getSymbolicName(), o2.getSymbolicName(), NaturalOrderComparator.INSTANCE) + .compareFalseFirst(o1.isDisabled(), o2.isDisabled()) + .compareFalseFirst(o1.isDeprecated(), o2.isDeprecated()) + .compare(o2.getVersion(), o1.getVersion(), VersionComparator.INSTANCE) + .result(); + } + } + /** validates that the given object (required) satisfies the constraints implied by the given * type and context object, using {@link Maybe} as the result set absent containing the error(s) * if not satisfied. returns an {@link Absent} if failed with details of the error, diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java index 8e1f5d8146..698f97fb56 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java @@ -233,9 +233,10 @@ public List listEntities( @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions); + // bad name - it is just templates @GET @Path("/applications") - @ApiOperation(value = "Fetch a list of application templates optionally matching a query", + @ApiOperation(value = "Fetch a list of templates (for applications) optionally matching a query", response = CatalogItemSummary.class, responseContainer = "List") public List listApplications( diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java index 6983e5d587..0133962ffa 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java @@ -35,25 +35,15 @@ import javax.ws.rs.core.UriInfo; import org.apache.brooklyn.api.catalog.CatalogItem; -import org.apache.brooklyn.api.entity.Application; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntitySpec; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.api.policy.Policy; -import org.apache.brooklyn.api.policy.PolicySpec; -import org.apache.brooklyn.api.sensor.Enricher; -import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.core.catalog.CatalogPredicates; -import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; +import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.rest.api.CatalogApi; import org.apache.brooklyn.rest.domain.ApiError; import org.apache.brooklyn.rest.domain.CatalogEnricherSummary; @@ -94,11 +84,10 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat private static final Logger log = LoggerFactory.getLogger(CatalogResource.class); private static final String LATEST = "latest"; - @SuppressWarnings("rawtypes") - private Function toCatalogItemSummary(final UriInfo ui) { - return new Function() { + private Function toCatalogItemSummary(final UriInfo ui) { + return new Function() { @Override - public CatalogItemSummary apply(@Nullable CatalogItem input) { + public CatalogItemSummary apply(@Nullable RegisteredType input) { return CatalogTransformer.catalogItemSummary(brooklyn(), input, ui.getBaseUriBuilder()); } }; @@ -138,6 +127,7 @@ public Response create(String yaml, boolean forceUpdate) { return createFromYaml(yaml, forceUpdate); } + @SuppressWarnings("deprecation") @Override public Response createFromYaml(String yaml, boolean forceUpdate) { if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) { @@ -147,7 +137,13 @@ public Response createFromYaml(String yaml, boolean forceUpdate) { try { final Iterable> items = brooklyn().getCatalog().addItems(yaml, forceUpdate); - return buildCreateResponse(items); + List itemsRT = MutableList.of(); + for (CatalogItem ci: items) { + RegisteredType rt = brooklyn().getTypeRegistry().get(ci.getId()); + if (rt!=null) itemsRT.add(rt); + else itemsRT.add(RegisteredTypes.of(ci)); + } + return buildCreateResponse(itemsRT); } catch (Exception e) { Exceptions.propagateIfFatal(e); return badRequest(e); @@ -167,7 +163,6 @@ public String getMessage() { return message; } - @SuppressWarnings("deprecation") public static BundleInstallationRestResult of(OsgiBundleInstallationResult in, ManagementContext mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) { BundleInstallationRestResult result = new BundleInstallationRestResult(); result.message = in.getMessage(); @@ -176,10 +171,7 @@ public static BundleInstallationRestResult of(OsgiBundleInstallationResult in, M if (in.getCatalogItemsInstalled()!=null) { result.types = MutableMap.of(); for (String id: in.getCatalogItemsInstalled()) { - // TODO prefer to use RegisteredType, but we need transformer for those in REST - //RegisteredType ci = mgmt.getTypeRegistry().get(id); - - CatalogItem ci = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id); + RegisteredType ci = mgmt.getTypeRegistry().get(id); CatalogItemSummary summary = CatalogTransformer.catalogItemSummary(brooklynU, ci, ui.getBaseUriBuilder()); result.types.put(id, summary); } @@ -217,12 +209,12 @@ public Response createFromArchive(byte[] zipInput, boolean detail, boolean force return Response.status(Status.CREATED).entity( detail ? resultR : resultR.types ).build(); } - private Response buildCreateResponse(Iterable> catalogItems) { + private Response buildCreateResponse(Iterable catalogItems) { log.info("REST created catalog items: "+catalogItems); Map result = MutableMap.of(); - for (CatalogItem catalogItem: catalogItems) { + for (RegisteredType catalogItem: catalogItems) { try { result.put( catalogItem.getId(), @@ -303,10 +295,10 @@ public void deleteLocation(String locationId, String version) throws Exception { @Override public List listEntities(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_ENTITY, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_ENTITY, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogEntitySummary.class); } @@ -314,11 +306,11 @@ public List listEntities(String regex, String fragment, bo @Override public List listApplications(String regex, String fragment, boolean allVersions) { @SuppressWarnings("unchecked") - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_TEMPLATE, - CatalogPredicates.>deprecated(false), - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.template(true), + RegisteredTypePredicates.deprecated(false), + RegisteredTypePredicates.disabled(false)); return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); } @@ -331,12 +323,7 @@ public CatalogEntitySummary getEntity(String symbolicName, String version) { version = processVersion(version); - //TODO These casts are not pretty, we could just provide separate get methods for the different types? - //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>) brooklyn().getCatalog().getCatalogItem(symbolicName, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(symbolicName, version); if (result==null) { throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version); } @@ -351,10 +338,10 @@ public CatalogEntitySummary getApplication(String symbolicName, String version) @Override public List listPolicies(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_POLICY, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_POLICY, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogPolicySummary.class); } @@ -367,11 +354,7 @@ public CatalogPolicySummary getPolicy(String policyId, String version) throws Ex } version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(policyId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(policyId, version); if (result==null) { throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version); } @@ -381,10 +364,10 @@ public CatalogPolicySummary getPolicy(String policyId, String version) throws Ex @Override public List listLocations(String regex, String fragment, boolean allVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_LOCATION, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_LOCATION, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions); return castList(result, CatalogLocationSummary.class); } @@ -397,11 +380,7 @@ public CatalogLocationSummary getLocation(String locationId, String version) thr } version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(locationId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(locationId, version); if (result==null) { throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); } @@ -410,22 +389,22 @@ public CatalogLocationSummary getLocation(String locationId, String version) thr } @SuppressWarnings({ "unchecked", "rawtypes" }) - private List getCatalogItemSummariesMatchingRegexFragment(Predicate> type, String regex, String fragment, boolean allVersions) { - List filters = new ArrayList(); + private List getCatalogItemSummariesMatchingRegexFragment( + Predicate type, String regex, String fragment, boolean allVersions) { + List> filters = new ArrayList(); filters.add(type); if (Strings.isNonEmpty(regex)) - filters.add(CatalogPredicates.stringRepresentationMatches(StringPredicates.containsRegex(regex))); + filters.add(RegisteredTypePredicates.stringRepresentationMatches(StringPredicates.containsRegex(regex))); if (Strings.isNonEmpty(fragment)) - filters.add(CatalogPredicates.stringRepresentationMatches(StringPredicates.containsLiteralIgnoreCase(fragment))); + filters.add(RegisteredTypePredicates.stringRepresentationMatches(StringPredicates.containsLiteralIgnoreCase(fragment))); if (!allVersions) - filters.add(CatalogPredicates.isBestVersion(mgmt())); + filters.add(RegisteredTypePredicates.isBestVersion(mgmt())); - filters.add(CatalogPredicates.entitledToSee(mgmt())); + filters.add(RegisteredTypePredicates.entitledToSee(mgmt())); - ImmutableList> sortedItems = - FluentIterable.from(brooklyn().getCatalog().getCatalogItems()) - .filter(Predicates.and(filters)) - .toSortedList(CatalogItemComparator.getInstance()); + ImmutableList sortedItems = + FluentIterable.from(brooklyn().getTypeRegistry().getMatching(Predicates.and(filters))) + .toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE); return Lists.transform(sortedItems, toCatalogItemSummary(ui)); } @@ -463,10 +442,10 @@ public void setDisabled(String itemId, boolean disabled) { @Override public List listEnrichers(@ApiParam(name = "regex", value = "Regular expression to search for") @DefaultValue("") String regex, @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @DefaultValue("") String fragment, @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)") @DefaultValue("false") boolean includeAllVersions) { - Predicate>> filter = + Predicate filter = Predicates.and( - CatalogPredicates.IS_ENRICHER, - CatalogPredicates.>disabled(false)); + RegisteredTypePredicates.IS_ENRICHER, + RegisteredTypePredicates.disabled(false)); List result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, includeAllVersions); return castList(result, CatalogEnricherSummary.class); } @@ -477,13 +456,8 @@ public CatalogEnricherSummary getEnricher(@ApiParam(name = "enricherId", value = throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry", Entitlements.getEntitlementContext().user()); } - version = processVersion(version); - - @SuppressWarnings("unchecked") - CatalogItem> result = - (CatalogItem>)brooklyn().getCatalog().getCatalogItem(enricherId, version); - + RegisteredType result = brooklyn().getTypeRegistry().get(enricherId, version); if (result==null) { throw WebResourceUtils.notFound("Enricher with id '%s:%s' not found", enricherId, version); } diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java index 4259120c57..b1ec6e560b 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.Application; import javax.ws.rs.core.UriBuilder; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -41,9 +42,12 @@ import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.entity.EntityDynamicType; import org.apache.brooklyn.core.mgmt.BrooklynTags; import org.apache.brooklyn.core.objs.BrooklynTypes; +import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.rest.api.CatalogApi; import org.apache.brooklyn.rest.domain.CatalogEnricherSummary; import org.apache.brooklyn.rest.domain.CatalogEntitySummary; @@ -67,11 +71,177 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -@SuppressWarnings("deprecation") public class CatalogTransformer { private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class); + + public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + Set config = Sets.newLinkedHashSet(); + Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); + Set effectors = Sets.newTreeSet(SummaryComparators.nameComparator()); + + EntitySpec spec = null; + + try { + spec = b.getTypeRegistry().createSpec(item, null, EntitySpec.class); + EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType()); + EntityType type = typeMap.getSnapshot(); + + AtomicInteger paramPriorityCnt = new AtomicInteger(); + for (SpecParameter input: spec.getParameters()) + config.add(EntityTransformer.entityConfigSummary(input, paramPriorityCnt)); + for (Sensor x: type.getSensors()) + sensors.add(SensorTransformer.sensorSummaryForCatalog(x)); + for (Effector x: type.getEffectors()) + effectors.add(EffectorTransformer.effectorSummaryForCatalog(x)); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + + // templates with multiple entities can't have spec created in the manner above; just ignore + if (item.getSuperTypes().contains(Entity.class)) { + log.warn("Unable to create spec for "+item+": "+e, e); + } + if (log.isTraceEnabled()) { + log.trace("Unable to create spec for "+item+": "+e, e); + } + } + + return new CatalogEntitySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + spec!=null ? + CatalogItemType.ofTargetClass(spec.getType()).name() : + // RegisteredTypes.isTemplate(item) ? "template" : // could check this, but more reliable for clients to rely on tag + "unknown", + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), + makeTags(spec, item), config, sensors, effectors, + item.isDeprecated(), makeLinks(item, ub)); + } + public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + try { + if (item.getSuperTypes().contains(Application.class) || + item.getSuperTypes().contains(Entity.class)) { + return catalogEntitySummary(b, item, ub); + } else if (item.getSuperTypes().contains(Policy.class)) { + return catalogPolicySummary(b, item, ub); + } else if (item.getSuperTypes().contains(Enricher.class)) { + return catalogEnricherSummary(b, item, ub); + } else if (item.getSuperTypes().contains(Location.class)) { + return catalogLocationSummary(b, item, ub); + } else { + log.debug("Misc catalog item type when getting self link (supplying generic item): "+item+" "+item.getSuperTypes()); + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Invalid item in catalog when converting REST summaries (supplying generic item), at "+item+": "+e, e); + } + return new CatalogItemSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getSuperTypes().toString(), + item.getKind()==RegisteredTypeKind.BEAN ? "bean" : "unknown", + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + final Set config = Sets.newLinkedHashSet(); + PolicySpec spec = null; + try{ + spec = b.getTypeRegistry().createSpec(item, null, PolicySpec.class); + for (final SpecParameter input : spec.getParameters()){ + config.add(EntityTransformer.policyConfigSummary(input)); + } + }catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.trace("Unable to create policy spec for "+item+": "+e, e); + } + return new CatalogPolicySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + CatalogItemType.POLICY.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + final Set config = Sets.newLinkedHashSet(); + EnricherSpec spec = null; + try{ + spec = b.getTypeRegistry().createSpec(item, null, EnricherSpec.class); + for (final SpecParameter input : spec.getParameters()){ + config.add(EntityTransformer.enricherConfigSummary(input)); + } + }catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.trace("Unable to create policy spec for "+item+": "+e, e); + } + return new CatalogEnricherSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + spec!=null ? spec.getType().getName() : item.getSuperTypes().toString(), + CatalogItemType.ENRICHER.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) { + Set config = ImmutableSet.of(); + return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getSuperTypes().toString(), + CatalogItemType.LOCATION.toString(), + RegisteredTypes.getImplementationDataStringForSpec(item), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), config, + item.getTags(), item.isDeprecated(), makeLinks(item, ub)); + } + + protected static Map makeLinks(RegisteredType item, UriBuilder ub) { + return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); + } + + protected static URI getSelfLink(RegisteredType item, UriBuilder ub) { + String itemId = item.getId(); + if (item.getSuperTypes().contains(Application.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getApplication").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Entity.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Policy.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getPolicy").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Enricher.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getEnricher").build(itemId, item.getVersion()); + } else if (item.getSuperTypes().contains(Location.class)) { + return serviceUriBuilder(ub, CatalogApi.class, "getLocation").build(itemId, item.getVersion()); + } else { + log.warn("Unexpected catalog item type when getting self link (not supplying self link): "+item+" "+item.getSuperTypes()); + return null; + } + } + private static String tidyIconLink(BrooklynRestResourceUtils b, RegisteredType item, String iconUrl, UriBuilder ub) { + if (b.isUrlServerSideAndSafe(iconUrl)) { + return serviceUriBuilder(ub, CatalogApi.class, "getIcon").build(item.getSymbolicName(), item.getVersion()).toString(); + } + return iconUrl; + } + + private static Set makeTags(EntitySpec spec, RegisteredType item) { + // Combine tags on item with an InterfacesTag. + Set tags = MutableSet.copyOf(item.getTags()); + if (spec != null) { + Class type; + if (spec.getImplementation() != null) { + type = spec.getImplementation(); + } else { + type = spec.getType(); + } + if (type != null) { + tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type))); + } + } + return tags; + } + + + + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { Set config = Sets.newLinkedHashSet(); Set sensors = Sets.newTreeSet(SummaryComparators.nameComparator()); @@ -111,6 +281,7 @@ public static CatalogEntitySummary catalogEntitySummary(Brook item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated @SuppressWarnings({ "unchecked", "rawtypes" }) public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem item, UriBuilder ub) { try { @@ -136,6 +307,7 @@ public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, item.getDescription(), tidyIconLink(b, item, item.getIconUrl(), ub), item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { final Set config = Sets.newLinkedHashSet(); try{ @@ -153,6 +325,7 @@ public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtil item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { final Set config = Sets.newLinkedHashSet(); try{ @@ -170,6 +343,7 @@ public static CatalogEnricherSummary catalogEnricherSummary(BrooklynRestResource item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } + /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { Set config = ImmutableSet.of(); return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), @@ -178,11 +352,11 @@ public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResource item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub)); } - protected static Map makeLinks(CatalogItem item, UriBuilder ub) { + private static Map makeLinks(CatalogItem item, UriBuilder ub) { return MutableMap.of().addIfNotNull("self", getSelfLink(item, ub)); } - protected static URI getSelfLink(CatalogItem item, UriBuilder ub) { + private static URI getSelfLink(CatalogItem item, UriBuilder ub) { String itemId = item.getId(); switch (item.getCatalogItemType()) { case TEMPLATE: diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java index fa3fb2c2db..430cbbe95f 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java @@ -25,12 +25,11 @@ import javax.ws.rs.core.UriBuilder; -import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationDefinition; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.config.Sanitizer; import org.apache.brooklyn.core.location.BasicLocationDefinition; import org.apache.brooklyn.core.location.CatalogLocationResolver; @@ -62,7 +61,6 @@ public static LocationSummary newInstance(String id, org.apache.brooklyn.rest.do return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub); } - @SuppressWarnings("unchecked") private static LocationSummary newInstance(ManagementContext mgmt, LocationSpec spec, ConfigBag explicitConfig, String optionalExplicitId, String name, String specString, @@ -91,10 +89,7 @@ private static LocationSummary newInstance(ManagementContext mgmt, CatalogLocationSummary catalogSummary = null; if (CatalogLocationResolver.isLegacyWrappedReference(specString)) { -// RegisteredType type = mgmt.getTypeRegistry().get(CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); - // TODO REST items should switch to using the RegisteredType - @SuppressWarnings({ "rawtypes", "deprecation" }) - CatalogItem ci = CatalogUtils.getCatalogItemOptionalVersion(mgmt, CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); + RegisteredType ci = mgmt.getTypeRegistry().get(CatalogLocationResolver.unwrapLegacyWrappedReference(specString)); if (ci!=null) { BrooklynRestResourceUtils br = new BrooklynRestResourceUtils(mgmt); catalogSummary = CatalogTransformer.catalogLocationSummary(br, ci, ub); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java index 66467a10cd..9e1ce53670 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent; @@ -86,6 +87,10 @@ public BrooklynRestResourceUtils(ManagementContext mgmt) { this.mgmt = mgmt; } + public BrooklynTypeRegistry getTypeRegistry() { + return mgmt.getTypeRegistry(); + } + public BrooklynCatalog getCatalog() { return mgmt.getCatalog(); } diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java index 277fd3225e..2a53cc9fee 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java @@ -32,7 +32,11 @@ import java.util.Set; import java.util.concurrent.TimeoutException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -71,7 +75,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.http.HttpAsserts; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.time.Duration; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.HttpHeaders; import org.apache.http.entity.ContentType; import org.slf4j.Logger; @@ -87,13 +91,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MultivaluedHashMap; - -import org.apache.cxf.jaxrs.client.WebClient; - @Test(singleThreaded = true, // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures suiteName = "ApplicationResourceTest") @@ -608,11 +605,7 @@ public void testDisabledApplicationCatalog() throws TimeoutException, Interrupte String itemVersion = "1.0"; String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication"; - // Deploy the catalog item - addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType); - List itemSummaries = client().path("/catalog/applications") - .query("fragment", itemSymbolicName).query("allVersions", "true").get(new GenericType>() {}); - CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries); + CatalogItemSummary itemSummary = testTemplateItem(itemSymbolicName, itemVersion, serviceType); String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion()); assertEquals(itemSummary.getId(), itemVersionedId); @@ -660,6 +653,15 @@ public void testDisabledApplicationCatalog() throws TimeoutException, Interrupte } } + protected CatalogEntitySummary testTemplateItem(String itemSymbolicName, String itemVersion, String serviceType) { + // Deploy the catalog item + addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType); + log.info("Types after adding template: "+manager.getTypeRegistry().getAll()); + List itemSummaries = client().path("/catalog/applications") + .query("fragment", itemSymbolicName).query("allVersions", "true").get(new GenericType>() {}); + return Iterables.getOnlyElement(itemSummaries); + } + private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) { String id = String.format("%s:%s", symbolicName, version); Response response = client().path(String.format("/catalog/entities/%s/deprecated", id)) diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java index faec520cf0..0a91d5994e 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java @@ -22,7 +22,8 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -import java.awt.*; +import java.awt.Image; +import java.awt.Toolkit; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -47,10 +48,13 @@ import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.objs.Configurable; import org.apache.brooklyn.api.objs.Identifiable; +import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.mgmt.ha.OsgiManager; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.enricher.stock.Aggregator; @@ -66,6 +70,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.osgi.OsgiTestResources; @@ -93,10 +98,32 @@ public class CatalogResourceTest extends BrooklynRestResourceTest { private static String TEST_VERSION = "0.1.2"; private static String TEST_LASTEST_VERSION = "0.1.3"; + private Collection initialBundles; + @Override protected boolean useLocalScannedCatalog() { return true; } + + @Override + protected void initClass() throws Exception { + super.initClass(); + // cache initially installed bundles + OsgiManager osgi = ((ManagementContextInternal)getManagementContext()).getOsgiManager().get(); + initialBundles = osgi.getManagedBundles().values(); + } + + protected void initMethod() throws Exception { + super.initMethod(); + + // and reset OSGi container + OsgiManager osgi = ((ManagementContextInternal)getManagementContext()).getOsgiManager().get(); + for (ManagedBundle b: osgi.getManagedBundles().values()) { + if (!initialBundles.contains(b)) { + osgi.uninstallUploadedBundle(b); + } + } + } @Test /** based on CampYamlLiteTest */ @@ -167,7 +194,7 @@ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCor public void testRegisterOsgiPolicyTopLevelSyntax() { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); - String symbolicName = "my.catalog.policy.id"; + String symbolicName = "my.catalog.entity.id."+JavaClassNames.niceClassAndMethod(); String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy"; String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL; @@ -214,7 +241,7 @@ public void testFilterListOfEntitiesByName() { .query("fragment", "vaNIllasOFTWAREpROCESS").get(new GenericType>() {}); assertEquals(entities.size(), 1); - log.info("RedisCluster-like entities are: " + entities); + log.info("MAtching entities are: " + entities); List entities2 = client().path("/catalog/entities") .query("regex", "[Vv]an.[alS]+oftware\\w+").get(new GenericType>() {}); @@ -963,8 +990,11 @@ public void testGetOnlyLatestApplication() { assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestApplication"}) + @Test public void testGetOnlyLatestDifferentCases() { + // depends on installation of this + testGetOnlyLatestApplication(); + String symbolicName = "latest.catalog.application.id"; CatalogItemSummary application = client().path("/catalog/applications/" + symbolicName + "/LaTeSt") @@ -1018,8 +1048,11 @@ public void testGetOnlyLatestLocation() { assertEquals(application.getVersion(), TEST_LASTEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestApplication", "testGetOnlyLatestDifferentCases"}) + @Test public void testDeleteOnlyLatestApplication() throws IOException { + // depends on installation of this + testGetOnlyLatestApplication(); + String symbolicName = "latest.catalog.application.id"; Response deleteResponse = client().path("/catalog/applications/" + symbolicName + "/latest").delete(); @@ -1031,8 +1064,11 @@ public void testDeleteOnlyLatestApplication() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestEntity"}) + @Test public void testDeleteOnlyLatestEntity() throws IOException { + // depends on installation of this + testGetOnlyLatestEntity(); + String symbolicName = "latest.catalog.entity.id"; Response deleteResponse = client().path("/catalog/entities/" + symbolicName + "/latest").delete(); @@ -1044,8 +1080,11 @@ public void testDeleteOnlyLatestEntity() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestPolicy"}) + @Test public void testDeleteOnlyLatestPolicy() throws IOException { + // depends on installation of this + testGetOnlyLatestPolicy(); + String symbolicName = "latest.catalog.policy.id"; Response deleteResponse = client().path("/catalog/policies/" + symbolicName + "/latest").delete(); @@ -1057,8 +1096,10 @@ public void testDeleteOnlyLatestPolicy() throws IOException { assertEquals(applications.get(0).getVersion(), TEST_VERSION); } - @Test(dependsOnMethods = {"testGetOnlyLatestLocation"}) + @Test public void testDeleteOnlyLatestLocation() throws IOException { + testGetOnlyLatestLocation(); + String symbolicName = "latest.catalog.location.id"; Response deleteResponse = client().path("/catalog/locations/" + symbolicName + "/latest").delete(); diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java index d4a484d929..2c2acfdf96 100644 --- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java +++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java @@ -31,6 +31,7 @@ import javax.ws.rs.core.Response; import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; import org.apache.brooklyn.core.location.LocationConfigKeys; import org.apache.brooklyn.core.location.SimulatedLocation; import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; @@ -58,7 +59,6 @@ public class LocationResourceTest extends BrooklynRestResourceTest { private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class); private String legacyLocationName = "my-jungle-legacy"; - private String legacyLocationVersion = "0.0.0.SNAPSHOT"; private String locationName = "my-jungle"; private String locationVersion = "0.1.2"; @@ -81,7 +81,7 @@ public void testAddLegacyLocationDefinition() { log.info("added legacy, at: " + addedLegacyLocationUri); LocationSummary location = client().path(response.getLocation()).get(LocationSummary.class); log.info(" contents: " + location); - assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion); + assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+BasicBrooklynCatalog.NO_VERSION); assertTrue(addedLegacyLocationUri.getPath().startsWith("/locations/")); JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().getLocationManaged(legacyLocationName); @@ -144,7 +144,7 @@ public boolean apply(@Nullable LocationSummary l) { }); LocationSummary location = Iterables.getOnlyElement(matching); - URI expectedLocationUri = URI.create(getEndpointAddress() + "/locations/"+locationName).normalize(); + URI expectedLocationUri = URI.create(getEndpointAddress() + "/locations/"+locationName+":"+locationVersion).normalize(); Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion); Assert.assertEquals(location.getLinks().get("self").toString(), expectedLocationUri.getPath()); } From f344430e46b55065e1b95bcf1a297f3395afed2a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 7 Jun 2017 23:40:09 +0100 Subject: [PATCH 22/33] better error messages and warnings around catalog item failures many caused by misleading impl of propagate(msg, err), deprecate that for a version to let us fix --- .../catalog/internal/CatalogBundleLoader.java | 2 +- .../core/catalog/internal/CatalogDo.java | 2 +- .../catalog/internal/CatalogInitialization.java | 16 ++++++++++------ .../core/catalog/internal/CatalogUtils.java | 2 +- .../persist/FileBasedStoreObjectAccessor.java | 8 ++++---- .../core/mgmt/rebind/RebindIteration.java | 10 +++++----- .../core/objs/proxy/InternalFactory.java | 2 +- .../brooklyn/util/core/ResourceUtils.java | 2 +- .../brooklyn/util/core/osgi/BundleMaker.java | 10 +++++----- .../deserializingClassRenames.properties | 2 ++ .../location/jclouds/JcloudsLocation.java | 4 ++-- .../main/java/org/apache/brooklyn/cli/Main.java | 2 +- .../brooklyn/util/exceptions/Exceptions.java | 17 +++++++++++++++-- .../util/exceptions/ExceptionsTest.java | 8 ++++---- 14 files changed, 53 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index 90bc628142..e55617a3bf 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -163,7 +163,7 @@ private String readBom(URL bom) { try (final InputStream ins = bom.openStream()) { return Streams.readFullyString(ins); } catch (IOException e) { - throw Exceptions.propagate("Error loading Catalog BOM from " + bom, e); + throw Exceptions.propagateAnnotated("Error loading Catalog BOM from " + bom, e); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java index 48529297f4..39cc8d231d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogDo.java @@ -137,7 +137,7 @@ private void loadCatalogItems(boolean failOnLoadError) { } catch (Exception e) { Exceptions.propagateIfFatal(e); if (failOnLoadError) { - Exceptions.propagate(e); + Exceptions.propagateAnnotated("Loading bundles for catalog item " + entry.getCatalogItemId() + " failed", e); } else { log.error("Loading bundles for catalog item " + entry + " failed: " + e.getMessage(), e); } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java index 45e7e7aba9..f906344101 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogInitialization.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.FatalRuntimeException; +import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.JavaClassNames; @@ -394,15 +395,18 @@ public void handleException(Throwable throwable, Object details) { if (throwable instanceof RuntimeInterruptedException) throw (RuntimeInterruptedException) throwable; - String throwableText = Exceptions.collapseText(throwable); - log.error("Error loading catalog item '"+details+"': "+throwableText); - log.debug("Trace for error loading catalog item '"+details+"': "+throwableText, throwable); + if (details instanceof CatalogItem) { + if (((CatalogItem)details).getCatalogItemId() != null) { + details = ((CatalogItem)details).getCatalogItemId(); + } + } + PropagatedRuntimeException wrap = new PropagatedRuntimeException("Error loading catalog item "+details, throwable); + log.debug("Trace for: "+wrap, wrap); - // TODO give more detail when adding - ((ManagementContextInternal)getManagementContext()).errors().add(throwable); + ((ManagementContextInternal)getManagementContext()).errors().add(wrap); if (isStartingUp && failOnStartupErrors) { - throw new FatalRuntimeException("Unable to load catalog item '"+details+"': "+throwableText, throwable); + throw new FatalRuntimeException("Unable to load catalog item "+details, wrap); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index 81cbbcf8dc..7fa363173d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -396,7 +396,7 @@ private static void addSearchItem(ManagementContext managementContext, BrooklynC if (!didSomething) { // TODO review what to do here - log.warn("Can't find catalog item " + itemId); + log.warn("Can't find catalog item " + itemId+"; ignoring, but a search path may be incomplete and other errors may follow"); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java index 2028c4383a..a8d7cf155a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/persist/FileBasedStoreObjectAccessor.java @@ -58,7 +58,7 @@ public String get() { if (!exists()) return null; return Files.asCharSource(file, Charsets.UTF_8).read(); } catch (IOException e) { - throw Exceptions.propagate("Problem reading String contents of file "+file, e); + throw Exceptions.propagateAnnotated("Problem reading String contents of file "+file, e); } } @@ -68,7 +68,7 @@ public byte[] getBytes() { if (!exists()) return null; return Files.asByteSource(file).read(); } catch (IOException e) { - throw Exceptions.propagate("Problem reading bytes of file "+file, e); + throw Exceptions.propagateAnnotated("Problem reading bytes of file "+file, e); } } @@ -90,7 +90,7 @@ public void put(ByteSource bytes) { Streams.copyClose(bytes.openStream(), new FileOutputStream(tmpFile)); FileBasedObjectStore.moveFile(tmpFile, file); } catch (IOException e) { - throw Exceptions.propagate("Problem writing data to file "+file+" (via temporary file "+tmpFile+")", e); + throw Exceptions.propagateAnnotated("Problem writing data to file "+file+" (via temporary file "+tmpFile+")", e); } catch (InterruptedException e) { throw Exceptions.propagate(e); } @@ -105,7 +105,7 @@ public void append(String val) { Files.append(val, file, Charsets.UTF_8); } catch (IOException e) { - throw Exceptions.propagate("Problem appending to file "+file, e); + throw Exceptions.propagateAnnotated("Problem appending to file "+file, e); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java index 6ee3dc74f4..0bde0d1d0c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/rebind/RebindIteration.java @@ -1063,7 +1063,7 @@ protected LoadedClass load(Class bTyp reboundSearchPath.add(fixedSearchItemId); } else { LOG.warn("Unable to load catalog item "+ searchItemId - +" for search path of "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting load nevertheless"); + + " for search path of "+contextSuchAsId + " (" + bType.getSimpleName()+"); attempting to load "+jType+" nevertheless"); } } } @@ -1091,7 +1091,7 @@ protected LoadedClass load(Class bTyp return new LoadedClass(loader.loadClass(jType, bType), transformedCatalogItemId, reboundSearchPath); } catch (Exception e) { Exceptions.propagateIfFatal(e); - LOG.warn("Unable to load "+jType+" using loader; will try reflections"); + LOG.warn("Unable to load class "+jType+" needed for "+catalogItemId+" for "+contextSuchAsId+", via "+transformedCatalogItemId+" loader (will try reflections)"); } } else { LOG.warn("Unable to load catalog item "+catalogItemId+" ("+bType+") for " + contextSuchAsId + @@ -1103,12 +1103,12 @@ protected LoadedClass load(Class bTyp return new LoadedClass((Class)loadClass(jType), catalogItemId, reboundSearchPath); } catch (Exception e) { Exceptions.propagateIfFatal(e); - LOG.warn("Unable to load "+jType+" using reflections; will try standard context"); + LOG.warn("Unable to load class "+jType+" needed for "+catalogItemId+" for "+contextSuchAsId+", via reflections (may try others, will throw if fails)"); } if (catalogItemId != null) { - throw new IllegalStateException("Unable to load catalog item " + catalogItemId + " for " + - contextSuchAsId + ", or load class from classpath"); + throw new IllegalStateException("Unable to load "+jType+" for catalog item " + catalogItemId + " for " + contextSuchAsId); + } else if (BrooklynFeatureEnablement.isEnabled(FEATURE_BACKWARDS_COMPATIBILITY_INFER_CATALOG_ITEM_ON_REBIND)) { //Try loading from whichever catalog bundle succeeds. BrooklynCatalog catalog = managementContext.getCatalog(); diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java index 4af090096f..fec97f2ece 100644 --- a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java +++ b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalFactory.java @@ -121,7 +121,7 @@ private T constructWithSpecial(Class getReachableAddressesPredicate(ConfigBag try { return pollForFirstReachableHostAndPortPredicate = predicateType.newInstance(); } catch (IllegalAccessException | InstantiationException newInstanceException) { - throw Exceptions.propagate("Failed to instantiate " + predicateType, newInstanceException); + throw Exceptions.propagateAnnotated("Failed to instantiate " + predicateType, newInstanceException); } } catch (InvocationTargetException | InstantiationException e) { - throw Exceptions.propagate("Failed to instantiate " + predicateType + " with Map constructor", e); + throw Exceptions.propagateAnnotated("Failed to instantiate " + predicateType + " with Map constructor", e); } } } diff --git a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java index f68f14287a..606a2a1933 100644 --- a/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java +++ b/server-cli/src/main/java/org/apache/brooklyn/cli/Main.java @@ -433,7 +433,7 @@ public Void apply(CatalogInitialization catInit) { try { populateCatalog(catInit.getManagementContext().getCatalog()); } catch (Throwable e) { - catInit.handleException(e, "overridden main class populate catalog"); + catInit.handleException(e, "in main class populate catalog override"); } // Force load of catalog (so web console is up to date) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 54870722ea..82fcce7984 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -129,6 +129,18 @@ public static RuntimeException propagate(Throwable throwable) { throw new PropagatedRuntimeException(throwable); } + /** + * Convenience for {@link #propagateAnnotateIfWrapping(String, Throwable)}. + * + * @deprecated since 0.12.0 behaviour will change (default should be to always wrap and annotate); + * for now users should review and switch to either + * {@link #propagateAnnotated(String, Throwable)} or {@link #propagateAnnotateIfWrapping(String, Throwable)} as appropriate. + */ + @Deprecated + public static RuntimeException propagate(String msg, Throwable throwable) { + return propagate(msg, throwable, false); + } + /** * See {@link #propagate(Throwable)}. *

    @@ -136,11 +148,12 @@ public static RuntimeException propagate(Throwable throwable) { * needs to be wrapped; otherwise the message is not used. * To always include the message, use {@link #propagateAnnotated(String, Throwable)}. */ - public static RuntimeException propagate(String msg, Throwable throwable) { + public static RuntimeException propagateAnnotateIfWrapping(String msg, Throwable throwable) { return propagate(msg, throwable, false); } - /** As {@link #propagate(String, Throwable)} but always re-wraps including the given message. */ + /** As {@link #propagate(String)} but always re-wraps including the given message. + * See {@link #propagateAnnotateIfWrapping(String, Throwable)} if the message is optional. */ public static RuntimeException propagateAnnotated(String msg, Throwable throwable) { return propagate(msg, throwable, true); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java index 272d3f6df2..345db932fd 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java @@ -65,7 +65,7 @@ public void testPropagateCheckedExceptionWithMessage() throws Exception { String extraMsg = "my message"; Exception tothrow = new Exception("simulated"); try { - throw Exceptions.propagate(extraMsg, tothrow); + throw Exceptions.propagateAnnotateIfWrapping(extraMsg, tothrow); } catch (RuntimeException e) { assertEquals(e.getMessage(), "my message"); assertEquals(e.getCause(), tothrow); @@ -76,7 +76,7 @@ public void testPropagateCheckedExceptionWithMessage() throws Exception { public void testPropagateRuntimeExceptionIgnoresMessage() throws Exception { NullPointerException tothrow = new NullPointerException("simulated"); try { - throw Exceptions.propagate("my message", tothrow); + throw Exceptions.propagateAnnotateIfWrapping("my message", tothrow); } catch (NullPointerException e) { assertEquals(e, tothrow); } @@ -257,14 +257,14 @@ public void testPropagateWithoutAnnotationSuppressed() throws Exception { @Test public void testPropagateWithAnnotationNotExplicitIncludedWhenWrapped() throws Exception { Throwable t = new Throwable("test"); - try { Exceptions.propagate("important", t); } catch (Throwable t2) { t = t2; } + try { Exceptions.propagateAnnotateIfWrapping("important", t); } catch (Throwable t2) { t = t2; } Assert.assertEquals(Exceptions.collapseText(t), "important: test"); } @Test public void testPropagateWithAnnotationNotExplicitIgnoredWhenNotWrapped() throws Exception { Throwable t = new RuntimeException("test"); - try { Exceptions.propagate("ignore", t); } catch (Throwable t2) { t = t2; } + try { Exceptions.propagateAnnotateIfWrapping("ignore", t); } catch (Throwable t2) { t = t2; } Assert.assertEquals(Exceptions.collapseText(t), "test"); } From bf7d292be0189591dbf274f1b238e258c1da7921 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 7 Jul 2017 12:03:44 +0100 Subject: [PATCH 23/33] address two edge cases where osgi bundles out of sync with brooklyn-managed bundles if in osgi but not in brooklyn, silently uninstall then reinstall. this happens on restart when persisting with karaf, or if user does `bundle:install`. if not in osgi but is in brooklyn, warn then reinstall. this happens if user does `bundle:uninstall` in karaf. --- .../core/mgmt/ha/OsgiArchiveInstaller.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 1b06463c8f..247bebd7fb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -306,9 +306,11 @@ public ReferenceWithError install() { if (canUpdate()) { result.bundle = osgiManager.framework.getBundleContext().getBundle(result.getMetadata().getOsgiUniqueUrl()); if (result.getBundle()==null) { - throw new IllegalStateException("Detected already managing bundle "+result.getMetadata().getVersionedName()+" but framework cannot find it"); + log.warn("Brooklyn thought is was already managing bundle "+result.getMetadata().getVersionedName()+" but it's not installed to framework; reinstalling it"); + updating = false; + } else { + updating = true; } - updating = true; } else { if (result.getMetadata().getChecksum()==null || inferredMetadata.getChecksum()==null) { log.warn("Missing bundle checksum data for "+result+"; assuming bundle replacement is permitted"); @@ -324,11 +326,16 @@ public ReferenceWithError install() { // no such managed bundle Maybe b = Osgis.bundleFinder(osgiManager.framework).symbolicName(result.getMetadata().getSymbolicName()).version(result.getMetadata().getSuppliedVersionString()).find(); if (b.isPresent()) { - // if it's non-brooklyn installed then fail - // (e.g. someone trying to install brooklyn or guice through this mechanism!) - result.bundle = b.get(); - result.code = OsgiBundleInstallationResult.ResultCode.ERROR_LAUNCHING_BUNDLE; - throw new IllegalStateException("Bundle "+result.getMetadata().getVersionedName()+" already installed in framework but not managed by Brooklyn; cannot install or update through Brooklyn"); + // bundle already installed to OSGi subsystem but brooklyn not aware of it; + // this will often happen on a karaf restart so don't be too strict! + // in this case let's uninstall it to make sure we have the right bundle and checksum + // (in case where user has replaced a JAR file in persisted state, + // or where they osgi installed something and are now uploading it or something else) + // but let's just assume it's the same; worst case if not user will + // have to uninstall it then reinstall it to do the replacement + // (means you can't just replace a JAR in persisted state however) + log.debug("Brooklyn install of "+result.getMetadata().getVersionedName()+" detected already loaded in OSGi; uninstalling that to reinstall as Brooklyn-managed"); + b.get().uninstall(); } // normal install updating = false; @@ -337,7 +344,6 @@ public ReferenceWithError install() { startedInstallation = true; try (InputStream fin = new FileInputStream(zipFile)) { if (!updating) { - // install new assert result.getBundle()==null; result.bundle = osgiManager.framework.getBundleContext().installBundle(result.getMetadata().getOsgiUniqueUrl(), fin); } else { From ab7519513254c7b6990b694caea9b425dbbbb1d5 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 7 Jul 2017 12:54:14 +0100 Subject: [PATCH 24/33] suppress some warnings --- .../brooklyn/core/location/BasicLocationRegistry.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java index cbfd140fe0..b28e56047e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.config.StringConfigMap; import org.apache.brooklyn.core.config.ConfigPredicates; @@ -214,8 +215,11 @@ private LocationDefinition getDefinedLocation(String nameOrId, boolean isId) { // fall back to ignoring supertypes, in case they weren't set lt = mgmt.getTypeRegistry().get(nameOrId); - if (lt!=null) { - log.warn("Location registry only found "+nameOrId+" when ignoring supertypes; check it is correctly resolved."); + if (lt!=null && lt.getSuperTypes().isEmpty()) { + if (lt.getKind()!=RegisteredTypeKind.UNRESOLVED) { + // don't warn when still trying to resolve + log.warn("Location registry only found "+nameOrId+" when ignoring supertypes; check it is correctly resolved."); + } return newDefinedLocation(lt); } From d0183c264914ebca14f74e41e5d080d91fe85f1d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 12 Jul 2017 09:20:02 +0100 Subject: [PATCH 25/33] address PR comments --- .../core/catalog/internal/BasicBrooklynCatalog.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index f718180640..046543ba59 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -512,10 +512,9 @@ private void collectCatalogItemsFromCatalogBomRoot(String yaml, ManagedBundle co if (!itemDef.isEmpty()) { // AH - i forgot we even supported this. probably no point anymore, // now that catalog defs can reference an item yaml and things can be bundled together? - log.warn("Reading catalog item from sibling keys of `brooklyn.catalog` section, " + log.warn("Deprecated read of catalog item from sibling keys of `brooklyn.catalog` section, " + "instead of the more common appraoch of putting inside an `item` within it. " - + "This behavior is not deprecated yet but it is being considered. " - + "If you find it useful please inform the community."); + + "Rewrite to use nested/reference syntax instead or contact the community for assistance or feedback."); Map rootItem = MutableMap.of("item", itemDef); String rootItemYaml = yaml; YamlExtract yamlExtract = Yamls.getTextOfYamlAtPath(rootItemYaml, "brooklyn.catalog"); @@ -532,12 +531,12 @@ private void collectCatalogItemsFromCatalogBomRoot(String yaml, ManagedBundle co * Expects item metadata, containing an `item` containing the definition, * and/or `items` containing a list of item metadata (recursing with depth). * - * Supports two modes: + * Supports two modes depending whether result is passed here: * - * * CatalogItems validated and returned, but not added to catalog here; + * * CatalogItems validated and returned, but not added to catalog here, instead returned in result; * caller does that, and CI instances are persisted and loaded directly after rebind * - * * RegisteredTypes added to (unpersisted) type registry; + * * RegisteredTypes added to (unpersisted) type registry if result is null; * caller than validates, optionally removes broken ones, * given the ability to add multiple interdependent BOMs/bundles and then validate; * bundles with BOMs are persisted instead of catalog items @@ -1050,7 +1049,7 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt if (url==null) { // NOT available after persistence/rebind // as shown by test in CatalogOsgiVersionMoreEntityRebindTest - throw new IllegalArgumentException("Error prepaing to scan "+containingBundle.getVersionedName()+": no URL available"); + throw new IllegalArgumentException("Error preparing to scan "+containingBundle.getVersionedName()+": no URL available"); } // org.reflections requires the URL to be "file:" containg ".jar" File fJar = Os.newTempFile(containingBundle.getVersionedName().toOsgiString(), ".jar"); From 5868124a4eba5e02847479178bfa7415340ffc0a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 12 Jul 2017 09:23:37 +0100 Subject: [PATCH 26/33] remove CatalogBomScanner --- .../catalog/CatalogMakeOsgiBundleTest.java | 20 --- .../core/BrooklynFeatureEnablement.java | 12 -- .../internal/BasicBrooklynCatalog.java | 2 +- .../catalog/internal/CatalogBomScanner.java | 136 ------------------ .../internal/CatalogBundleTracker.java | 71 --------- .../brooklyn/core/mgmt/ha/OsgiManager.java | 40 +++--- 6 files changed, 18 insertions(+), 263 deletions(-) delete mode 100644 core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java delete mode 100644 core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java index 397bcf70fd..290c9e87f1 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java @@ -29,12 +29,10 @@ import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.core.BrooklynFeatureEnablement; -import org.apache.brooklyn.core.catalog.internal.CatalogBomScanner; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult; @@ -53,8 +51,6 @@ import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.text.Strings; import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -80,7 +76,6 @@ protected LocalManagementContext newTestManagementContext() { @BeforeClass(alwaysRun = true) public void setUp() throws Exception { super.setUp(); - manuallyEnableBomScanner(mgmt()); bm = new BundleMaker( ((LocalManagementContext)mgmt()).getOsgiManager().get().getFramework(), ResourceUtils.create(this) ); } @@ -102,21 +97,6 @@ public void tearDown() throws Exception { super.tearDown(); } - public static void manuallyEnableBomScanner(ManagementContext mgmt) { - BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM); - - CatalogBomScanner scanner = new CatalogBomScanner(); - BundleContext context = ((LocalManagementContext)mgmt).getOsgiManager().get().getFramework().getBundleContext(); - - context.registerService(ManagementContext.class.getName(), mgmt, null); - ServiceReference ref = context.getServiceReference(ManagementContext.class); - try { - scanner.bind(ref); - } catch (Exception e) { - throw Exceptions.propagate(e); - } - } - @AfterMethod(alwaysRun=true) public void clearFeatureEnablement() throws Exception { BrooklynFeatureEnablement.clearCache(); diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java index 2776d9b69d..8bbe2a4ca5 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java @@ -114,17 +114,6 @@ public class BrooklynFeatureEnablement { public static final String FEATURE_VALIDATE_LOCATION_SSH_KEYS = "brooklyn.validate.locationSshKeys"; - /** - * Whether to scan newly loaded bundles for catalog.bom and load it. - * - * The functionality loads catalog items regardless of the persistence state so best used with persistence disabled. - * If a bundle is uploaded its BOM is scanned regardless of this property (this only applies to bundles - * installed through a non-brooklyn method, eg karaf.) - * - * This installs legacy items and so should be deprecated in favour of uploading BOMs which Brooklyn manages. - */ - public static final String FEATURE_LOAD_BUNDLE_CATALOG_BOM = FEATURE_PROPERTY_PREFIX+".osgi.catalog_bom"; - /** * Values explicitly set by Java calls. */ @@ -161,7 +150,6 @@ static void setDefaults() { setDefault(FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, false); setDefault(FEATURE_SSH_ASYNC_EXEC, false); setDefault(FEATURE_VALIDATE_LOCATION_SSH_KEYS, true); - setDefault(FEATURE_LOAD_BUNDLE_CATALOG_BOM, false); } static { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 046543ba59..739f225390 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1416,7 +1416,7 @@ public List> addItems(String yaml, ManagedBundle bund log.debug("Adding catalog item to "+mgmt+": "+yaml); checkNotNull(yaml, "yaml"); List> result = MutableList.of(); - collectCatalogItemsFromCatalogBomRoot(yaml, bundle, result, true, ImmutableMap.of(), 0, false); + collectCatalogItemsFromCatalogBomRoot(yaml, bundle, result, true, ImmutableMap.of(), 0, forceUpdate); // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially for (CatalogItemDtoAbstract item: result) { diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java deleted file mode 100644 index 90efaa231d..0000000000 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.brooklyn.core.catalog.internal; - -import java.util.List; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.core.BrooklynFeatureEnablement; -import org.apache.brooklyn.util.text.Strings; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; - -/** Scans bundles being added to add their catalog.bom to Brooklyn, - * filtering which bundles are allowed add _applications_ through a whitelist and blacklist. - * (All bundles are allowed to add other types.) - * See karaf blueprint.xml for configuration, and tests in dist project. */ -// TODO AH: i wonder if we can remove this or generalize it; disallowing just app/templates seems a little weird -// (makes sense as those appear more prominently in some displays, but feels like there'd be a better way -// to customize; intercepting at this particular point is surprising) -@Beta -public class CatalogBomScanner { - - private final String ACCEPT_ALL_BY_DEFAULT = ".*"; - - private static final Logger LOG = LoggerFactory.getLogger(CatalogBomScanner.class); - - // configured by `brooklyn.catalog.osgi.application.{white,black}list` - private List bundlesAllowedToAddAppsWhitelist = ImmutableList.of(ACCEPT_ALL_BY_DEFAULT); - private List bundlesAllowedToAddAppsBlacklist = ImmutableList.of(); - - private CatalogBundleTracker catalogBundleTracker; - - public void bind(ServiceReference mgmtContextReference) throws Exception { - if (isEnabled()) { - LOG.debug("Binding management context with whiteList [{}] and blacklist [{}]", - Strings.join(getWhiteList(), "; "), - Strings.join(getBlackList(), "; ")); - - final BundleContext bundleContext = mgmtContextReference.getBundle().getBundleContext(); - ManagementContext mgmt = bundleContext.getService(mgmtContextReference); - CatalogBundleLoader bundleLoader = new CatalogBundleLoader(new BundlesAllowedToAddAddsFilter(), mgmt); - - catalogBundleTracker = new CatalogBundleTracker(bundleContext, bundleLoader); - catalogBundleTracker.open(); - } - } - - public void unbind(ServiceReference mgmtContextReference) throws Exception { - if (isEnabled()) { - LOG.debug("Unbinding management context"); - if (null != catalogBundleTracker) { - CatalogBundleTracker temp = catalogBundleTracker; - catalogBundleTracker = null; - temp.close(); - } - mgmtContextReference.getBundle().getBundleContext().ungetService(mgmtContextReference); - } - } - - private boolean isEnabled() { - return BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM); - } - - public List getWhiteList() { - return bundlesAllowedToAddAppsWhitelist; - } - - public void setWhiteList(List whiteList) { - this.bundlesAllowedToAddAppsWhitelist = whiteList; - } - - public void setWhiteList(String whiteListText) { - LOG.debug("Setting whiteList to ", whiteListText); - this.bundlesAllowedToAddAppsWhitelist = Strings.parseCsv(whiteListText); - } - - public List getBlackList() { - return bundlesAllowedToAddAppsBlacklist; - } - - public void setBlackList(List blackList) { - this.bundlesAllowedToAddAppsBlacklist = blackList; - } - - public void setBlackList(String blackListText) { - LOG.debug("Setting blackList to ", blackListText); - this.bundlesAllowedToAddAppsBlacklist = Strings.parseCsv(blackListText); - } - - public class BundlesAllowedToAddAddsFilter implements Predicate { - @Override - public boolean apply(@Nullable Bundle input) { - return passesWhiteAndBlacklists(input); - } - } - - private boolean passesWhiteAndBlacklists(Bundle bundle) { - return on(bundle, getWhiteList()) && !on(bundle, getBlackList()); - } - - private boolean on(Bundle bundle, List list) { - for (String candidate : list) { - final String symbolicName = bundle.getSymbolicName(); - if (symbolicName.matches(candidate.trim())) { - return true; - } - } - return false; - } - -} diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java deleted file mode 100644 index 1ec4cd2dfc..0000000000 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleTracker.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.brooklyn.core.catalog.internal; - -import org.apache.brooklyn.util.osgi.VersionedName; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.util.tracker.BundleTracker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; - -@Beta -public class CatalogBundleTracker extends BundleTracker { - - private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleTracker.class); - - private CatalogBundleLoader catalogBundleLoader; - - public CatalogBundleTracker(BundleContext bundleContext, CatalogBundleLoader catalogBundleLoader) { - super(bundleContext, Bundle.ACTIVE, null); - this.catalogBundleLoader = catalogBundleLoader; - } - - /** - * Scans the bundle being added for a catalog.bom file and adds any entries in it to the catalog. - * - * @param bundle The bundle being added to the bundle context. - * @param bundleEvent The event of the addition. - * @return null - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - @Override - public Object addingBundle(Bundle bundle, BundleEvent bundleEvent) { - catalogBundleLoader.scanForCatalog(bundle, false, true); - return null; - } - - /** - * Remove the given entries from the catalog, related to the given bundle. - * - * @param bundle The bundle being removed to the bundle context. - * @param bundleEvent The event of the removal. - * @param callback Ignored - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - @Override - public void removedBundle(Bundle bundle, BundleEvent bundleEvent, Object callback) { - LOG.debug("Unloading catalog BOM entries from {} {} {}", CatalogUtils.bundleIds(bundle)); - catalogBundleLoader.removeFromCatalog(new VersionedName(bundle)); - } -} diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index 9b92a8cb47..9d6587c31a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -41,11 +41,9 @@ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.BrooklynFeatureEnablement; import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.catalog.internal.CatalogBundleLoader; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.server.BrooklynServerConfig; import org.apache.brooklyn.core.server.BrooklynServerPaths; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; @@ -395,33 +393,29 @@ public void loadCatalogBom(Bundle bundle, boolean force, boolean validate) { private static Iterable> loadCatalogBomInternal(ManagementContext mgmt, Bundle bundle, boolean force, boolean validate, boolean legacy) { Iterable> catalogItems = MutableList.of(); - if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM)) { - // if the above feature is not enabled, let's do it manually (as a contract of this method) - try { - // TODO improve on this - it ignores the configuration of whitelists, see CatalogBomScanner. - // One way would be to add the CatalogBomScanner to the new Scratchpad area, then retrieving the singleton - // here to get back the predicate from it. - final Predicate applicationsPermitted = Predicates.alwaysTrue(); - - CatalogBundleLoader cl = new CatalogBundleLoader(applicationsPermitted, mgmt); - if (legacy) { - catalogItems = cl.scanForCatalogLegacy(bundle, force); - } else { - cl.scanForCatalog(bundle, force, validate); - catalogItems = null; - } - } catch (RuntimeException ex) { - // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; - // caller needs to upgrade, or uninstall then reinstall - // (this uninstall wouldn't have unmanaged it in brooklyn in any case) + + try { + final Predicate applicationsPermitted = Predicates.alwaysTrue(); + + CatalogBundleLoader cl = new CatalogBundleLoader(applicationsPermitted, mgmt); + if (legacy) { + catalogItems = cl.scanForCatalogLegacy(bundle, force); + } else { + cl.scanForCatalog(bundle, force, validate); + catalogItems = null; + } + } catch (RuntimeException ex) { + // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; + // caller needs to upgrade, or uninstall then reinstall + // (this uninstall wouldn't have unmanaged it in brooklyn in any case) // try { // bundle.uninstall(); // } catch (BundleException e) { // log.error("Cannot uninstall bundle " + bundle.getSymbolicName() + ":" + bundle.getVersion()+" (after error installing catalog items)", e); // } - throw new IllegalArgumentException("Error installing catalog items", ex); - } + throw new IllegalArgumentException("Error installing catalog items", ex); } + return catalogItems; } From a9e770f3df019e30b8d7feaab7d54e435ea9e0ad Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 17 Jul 2017 11:26:26 +0100 Subject: [PATCH 27/33] remove more of the legacy bom-scanning support --- .../catalog/internal/CatalogBundleLoader.java | 39 +++---------------- .../brooklyn/core/mgmt/ha/OsgiManager.java | 33 +++------------- 2 files changed, 11 insertions(+), 61 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java index e55617a3bf..339280e5ff 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleLoader.java @@ -31,7 +31,6 @@ import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.ManagedBundle; -import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.typereg.RegisteredTypePredicates; @@ -45,7 +44,6 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; -import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @Beta @@ -54,30 +52,20 @@ public class CatalogBundleLoader { private static final Logger LOG = LoggerFactory.getLogger(CatalogBundleLoader.class); private static final String CATALOG_BOM_URL = "catalog.bom"; - private Predicate applicationsPermitted; private ManagementContext managementContext; - public CatalogBundleLoader(Predicate applicationsPermitted, ManagementContext managementContext) { - this.applicationsPermitted = applicationsPermitted; + public CatalogBundleLoader(ManagementContext managementContext) { this.managementContext = managementContext; } public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { scanForCatalogInternal(bundle, force, validate, false); } - - /** - * Scan the given bundle for a catalog.bom and adds it to the catalog. - * - * @param bundle The bundle to add - * @return A list of items added to the catalog - * @throws RuntimeException if the catalog items failed to be added to the catalog - */ - public Iterable> scanForCatalogLegacy(Bundle bundle) { - return scanForCatalogLegacy(bundle, false); - } - + + /** @deprecated since 0.12.0 */ + @Deprecated // scans a bundle which is installed but Brooklyn isn't managing (will probably remove) public Iterable> scanForCatalogLegacy(Bundle bundle, boolean force) { + LOG.warn("Bundle "+bundle+" being loaded with deprecated legacy loader"); return scanForCatalogInternal(bundle, force, true, true); } @@ -119,26 +107,9 @@ public void scanForCatalog(Bundle bundle, boolean force, boolean validate) { LOG.debug("No BOM found in {} {} {}", CatalogUtils.bundleIds(bundle)); } - if (!applicationsPermitted.apply(bundle)) { - if (legacy) { - catalogItems = removeApplications(catalogItems); - } else { - removeApplications(mb); - } - } - return catalogItems; } - private void removeApplications(ManagedBundle mb) { - for (RegisteredType t: managementContext.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(mb.getVersionedName()))) { - // TODO support templates, and remove them here -// if (t.getKind() == RegisteredTypeKind.TEMPLATE) { -// ((BasicBrooklynTypeRegistry) managementContext.getTypeRegistry()).delete(t); -// } - } - } - /** * Remove the given items from the catalog. * diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java index 9d6587c31a..98c962e6e8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java @@ -73,8 +73,6 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -371,39 +369,20 @@ public synchronized Bundle registerBundle(CatalogBundle bundleMetadata) { } } - @Beta - // TODO this is designed to work if the FEATURE_LOAD_BUNDLE_CATALOG_BOM is disabled, the default, but unintuitive here - // it probably works even if that is true, but we should consider what to do; - // possibly remove that other capability, so that bundles with BOMs _have_ to be installed via this method. - // (load order gets confusing with auto-scanning...) - public List> loadCatalogBomLegacy(Bundle bundle) { - return loadCatalogBomLegacy(bundle, false); - } - - @Beta // as above - public List> loadCatalogBomLegacy(Bundle bundle, boolean force) { - return MutableList.copyOf(loadCatalogBomInternal(mgmt, bundle, force, true, true)); - } - // since 0.12.0 no longer returns items; it installs non-persisted RegisteredTypes to the type registry instead @Beta public void loadCatalogBom(Bundle bundle, boolean force, boolean validate) { - loadCatalogBomInternal(mgmt, bundle, force, validate, false); + loadCatalogBomInternal(mgmt, bundle, force, validate); } - private static Iterable> loadCatalogBomInternal(ManagementContext mgmt, Bundle bundle, boolean force, boolean validate, boolean legacy) { + private static Iterable> loadCatalogBomInternal(ManagementContext mgmt, Bundle bundle, boolean force, boolean validate) { Iterable> catalogItems = MutableList.of(); try { - final Predicate applicationsPermitted = Predicates.alwaysTrue(); - - CatalogBundleLoader cl = new CatalogBundleLoader(applicationsPermitted, mgmt); - if (legacy) { - catalogItems = cl.scanForCatalogLegacy(bundle, force); - } else { - cl.scanForCatalog(bundle, force, validate); - catalogItems = null; - } + CatalogBundleLoader cl = new CatalogBundleLoader(mgmt); + cl.scanForCatalog(bundle, force, validate); + catalogItems = null; + } catch (RuntimeException ex) { // TODO confirm -- as of May 2017 we no longer uninstall the bundle if install of catalog items fails; // caller needs to upgrade, or uninstall then reinstall From d6fd2aebd60d668dfcdfc082ac7a442bdd0ff6fc Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 17 Jul 2017 11:30:43 +0100 Subject: [PATCH 28/33] remove BomScanner reference in blueprint.xml --- .../main/resources/OSGI-INF/blueprint/blueprint.xml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 30e55a7626..ed6a0cb0c0 100644 --- a/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/karaf/init/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -119,15 +119,4 @@ limitations under the License. - - - - - - - - - From c39eb71ad0ef066b479cec10ff4e97166b3558c1 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 17 Jul 2017 11:41:22 +0100 Subject: [PATCH 29/33] address misc PR comments --- .../core/catalog/internal/CatalogItemDtoAbstract.java | 2 +- .../brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java index 0557a8c490..9f6e3d478c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemDtoAbstract.java @@ -391,7 +391,7 @@ protected void setTags(Set tags) { *

    * If a string is supplied, this tries heuristically to identify whether a reference is a bundle or a URL, as follows: * - if the string contains a slash, it is treated as a URL (or classpath reference), e.g. /file.txt; - * - if the string is {@link RegisteredTypeNaming#isGoodTypeColonVersion(String)} with an OSGi version it is treated as a bundle, e.g. file:1; + * - if the string is {@link RegisteredTypeNaming#isGoodBrooklynTypeColonVersion(String))} with an OSGi version it is treated as a bundle, e.g. file:1; * - if the string is ambiguous (has a single colon) a warning is given, * and typically it is treated as a URL because OSGi versions are needed here, e.g. file:v1 is a URL, * but for a transitional period (possibly ending in 0.13 as warning is introduced in 0.12) for compatibility with previous versions, diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java index 247bebd7fb..bd5176771a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java @@ -411,9 +411,13 @@ public void run() { if (!result.catalogItemsInstalled.isEmpty()) { // show fewer info messages, only for 'interesting' and non-deferred installations // (rebind is deferred, as are tests, but REST is not) - MutableList firstFive = MutableList.copyOf(Iterables.limit(result.catalogItemsInstalled, 5)); - log.info(result.message+", items: "+firstFive+ - (result.catalogItemsInstalled.size() > 5 ? " (and others, "+result.catalogItemsInstalled.size()+" total)" : "") ); + final int MAX_TO_LIST_EXPLICITLY = 5; + MutableList firstN = MutableList.copyOf(Iterables.limit(result.catalogItemsInstalled, MAX_TO_LIST_EXPLICITLY)); + log.info(result.message+", items: "+firstN+ + (result.catalogItemsInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.catalogItemsInstalled.size()+" total)" : "") ); + if (log.isDebugEnabled() && result.catalogItemsInstalled.size()>MAX_TO_LIST_EXPLICITLY) { + log.debug(result.message+", all items: "+result.catalogItemsInstalled); + } } else { log.debug(result.message+" (into Brooklyn), with no catalog items"); } From 98d9b0a37af4df33198ad8ad10aea5ba6fff52a8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 18 Jul 2017 08:52:19 +0100 Subject: [PATCH 30/33] tests for deletion of empty wrapper bundles --- .../brooklyn/catalog/CatalogScanOsgiTest.java | 34 +++++++++++ .../catalog/CatalogYamlRebindTest.java | 59 ++++++++++++++++++- .../internal/BasicBrooklynCatalog.java | 2 +- .../JavaCatalogToSpecTransformer.java | 33 +++++++++-- .../core/mgmt/rebind/RebindTestUtils.java | 14 ++++- 5 files changed, 131 insertions(+), 11 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java index 4d1dfd5e00..7b6a95a106 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogScanOsgiTest.java @@ -25,13 +25,16 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.util.Map; import java.util.zip.ZipEntry; import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.ManagedBundle; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.brooklyn.test.lite.CampYamlLiteTest; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.entity.stock.BasicEntity; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.support.TestResourceUnavailableException; import org.apache.brooklyn.util.collections.MutableMap; @@ -117,4 +120,35 @@ static String bomForLegacySiblingLibraries() { " - classpath://" + OsgiTestResources.BROOKLYN_TEST_MORE_ENTITIES_V2_PATH); } + @Test + public void testAddAnonymousBomTwiceDeletesOldEmptyOne() { + Map b1 = ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles(); + addCatalogItems(bomAnonymous()); + Map b2_new = MutableMap.copyOf( + ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles() ); + for (String old: b1.keySet()) b2_new.remove(old); + + RegisteredType sample = mgmt().getTypeRegistry().get("sample"); + Asserts.assertNotNull(sample); + Asserts.assertSize(b2_new.values(), 1); + + addCatalogItems(bomAnonymous()); + Map b3 = MutableMap.copyOf( + ((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles() ); + Map b3_new = MutableMap.copyOf(b3); + for (String old: b1.keySet()) b3_new.remove(old); + for (String old: b2_new.keySet()) b3_new.remove(old); + Asserts.assertSize(b3_new.values(), 1); + + Asserts.assertFalse(b3_new.keySet().contains( Iterables.getOnlyElement(b2_new.keySet()) )); + } + + static String bomAnonymous() { + return Strings.lines("brooklyn.catalog:", + " items:", + " - item:", + " id: sample", + " type: "+BasicEntity.class.getName()); + } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index d8cd514df5..0aa01be38f 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -25,6 +25,8 @@ import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import java.io.File; +import java.util.Arrays; import java.util.List; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -38,6 +40,7 @@ import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest; import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore; import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore; @@ -84,8 +87,14 @@ enum RebindWithCatalogTestMode { enum OsgiMode { NONE, + // we don't test OSGi with this because CAMP isn't available + // (and the library bundles assume CAMP in their catalog.bom) + // OSGi rebind isn't the focus of the tests which use this parameter + // so it's okay, though a bit ugly +// NORMAL, } + boolean useOsgi = false; private Boolean defaultEnablementOfFeatureAutoFixatalogRefOnRebind; @BeforeMethod(alwaysRun=true) @@ -106,7 +115,7 @@ public void tearDown() throws Exception { @Override protected boolean useOsgi() { - return false; + return useOsgi || (origManagementContext!=null && ((ManagementContextInternal)origManagementContext).getOsgiManager().isPresent()); } @DataProvider @@ -192,6 +201,8 @@ private String idFromPath(BrooklynObjectType type, String path) { public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mode, OsgiMode osgiMode, RebindOptions options) throws Exception { if (osgiMode != OsgiMode.NONE) { TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + + recreateOrigManagementContextWithOsgi(); } if (mode == RebindWithCatalogTestMode.REPLACE_CATALOG_WITH_NEWER_VERSION) { @@ -351,9 +362,33 @@ public void testRebindWithCatalogAndAppUsingOptions(RebindWithCatalogTestMode mo } } } + + protected void recreateOrigManagementContextWithOsgi() { + // replace with OSGi context + Entities.destroyAll(origManagementContext); + try { + useOsgi = true; + origManagementContext = createOrigManagementContext(); + origApp = createApp(); + } finally { + useOsgi = false; + } + } @Test - public void testLongReferenceSequence() throws Exception { + public void testLongReferenceSequenceWithoutOsgi() throws Exception { + doTestLongReferenceSequence(); + } + + @Test(groups="Broken") + public void testLongReferenceSequenceWithOsgi() throws Exception { + // won't work in OSGi mode because we need CAMP to resolve type "a0" + // (poor man's resolver only resolves Java classes) + recreateOrigManagementContextWithOsgi(); + doTestLongReferenceSequence(); + } + + private void doTestLongReferenceSequence() throws Exception { // adds a0, a1 extending a0, a2 extending a1, ... a9 extending a8 // osgi rebind of types can fail because bundles are restored in any order // and dependencies might not yet be installed; @@ -373,5 +408,23 @@ public void testLongReferenceSequence() throws Exception { Asserts.assertTrue(child instanceof BasicEntity); Asserts.assertEquals(child.getCatalogItemId(), "a9:1"); } - + + @Test + public void testDeleteEmptyBundleRemovedFromPersistence() throws Exception { + recreateOrigManagementContextWithOsgi(); + + String bom = Strings.lines( + "brooklyn.catalog:", + " itemType: entity", + " items:", + " - id: sample", + " item:", + " type: " + BasicEntity.class.getName()); + addCatalogItems(bom); + addCatalogItems(bom); + rebind(); + // should only contain one bundle / bundle.jar pair + Asserts.assertSize(Arrays.asList( new File(mementoDir, "bundles").list() ), 2); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 739f225390..cb1a138004 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1858,7 +1858,7 @@ public void uninstallEmptyWrapperBundles() { if (isNoBundleOrSimpleWrappingBundle(mgmt, b)) { Iterable typesInBundle = osgi.get().getTypesFromBundle(b.getVersionedName()); if (Iterables.isEmpty(typesInBundle)) { - log.debug("uninstalling empty wrapper bundle "+b); + log.info("Uninstalling now-empty BOM wrapper bundle "+b.getVersionedName()+" ("+b.getOsgiUniqueUrl()+")"); osgi.get().uninstallUploadedBundle(b); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java index f320e5d0e0..7860fef5ac 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.core.catalog.internal; +import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -32,13 +33,18 @@ import org.apache.brooklyn.api.sensor.EnricherSpec; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential; +import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.core.objs.BasicSpecParameter; import org.apache.brooklyn.core.plan.PlanToSpecTransformer; import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yaml.Yamls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Iterables; + /** * Instantiates classes from a registered type which simply * defines the java class name and OSGi bundles to use. @@ -76,12 +82,26 @@ public > SpecT c CatalogItem item, Set encounteredTypes) throws UnsupportedTypePlanException { @SuppressWarnings("deprecation") String javaType = item.getJavaType(); + boolean poorMansCamp = false; + if (javaType == null) { + if (Strings.isNonBlank( item.getPlanYaml() )) { + try { + Map parsed = (Map) Iterables.getOnlyElement( Yamls.parseAll(item.getPlanYaml()) ); + if ("type".equals(Iterables.getOnlyElement(parsed.keySet()))) { + javaType = (String) Iterables.getOnlyElement(parsed.values()); + poorMansCamp = true; + } + } catch (Exception e) { + throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing only 'type: JavaClass' in YAML (or javaType in DTO)", e); + } + } + } if (javaType != null) { - // TODO This log.warn previously said "Using old-style xml catalog items with java type attribute". - // However, nothing in this code is specific to catalog.xml. Perhaps that is the only way this code - // can be reached? We should investigate further when we have time - if we can confirm it was only - // used for catalog.xml, then this can be deleted. - log.warn("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); + // TODO "JavaType" should never be used any more; but we do want to support a poor-man's camp + // for tests that expect CAMP in core where CAMP module isn't available + if (poorMansCamp) { + log.warn("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); + } Class type; try { // java types were deprecated before we added osgi support so this isn't necessary, @@ -90,6 +110,7 @@ public > SpecT c final BrooklynClassLoadingContextSequential ctx = new BrooklynClassLoadingContextSequential(mgmt); ctx.add(CatalogUtils.newClassLoadingContextForCatalogItems(mgmt, item.getCatalogItemId(), item.getCatalogItemIdSearchPath())); + ctx.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt)); type = ctx.loadClass(javaType); } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -117,7 +138,7 @@ public > SpecT c SpecT untypedSpc = (SpecT) spec; return untypedSpc; } else { - throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing javaType"); + throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing only 'type: JavaClass' or javaType in DTO"); } } diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java index 912f7ef348..ba0ece2558 100644 --- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java +++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestUtils.java @@ -379,8 +379,20 @@ public static Application rebind(ManagementContext newManagementContext, File me } public static Application rebind(RebindOptions options) throws Exception { + boolean hadApps = true; + if (options!=null && options.origManagementContext!=null && options.origManagementContext.getApplications().isEmpty()) { + // clearly had no apps before, so don't treat as an error + hadApps = false; + } Collection newApps = rebindAll(options); - if (newApps.isEmpty()) throw new IllegalStateException("Application could not be found after rebind; serialization probably failed"); + if (newApps.isEmpty()) { + if (hadApps) { + throw new IllegalStateException("Application could not be found after rebind; serialization probably failed"); + } else { + // no apps before; probably testing catalog + return null; + } + } Function, Application> chooser = options.applicationChooserOnRebind; if (chooser != null) { return chooser.apply(newApps); From c2d9bff57868cc79e533007f1fad58773624ee3a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 18 Jul 2017 10:58:50 +0100 Subject: [PATCH 31/33] tidy warnings observed these in normal operation so bumping them down, with justification --- .../core/catalog/internal/CatalogUtils.java | 14 +++++++++----- .../internal/JavaCatalogToSpecTransformer.java | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index 7fa363173d..3e25384f08 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -153,9 +153,9 @@ public static BrooklynClassLoadingContext newClassLoadingContextForCatalogItems( ManagementContext managementContext, String primaryItemId, List searchPath) { BrooklynClassLoadingContextSequential seqLoader = new BrooklynClassLoadingContextSequential(managementContext); - addSearchItem(managementContext, seqLoader, primaryItemId); + addSearchItem(managementContext, seqLoader, primaryItemId, false /* primary ID may be temporary */); for (String searchId : searchPath) { - addSearchItem(managementContext, seqLoader, searchId); + addSearchItem(managementContext, seqLoader, searchId, true); } return seqLoader; } @@ -375,7 +375,7 @@ public static void setDisabled(ManagementContext mgmt, String symbolicName, Stri } } - private static void addSearchItem(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String itemId) { + private static void addSearchItem(ManagementContext managementContext, BrooklynClassLoadingContextSequential loader, String itemId, boolean warnIfNotFound) { OsgiManager osgi = ((ManagementContextInternal)managementContext).getOsgiManager().orNull(); boolean didSomething = false; if (osgi!=null) { @@ -395,8 +395,12 @@ private static void addSearchItem(ManagementContext managementContext, BrooklynC } if (!didSomething) { - // TODO review what to do here - log.warn("Can't find catalog item " + itemId+"; ignoring, but a search path may be incomplete and other errors may follow"); + if (warnIfNotFound) { + log.warn("Can't find catalog item " + itemId+" when searching; a search path may be incomplete and other errors may follow"); + } else { + log.trace("Can't find catalog item " + itemId+" when searching; ignoring as this can be normal in setup/scans, " + + "but it can also mean a search path may be incomplete and other errors may follow"); + } } } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java index 7860fef5ac..83dabc430c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java @@ -100,7 +100,9 @@ public > SpecT c // TODO "JavaType" should never be used any more; but we do want to support a poor-man's camp // for tests that expect CAMP in core where CAMP module isn't available if (poorMansCamp) { - log.warn("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); + // cannot warn here as -- however all CatalogToSpec transformers including this will be removed; + // in favour of TypePlanTransformer instances + log.trace("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); } Class type; try { From c7034e60367b95945351558b4267c085e5b52909 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 18 Jul 2017 13:42:32 +0100 Subject: [PATCH 32/33] fix broken tests by reverting the increased flexibility to the java catalog spec transformer andre-enable camp items in rebind-osgi tests a different way --- .../catalog/CatalogYamlRebindTest.java | 24 +++++++---------- .../JavaCatalogToSpecTransformer.java | 26 ------------------- .../core/typereg/TypePlanTransformers.java | 1 + .../apache/brooklyn/util/core/task/Tasks.java | 8 +++++- 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java index 0aa01be38f..66e5663d36 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlRebindTest.java @@ -87,28 +87,24 @@ enum RebindWithCatalogTestMode { enum OsgiMode { NONE, - // we don't test OSGi with this because CAMP isn't available - // (and the library bundles assume CAMP in their catalog.bom) - // OSGi rebind isn't the focus of the tests which use this parameter - // so it's okay, though a bit ugly -// NORMAL, + NORMAL } boolean useOsgi = false; - private Boolean defaultEnablementOfFeatureAutoFixatalogRefOnRebind; + private Boolean defaultEnablementOfFeatureAutoFixCatalogRefOnRebind; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { - defaultEnablementOfFeatureAutoFixatalogRefOnRebind = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND); + defaultEnablementOfFeatureAutoFixCatalogRefOnRebind = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND); super.setUp(); } @AfterMethod(alwaysRun=true) @Override public void tearDown() throws Exception { - if (defaultEnablementOfFeatureAutoFixatalogRefOnRebind != null) { - BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, defaultEnablementOfFeatureAutoFixatalogRefOnRebind); + if (defaultEnablementOfFeatureAutoFixCatalogRefOnRebind != null) { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_AUTO_FIX_CATALOG_REF_ON_REBIND, defaultEnablementOfFeatureAutoFixCatalogRefOnRebind); } super.tearDown(); } @@ -368,8 +364,10 @@ protected void recreateOrigManagementContextWithOsgi() { Entities.destroyAll(origManagementContext); try { useOsgi = true; - origManagementContext = createOrigManagementContext(); - origApp = createApp(); + tearDown(); + setUp(); + } catch (Exception e) { + throw Exceptions.propagate(e); } finally { useOsgi = false; } @@ -380,10 +378,8 @@ public void testLongReferenceSequenceWithoutOsgi() throws Exception { doTestLongReferenceSequence(); } - @Test(groups="Broken") + @Test public void testLongReferenceSequenceWithOsgi() throws Exception { - // won't work in OSGi mode because we need CAMP to resolve type "a0" - // (poor man's resolver only resolves Java classes) recreateOrigManagementContextWithOsgi(); doTestLongReferenceSequence(); } diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java index 83dabc430c..9d95542648 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java @@ -18,7 +18,6 @@ */ package org.apache.brooklyn.core.catalog.internal; -import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.catalog.CatalogItem; @@ -38,13 +37,9 @@ import org.apache.brooklyn.core.plan.PlanToSpecTransformer; import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yaml.Yamls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Iterables; - /** * Instantiates classes from a registered type which simply * defines the java class name and OSGi bundles to use. @@ -82,28 +77,7 @@ public > SpecT c CatalogItem item, Set encounteredTypes) throws UnsupportedTypePlanException { @SuppressWarnings("deprecation") String javaType = item.getJavaType(); - boolean poorMansCamp = false; - if (javaType == null) { - if (Strings.isNonBlank( item.getPlanYaml() )) { - try { - Map parsed = (Map) Iterables.getOnlyElement( Yamls.parseAll(item.getPlanYaml()) ); - if ("type".equals(Iterables.getOnlyElement(parsed.keySet()))) { - javaType = (String) Iterables.getOnlyElement(parsed.values()); - poorMansCamp = true; - } - } catch (Exception e) { - throw new UnsupportedTypePlanException(getClass().getName() + " parses only old-style catalog items containing only 'type: JavaClass' in YAML (or javaType in DTO)", e); - } - } - } if (javaType != null) { - // TODO "JavaType" should never be used any more; but we do want to support a poor-man's camp - // for tests that expect CAMP in core where CAMP module isn't available - if (poorMansCamp) { - // cannot warn here as -- however all CatalogToSpec transformers including this will be removed; - // in favour of TypePlanTransformer instances - log.trace("Deprecated functionality (since 0.9.0). Using old-style java type attribute for " + item); - } Class type; try { // java types were deprecated before we added osgi support so this isn't necessary, diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java index ba81e07fc7..cc4845c286 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java @@ -125,6 +125,7 @@ public static Maybe transform(ManagementContext mgmt, RegisteredType typ (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : "")); } catch (Throwable e) { Exceptions.propagateIfFatal(e); + log.debug("Transformer for "+t.getFormatCode()+" gave an error creating this plan (retrying with others): "+e, e); failuresFromTransformers.add(new PropagatedRuntimeException( (type.getSymbolicName()!=null ? "Error in definition of "+type.getId() : "Transformer for "+t.getFormatCode()+" gave an error creating this plan") + ": "+ diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java index 09116af7e4..ac07c35e22 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java @@ -33,13 +33,14 @@ import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.api.mgmt.TaskFactory; import org.apache.brooklyn.api.mgmt.TaskQueueingContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.CountdownTimer; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -133,6 +134,11 @@ public static ValueResolver resolving(Object v, Class type) { public static ValueResolver.ResolverBuilderPretype resolving(Object v) { return new ValueResolver.ResolverBuilderPretype(v); } + + @SuppressWarnings("unchecked") + public static ValueResolver resolving(ConfigBag config, ConfigKey key) { + return new ValueResolver.ResolverBuilderPretype(config.getStringKey(key.getName())).as((Class)key.getType()); + } /** @see #resolveValue(Object, Class, ExecutionContext, String) */ public static T resolveValue(Object v, Class type, @Nullable ExecutionContext exec) throws ExecutionException, InterruptedException { From 97f252439a1b247e6820dd4b2d1ebc9cd1323eca Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 19 Jul 2017 14:54:15 +0100 Subject: [PATCH 33/33] address review comments --- .../internal/BasicBrooklynCatalog.java | 14 +++++----- .../core/typereg/BasicManagedBundle.java | 13 +++++----- .../core/typereg/BasicRegisteredType.java | 1 + .../brooklyn/util/core/LoaderDispatcher.java | 1 - .../rest/resources/CatalogResource.java | 3 +-- .../rest/transform/CatalogTransformer.java | 26 +++++-------------- .../brooklyn/util/exceptions/Exceptions.java | 6 +++-- .../brooklyn/util/osgi/VersionedName.java | 4 +-- 8 files changed, 29 insertions(+), 39 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index cb1a138004..30c2f0b2aa 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -1055,13 +1055,14 @@ private String setFromItemIfUnset(String oldValue, Map item, String fieldAt File fJar = Os.newTempFile(containingBundle.getVersionedName().toOsgiString(), ".jar"); try { Streams.copy(ResourceUtils.create().getResourceFromUrl(url), new FileOutputStream(fJar)); + subCatalog.addToClasspath(new String[] { "file:"+fJar.getAbsolutePath() }); + Collection> result = scanAnnotationsInternal(mgmt, subCatalog, MutableMap.of("version", containingBundle.getSuppliedVersionString()), containingBundle); + return result; } catch (FileNotFoundException e) { - throw Exceptions.propagate("Error extracting "+url+" to scan "+containingBundle.getVersionedName(), e); + throw Exceptions.propagateAnnotated("Error extracting "+url+" to scan "+containingBundle.getVersionedName(), e); + } finally { + fJar.delete(); } - subCatalog.addToClasspath(new String[] { "file:"+fJar.getAbsolutePath() }); - Collection> result = scanAnnotationsInternal(mgmt, subCatalog, MutableMap.of("version", containingBundle.getSuppliedVersionString()), containingBundle); - fJar.delete(); - return result; } private Collection> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map catalogMetadata, ManagedBundle containingBundle) { @@ -1376,8 +1377,9 @@ public List> addItems(String yaml, boolean forceUpdat result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get(); } catch (FileNotFoundException e) { throw Exceptions.propagate(e); + } finally { + bf.delete(); } - bf.delete(); uninstallEmptyWrapperBundles(); if (result.getCode().isError()) { throw new IllegalStateException(result.getMessage()); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java index 1cf4e8482c..19c16994d8 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicManagedBundle.java @@ -81,7 +81,7 @@ public String getSuppliedVersionString() { @Override public String getOsgiVersionString() { - return version==null ? version : BrooklynVersionSyntax.toValidOsgiVersion(version); + return version==null ? null : BrooklynVersionSyntax.toValidOsgiVersion(version); } public void setVersion(String version) { @@ -130,7 +130,7 @@ public String toString() { @Override public int hashCode() { // checksum deliberately omitted here to match with OsgiBundleWithUrl - return Objects.hashCode(symbolicName, version, url); + return Objects.hashCode(symbolicName, getOsgiVersionString(), url); } @Override @@ -142,10 +142,11 @@ public boolean equals(Object obj) { if (!Objects.equal(symbolicName, other.getSymbolicName())) return false; if (!Objects.equal(getOsgiVersionString(), other.getOsgiVersionString())) return false; if (!Objects.equal(url, other.getUrl())) return false; - if (other instanceof BasicManagedBundle) { - // make equality symetricm, OsgiBunde equals this iff this equals OsgiBundle; - // checksum compared if available, but not required - if (!Objects.equal(checksum, ((BasicManagedBundle)other).getChecksum())) return false; + if (other instanceof ManagedBundle) { + // checksum compared if available, but not required; + // this makes equality with other OsgiBundleWithUrl items symmetric, + // but for two MB's we look additionally at checksum + if (!Objects.equal(checksum, ((ManagedBundle)other).getChecksum())) return false; } return true; } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java index 2edd49fa92..f76a9f4c87 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicRegisteredType.java @@ -192,6 +192,7 @@ public boolean equals(Object obj) { if (!Objects.equal(bundles, other.bundles)) return false; if (!Objects.equal(containingBundle, other.containingBundle)) return false; if (!Objects.equal(deprecated, other.deprecated)) return false; + if (!Objects.equal(description, other.description)) return false; if (!Objects.equal(disabled, other.disabled)) return false; if (!Objects.equal(iconUrl, other.iconUrl)) return false; if (!Objects.equal(implementationPlan, other.implementationPlan)) return false; diff --git a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java index 4d13aa888a..54af77bde3 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/LoaderDispatcher.java @@ -54,7 +54,6 @@ public Maybe> tryLoadFrom(Bundle bundle, String className) { @Override public Maybe> tryLoadFrom(BrooklynClassLoadingContext loader, String className) { try { - // return Maybe.>of(loader.loadClass(className)); return loader.tryLoadClass(className); } catch (IllegalStateException e) { propagateIfCauseNotClassNotFound(e); diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java index 0133962ffa..fcb26f9625 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java @@ -140,8 +140,7 @@ public Response createFromYaml(String yaml, boolean forceUpdate) { List itemsRT = MutableList.of(); for (CatalogItem ci: items) { RegisteredType rt = brooklyn().getTypeRegistry().get(ci.getId()); - if (rt!=null) itemsRT.add(rt); - else itemsRT.add(RegisteredTypes.of(ci)); + itemsRT.add(rt!=null ? rt : RegisteredTypes.of(ci)); } return buildCreateResponse(itemsRT); } catch (Exception e) { diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java index b1ec6e560b..9fe024a795 100644 --- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java @@ -223,8 +223,13 @@ private static String tidyIconLink(BrooklynRestResourceUtils b, RegisteredType i } private static Set makeTags(EntitySpec spec, RegisteredType item) { + return makeTags(spec, MutableSet.copyOf(item.getTags())); + } + private static Set makeTags(EntitySpec spec, CatalogItem item) { + return makeTags(spec, MutableSet.copyOf(item.tags().getTags())); + } + private static Set makeTags(EntitySpec spec, Set tags) { // Combine tags on item with an InterfacesTag. - Set tags = MutableSet.copyOf(item.getTags()); if (spec != null) { Class type; if (spec.getImplementation() != null) { @@ -238,8 +243,6 @@ private static Set makeTags(EntitySpec spec, RegisteredType item) { } return tags; } - - /** @deprecated since 0.12.0 use {@link RegisteredType} methods instead */ @Deprecated public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem> item, UriBuilder ub) { @@ -381,21 +384,4 @@ private static String tidyIconLink(BrooklynRestResourceUtils b, CatalogItem return iconUrl; } - private static Set makeTags(EntitySpec spec, CatalogItem item) { - // Combine tags on item with an InterfacesTag. - Set tags = MutableSet.copyOf(item.tags().getTags()); - if (spec != null) { - Class type; - if (spec.getImplementation() != null) { - type = spec.getImplementation(); - } else { - type = spec.getType(); - } - if (type != null) { - tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type))); - } - } - return tags; - } - } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 82fcce7984..55bf8a266e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -152,8 +152,10 @@ public static RuntimeException propagateAnnotateIfWrapping(String msg, Throwable return propagate(msg, throwable, false); } - /** As {@link #propagate(String)} but always re-wraps including the given message. - * See {@link #propagateAnnotateIfWrapping(String, Throwable)} if the message is optional. */ + /** As {@link #propagate(String, Throwable)} but unlike earlier deprecated version + * this always re-wraps including the given message, until semantics of that method change to match this. + * See {@link #propagateAnnotateIfWrapping(String, Throwable)} if the message + * should be omitted and the given throwable preserved if it can already be propagated. */ public static RuntimeException propagateAnnotated(String msg, Throwable throwable) { return propagate(msg, throwable, true); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java index dc35334ed6..9fec03c03c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/osgi/VersionedName.java @@ -127,8 +127,8 @@ public boolean equals(Object other) { } /** As {@link #equals(Object)} but accepting the argument as equal - * if versions are identical when injected to OSGi-valid versions, - * and accepting strings as the other */ + * if versions are identical under the {@link #getOsgiVersion()} conversion; + * also accepts strings as the other, converting as per {@link #fromString(String)} */ public boolean equalsOsgi(Object other) { if (other instanceof String) { other = VersionedName.fromString((String)other);