You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2017/03/23 17:19:56 UTC

[1/3] brooklyn-server git commit: add ZIP or JAR via REST API, with tests

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 8930ab6dd -> 48beb5c2f


add ZIP or JAR via REST API, with tests


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/5df28249
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/5df28249
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/5df28249

Branch: refs/heads/master
Commit: 5df28249e54fd6bc3a52c257944d0101ee269576
Parents: 2fe2ba0
Author: Alex Heneveld <al...@Alexs-MacBook-Pro.local>
Authored: Mon Dec 12 16:55:49 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Mar 8 09:35:42 2017 +0000

----------------------------------------------------------------------
 .../brooklyn/rest/api/ApplicationApi.java       |   3 -
 .../apache/brooklyn/rest/api/CatalogApi.java    |  46 +++++++-
 .../rest/resources/CatalogResource.java         | 104 ++++++++++++++++++-
 .../rest/resources/CatalogResourceTest.java     |  90 +++++++++++++++-
 4 files changed, 229 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5df28249/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index f1e167f..ba2fec2 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -111,9 +111,6 @@ public interface ApplicationApi {
                     required = true)
             String yaml);
 
-    // TODO archives
-//    @Consumes({"application/x-tar", "application/x-tgz", "application/x-zip"})
-
     @POST
     @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN})
     @ApiOperation(

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5df28249/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
----------------------------------------------------------------------
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 f561759..93a812e 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
@@ -38,11 +38,13 @@ import org.apache.brooklyn.rest.domain.CatalogItemSummary;
 import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
 import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
 
+import com.google.common.annotations.Beta;
+
 import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
 
 @Path("/catalog")
 @Api("Catalog")
@@ -50,15 +52,49 @@ import io.swagger.annotations.ApiParam;
 @Produces(MediaType.APPLICATION_JSON)
 public interface CatalogApi {
 
-    @Consumes
+    @Deprecated /** @deprecated since 0.11.0 use {@link #createFromYaml(String)} instead */
+    public Response create(String yaml);
+
+    @Consumes({MediaType.APPLICATION_JSON, "application/x-yaml",
+        // see http://stackoverflow.com/questions/332129/yaml-mime-type
+        "text/yaml", "text/x-yaml", "application/yaml"})
     @POST
-    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor "
+    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor. "
         + "Return value is map of ID to CatalogItemSummary, with code 201 CREATED.", response = String.class)
-    public Response create(
+    public Response createFromYaml(
             @ApiParam(name = "yaml", value = "YAML descriptor of catalog item", required = true)
             @Valid String yaml);
 
     @POST
+    @Beta
+    @Consumes  // anything (if doesn't match other methods with specific content types
+    @ApiOperation(value = "Add items to the catalog, either YAML or JAR/ZIP, format autodetected. "
+            + "Specify a content-type header to skip auto-detection and invoke one of the more specific methods. "
+            + "Return value is 201 CREATED if bundle could be added.", response = String.class)
+    public Response createPoly(
+            @ApiParam(
+                    name = "item",
+                    value = "Item to install, as JAR/ZIP or Catalog YAML (autodetected)",
+                    required = true)
+            byte[] item,
+            @ApiParam(name="name", value="Symbolic name to use for bundle", required=false, defaultValue="") String bundleName, 
+            @ApiParam(name="version", value="Version to set for bundle", required=false, defaultValue="") String bundleVersion);
+    
+    @POST
+    @Beta
+    @Consumes({"application/x-zip", "application/x-jar"})
+    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading OSGi bundle JAR, or ZIP if bundle name and optionally version are supplied. "
+            + "Return value is 201 CREATED if bundle could be added.", response = String.class)
+    public Response createFromArchive(
+            @ApiParam(
+                    name = "archive",
+                    value = "Bundle to install, in JAR format, or ZIP if bundle name and optionally version are supplied, optionally with catalog.bom contained within",
+                    required = true)
+            byte[] archive,
+            @ApiParam(name="name", value="Symbolic name to use for bundle", required=false, defaultValue="") String bundleName, 
+            @ApiParam(name="version", value="Version to set for bundle", required=false, defaultValue="") String bundleVersion);
+    
+    @POST
     @Consumes(MediaType.APPLICATION_XML)
     @Path("/reset")
     @ApiOperation(value = "Resets the catalog to the given (XML) format")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5df28249/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
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 2ac5b35..edc8b4d 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
@@ -18,6 +18,10 @@
  */
 package org.apache.brooklyn.rest.resources;
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -25,6 +29,9 @@ 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.JarFile;
+import java.util.jar.Manifest;
 
 import javax.annotation.Nullable;
 import javax.ws.rs.core.MediaType;
@@ -41,13 +48,16 @@ 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.typereg.RegisteredType;
+import org.apache.brooklyn.core.BrooklynFeatureEnablement;
 import org.apache.brooklyn.core.catalog.CatalogPredicates;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+import org.apache.brooklyn.core.catalog.internal.CatalogBomScanner;
 import org.apache.brooklyn.core.catalog.internal.CatalogDto;
 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.internal.LocalManagementContext;
 import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
@@ -60,13 +70,19 @@ import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
 import org.apache.brooklyn.rest.filter.HaHotStateRequired;
 import org.apache.brooklyn.rest.transform.CatalogTransformer;
 import org.apache.brooklyn.rest.util.WebResourceUtils;
+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.osgi.BundleMaker;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -94,9 +110,42 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     };
 
     static Set<String> missingIcons = MutableSet.of();
