Repository: incubator-brooklyn Updated Branches: refs/heads/master 861b86a38 -> 4f6fa6ca9
Fix delete location from catalog - On REST api (LocationResource.delete), when location is deleted then delete from catalog as well as from LocationRegistry. (Otherwise on rebind it will come back again!) - Adds LocationApi.delete(symbolicName, version); Previously just had delete(id)) - Adds to CatalogApi: - deletePolicy - deleteLocation - listLocations - Change CatalogApi.listEntities return type to List<CatalogEntitySummary> instead of List<CatalogItemSummary> - Cleanup LocationApi: deprecated methods to do with definitions, preferring CatalogApi usage. - Adds CatalogLocationSummary and LocationConfigSummary Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/f114676f Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/f114676f Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/f114676f Branch: refs/heads/master Commit: f114676fd22b78d11ce1affa5afdbd6804b76c77 Parents: 4f1eb45 Author: Aled Sage <[email protected]> Authored: Thu Apr 2 21:07:04 2015 +0100 Committer: Aled Sage <[email protected]> Committed: Sun Apr 12 10:12:08 2015 -0500 ---------------------------------------------------------------------- .../entity/rebind/RebindCatalogItemTest.java | 13 +- .../entity/rebind/RebindTestFixture.java | 6 + .../main/java/brooklyn/rest/api/CatalogApi.java | 81 ++++++++++-- .../java/brooklyn/rest/api/LocationApi.java | 24 +++- .../rest/domain/CatalogLocationSummary.java | 59 +++++++++ .../rest/domain/LocationConfigSummary.java | 62 +++++++++ .../rest/resources/CatalogResource.java | 126 +++++++++++++++++-- .../rest/resources/LocationResource.java | 4 +- .../rest/transform/CatalogTransformer.java | 12 ++ .../rest/resources/ApiDocResourceTest.java | 2 +- .../rest/resources/CatalogResourceTest.java | 55 +++++++- .../rest/resources/LocationResourceTest.java | 98 +++++++++++---- 12 files changed, 489 insertions(+), 53 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java index 5ca275a..4036dc5 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java @@ -146,7 +146,7 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { } @Test - public void testAddAndRebindLocation() { + public void testAddAndRebindAndDeleteLocation() { String yaml = Joiner.on("\n").join(ImmutableList.of( "name: Test Location", "brooklyn.catalog:", @@ -160,6 +160,11 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { CatalogItem<?, ?> added = addItem(origManagementContext, yaml); assertEquals(added.getCatalogItemType(), CatalogItemType.LOCATION); rebindAndAssertCatalogsAreEqual(); + + deleteItem(newManagementContext, added.getSymbolicName(), added.getVersion()); + + switchOriginalToNewManagementContext(); + rebindAndAssertCatalogsAreEqual(); } @Test(enabled = false) @@ -239,6 +244,12 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { return added; } + protected void deleteItem(ManagementContext mgmt, String symbolicName, String version) { + mgmt.getCatalog().deleteCatalogItem(symbolicName, version); + LOG.info("Deleted item from catalog: {}:{}", symbolicName, version); + assertCatalogDoesNotContain(mgmt.getCatalog(), symbolicName, version); + } + private void rebindAndAssertCatalogsAreEqual() { try { rebind(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java index b74ab74..c307aa4 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java @@ -20,6 +20,7 @@ package brooklyn.entity.rebind; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import java.io.File; import java.util.List; @@ -316,4 +317,9 @@ public abstract class RebindTestFixture<T extends StartableApplication> { assertNotNull(found); assertCatalogItemsEqual(found, item); } + + protected void assertCatalogDoesNotContain(BrooklynCatalog catalog, String symbolicName, String version) { + CatalogItem<?, ?> found = catalog.getCatalogItem(symbolicName, version); + assertNull(found); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java index 1a03134..bf097e2 100644 --- a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java +++ b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java @@ -37,6 +37,8 @@ import javax.ws.rs.core.Response; import brooklyn.rest.apidoc.Apidoc; import brooklyn.rest.domain.CatalogEntitySummary; import brooklyn.rest.domain.CatalogItemSummary; +import brooklyn.rest.domain.CatalogLocationSummary; +import brooklyn.rest.domain.CatalogPolicySummary; import com.sun.jersey.core.header.FormDataContentDisposition; import com.sun.jersey.multipart.FormDataParam; @@ -52,7 +54,7 @@ import com.wordnik.swagger.core.ApiParam; public interface CatalogApi { @POST - @ApiOperation(value = "Add a catalog item (e.g. new entity or policy type) by uploading YAML descriptor from browser using multipart/form-data", + @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor from browser using multipart/form-data", responseClass = "String") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response createFromMultipart( @@ -62,7 +64,7 @@ public interface CatalogApi { @Consumes @POST - @ApiOperation(value = "Add a catalog item (e.g. new entity or policy type) by uploading YAML descriptor", responseClass = "String") + @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor", responseClass = "String") public Response create( @ApiParam(name = "yaml", value = "YAML descriptor of catalog item", required = true) @Valid String yaml); @@ -75,7 +77,7 @@ public interface CatalogApi { @ApiParam(name = "xml", value = "XML descriptor of the entire catalog to install", required = true) @Valid String xml); - /** @deprecated since 0.7.0 use {@link #getEntity(String, String)} */ + /** @deprecated since 0.7.0 use {@link #deleteEntity(String, String)} */ @Deprecated @DELETE @Path("/entities/{entityId}") @@ -100,10 +102,36 @@ public interface CatalogApi { @ApiParam(name = "version", value = "The version identifier of the entity or template to delete", required = true) @PathParam("version") String version) throws Exception; + @DELETE + @Path("/policies/{policyId}/{version}") + @ApiOperation(value = "Deletes a specific version of an policy's definition from the catalog") + @ApiErrors(value = { + @ApiError(code = 404, reason = "Policy not found") + }) + public void deletePolicy( + @ApiParam(name = "policyId", value = "The ID of the policy to delete", required = true) + @PathParam("policyId") String policyId, + + @ApiParam(name = "version", value = "The version identifier of the policy to delete", required = true) + @PathParam("version") String version) throws Exception; + + @DELETE + @Path("/locations/{locationId}/{version}") + @ApiOperation(value = "Deletes a specific version of an location's definition from the catalog") + @ApiErrors(value = { + @ApiError(code = 404, reason = "Location not found") + }) + public void deleteLocation( + @ApiParam(name = "locationId", value = "The ID of the location to delete", required = true) + @PathParam("locationId") String locationId, + + @ApiParam(name = "version", value = "The version identifier of the location to delete", required = true) + @PathParam("version") String version) throws Exception; + @GET @Path("/entities") @ApiOperation(value = "List available entity types optionally matching a query", responseClass = "CatalogItemSummary", multiValueResponse = true) - public List<CatalogItemSummary> listEntities( + public List<CatalogEntitySummary> listEntities( @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @@ -170,14 +198,14 @@ public interface CatalogApi { @GET @Path("/policies") - @ApiOperation(value = "List available policies optionally matching a query", responseClass = "CatalogItemSummary", multiValueResponse = true) - public List<CatalogItemSummary> listPolicies( + @ApiOperation(value = "List available policies optionally matching a query", responseClass = "CatalogPolicySummary", multiValueResponse = true) + public List<CatalogPolicySummary> listPolicies( @ApiParam(name = "regex", value = "Regular expression to search for") @QueryParam("regex") @DefaultValue("") String regex, @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") @QueryParam("fragment") @DefaultValue("") String fragment); - /** @deprecated since 0.7.0 use {@link #getEntity(String, String)} */ + /** @deprecated since 0.7.0 use {@link #getPolicy(String, String)} */ @Deprecated @GET @Path("/policies/{policyId}") @@ -201,6 +229,39 @@ public interface CatalogApi { @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) @PathParam("version") String version) throws Exception; + @GET + @Path("/locations") + @ApiOperation(value = "List available locations optionally matching a query", responseClass = "CatalogLocationSummary", multiValueResponse = true) + public List<CatalogLocationSummary> listLocations( + @ApiParam(name = "regex", value = "Regular expression to search for") + @QueryParam("regex") @DefaultValue("") String regex, + @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") + @QueryParam("fragment") @DefaultValue("") String fragment); + + /** @deprecated since 0.7.0 use {@link #getLocation(String, String)} */ + @Deprecated + @GET + @Path("/locations/{locationId}") + @ApiOperation(value = "Fetch a location's definition from the catalog", responseClass = "CatalogItemSummary", multiValueResponse = true) + @ApiErrors(value = { + @ApiError(code = 404, reason = "Entity not found") + }) + public CatalogItemSummary getLocation( + @ApiParam(name = "locationId", value = "The ID of the location to retrieve", required = true) + @PathParam("locationId") String locationId) throws Exception; + + @GET + @Path("/locations/{locationId}/{version}") + @ApiOperation(value = "Fetch a location's definition from the catalog", responseClass = "CatalogItemSummary", multiValueResponse = true) + @ApiErrors(value = { + @ApiError(code = 404, reason = "Entity not found") + }) + public CatalogItemSummary getLocation( + @ApiParam(name = "locationId", value = "The ID of the location to retrieve", required = true) + @PathParam("locationId") String locationId, + @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true) + @PathParam("version") String version) throws Exception; + /** @deprecated since 0.7.0 use {@link #getIcon(String, String)} */ @Deprecated @GET @@ -211,7 +272,7 @@ public interface CatalogApi { }) @Produces("application/image") public Response getIcon( - @ApiParam(name = "itemId", value = "ID of catalog item (application, entity, policy)") + @ApiParam(name = "itemId", value = "ID of catalog item (application, entity, policy, location)") @PathParam("itemId") @DefaultValue("") String itemId); @GET @@ -222,10 +283,10 @@ public interface CatalogApi { }) @Produces("application/image") public Response getIcon( - @ApiParam(name = "itemId", value = "ID of catalog item (application, entity, policy)", required=true) + @ApiParam(name = "itemId", value = "ID of catalog item (application, entity, policy, location)", required=true) @PathParam("itemId") String itemId, - @ApiParam(name = "version", value = "version identifier of catalog item (application, entity, policy)", required=true) + @ApiParam(name = "version", value = "version identifier of catalog item (application, entity, policy, location)", required=true) @PathParam("version") String version); @POST http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java index 4b6e32f..a5c5207 100644 --- a/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java +++ b/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java @@ -47,10 +47,14 @@ import com.wordnik.swagger.core.ApiParam; @Consumes(MediaType.APPLICATION_JSON) public interface LocationApi { + /** + * @deprecated since 0.7.0; use {@link CatalogApi#listLocations(String, String)} + */ @GET - @ApiOperation(value = "Fetch the list of locations", + @ApiOperation(value = "Fetch the list of location definitions", responseClass = "brooklyn.rest.domain.LocationSummary", multiValueResponse = true) + @Deprecated public List<LocationSummary> list(); // this is here to support the web GUI's circles @@ -59,9 +63,13 @@ public interface LocationApi { @ApiOperation(value = "Return a summary of all usage", notes="interim API, expected to change") public Map<String,Map<String,Object>> getLocatedLocations(); + /** + * WARNING: behaviour will change in a future release; this will only return location instances. + * See {@link CatalogApi#getLocation(String, String)} for retrieving location definitions. + */ @GET @Path("/{locationId}") - @ApiOperation(value = "Fetch details about a location", + @ApiOperation(value = "Fetch details about a location instance, or a location definition", responseClass = "brooklyn.rest.domain.LocationSummary", multiValueResponse = true) public LocationSummary get( @@ -71,15 +79,23 @@ public interface LocationApi { @DefaultValue("false") @QueryParam("full") String fullConfig); + /** + * @deprecated since 0.7.0; use {@link CatalogApi#create(String)} + */ @POST - @ApiOperation(value = "Create a new location", responseClass = "String") + @ApiOperation(value = "Create a new location definition", responseClass = "String") + @Deprecated public Response create( @ApiParam(name = "locationSpec", value = "Location specification object", required = true) @Valid LocationSpec locationSpec); + /** + * @deprecated since 0.7.0; use {@link CatalogApi#deleteLocation(String, String)} + */ @DELETE @Path("/{locationId}") - @ApiOperation(value = "Delete a location object by id") + @ApiOperation(value = "Deletes a location definition by id") + @Deprecated public void delete( @ApiParam(value = "Location id to delete", required = true) @PathParam("locationId") String locationId); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogLocationSummary.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogLocationSummary.java b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogLocationSummary.java new file mode 100644 index 0000000..874b848 --- /dev/null +++ b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogLocationSummary.java @@ -0,0 +1,59 @@ +/* + * 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 brooklyn.rest.domain; + +import java.net.URI; +import java.util.Map; +import java.util.Set; + +import org.codehaus.jackson.annotate.JsonProperty; + +import com.google.common.collect.ImmutableSet; + +public class CatalogLocationSummary extends CatalogItemSummary { + + private final Set<LocationConfigSummary> config; + + public CatalogLocationSummary( + @JsonProperty("symbolicName") String symbolicName, + @JsonProperty("version") String version, + @JsonProperty("name") String name, + @JsonProperty("javaType") String javaType, + @JsonProperty("planYaml") String planYaml, + @JsonProperty("description") String description, + @JsonProperty("iconUrl") String iconUrl, + @JsonProperty("config") Set<LocationConfigSummary> config, + @JsonProperty("deprecated") boolean deprecated, + @JsonProperty("links") Map<String, URI> links + ) { + super(symbolicName, version, name, javaType, planYaml, description, iconUrl, deprecated, links); + // TODO expose config from policies + this.config = (config == null) ? ImmutableSet.<LocationConfigSummary>of() : config; + } + + public Set<LocationConfigSummary> getConfig() { + return config; + } + + @Override + public String toString() { + return super.toString()+"["+ + "config="+getConfig()+"]"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-api/src/main/java/brooklyn/rest/domain/LocationConfigSummary.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/domain/LocationConfigSummary.java b/usage/rest-api/src/main/java/brooklyn/rest/domain/LocationConfigSummary.java new file mode 100644 index 0000000..b315459 --- /dev/null +++ b/usage/rest-api/src/main/java/brooklyn/rest/domain/LocationConfigSummary.java @@ -0,0 +1,62 @@ +/* + * 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 brooklyn.rest.domain; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion; + +import com.google.common.collect.ImmutableMap; + +public class LocationConfigSummary extends ConfigSummary { + + @JsonSerialize(include = Inclusion.NON_NULL) + private final Map<String, URI> links; + + public LocationConfigSummary( + @JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("description") String description, + @JsonProperty("defaultValue") Object defaultValue, + @JsonProperty("reconfigurable") boolean reconfigurable, + @JsonProperty("label") String label, + @JsonProperty("priority") Double priority, + @JsonProperty("possibleValues") List<Map<String, String>> possibleValues, + @JsonProperty("links") Map<String, URI> links) { + super(name, type, description, defaultValue, reconfigurable, label, priority, possibleValues); + this.links = (links == null) ? ImmutableMap.<String, URI>of() : ImmutableMap.copyOf(links); + } + + @Override + public Map<String, URI> getLinks() { + return links; + } + + @Override + public String toString() { + return "LocationConfigSummary{" + + "name='" + getName() + '\'' + + ", type='" + getType() + '\'' + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java index 7040f13..98e5a6b 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java @@ -35,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import brooklyn.catalog.CatalogItem; +import brooklyn.catalog.CatalogItem.CatalogItemType; import brooklyn.catalog.CatalogPredicates; import brooklyn.catalog.internal.BasicBrooklynCatalog; import brooklyn.catalog.internal.CatalogDto; @@ -43,6 +44,8 @@ import brooklyn.catalog.internal.CatalogUtils; import brooklyn.entity.Application; import brooklyn.entity.Entity; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; import brooklyn.management.entitlement.Entitlements; import brooklyn.management.entitlement.Entitlements.StringAndArgument; import brooklyn.policy.Policy; @@ -51,6 +54,8 @@ import brooklyn.rest.api.CatalogApi; import brooklyn.rest.domain.ApiError; import brooklyn.rest.domain.CatalogEntitySummary; import brooklyn.rest.domain.CatalogItemSummary; +import brooklyn.rest.domain.CatalogLocationSummary; +import brooklyn.rest.domain.CatalogPolicySummary; import brooklyn.rest.filter.HaHotStateRequired; import brooklyn.rest.transform.CatalogTransformer; import brooklyn.rest.util.WebResourceUtils; @@ -69,7 +74,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.io.Files; import com.sun.jersey.core.header.FormDataContentDisposition; -import com.wordnik.swagger.core.ApiParam; @HaHotStateRequired public class CatalogResource extends AbstractBrooklynRestResource implements CatalogApi { @@ -126,6 +130,10 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat return Response.created(URI.create("policies/" + itemId)) .entity(CatalogTransformer.catalogPolicySummary(brooklyn(), (CatalogItem<? extends Policy, PolicySpec<?>>) item)) .build(); + case LOCATION: + return Response.created(URI.create("locations/" + itemId + "/" + item.getVersion())) + .entity(CatalogTransformer.catalogLocationSummary(brooklyn(), (CatalogItem<? extends Location, LocationSpec<?>>) item)) + .build(); default: throw new IllegalStateException("Unsupported catalog item type "+item.getCatalogItemType()+": "+item); } @@ -152,8 +160,11 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat } try { CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt(), entityId); - if (item==null) + if (item==null) { throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId); + } else if (item.getCatalogItemType() != CatalogItemType.ENTITY && item.getCatalogItemType() != CatalogItemType.TEMPLATE) { + throw WebResourceUtils.preconditionFailed("Item with id '%s' not an entity", entityId); + } brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); } catch (NoSuchElementException e) { throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId); @@ -166,16 +177,55 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify catalog", Entitlements.getEntitlementContext().user()); } - try { - brooklyn().getCatalog().deleteCatalogItem(entityId, version); - } catch (NoSuchElementException e) { + + CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(entityId, version); + if (item == null) { throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", entityId, version); + } else if (item.getCatalogItemType() != CatalogItemType.ENTITY && item.getCatalogItemType() != CatalogItemType.TEMPLATE) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not an entity", entityId, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + } + } + + @Override + public void deletePolicy(String policyId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(policyId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) { + throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(policyId, version); + if (item == null) { + throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version); + } else if (item.getCatalogItemType() != CatalogItemType.POLICY) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a policy", policyId, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); + } + } + + @Override + public void deleteLocation(String locationId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(locationId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) { + throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify catalog", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(locationId, version); + if (item == null) { + throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); + } else if (item.getCatalogItemType() != CatalogItemType.LOCATION) { + throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a location", locationId, version); + } else { + brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); } } @Override - public List<CatalogItemSummary> listEntities(String regex, String fragment) { - return getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_ENTITY, regex, fragment); + public List<CatalogEntitySummary> listEntities(String regex, String fragment) { + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_ENTITY, regex, fragment); + return cast(result, CatalogEntitySummary.class); } @Override @@ -236,13 +286,14 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat } @Override - public List<CatalogItemSummary> listPolicies(String regex, String fragment) { - return getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_POLICY, regex, fragment); + public List<CatalogPolicySummary> listPolicies(String regex, String fragment) { + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_POLICY, regex, fragment); + return cast(result, CatalogPolicySummary.class); } - + @Override @Deprecated - public CatalogItemSummary getPolicy(String policyId) { + public CatalogPolicySummary getPolicy(String policyId) { if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId)) { throw WebResourceUtils.unauthorized("User '%s' is not authorized to see catalog entry", Entitlements.getEntitlementContext().user()); @@ -259,7 +310,7 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat } @Override - public CatalogItemSummary getPolicy(String policyId, String version) throws Exception { + public CatalogPolicySummary getPolicy(String policyId, String version) throws Exception { if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId+(Strings.isBlank(version)?"":":"+version))) { throw WebResourceUtils.unauthorized("User '%s' is not authorized to see catalog entry", Entitlements.getEntitlementContext().user()); @@ -276,6 +327,48 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat return CatalogTransformer.catalogPolicySummary(brooklyn(), result); } + @Override + public List<CatalogLocationSummary> listLocations(String regex, String fragment) { + List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_LOCATION, regex, fragment); + return cast(result, CatalogLocationSummary.class); + } + + @Override + @Deprecated + public CatalogLocationSummary getLocation(String locationId) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId)) { + throw WebResourceUtils.unauthorized("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + CatalogItem<? extends Location, LocationSpec<?>> result = + CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Location.class, locationId); + + if (result==null) { + throw WebResourceUtils.notFound("Location with id '%s' not found", locationId); + } + + return CatalogTransformer.catalogLocationSummary(brooklyn(), result); + } + + @Override + public CatalogLocationSummary getLocation(String locationId, String version) throws Exception { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId+(Strings.isBlank(version)?"":":"+version))) { + throw WebResourceUtils.unauthorized("User '%s' is not authorized to see catalog entry", + Entitlements.getEntitlementContext().user()); + } + + @SuppressWarnings("unchecked") + CatalogItem<? extends Location, LocationSpec<?>> result = + (CatalogItem<? extends Location, LocationSpec<?>>)brooklyn().getCatalog().getCatalogItem(locationId, version); + + if (result==null) { + throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version); + } + + return CatalogTransformer.catalogLocationSummary(brooklyn(), result); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment) { List filters = new ArrayList(); @@ -369,4 +462,13 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat return Response.temporaryRedirect(URI.create(url)).build(); } + // TODO Move to an appropriate utility class? + @SuppressWarnings("unchecked") + private static <T> List<T> cast(List<? super T> list, Class<T> elementType) { + List<T> result = Lists.newArrayList(); + for (Object element : list) { + result.add((T) element); + } + return result; + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java index 8d7e3f6..362d152 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java @@ -149,7 +149,9 @@ public class LocationResource extends AbstractBrooklynRestResource implements Lo .build(); } + @Override + @Deprecated public void delete(String locationId) { - brooklyn().getLocationRegistry().removeDefinedLocation(locationId); + brooklyn().getCatalog().deleteCatalogItem(locationId); } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java b/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java index 7222141..adee3e2 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java @@ -33,13 +33,17 @@ import brooklyn.entity.EntityType; import brooklyn.entity.basic.EntityDynamicType; import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.Sensor; +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; import brooklyn.policy.Policy; import brooklyn.policy.PolicySpec; import brooklyn.rest.domain.CatalogEntitySummary; import brooklyn.rest.domain.CatalogItemSummary; +import brooklyn.rest.domain.CatalogLocationSummary; import brooklyn.rest.domain.CatalogPolicySummary; import brooklyn.rest.domain.EffectorSummary; import brooklyn.rest.domain.EntityConfigSummary; +import brooklyn.rest.domain.LocationConfigSummary; import brooklyn.rest.domain.PolicyConfigSummary; import brooklyn.rest.domain.SensorSummary; import brooklyn.rest.domain.SummaryComparators; @@ -91,6 +95,14 @@ public class CatalogTransformer { item.isDeprecated(), makeLinks(item)); } + public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem<? extends Location,LocationSpec<?>> item) { + Set<LocationConfigSummary> config = ImmutableSet.of(); + return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(), + item.getJavaType(), item.getPlanYaml(), + item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), config, + item.isDeprecated(), makeLinks(item)); + } + protected static Map<String, URI> makeLinks(CatalogItem<?,?> item) { return MutableMap.<String, URI>of(); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/test/java/brooklyn/rest/resources/ApiDocResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApiDocResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApiDocResourceTest.java index e58b237..bbd96a3 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApiDocResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApiDocResourceTest.java @@ -98,7 +98,7 @@ public class ApiDocResourceTest extends BrooklynRestResourceTest { @Test public void testCatalogDetails() throws Exception { ApidocRoot response = client().resource("/v1/apidoc/brooklyn.rest.resources.CatalogResource").get(ApidocRoot.class); - assertEquals(countOperations(response), 16, "ops="+getOperations(response)); + assertEquals(countOperations(response), 21, "ops="+getOperations(response)); } @SuppressWarnings("rawtypes") http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java index 98b20c3..eb8b848 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java @@ -32,7 +32,6 @@ import java.util.Set; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import brooklyn.test.TestResourceUnavailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -47,9 +46,12 @@ import brooklyn.management.osgi.OsgiStandaloneTest; import brooklyn.policy.autoscaling.AutoScalerPolicy; import brooklyn.rest.domain.CatalogEntitySummary; import brooklyn.rest.domain.CatalogItemSummary; +import brooklyn.rest.domain.CatalogLocationSummary; import brooklyn.rest.domain.CatalogPolicySummary; import brooklyn.rest.testing.BrooklynRestResourceTest; +import brooklyn.test.TestResourceUnavailableException; +import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.GenericType; @@ -259,6 +261,57 @@ public class CatalogResourceTest extends BrooklynRestResourceTest { } @Test + public void testLocationAddGetAndRemove() { + String symbolicName = "my.catalog.location.id"; + String locationType = "localhost"; + String yaml = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + symbolicName, + " name: My Catalog Location", + " description: My description", + " version: " + TEST_VERSION, + "", + "brooklyn.locations:", + "- type: " + locationType); + + // Create location item + CatalogLocationSummary locationItem = client().resource("/v1/catalog") + .post(CatalogLocationSummary.class, yaml); + + Assert.assertNotNull(locationItem.getPlanYaml()); + Assert.assertTrue(locationItem.getPlanYaml().contains(locationType)); + assertEquals(locationItem.getId(), ver(symbolicName)); + assertEquals(locationItem.getSymbolicName(), symbolicName); + assertEquals(locationItem.getVersion(), TEST_VERSION); + + // Retrieve location item + CatalogLocationSummary location = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION) + .get(CatalogLocationSummary.class); + assertEquals(location.getSymbolicName(), symbolicName); + + // Retrieve all locations + Set<CatalogLocationSummary> locations = client().resource("/v1/catalog/locations") + .get(new GenericType<Set<CatalogLocationSummary>>() {}); + boolean found = false; + for (CatalogLocationSummary contender : locations) { + if (contender.getSymbolicName().equals(symbolicName)) { + found = true; + break; + } + } + Assert.assertTrue(found, "contenders="+locations); + + // Delete + ClientResponse deleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION) + .delete(ClientResponse.class); + assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode()); + + ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION) + .get(ClientResponse.class); + assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode()); + } + + @Test public void testDeleteCustomEntityFromCatalog() { String symbolicName = "my.catalog.app.id.to.subsequently.delete"; String yaml = http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f114676f/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java index 5ccf591..aab0a43 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java @@ -18,8 +18,8 @@ */ package brooklyn.rest.resources; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.net.URI; import java.util.Map; @@ -35,12 +35,15 @@ import org.testng.Assert; import org.testng.annotations.Test; import brooklyn.location.jclouds.JcloudsLocation; +import brooklyn.rest.domain.CatalogLocationSummary; import brooklyn.rest.domain.LocationSpec; import brooklyn.rest.domain.LocationSummary; import brooklyn.rest.testing.BrooklynRestResourceTest; import brooklyn.test.Asserts; +import com.google.api.client.repackaged.com.google.common.base.Joiner; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.sun.jersey.api.client.ClientResponse; @@ -50,62 +53,111 @@ import com.sun.jersey.api.client.GenericType; public class LocationResourceTest extends BrooklynRestResourceTest { private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class); - private URI addedLocationUri; + private String legacyLocationName = "my-jungle-legacy"; + private String legacyLocationVersion = "0.0.0.SNAPSHOT"; + + private String locationName = "my-jungle"; + private String locationVersion = "0.1.2"; @Test - public void testAddNewLocation() { + @Deprecated + public void testAddLegacyLocationDefinition() { Map<String, String> expectedConfig = ImmutableMap.of( "identity", "bob", "credential", "CR3dential"); ClientResponse response = client().resource("/v1/locations") .type(MediaType.APPLICATION_JSON_TYPE) - .post(ClientResponse.class, new LocationSpec("my-jungle", "aws-ec2:us-east-1", expectedConfig)); + .post(ClientResponse.class, new LocationSpec(legacyLocationName, "aws-ec2:us-east-1", expectedConfig)); - addedLocationUri = response.getLocation(); - log.info("added, at: " + addedLocationUri); + URI addedLegacyLocationUri = response.getLocation(); + log.info("added legacy, at: " + addedLegacyLocationUri); LocationSummary location = client().resource(response.getLocation()).get(LocationSummary.class); log.info(" contents: " + location); - Assert.assertEquals(location.getSpec(), "brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT"); - Assert.assertTrue(addedLocationUri.toString().startsWith("/v1/locations/")); + assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion); + assertTrue(addedLegacyLocationUri.toString().startsWith("/v1/locations/")); + + JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(legacyLocationName); + Assert.assertEquals(l.getProvider(), "aws-ec2"); + Assert.assertEquals(l.getRegion(), "us-east-1"); + Assert.assertEquals(l.getIdentity(), "bob"); + Assert.assertEquals(l.getCredential(), "CR3dential"); + } + + @Test + public void testAddNewLocationDefinition() { + String yaml = Joiner.on("\n").join(ImmutableList.of( + "brooklyn.catalog:", + " symbolicName: "+locationName, + " version: " + locationVersion, + "", + "brooklyn.locations:", + "- type: "+"aws-ec2:us-east-1", + " brooklyn.config:", + " identity: bob", + " credential: CR3dential")); + + + ClientResponse response = client().resource("/v1/catalog") + .post(ClientResponse.class, yaml); + + assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode()); + + + URI addedCatalogItemUri = response.getLocation(); + log.info("added, at: " + addedCatalogItemUri); + + // Ensure location definition exists + CatalogLocationSummary locationItem = client().resource("/v1/catalog/locations/"+locationName + "/" + locationVersion) + .get(CatalogLocationSummary.class); + log.info(" item: " + locationItem); + LocationSummary locationSummary = client().resource(URI.create("/v1/locations/"+locationName+"/")).get(LocationSummary.class); + log.info(" summary: " + locationSummary); + Assert.assertEquals(locationSummary.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion); - JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve("my-jungle"); + // Ensure location is usable - can instantiate, and has right config + JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(locationName); Assert.assertEquals(l.getProvider(), "aws-ec2"); Assert.assertEquals(l.getRegion(), "us-east-1"); Assert.assertEquals(l.getIdentity(), "bob"); Assert.assertEquals(l.getCredential(), "CR3dential"); } - @Test(dependsOnMethods = { "testAddNewLocation" }) - public void testListAllLocations() { + @Test(dependsOnMethods = { "testAddNewLocationDefinition" }) + public void testListAllLocationDefinitions() { Set<LocationSummary> locations = client().resource("/v1/locations") .get(new GenericType<Set<LocationSummary>>() {}); Iterable<LocationSummary> matching = Iterables.filter(locations, new Predicate<LocationSummary>() { @Override public boolean apply(@Nullable LocationSummary l) { - return "my-jungle".equals(l.getName()); + return locationName.equals(l.getName()); } }); LocationSummary location = Iterables.getOnlyElement(matching); - assertThat(location.getSpec(), is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT")); - Assert.assertEquals(location.getLinks().get("self"), addedLocationUri); + + URI expectedLocationUri = URI.create("/v1/locations/"+locationName); + Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion); + Assert.assertEquals(location.getLinks().get("self"), expectedLocationUri); } - @Test(dependsOnMethods = { "testListAllLocations" }) - public void testGetASpecificLocation() { - LocationSummary location = client().resource(addedLocationUri.toString()).get(LocationSummary.class); - assertThat(location.getSpec(), is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT")); + @Test(dependsOnMethods = { "testListAllLocationDefinitions" }) + public void testGetSpecificLocation() { + URI expectedLocationUri = URI.create("/v1/locations/"+locationName); + LocationSummary location = client().resource(expectedLocationUri).get(LocationSummary.class); + assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion); } - @Test(dependsOnMethods = { "testGetASpecificLocation" }) + @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" }) + @Deprecated public void testDeleteLocation() { final int size = getLocationRegistry().getDefinedLocations().size(); + URI expectedLocationUri = URI.create("/v1/locations/"+legacyLocationName); - ClientResponse response = client().resource(addedLocationUri).delete(ClientResponse.class); - assertThat(response.getStatus(), is(Response.Status.NO_CONTENT.getStatusCode())); + ClientResponse response = client().resource(expectedLocationUri).delete(ClientResponse.class); + assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode()); Asserts.succeedsEventually(new Runnable() { @Override public void run() { - assertThat(getLocationRegistry().getDefinedLocations().size(), is(size - 1)); + assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1); } }); }
