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(-)
----------------------------------------------------------------------