-    
+
     @Override
+    public Response createPoly(
+            byte[] item,
+            String bundleName, 
+            String bundleVersion) {
+        Throwable yamlException = null;
+        try {
+            MutableList.copyOf( Yamls.parseAll(new InputStreamReader(new ByteArrayInputStream(item))) );
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            yamlException = e;
+        }
+        
+        if (yamlException==null) {
+            // treat as yaml
+            if (Strings.isNonBlank(bundleName) || Strings.isNonBlank(bundleVersion)) {
+                throw new IllegalArgumentException("Bundle name/version not permitted when installing catalog YAML");
+            }
+            return createFromYaml(new String(item));
+        }
+        
+        try {
+            return createFromArchive(item, bundleName, bundleVersion);
+        } catch (Exception e) {
+            throw Exceptions.propagate("Unable to handle input: not YAML or compatible ZIP. Specify Content-Type to clarify.", e);
+        }
+    }
+    
+    @Override @Deprecated
     public Response create(String yaml) {
+        return createFromYaml(yaml);
+    }
+    
+    @Override
+    public Response createFromYaml(String yaml) {
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) {
             throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
                 Entitlements.getEntitlementContext().user());
@@ -106,6 +155,7 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
         try {
             items = brooklyn().getCatalog().addItems(yaml);
         } catch (Exception e) {
+            e.printStackTrace();
             Exceptions.propagateIfFatal(e);
             return ApiError.of(e).asBadRequestResponseJson();
         }
@@ -129,6 +179,57 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
         return Response.status(Status.CREATED).entity(result).build();
     }
 
+    @Override
+    public Response createFromArchive(byte[] zipInput, String bundleName, String bundleVersion) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ROOT, null)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        BundleMaker bm = new BundleMaker(mgmt());
+        File f = Os.newTempFile("brooklyn-posted-archive", "zip");
+        try {
+            Files.write(zipInput, f);
+        } catch (IOException e) {
+            Exceptions.propagate(e);
+        }
+        Manifest mf = bm.getManifest(f);
+        if (mf==null) {
+            mf = new Manifest();
+        }
+        String bundleNameInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+        if (Strings.isBlank(bundleName) && Strings.isBlank(bundleNameInMF)) {
+            throw new IllegalStateException("Require either "+Constants.BUNDLE_SYMBOLICNAME+" in "+JarFile.MANIFEST_NAME+" or name supplied in REST API");
+        }
+        if (!Strings.isBlank(bundleName)) {
+            mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, bundleName);
+        }
+        if (!Strings.isBlank(bundleVersion) || Strings.isBlank(mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION))) {
+            if (Strings.isBlank(bundleVersion)) {
+                bundleVersion = "0.0.0.SNAPSHOT";
+            }
+            mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, bundleVersion);
+        }
+        if (mf.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
+            mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
+        }
+        
+        File f2 = bm.copyAddingManifest(f, mf);
+        f.delete();
+        
+        Bundle bundle = bm.installBundle(f2, true);
+        f2.delete();
+        
+        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)
+            new CatalogBomScanner().new CatalogPopulator(
+                    ((LocalManagementContext) mgmt()).getOsgiManager().get().getFramework().getBundleContext(),
+                    mgmt()).addingBundle(bundle, null);
+        }
+        
+        return Response.status(Status.CREATED).build();
+    }
+    
     @SuppressWarnings("deprecation")
     @Override
     public Response resetXml(String xml, boolean ignoreErrors) {
@@ -514,3 +615,4 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
         return result;
     }
 }
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5df28249/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
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 af5d705..d1a74ae 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,14 +22,21 @@ import static com.google.common.base.Preconditions.checkNotNull;
 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;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.zip.ZipEntry;
 
+import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
@@ -45,7 +52,12 @@ import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
 import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
 import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+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.Reflections;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.stream.Streams;
 import org.apache.http.HttpHeaders;
 import org.apache.http.entity.ContentType;
 import org.eclipse.jetty.http.HttpStatus;
@@ -58,10 +70,6 @@ import org.testng.reporters.Files;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Iterables;
 
-import java.io.InputStream;
-
-import javax.ws.rs.core.GenericType;
-
 @Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
         suiteName = "CatalogResourceTest")
 public class CatalogResourceTest extends BrooklynRestResourceTest {
@@ -487,6 +495,78 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         //equivalent to HTTP response 404 text/html
         addAddCatalogItemWithInvalidBundleUrl("classpath://missing-jar-file.txt");
     }
+    
+    @Test
+    public void testOsgiBundleWithBom() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+        BundleMaker bm = new BundleMaker(manager);
+        File f = Os.newTempFile("osgi", "jar");
+        Files.copyFile(ResourceUtils.create(this).getResourceFromUrl(bundleUrl), f);
+        
+        String symbolicName = "my.catalog.entity.id.testOsgiBundleWithBom";
+        String bom = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "  id: " + symbolicName,
+                "  version: " + TEST_VERSION,
+                "  itemType: entity",
+                "  name: My Catalog App",
+                "  description: My description",
+                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif",
+                "  item:",
+                "    type: org.apache.brooklyn.core.test.entity.TestEntity");
+        
+        f = bm.copyAdding(f, MutableMap.of(new ZipEntry("catalog.bom"), (InputStream) new ByteArrayInputStream(bom.getBytes())));
+
+        Response response = client().path("/catalog")
+                .header(HttpHeaders.CONTENT_TYPE, "application/x-zip")
+                .post(Streams.readFully(new FileInputStream(f)));
+
+        
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+
+        CatalogEntitySummary entityItem = client().path("/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+                .get(CatalogEntitySummary.class);
+
+        Assert.assertNotNull(entityItem.getPlanYaml());
+        Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
+
+        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getSymbolicName(), symbolicName);
+        assertEquals(entityItem.getVersion(), TEST_VERSION);
+
+        // and internally let's check we have libraries
+        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+        Assert.assertNotNull(item);
+        Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+        assertEquals(libs.size(), 1);
+        OsgiBundleWithUrl lib = Iterables.getOnlyElement(libs);
+        Assert.assertNull(lib.getUrl());
+
+        assertEquals(lib.getSymbolicName(), "org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-entities");
+        assertEquals(lib.getVersion(), "0.1.0");
+
+        // now let's check other things on the item
+        URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/icon/" + symbolicName + "/" + entityItem.getVersion()).normalize();
+        assertEquals(entityItem.getName(), "My Catalog App");
+        assertEquals(entityItem.getDescription(), "My description");
+        assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath());
+        assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+
+        // an InterfacesTag should be created for every catalog item
+        assertEquals(entityItem.getTags().size(), 1);
+        Object tag = entityItem.getTags().iterator().next();
+        @SuppressWarnings("unchecked")
+        List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
+        List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
+        assertEquals(actualInterfaces.size(), expectedInterfaces.size());
+        for (Class<?> expectedInterface : expectedInterfaces) {
+            assertTrue(actualInterfaces.contains(expectedInterface.getName()));
+        }
+
+        byte[] iconData = client().path("/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+        assertEquals(iconData.length, 43);
+    }
 
     private void addAddCatalogItemWithInvalidBundleUrl(String bundleUrl) {
         String symbolicName = "my.catalog.entity.id";


[2/3] brooklyn-server git commit: require bundle name/version defined in catalog.bom when POSTing a ZIP/JAR

Posted by al...@apache.org.
require bundle name/version defined in catalog.bom when POSTing a ZIP/JAR

accepts id, symbolicName, or symbolic-name under brooklyn.catalog;
if osgi bundle manifest supplied, it must match.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/67419b65
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/67419b65
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/67419b65

Branch: refs/heads/master
Commit: 67419b65c3976292ca35b7c26cd3804fca7cd182
Parents: 5df2824
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Mar 9 09:59:56 2017 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Mar 9 10:02:33 2017 +0000

----------------------------------------------------------------------
 .../catalog/internal/BasicBrooklynCatalog.java  |  51 +++++++-
 .../apache/brooklyn/rest/api/CatalogApi.java    |  13 +-
 .../rest/resources/CatalogResource.java         | 126 +++++++++++--------
 .../rest/resources/CatalogResourceTest.java     |  19 +--
 4 files changed, 142 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/67419b65/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
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 106240e..6e2a802 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
@@ -57,11 +57,13 @@ import org.apache.brooklyn.util.guava.Maybe;
 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.osgi.VersionedName;
 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.Version;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.yaml.snakeyaml.Yaml;
@@ -407,7 +409,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
     }
     
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    private Maybe<Map<?,?>> getFirstAsMap(Map<?,?> map, String firstKey, String ...otherKeys) {
+    private static Maybe<Map<?,?>> getFirstAsMap(Map<?,?> map, String firstKey, String ...otherKeys) {
         return (Maybe) getFirstAs(map, Map.class, firstKey, otherKeys);
     }
 
@@ -417,6 +419,53 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return result;
     }
 
+    public static Map<?,?> getCatalogMetadata(String yaml) {
+        Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
+        return getFirstAsMap(itemDef, "brooklyn.catalog").orNull();        
+    }
+    
+    public static VersionedName getVersionedName(Map<?,?> catalogMetadata) {
+        String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
+        String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
+        String symbolicName = getFirstAs(catalogMetadata, String.class, "symbolicName").orNull();
+        symbolicName = findAssertingConsistentIfSet("symbolicName", symbolicName, getFirstAs(catalogMetadata, String.class, "symbolic-name").orNull());
+        // prefer symbolic name and version, if supplied
+        // else use id as symbolic name : version or just symbolic name
+        // (must match if both supplied)
+        if (Strings.isNonBlank(id)) {
+            if (CatalogUtils.looksLikeVersionedId(id)) {
+                symbolicName = findAssertingConsistentIfSet("symbolicName", symbolicName, CatalogUtils.getSymbolicNameFromVersionedId(id));
+                version = findAssertingConsistentIfSet("version", version, CatalogUtils.getVersionFromVersionedId(id));
+            } else {
+                symbolicName = findAssertingConsistentIfSet("symbolicName", symbolicName, id);
+            }
+        }
+        if (Strings.isBlank(symbolicName) && Strings.isBlank(version)) {
+            throw new IllegalStateException("Catalog BOM must define symbolicName and version");
+        }
+        if (Strings.isBlank(symbolicName)) {
+            throw new IllegalStateException("Catalog BOM must define symbolicName");
+        }
+        if (Strings.isBlank(version)) {
+            throw new IllegalStateException("Catalog BOM must define version");
+        }
+        return new VersionedName(symbolicName, Version.valueOf(version));
+    }
+    
+    private static String findAssertingConsistentIfSet(String context, String ...values) {
+        String v = null;
+        for (String vi: values) {
+            if (Strings.isNonBlank(vi)) {
+                if (Strings.isNonBlank(v)) {
+                    if (!v.equals(vi)) throw new IllegalStateException("Mismatch in "+context+": '"+v+"' or '"+vi+"' supplied");
+                } else {
+                    v = vi;
+                }
+            }
+        }
+        return v;
+    }
+
     private void collectCatalogItems(String yaml, List<CatalogItemDtoAbstract<?, ?>> result, Map<?, ?> parentMeta) {
         Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
         Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/67419b65/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
----------------------------------------------------------------------
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 93a812e..79b6186 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
@@ -76,23 +76,20 @@ public interface CatalogApi {
                     name = "item",
                     value = "Item to install, as JAR/ZIP or Catalog YAML (autodetected)",
                     required = true)
-            byte[] item,
-            @ApiParam(name="name", value="Symbolic name to use for bundle", required=false, defaultValue="") String bundleName, 
-            @ApiParam(name="version", value="Version to set for bundle", required=false, defaultValue="") String bundleVersion);
+            byte[] item);
     
     @POST
     @Beta
     @Consumes({"application/x-zip", "application/x-jar"})
-    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading OSGi bundle JAR, or ZIP if bundle name and optionally version are supplied. "
+    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading OSGi bundle JAR, or ZIP which will be turned into bundle JAR, "
+            + "containing catalog.bom containing bundle name and version. "
             + "Return value is 201 CREATED if bundle could be added.", response = String.class)
     public Response createFromArchive(
             @ApiParam(
                     name = "archive",
-                    value = "Bundle to install, in JAR format, or ZIP if bundle name and optionally version are supplied, optionally with catalog.bom contained within",
+                    value = "Bundle to install, in ZIP or JAR format, requiring catalog.bom containing bundle name and version",
                     required = true)
-            byte[] archive,
-            @ApiParam(name="name", value="Symbolic name to use for bundle", required=false, defaultValue="") String bundleName, 
-            @ApiParam(name="version", value="Version to set for bundle", required=false, defaultValue="") String bundleVersion);
+            byte[] archive);
     
     @POST
     @Consumes(MediaType.APPLICATION_XML)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/67419b65/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
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 edc8b4d..9fdf977 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
@@ -30,7 +30,6 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.jar.Attributes;
-import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 
 import javax.annotation.Nullable;
@@ -78,9 +77,13 @@ import org.apache.brooklyn.util.core.osgi.BundleMaker;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 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.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.yaml.Yamls;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.slf4j.Logger;
@@ -112,10 +115,7 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     static Set<String> missingIcons = MutableSet.of();
 
     @Override
-    public Response createPoly(
-            byte[] item,
-            String bundleName, 
-            String bundleVersion) {
+    public Response createPoly(byte[] item) {
         Throwable yamlException = null;
         try {
             MutableList.copyOf( Yamls.parseAll(new InputStreamReader(new ByteArrayInputStream(item))) );
@@ -125,15 +125,12 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
         }
         
         if (yamlException==null) {
-            // treat as yaml
-            if (Strings.isNonBlank(bundleName) || Strings.isNonBlank(bundleVersion)) {
-                throw new IllegalArgumentException("Bundle name/version not permitted when installing catalog YAML");
-            }
+            // treat as yaml if it parsed
             return createFromYaml(new String(item));
         }
         
         try {
-            return createFromArchive(item, bundleName, bundleVersion);
+            return createFromArchive(item);
         } catch (Exception e) {
             throw Exceptions.propagate("Unable to handle input: not YAML or compatible ZIP. Specify Content-Type to clarify.", e);
         }
@@ -180,54 +177,85 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     }
 
     @Override
-    public Response createFromArchive(byte[] zipInput, String bundleName, String bundleVersion) {
+    public Response createFromArchive(byte[] zipInput) {
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ROOT, null)) {
             throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
                 Entitlements.getEntitlementContext().user());
         }
 
         BundleMaker bm = new BundleMaker(mgmt());
-        File f = Os.newTempFile("brooklyn-posted-archive", "zip");
+        File f=null, f2=null;
         try {
-            Files.write(zipInput, f);
-        } catch (IOException e) {
-            Exceptions.propagate(e);
-        }
-        Manifest mf = bm.getManifest(f);
-        if (mf==null) {
-            mf = new Manifest();
-        }
-        String bundleNameInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-        if (Strings.isBlank(bundleName) && Strings.isBlank(bundleNameInMF)) {
-            throw new IllegalStateException("Require either "+Constants.BUNDLE_SYMBOLICNAME+" in "+JarFile.MANIFEST_NAME+" or name supplied in REST API");
-        }
-        if (!Strings.isBlank(bundleName)) {
-            mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, bundleName);
-        }
-        if (!Strings.isBlank(bundleVersion) || Strings.isBlank(mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION))) {
-            if (Strings.isBlank(bundleVersion)) {
-                bundleVersion = "0.0.0.SNAPSHOT";
+            f = Os.newTempFile("brooklyn-posted-archive", "zip");
+            try {
+                Files.write(zipInput, f);
+            } catch (IOException e) {
+                Exceptions.propagate(e);
             }
-            mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, bundleVersion);
-        }
-        if (mf.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
-            mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
-        }
-        
-        File f2 = bm.copyAddingManifest(f, mf);
-        f.delete();
-        
-        Bundle bundle = bm.installBundle(f2, true);
-        f2.delete();
-        
-        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)
-            new CatalogBomScanner().new CatalogPopulator(
-                    ((LocalManagementContext) mgmt()).getOsgiManager().get().getFramework().getBundleContext(),
-                    mgmt()).addingBundle(bundle, null);
+            
+            ZipFile zf;
+            try {
+                zf = new ZipFile(f);
+            } catch (IOException e) {
+                throw WebResourceUtils.badRequest("Invalid ZIP/JAR archive: "+e);
+            }
+            ZipArchiveEntry bom = zf.getEntry("catalog.bom");
+            if (bom==null) {
+                bom = zf.getEntry("/catalog.bom");
+            }
+            if (bom==null) {
+                throw WebResourceUtils.badRequest("Archive must contain a catalog.bom file in the root");
+            }
+            String bomS;
+            try {
+                bomS = Streams.readFullyString(zf.getInputStream(bom));
+            } catch (IOException e) {
+                throw WebResourceUtils.badRequest("Error reading catalog.bom from ZIP/JAR archive: "+e);
+            }
+            VersionedName vn = BasicBrooklynCatalog.getVersionedName( BasicBrooklynCatalog.getCatalogMetadata(bomS) );
+            
+            Manifest mf = bm.getManifest(f);
+            if (mf==null) {
+                mf = new Manifest();
+            }
+            String bundleNameInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+            if (Strings.isNonBlank(bundleNameInMF)) {
+                if (!bundleNameInMF.equals(vn.getSymbolicName())) {
+                    throw new IllegalStateException("JAR MANIFEST symbolic-name '"+bundleNameInMF+"' does not match '"+vn.getSymbolicName()+"' defined in BOM");
+                }
+            } else {
+                mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName());
+            }
+            
+            String bundleVersionInMF = mf.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+            if (Strings.isNonBlank(bundleVersionInMF)) {
+                if (!bundleVersionInMF.equals(vn.getVersion().toString())) {
+                    throw new IllegalStateException("JAR MANIFEST version '"+bundleVersionInMF+"' does not match '"+vn.getVersion()+"' defined in BOM");
+                }
+            } else {
+                mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getVersion().toString());
+            }
+            if (mf.getMainAttributes().getValue(Attributes.Name.MANIFEST_VERSION)==null) {
+                mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
+            }
+            
+            f2 = bm.copyAddingManifest(f, mf);
+            
+            Bundle bundle = bm.installBundle(f2, true);
+            
+            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)
+                new CatalogBomScanner().new CatalogPopulator(
+                        ((LocalManagementContext) mgmt()).getOsgiManager().get().getFramework().getBundleContext(),
+                        mgmt()).addingBundle(bundle, null);
+            }
+            
+            return Response.status(Status.CREATED).build();
+            
+        } finally {
+            if (f!=null) f.delete();
+            if (f2!=null) f2.delete();
         }
-        
-        return Response.status(Status.CREATED).build();
     }
     
     @SuppressWarnings("deprecation")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/67419b65/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
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 d1a74ae..9e70156 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
@@ -499,16 +499,17 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     @Test
     public void testOsgiBundleWithBom() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
-        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+        final String symbolicName = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_SYMBOLIC_NAME_FULL;
+        final String version = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION;
+        final String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
         BundleMaker bm = new BundleMaker(manager);
         File f = Os.newTempFile("osgi", "jar");
         Files.copyFile(ResourceUtils.create(this).getResourceFromUrl(bundleUrl), f);
         
-        String symbolicName = "my.catalog.entity.id.testOsgiBundleWithBom";
         String bom = Joiner.on("\n").join(
                 "brooklyn.catalog:",
                 "  id: " + symbolicName,
-                "  version: " + TEST_VERSION,
+                "  version: " + version,
                 "  itemType: entity",
                 "  name: My Catalog App",
                 "  description: My description",
@@ -525,18 +526,18 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         
         assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
 
-        CatalogEntitySummary entityItem = client().path("/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+        CatalogEntitySummary entityItem = client().path("/catalog/entities/"+symbolicName + "/" + version)
                 .get(CatalogEntitySummary.class);
 
         Assert.assertNotNull(entityItem.getPlanYaml());
         Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
 
-        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getId(), CatalogUtils.getVersionedId(symbolicName, version));
         assertEquals(entityItem.getSymbolicName(), symbolicName);
-        assertEquals(entityItem.getVersion(), TEST_VERSION);
+        assertEquals(entityItem.getVersion(), version);
 
         // and internally let's check we have libraries
-        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, version);
         Assert.assertNotNull(item);
         Collection<OsgiBundleWithUrl> libs = item.getLibraries();
         assertEquals(libs.size(), 1);
@@ -544,7 +545,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         Assert.assertNull(lib.getUrl());
 
         assertEquals(lib.getSymbolicName(), "org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-entities");
-        assertEquals(lib.getVersion(), "0.1.0");
+        assertEquals(lib.getVersion(), version);
 
         // now let's check other things on the item
         URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/icon/" + symbolicName + "/" + entityItem.getVersion()).normalize();
@@ -564,7 +565,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
             assertTrue(actualInterfaces.contains(expectedInterface.getName()));
         }
 
-        byte[] iconData = client().path("/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+        byte[] iconData = client().path("/catalog/icon/" + symbolicName + "/" + version).get(byte[].class);
         assertEquals(iconData.length, 43);
     }
 


[3/3] brooklyn-server git commit: This closes #485

Posted by al...@apache.org.
This closes #485


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/48beb5c2
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/48beb5c2
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/48beb5c2

Branch: refs/heads/master
Commit: 48beb5c2f94370a23d6181d7c24159c3be96133b
Parents: 8930ab6 67419b6
Author: Aled Sage <al...@gmail.com>
Authored: Thu Mar 23 17:19:15 2017 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Thu Mar 23 17:19:15 2017 +0000

----------------------------------------------------------------------
 .../catalog/internal/BasicBrooklynCatalog.java  |  51 ++++++-
 .../brooklyn/rest/api/ApplicationApi.java       |   3 -
 .../apache/brooklyn/rest/api/CatalogApi.java    |  43 +++++-
 .../rest/resources/CatalogResource.java         | 132 ++++++++++++++++++-
 .../rest/resources/CatalogResourceTest.java     |  91 ++++++++++++-
 5 files changed, 305 insertions(+), 15 deletions(-)
----------------------------------------------------------------------