BROOKLYN-136: Persist dynamically added locs in catalog - Support CatalogItem of type location - Location items added to catalog automatically added to LocationRegistry - LocationResource.create adds new location type to Catalog
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/09a64755 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/09a64755 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/09a64755 Branch: refs/heads/master Commit: 09a64755e2c16c25f076af6a32f93ee0ff8c2599 Parents: d387887 Author: Aled Sage <[email protected]> Authored: Tue Mar 17 14:46:57 2015 +0000 Committer: Aled Sage <[email protected]> Committed: Wed Mar 18 11:02:52 2015 +0000 ---------------------------------------------------------------------- .../main/java/brooklyn/catalog/CatalogItem.java | 5 +- .../java/brooklyn/location/LocationSpec.java | 27 ++++ .../brooklyn/catalog/CatalogPredicates.java | 4 + .../catalog/internal/BasicBrooklynCatalog.java | 92 +++++++++-- .../catalog/internal/CatalogClasspathDo.java | 8 + .../catalog/internal/CatalogItemBuilder.java | 6 + .../internal/CatalogLocationItemDto.java | 43 ++++++ .../catalog/internal/CatalogXmlSerializer.java | 2 + .../location/basic/BasicLocationRegistry.java | 87 ++++++++++- .../location/basic/CatalogLocationResolver.java | 79 ++++++++++ .../services/brooklyn.location.LocationResolver | 1 + .../brooklyn/osgi/tests/SimpleLocation.java | 35 +++++ .../entity/rebind/RebindCatalogItemTest.java | 36 ++++- .../entity/rebind/RebindTestFixture.java | 9 +- .../osgi/brooklyn-test-osgi-entities.jar | Bin 12419 -> 13060 bytes .../camp/brooklyn/AbstractYamlTest.java | 20 +++ .../brooklyn/EmptySoftwareProcessYamlTest.java | 39 ++--- .../catalog/CatalogYamlLocationTest.java | 152 +++++++++++++++++++ .../rest/resources/LocationResource.java | 83 +++++----- .../rest/resources/LocationResourceTest.java | 20 ++- .../src/main/java/brooklyn/util/yaml/Yamls.java | 9 +- 21 files changed, 673 insertions(+), 84 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/api/src/main/java/brooklyn/catalog/CatalogItem.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/brooklyn/catalog/CatalogItem.java b/api/src/main/java/brooklyn/catalog/CatalogItem.java index 4dd63fa..8abaddc 100644 --- a/api/src/main/java/brooklyn/catalog/CatalogItem.java +++ b/api/src/main/java/brooklyn/catalog/CatalogItem.java @@ -33,7 +33,10 @@ import com.google.common.annotations.Beta; public interface CatalogItem<T,SpecT> extends BrooklynObject, Rebindable { public static enum CatalogItemType { - TEMPLATE, ENTITY, POLICY + TEMPLATE, + ENTITY, + POLICY, + LOCATION; } public static interface CatalogBundle { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/api/src/main/java/brooklyn/location/LocationSpec.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/brooklyn/location/LocationSpec.java b/api/src/main/java/brooklyn/location/LocationSpec.java index 6cd26ab..7e3c4f9 100644 --- a/api/src/main/java/brooklyn/location/LocationSpec.java +++ b/api/src/main/java/brooklyn/location/LocationSpec.java @@ -73,6 +73,25 @@ public class LocationSpec<T extends Location> extends AbstractBrooklynObjectSpec return LocationSpec.create(type).configure(config); } + /** + * Copies entity spec so its configuration can be overridden without modifying the + * original entity spec. + */ + public static <T extends Location> LocationSpec<T> create(LocationSpec<T> spec) { + // FIXME Why can I not use LocationSpec<T>? + LocationSpec<?> result = create(spec.getType()) + .displayName(spec.getDisplayName()) + .tags(spec.getTags()) + .configure(spec.getConfig()) + .configure(spec.getFlags()) + .catalogItemId(spec.getCatalogItemId()) + .extensions(spec.getExtensions()); + + if (spec.getParent() != null) result.parent(spec.getParent()); + + return (LocationSpec<T>) result; + } + private String id; private Location parent; private final Map<String, Object> flags = Maps.newLinkedHashMap(); @@ -157,6 +176,14 @@ public class LocationSpec<T extends Location> extends AbstractBrooklynObjectSpec return this; } + @SuppressWarnings("unchecked") + public <E> LocationSpec<T> extensions(Map<Class<?>, ?> extensions) { + for (Map.Entry<Class<?>, ?> entry : extensions.entrySet()) { + extension((Class)entry.getKey(), entry.getValue()); + } + return this; + } + /** * @return The id of the location to be created, or null if brooklyn can auto-generate an id * http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/CatalogPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java index 82ab689..4331a1a 100644 --- a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java +++ b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java @@ -24,6 +24,8 @@ import brooklyn.catalog.CatalogItem.CatalogItemType; import brooklyn.entity.Application; import brooklyn.entity.Entity; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; import brooklyn.management.ManagementContext; import brooklyn.management.entitlement.Entitlements; import brooklyn.policy.Policy; @@ -58,6 +60,8 @@ public class CatalogPredicates { CatalogPredicates.<Entity,EntitySpec<?>>isCatalogItemType(CatalogItemType.ENTITY); public static final Predicate<CatalogItem<Policy,PolicySpec<?>>> IS_POLICY = CatalogPredicates.<Policy,PolicySpec<?>>isCatalogItemType(CatalogItemType.POLICY); + public static final Predicate<CatalogItem<Location,LocationSpec<?>>> IS_LOCATION = + CatalogPredicates.<Location,LocationSpec<?>>isCatalogItemType(CatalogItemType.LOCATION); public static final Function<CatalogItem<?,?>,String> ID_OF_ITEM_TRANSFORMER = new Function<CatalogItem<?,?>, String>() { @Override @Nullable http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java index 5c5e05f..96760aa 100644 --- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import javax.annotation.Nullable; @@ -38,13 +39,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import brooklyn.basic.AbstractBrooklynObjectSpec; +import brooklyn.basic.BrooklynObjectInternal.ConfigurationSupportInternal; import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator; import brooklyn.catalog.BrooklynCatalog; import brooklyn.catalog.CatalogItem; import brooklyn.catalog.CatalogItem.CatalogBundle; +import brooklyn.catalog.CatalogItem.CatalogItemType; import brooklyn.catalog.CatalogPredicates; import brooklyn.config.BrooklynServerConfig; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; +import brooklyn.location.basic.BasicLocationRegistry; import brooklyn.management.ManagementContext; import brooklyn.management.classloading.BrooklynClassLoadingContext; import brooklyn.management.internal.EntityManagementUtils; @@ -75,6 +81,7 @@ import com.google.common.collect.Iterables; public class BasicBrooklynCatalog implements BrooklynCatalog { private static final String POLICIES_KEY = "brooklyn.policies"; + private static final String LOCATIONS_KEY = "brooklyn.locations"; public static final String NO_VERSION = "0.0.0.SNAPSHOT"; private static final Logger log = LoggerFactory.getLogger(BasicBrooklynCatalog.class); @@ -253,6 +260,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { if (log.isTraceEnabled()) { log.trace("Scheduling item for persistence removal: {}", itemDto.getId()); } + if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { + @SuppressWarnings("unchecked") + CatalogItem<Location,LocationSpec<?>> locationItem = (CatalogItem<Location, LocationSpec<?>>) itemDto; + ((BasicLocationRegistry)mgmt.getLocationRegistry()).removeDefinedLocation(locationItem); + } mgmt.getRebindManager().getChangeListener().onUnmanaged(itemDto); } @@ -318,7 +330,10 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { case POLICY: spec = createPolicySpec(plan, loader); break; - default: throw new RuntimeException("Only entity & policy catalog items are supported. Unsupported catalog item type " + item.getCatalogItemType()); + case LOCATION: + spec = createLocationSpec(plan, loader); + break; + default: throw new RuntimeException("Only entity, policy & location catalog items are supported. Unsupported catalog item type " + item.getCatalogItemType()); } ((AbstractBrooklynObjectSpec<?, ?>)spec).catalogItemId(item.getId()); return spec; @@ -366,7 +381,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { } } - @SuppressWarnings("unchecked") private <T, SpecT> SpecT createPolicySpec(DeploymentPlan plan, BrooklynClassLoadingContext loader) { //Would ideally re-use io.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver.PolicySpecResolver @@ -378,23 +392,70 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { Object policy = Iterables.getOnlyElement((Iterable<?>)policies); - Map<String, Object> policyConfig; + Map<String, Object> config; if (policy instanceof String) { - policyConfig = ImmutableMap.<String, Object>of("type", policy); + config = ImmutableMap.<String, Object>of("type", policy); } else if (policy instanceof Map) { - policyConfig = (Map<String, Object>) policy; + config = (Map<String, Object>) policy; } else { - throw new IllegalStateException("Policy exepcted to be string or map. Unsupported object type " + policy.getClass().getName() + " (" + policy.toString() + ")"); + throw new IllegalStateException("Policy expected to be string or map. Unsupported object type " + policy.getClass().getName() + " (" + policy.toString() + ")"); } - String policyType = (String) checkNotNull(Yamls.getMultinameAttribute(policyConfig, "policy_type", "policyType", "type"), "policy type"); - Map<String, Object> brooklynConfig = (Map<String, Object>) policyConfig.get("brooklyn.config"); - PolicySpec<? extends Policy> spec = PolicySpec.create(loader.loadClass(policyType, Policy.class)); + String type = (String) checkNotNull(Yamls.getMultinameAttribute(config, "policy_type", "policyType", "type"), "policy type"); + Map<String, Object> brooklynConfig = (Map<String, Object>) config.get("brooklyn.config"); + PolicySpec<? extends Policy> spec = PolicySpec.create(loader.loadClass(type, Policy.class)); if (brooklynConfig != null) { spec.configure(brooklynConfig); } return (SpecT) spec; } + + @SuppressWarnings("unchecked") + private <T, SpecT> SpecT createLocationSpec(DeploymentPlan plan, BrooklynClassLoadingContext loader) { + // See #createPolicySpec; this impl is modeled on that. + // spec.catalogItemId is set by caller + Object locations = checkNotNull(plan.getCustomAttributes().get(LOCATIONS_KEY), "location config"); + if (!(locations instanceof Iterable<?>)) { + throw new IllegalStateException("The value of " + LOCATIONS_KEY + " must be an Iterable."); + } + + Object location = Iterables.getOnlyElement((Iterable<?>)locations); + + Map<String, Object> config; + if (location instanceof String) { + config = ImmutableMap.<String, Object>of("type", location); + } else if (location instanceof Map) { + config = (Map<String, Object>) location; + } else { + throw new IllegalStateException("Location expected to be string or map. Unsupported object type " + location.getClass().getName() + " (" + location.toString() + ")"); + } + + String type = (String) checkNotNull(Yamls.getMultinameAttribute(config, "location_type", "locationType", "type"), "location type"); + Map<String, Object> brooklynConfig = (Map<String, Object>) config.get("brooklyn.config"); + Maybe<Class<? extends Location>> javaClass = loader.tryLoadClass(type, Location.class); + if (javaClass.isPresent()) { + LocationSpec<?> spec = LocationSpec.create(javaClass.get()); + if (brooklynConfig != null) { + spec.configure(brooklynConfig); + } + return (SpecT) spec; + } else { + Maybe<Location> loc = mgmt.getLocationRegistry().resolve(type, false, brooklynConfig); + if (loc.isPresent()) { + // TODO extensions? + Map<String, Object> locConfig = ((ConfigurationSupportInternal)loc.get().config()).getBag().getAllConfig(); + Class<? extends Location> locType = loc.get().getClass(); + Set<Object> locTags = loc.get().tags().getTags(); + String locDisplayName = loc.get().getDisplayName(); + return (SpecT) LocationSpec.create(locType) + .configure(locConfig) + .displayName(locDisplayName) + .tags(locTags); + } else { + throw new IllegalStateException("No class or resolver found for location type "+type); + } + } + } @SuppressWarnings("unchecked") @Override @@ -557,6 +618,8 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { private AbstractBrooklynObjectSpec<?,?> createSpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) { if (isPolicyPlan(plan)) { return createPolicySpec(plan, loader); + } else if (isLocationPlan(plan)) { + return createLocationSpec(plan, loader); } else { return createEntitySpec(symbolicName, plan, loader); } @@ -571,6 +634,8 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { } } else if (spec instanceof PolicySpec) { return CatalogItemBuilder.newPolicy(itemId, version); + } else if (spec instanceof LocationSpec) { + return CatalogItemBuilder.newLocation(itemId, version); } else { throw new IllegalStateException("Unknown spec type " + spec.getClass().getName() + " (" + spec + ")"); } @@ -584,6 +649,10 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { return plan.getCustomAttributes().containsKey(POLICIES_KEY); } + private boolean isLocationPlan(DeploymentPlan plan) { + return plan.getCustomAttributes().containsKey(LOCATIONS_KEY); + } + private DeploymentPlan makePlanFromYaml(String yaml) { CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get(); return camp.pdp().parseDeploymentPlan(Streams.newReaderWithContents(yaml)); @@ -611,6 +680,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { if (log.isTraceEnabled()) { log.trace("Scheduling item for persistence addition: {}", itemDto.getId()); } + if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) { + @SuppressWarnings("unchecked") + CatalogItem<Location,LocationSpec<?>> locationItem = (CatalogItem<Location, LocationSpec<?>>) itemDto; + ((BasicLocationRegistry)mgmt.getLocationRegistry()).updateDefinedLocation(locationItem); + } mgmt.getRebindManager().getChangeListener().onManaged(itemDto); return itemDto; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java index 6ec073f..095ebfd 100644 --- a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java +++ b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java @@ -36,6 +36,7 @@ import brooklyn.entity.Application; import brooklyn.entity.Entity; import brooklyn.entity.basic.ApplicationBuilder; import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.location.Location; import brooklyn.management.internal.ManagementContextInternal; import brooklyn.policy.Policy; import brooklyn.util.exceptions.Exceptions; @@ -193,6 +194,12 @@ public class CatalogClasspathDo { addCatalogEntry(new CatalogPolicyItemDto(), c); count++; } + + Iterable<Class<? extends Location>> locations = this.excludeInvalidClasses(scanner.getSubTypesOf(Location.class)); + for (Class<?> c: locations) { + addCatalogEntry(new CatalogLocationItemDto(), c); + count++; + } } else { throw new IllegalStateException("Unsupported catalog scan mode "+scanMode+" for "+this); } @@ -233,6 +240,7 @@ public class CatalogClasspathDo { if (ApplicationBuilder.class.isAssignableFrom(c)) return addCatalogEntry(new CatalogTemplateItemDto(), c); if (Entity.class.isAssignableFrom(c)) return addCatalogEntry(new CatalogEntityItemDto(), c); if (Policy.class.isAssignableFrom(c)) return addCatalogEntry(new CatalogPolicyItemDto(), c); + if (Location.class.isAssignableFrom(c)) return addCatalogEntry(new CatalogLocationItemDto(), c); throw new IllegalStateException("Cannot add "+c+" to catalog: unsupported type "+c.getName()); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java index 722220b..2b16cc6 100644 --- a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java +++ b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java @@ -46,6 +46,12 @@ public class CatalogItemBuilder<CatalogItemType extends CatalogItemDtoAbstract<? .version(version); } + public static CatalogItemBuilder<CatalogLocationItemDto> newLocation(String symbolicName, String version) { + return new CatalogItemBuilder<CatalogLocationItemDto>(new CatalogLocationItemDto()) + .symbolicName(symbolicName) + .version(version); + } + public CatalogItemBuilder(CatalogItemType dto) { this.dto = dto; this.dto.setLibraries(Collections.<CatalogBundle>emptyList()); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java b/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java new file mode 100644 index 0000000..e8bf2ec --- /dev/null +++ b/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java @@ -0,0 +1,43 @@ +/* + * 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.catalog.internal; + +import brooklyn.location.Location; +import brooklyn.location.LocationSpec; + + +public class CatalogLocationItemDto extends CatalogItemDtoAbstract<Location,LocationSpec<?>> { + + @Override + public CatalogItemType getCatalogItemType() { + return CatalogItemType.LOCATION; + } + + @Override + public Class<Location> getCatalogItemJavaType() { + return Location.class; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Class<LocationSpec<?>> getSpecType() { + return (Class)LocationSpec.class; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java index 46c014f..dbfd3fc 100644 --- a/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java +++ b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java @@ -45,10 +45,12 @@ public class CatalogXmlSerializer extends XmlSerializer<Object> { xstream.addImplicitCollection(CatalogDto.class, "entries", CatalogTemplateItemDto.class); xstream.addImplicitCollection(CatalogDto.class, "entries", CatalogEntityItemDto.class); xstream.addImplicitCollection(CatalogDto.class, "entries", CatalogPolicyItemDto.class); + xstream.addImplicitCollection(CatalogDto.class, "entries", CatalogLocationItemDto.class); xstream.aliasType("template", CatalogTemplateItemDto.class); xstream.aliasType("entity", CatalogEntityItemDto.class); xstream.aliasType("policy", CatalogPolicyItemDto.class); + xstream.aliasType("location", CatalogPolicyItemDto.class); xstream.aliasField("registeredType", CatalogItemDtoAbstract.class, "symbolicName"); xstream.aliasAttribute(CatalogItemDtoAbstract.class, "displayName", "name"); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java index 365b347..f0b466e 100644 --- a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java +++ b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java @@ -33,6 +33,9 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.catalog.BrooklynCatalog; +import brooklyn.catalog.CatalogItem; +import brooklyn.catalog.CatalogPredicates; import brooklyn.config.ConfigMap; import brooklyn.config.ConfigPredicates; import brooklyn.config.ConfigUtils; @@ -41,6 +44,7 @@ import brooklyn.location.LocationDefinition; import brooklyn.location.LocationRegistry; import brooklyn.location.LocationResolver; import brooklyn.location.LocationResolver.EnableableLocationResolver; +import brooklyn.location.LocationSpec; import brooklyn.management.ManagementContext; import brooklyn.management.internal.LocalLocationManager; import brooklyn.util.collections.MutableMap; @@ -59,9 +63,65 @@ import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; +/** + * See {@link LocationRegistry} for general description. + * <p> + * TODO The relationship between the catalog and the location registry is a bit messy. + * For all existing code, the location registry is the definitive way to resolve + * locations. + * <p> + * Any location item added to the catalog must therefore be registered here. + * Whenever an item is added to the catalog, it will automatically call + * {@link #updateDefinedLocation(CatalogItem)}. Similarly, when a location + * is deleted from the catalog it will call {@link #removeDefinedLocation(CatalogItem)}. + * <p> + * However, the location item in the catalog has an unparsed blob of YAML, which contains + * important things like the type and the config of the location. This is only parsed when + * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore jump through + * some hoops to wire together the catalog and the registry. + * <p> + * To add a location to the catalog, and then to resolve a location that is in the catalog, + * it goes through the following steps: + * + * <ol> + * <li>Call {@link BrooklynCatalog#addItem(String)} + * <ol> + * <li>This automatically calls {@link #updateDefinedLocation(CatalogItem)} + * <li>A LocationDefinition is creating, using as its id the {@link CatalogItem#getSymbolicName()}. + * The definition's spec is {@code brooklyn.catalog:<symbolicName>:<version>}, + * </ol> + * <li>A blueprint can reference the catalog item using its symbolic name, + * such as the YAML {@code location: my-new-location}. + * (this feels similar to the "named locations"). + * <ol> + * <li>This automatically calls {@link #resolve(String)}. + * <li>The LocationDefinition is found by lookig up this name. + * <li>The {@link LocationDefiniton.getSpec()} is retrieved; the right {@link LocationResolver} is + * found for it. + * <li>This uses the {@link CatalogLocationResolver}, because the spec starts with {@code brooklyn.catalog:}. + * <li>This resolver extracts from the spec the <symobolicName>:<version>, and looks up the + * catalog item using {@link BrooklynCatalog#getCatalogItem(String, String)}. + * <li>It then creates a {@link LocationSpec} by calling {@link BrooklynCatalog#createSpec(CatalogItem)}. + * <ol> + * <li>This first tries to use the type (that is in the YAML) as a simple Java class. + * <li>If that fails, it will resolve the type using {@link #resolve(String, Boolean, Map)}, which + * returns an actual location object. + * <li>It extracts from that location object the appropriate metadata to create a {@link LocationSpec}, + * returns the spec and discards the location object. + * </ol> + * <li>The resolver creates the {@link Location} from the {@link LocationSpec} + * </ol> + * </ol> + * + * There is no concept of a location version in this registry. The version + * in the catalog is generally ignored. + */ @SuppressWarnings({"rawtypes","unchecked"}) public class BasicLocationRegistry implements LocationRegistry { + // TODO save / serialize + // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry) + public static final Logger log = LoggerFactory.getLogger(BasicLocationRegistry.class); /** @@ -139,6 +199,25 @@ public class BasicLocationRegistry implements LocationRegistry { } } + /** + * Converts the given item from the catalog into a LocationDefinition, and adds it + * to the registry (overwriting anything already registered with the id + * {@link CatalogItem#getCatalogItemId()}. + */ + public void updateDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) { + String id = item.getCatalogItemId(); + String symbolicName = item.getSymbolicName(); + String spec = CatalogLocationResolver.NAME + ":" + id; + Map<String, Object> config = ImmutableMap.<String, Object>of(); + BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config); + + updateDefinedLocation(locDefinition); + } + + public void removeDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) { + removeDefinedLocation(item.getSymbolicName()); + } + @Override public void removeDefinedLocation(String id) { LocationDefinition removed; @@ -183,12 +262,14 @@ public class BasicLocationRegistry implements LocationRegistry { definedLocations.put(id, localhost(id)); definedLocations.putAll(oldDefined); } + + for (CatalogItem<Location, LocationSpec<?>> item : mgmt.getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)) { + updateDefinedLocation(item); + count++; + } } } - // TODO save / serialize - // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry) - @VisibleForTesting void disablePersistence() { // persistence isn't enabled yet anyway (have to manually save things, http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java new file mode 100644 index 0000000..4823b05 --- /dev/null +++ b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java @@ -0,0 +1,79 @@ +/* + * 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.location.basic; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.catalog.CatalogItem; +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.location.Location; +import brooklyn.location.LocationRegistry; +import brooklyn.location.LocationResolver; +import brooklyn.location.LocationSpec; +import brooklyn.management.ManagementContext; + +/** + * Given a location spec in the form {@code brooklyn.catalog:<symbolicName>:<version>}, + * looks up the catalog to get its definition and creates such a location. + */ +public class CatalogLocationResolver implements LocationResolver { + + private static final Logger log = LoggerFactory.getLogger(CatalogLocationResolver.class); + + public static final String NAME = "brooklyn.catalog"; + + private ManagementContext managementContext; + + @Override + public void init(ManagementContext managementContext) { + this.managementContext = checkNotNull(managementContext, "managementContext"); + } + + @Override + @SuppressWarnings({ "rawtypes" }) + public Location newLocationFromString(Map locationFlags, String spec, brooklyn.location.LocationRegistry registry) { + String id = spec.substring(NAME.length()+1); + CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id); + LocationSpec<?> origLocSpec = managementContext.getCatalog().createSpec((CatalogItem<Location, LocationSpec<?>>)item); + LocationSpec<?> locSpec = LocationSpec.create(origLocSpec) + .configure(locationFlags); + return managementContext.getLocationManager().createLocation(locSpec); + } + + @Override + public String getPrefix() { + return NAME; + } + + /** + * accepts anything that looks like it will be a YAML catalog item (e.g. starting "brooklyn.locations") + */ + @Override + public boolean accepts(String spec, LocationRegistry registry) { + if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) return true; + if (registry.getDefinedLocationByName(spec)!=null) return true; + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver ---------------------------------------------------------------------- diff --git a/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver b/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver index 74df3da..fd14323 100644 --- a/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver +++ b/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver @@ -1,5 +1,6 @@ brooklyn.location.basic.DefinedLocationByIdResolver brooklyn.location.basic.NamedLocationResolver +brooklyn.location.basic.CatalogLocationResolver brooklyn.location.basic.LocalhostLocationResolver brooklyn.location.basic.ByonLocationResolver brooklyn.location.basic.SingleMachineLocationResolver http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java ---------------------------------------------------------------------- diff --git a/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java b/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java new file mode 100644 index 0000000..253a2b0 --- /dev/null +++ b/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java @@ -0,0 +1,35 @@ +/* + * 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.osgi.tests; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.location.basic.AbstractLocation; +import brooklyn.util.flags.SetFromFlag; + +public class SimpleLocation extends AbstractLocation { + @SetFromFlag("config1") + public static final ConfigKey<String> CONFIG1 = ConfigKeys.newStringConfigKey("config1"); + + @SetFromFlag("config2") + public static final ConfigKey<String> CONFIG2 = ConfigKeys.newStringConfigKey("config2"); + + @SetFromFlag("config3") + public static final ConfigKey<String> CONFIG3 = ConfigKeys.newStringConfigKey("config3"); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 4454354..a64b269 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java @@ -34,6 +34,7 @@ import org.testng.annotations.Test; import brooklyn.camp.lite.CampPlatformWithJustBrooklynMgmt; import brooklyn.camp.lite.TestAppAssemblyInstantiator; import brooklyn.catalog.CatalogItem; +import brooklyn.catalog.CatalogItem.CatalogItemType; import brooklyn.catalog.CatalogLoadMode; import brooklyn.catalog.internal.BasicBrooklynCatalog; import brooklyn.catalog.internal.CatalogDto; @@ -41,11 +42,15 @@ import brooklyn.config.BrooklynProperties; import brooklyn.config.BrooklynServerConfig; import brooklyn.entity.proxying.EntitySpec; import brooklyn.internal.BrooklynFeatureEnablement; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.management.ManagementContext; import brooklyn.management.internal.LocalManagementContext; import brooklyn.policy.basic.AbstractPolicy; import brooklyn.test.entity.TestEntity; +import com.google.common.base.Joiner; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; public class RebindCatalogItemTest extends RebindTestFixtureWithApp { @@ -112,8 +117,7 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { " version: " + TEST_VERSION + "\n" + "services:\n" + "- type: io.camp.mock:AppServer"; - CatalogItem<?, ?> added = origManagementContext.getCatalog().addItem(yaml); - LOG.info("Added item to catalog: {}, id={}", added, added.getId()); + addItem(origManagementContext, yaml); rebindAndAssertCatalogsAreEqual(); } @@ -135,8 +139,24 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { " brooklyn.config:\n" + " cfg1: 111\n" + " cfg2: 222"; - CatalogItem<?, ?> added = origManagementContext.getCatalog().addItem(yaml); - LOG.info("Added item to catalog: {}, id={}", added, added.getId()); + addItem(origManagementContext, yaml); + rebindAndAssertCatalogsAreEqual(); + } + + @Test + public void testAddAndRebindLocation() { + String yaml = Joiner.on("\n").join(ImmutableList.of( + "name: Test Location", + "brooklyn.catalog:", + " id: sample_location", + " version: " + TEST_VERSION, + "brooklyn.locations:", + "- type: "+LocalhostMachineProvisioningLocation.class.getName(), + " brooklyn.config:", + " cfg1: 111", + " cfg2: 222")); + CatalogItem<?, ?> added = addItem(origManagementContext, yaml); + assertEquals(added.getCatalogItemType(), CatalogItemType.LOCATION); rebindAndAssertCatalogsAreEqual(); } @@ -210,6 +230,13 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { assertTrue(catalogItemAfterRebind.isDeprecated(), "Expected item to be deprecated"); } + protected CatalogItem<?, ?> addItem(ManagementContext mgmt, String yaml) { + CatalogItem<?, ?> added = mgmt.getCatalog().addItem(yaml); + LOG.info("Added item to catalog: {}, id={}", added, added.getId()); + assertCatalogContains(mgmt.getCatalog(), added); + return added; + } + private void rebindAndAssertCatalogsAreEqual() { try { rebind(); @@ -218,5 +245,4 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp { } assertCatalogsEqual(newManagementContext.getCatalog(), origManagementContext.getCatalog()); } - } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 8f1c1c6..21ab69f 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java @@ -19,6 +19,7 @@ package brooklyn.entity.rebind; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import java.io.File; import java.util.List; @@ -53,9 +54,9 @@ import brooklyn.util.task.BasicExecutionManager; import brooklyn.util.text.Identifiers; import brooklyn.util.time.Duration; -import com.google.common.collect.Sets; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; public abstract class RebindTestFixture<T extends StartableApplication> { @@ -303,4 +304,10 @@ public abstract class RebindTestFixture<T extends StartableApplication> { assertEquals(actual.getSymbolicName(), expected.getSymbolicName()); assertEquals(actual.getLibraries(), expected.getLibraries()); } + + protected void assertCatalogContains(BrooklynCatalog catalog, CatalogItem<?, ?> item) { + CatalogItem<?, ?> found = catalog.getCatalogItem(item.getSymbolicName(), item.getVersion()); + assertNotNull(found); + assertCatalogItemsEqual(found, item); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar ---------------------------------------------------------------------- diff --git a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar index 008150a..5a3b052 100644 Binary files a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar and b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar differ http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java index aa1922c..dd7ad58 100644 --- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java +++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java @@ -39,8 +39,12 @@ import brooklyn.management.Task; import brooklyn.management.internal.LocalManagementContext; import brooklyn.test.entity.LocalManagementContextForTests; import brooklyn.util.ResourceUtils; +import brooklyn.util.config.ConfigBag; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; public abstract class AbstractYamlTest { @@ -119,6 +123,14 @@ public abstract class AbstractYamlTest { getLogger().info("Test - created " + assembly); final Entity app = brooklynMgmt.getEntityManager().getEntity(assembly.getId()); getLogger().info("App - " + app); + + // wait for app to have started + Set<Task<?>> tasks = brooklynMgmt.getExecutionManager().getTasksWithAllTags(ImmutableList.of( + BrooklynTaskTags.EFFECTOR_TAG, + BrooklynTaskTags.tagForContextEntity(app), + BrooklynTaskTags.tagForEffectorCall(app, "start", ConfigBag.newInstance(ImmutableMap.of("locations", ImmutableMap.of()))))); + Iterables.getOnlyElement(tasks).get(); + return app; } @@ -132,6 +144,10 @@ public abstract class AbstractYamlTest { return app; } + protected void addCatalogItem(Iterable<String> catalogYaml) { + addCatalogItem(join(catalogYaml)); + } + protected void addCatalogItem(String... catalogYaml) { addCatalogItem(join(catalogYaml)); } @@ -148,6 +164,10 @@ public abstract class AbstractYamlTest { return LOG; } + private String join(Iterable<String> catalogYaml) { + return Joiner.on("\n").join(catalogYaml); + } + private String join(String[] catalogYaml) { return Joiner.on("\n").join(catalogYaml); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java index eb2358d..9b7bc7b 100644 --- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java +++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java @@ -32,17 +32,19 @@ import brooklyn.entity.basic.Entities; import brooklyn.location.Location; import brooklyn.location.basic.SshMachineLocation; import brooklyn.util.collections.Jsonya; -import brooklyn.util.stream.Streams; @Test public class EmptySoftwareProcessYamlTest extends AbstractYamlTest { private static final Logger log = LoggerFactory.getLogger(EnrichersYamlTest.class); - @Test + @Test(groups="Integration") public void testProvisioningProperties() throws Exception { - Entity app = createAndStartApplication(Streams.newReaderWithContents( - "services: [ { serviceType: "+EmptySoftwareProcess.class.getName()+"," - + " provisioning.properties: { minRam: 16384 } } ]")); + Entity app = createAndStartApplication( + "location: localhost", + "services:", + "- type: "+EmptySoftwareProcess.class.getName(), + " provisioning.properties:", + " minRam: 16384"); waitForApplicationTasks(app); log.info("App started:"); @@ -53,14 +55,15 @@ public class EmptySoftwareProcessYamlTest extends AbstractYamlTest { Assert.assertEquals(pp.get("minRam"), 16384); } - @Test + @Test(groups="Integration") public void testProvisioningPropertiesViaJsonya() throws Exception { - Entity app = createAndStartApplication(Streams.newReaderWithContents( - Jsonya.newInstance().at("services").list() - .put("serviceType", EmptySoftwareProcess.class.getName()) + Entity app = createAndStartApplication( + Jsonya.newInstance() + .put("location", "localhost") + .at("services").list() + .put("type", EmptySoftwareProcess.class.getName()) .at("provisioning.properties").put("minRam", 16384) - .root().toString() - )); + .root().toString()); waitForApplicationTasks(app); log.info("App started:"); @@ -71,13 +74,14 @@ public class EmptySoftwareProcessYamlTest extends AbstractYamlTest { Assert.assertEquals(pp.get("minRam"), 16384); } - @Test - // for issue #1377 + // for https://github.com/brooklyncentral/brooklyn/issues/1377 + @Test(groups="Integration") public void testWithAppAndEntityLocations() throws Exception { - Entity app = createAndStartApplication(Streams.newReaderWithContents("services:\n"+ - "- serviceType: "+EmptySoftwareProcess.class.getName()+"\n"+ - " location: localhost:(name=localhost on entity)"+"\n"+ - "location: byon:(hosts=\"127.0.0.1\", name=loopback on app)")); + Entity app = createAndStartApplication( + "services:", + "- type: "+EmptySoftwareProcess.class.getName(), + " location: localhost:(name=localhost on entity)", + "location: byon:(hosts=\"127.0.0.1\", name=loopback on app)"); waitForApplicationTasks(app); Entities.dumpInfo(app); @@ -96,5 +100,4 @@ public class EmptySoftwareProcessYamlTest extends AbstractYamlTest { // TODO this, below, probably should be 'localhost on entity', see #1377 Assert.assertEquals(actualMachine.getParent().getDisplayName(), "loopback on app"); } - } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java new file mode 100644 index 0000000..ef5aa0f --- /dev/null +++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java @@ -0,0 +1,152 @@ +/* + * 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 io.brooklyn.camp.brooklyn.catalog; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import io.brooklyn.camp.brooklyn.AbstractYamlTest; + +import java.util.List; + +import org.testng.annotations.Test; + +import brooklyn.catalog.CatalogItem; +import brooklyn.catalog.CatalogPredicates; +import brooklyn.entity.Entity; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.location.Location; +import brooklyn.location.LocationDefinition; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.management.osgi.OsgiStandaloneTest; +import brooklyn.test.TestResourceUnavailableException; +import brooklyn.util.text.StringFunctions; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class CatalogYamlLocationTest extends AbstractYamlTest { + private static final String LOCALHOST_LOCATION_SPEC = "localhost"; + private static final String LOCALHOST_LOCATION_TYPE = LocalhostMachineProvisioningLocation.class.getName(); + private static final String SIMPLE_LOCATION_TYPE = "brooklyn.osgi.tests.SimpleLocation"; + + @Test + public void testAddCatalogItem() throws Exception { + assertEquals(countCatalogLocations(), 0); + + String symbolicName = "my.catalog.location.id.load"; + addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE); + + CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION); + assertEquals(item.getSymbolicName(), symbolicName); + assertEquals(countCatalogLocations(), 1); + + // Item added to catalog should automatically be available in location registry + LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName); + assertEquals(def.getId(), symbolicName); + assertEquals(def.getName(), symbolicName); + + // Deleting item: should be gone from catalog, and from location registry + deleteCatalogEntity(symbolicName); + + assertEquals(countCatalogLocations(), 0); + assertNull(mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName)); + } + + @Test + public void testLaunchApplicationReferencingLocationClass() throws Exception { + String symbolicName = "my.catalog.location.id.launch"; + addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE); + runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE); + + deleteCatalogEntity(symbolicName); + } + + @Test + public void testLaunchApplicationReferencingLocationSpec() throws Exception { + String symbolicName = "my.catalog.location.id.launch"; + addCatalogLocation(symbolicName, LOCALHOST_LOCATION_SPEC); + runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE); + + deleteCatalogEntity(symbolicName); + } + + @Test + public void testLaunchApplicationReferencingOsgiLocation() throws Exception { + String symbolicName = "my.catalog.location.id.launch"; + addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE); + runLaunchApplicationReferencingLocation(symbolicName, SIMPLE_LOCATION_TYPE); + + deleteCatalogEntity(symbolicName); + } + + protected void runLaunchApplicationReferencingLocation(String locTypeInYaml, String locType) throws Exception { + Entity app = createAndStartApplication( + "name: simple-app-yaml", + "location: ", + " "+locTypeInYaml+":", + " config2: config2 override", + " config3: config3", + "services: ", + " - type: brooklyn.entity.basic.BasicStartable"); + + Entity simpleEntity = Iterables.getOnlyElement(app.getChildren()); + Location location = Iterables.getOnlyElement(simpleEntity.getLocations()); + assertEquals(location.getClass().getName(), locType); + assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config1")), "config1"); + assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config2")), "config2 override"); + assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config3")), "config3"); + } + + private void addCatalogLocation(String symbolicName, String serviceType) { + addCatalogLocation(symbolicName, serviceType, ImmutableList.<String>of()); + } + + private void addCatalogOSGiLocation(String symbolicName, String serviceType) { + TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH); + addCatalogLocation(symbolicName, serviceType, ImmutableList.of(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL)); + } + + private void addCatalogLocation(String symbolicName, String serviceType, List<String> libraries) { + ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add( + "brooklyn.catalog:", + " id: " + symbolicName, + " name: My Catalog Location", + " description: My description", + " version: " + TEST_VERSION); + if (libraries.size() > 0) { + yaml.add(" libraries:") + .addAll(Lists.transform(libraries, StringFunctions.prepend(" - url: "))); + } + yaml.add( + "", + "brooklyn.locations:", + "- type: " + serviceType, + " brooklyn.config:", + " config1: config1", + " config2: config2"); + + + addCatalogItem(yaml.build()); + } + + private int countCatalogLocations() { + return Iterables.size(mgmt().getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 d4f004c..aa73215 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 @@ -29,9 +29,9 @@ import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.catalog.CatalogItem; import brooklyn.location.Location; import brooklyn.location.LocationDefinition; -import brooklyn.location.basic.BasicLocationDefinition; import brooklyn.location.basic.LocationConfigKeys; import brooklyn.rest.api.LocationApi; import brooklyn.rest.domain.LocationSpec; @@ -43,10 +43,11 @@ import brooklyn.rest.util.EntityLocationUtils; import brooklyn.rest.util.WebResourceUtils; import brooklyn.util.collections.MutableMap; import brooklyn.util.exceptions.Exceptions; -import brooklyn.util.text.Identifiers; import com.google.common.base.Function; +import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; public class LocationResource extends AbstractBrooklynRestResource implements LocationApi { @@ -101,40 +102,52 @@ public class LocationResource extends AbstractBrooklynRestResource implements Lo return result; } - /** @deprecated since 0.7.0; REST call now handled by below (optional query parameter added) */ - public LocationSummary get(String locationId) { - return get(locationId, false); - } - - @Override - public LocationSummary get(String locationId, String fullConfig) { - return get(locationId, Boolean.valueOf(fullConfig)); - } - - public LocationSummary get(String locationId, boolean fullConfig) { - LocationDetailLevel configLevel = fullConfig ? LocationDetailLevel.FULL_EXCLUDING_SECRET : LocationDetailLevel.LOCAL_EXCLUDING_SECRET; - Location l1 = mgmt().getLocationManager().getLocation(locationId); - if (l1!=null) { - return LocationTransformer.newInstance(mgmt(), l1, configLevel); + /** @deprecated since 0.7.0; REST call now handled by below (optional query parameter added) */ + public LocationSummary get(String locationId) { + return get(locationId, false); } - LocationDefinition l2 = brooklyn().getLocationRegistry().getDefinedLocationById(locationId); - if (l2==null) throw WebResourceUtils.notFound("No location matching %s", locationId); - return LocationTransformer.newInstance(mgmt(), l2, configLevel); - } - - @Override - public Response create(LocationSpec locationSpec) { - String id = Identifiers.makeRandomId(8); - LocationDefinition l = new BasicLocationDefinition(id, locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig()); - brooklyn().getLocationRegistry().updateDefinedLocation(l); - return Response.created(URI.create(id)) - .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET)) - .build(); - } - - public void delete(String locationId) { - brooklyn().getLocationRegistry().removeDefinedLocation(locationId); - } + @Override + public LocationSummary get(String locationId, String fullConfig) { + return get(locationId, Boolean.valueOf(fullConfig)); + } + + public LocationSummary get(String locationId, boolean fullConfig) { + LocationDetailLevel configLevel = fullConfig ? LocationDetailLevel.FULL_EXCLUDING_SECRET : LocationDetailLevel.LOCAL_EXCLUDING_SECRET; + Location l1 = mgmt().getLocationManager().getLocation(locationId); + if (l1!=null) { + return LocationTransformer.newInstance(mgmt(), l1, configLevel); + } + + LocationDefinition l2 = brooklyn().getLocationRegistry().getDefinedLocationById(locationId); + if (l2==null) throw WebResourceUtils.notFound("No location matching %s", locationId); + return LocationTransformer.newInstance(mgmt(), l2, configLevel); + } + @Override + public Response create(LocationSpec locationSpec) { + String name = locationSpec.getName(); + ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add( + "brooklyn.catalog:", + " symbolicName: "+name, + "", + "brooklyn.locations:", + "- type: "+locationSpec.getSpec()); + if (locationSpec.getConfig().size() > 0) { + yaml.add(" brooklyn.config:"); + for (Map.Entry<String, ?> entry : locationSpec.getConfig().entrySet()) { + yaml.add(" "+entry.getKey()+": "+entry.getValue()); + } + } + + CatalogItem<?, ?> item = brooklyn().getCatalog().addItem(Joiner.on("\n").join(yaml.build())); + LocationDefinition l = brooklyn().getLocationRegistry().getDefinedLocationByName(name); + return Response.created(URI.create(name)) + .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET)) + .build(); + } + + public void delete(String locationId) { + brooklyn().getLocationRegistry().removeDefinedLocation(locationId); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 0511ca4..5ccf591 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 @@ -20,7 +20,6 @@ package brooklyn.rest.resources; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.testng.Assert.assertFalse; import java.net.URI; import java.util.Map; @@ -57,24 +56,23 @@ public class LocationResourceTest extends BrooklynRestResourceTest { public void testAddNewLocation() { Map<String, String> expectedConfig = ImmutableMap.of( "identity", "bob", - "credential", "CR3dential", - "location", "us-east-1"); + "credential", "CR3dential"); ClientResponse response = client().resource("/v1/locations") .type(MediaType.APPLICATION_JSON_TYPE) - .post(ClientResponse.class, new LocationSpec("my-jungle", "aws-ec2", expectedConfig)); + .post(ClientResponse.class, new LocationSpec("my-jungle", "aws-ec2:us-east-1", expectedConfig)); addedLocationUri = response.getLocation(); log.info("added, at: " + addedLocationUri); LocationSummary location = client().resource(response.getLocation()).get(LocationSummary.class); log.info(" contents: " + location); - assertThat(location.getSpec(), is("aws-ec2")); - - assertThat(location.getConfig().get("identity"), is((Object) "bob")); - assertFalse(location.getConfig().containsKey("CR3dential")); + Assert.assertEquals(location.getSpec(), "brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT"); Assert.assertTrue(addedLocationUri.toString().startsWith("/v1/locations/")); - JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(location.getId()); + JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve("my-jungle"); 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" }) @@ -88,14 +86,14 @@ public class LocationResourceTest extends BrooklynRestResourceTest { } }); LocationSummary location = Iterables.getOnlyElement(matching); - assertThat(location.getSpec(), is("aws-ec2")); + assertThat(location.getSpec(), is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT")); Assert.assertEquals(location.getLinks().get("self"), addedLocationUri); } @Test(dependsOnMethods = { "testListAllLocations" }) public void testGetASpecificLocation() { LocationSummary location = client().resource(addedLocationUri.toString()).get(LocationSummary.class); - assertThat(location.getSpec(), is("aws-ec2")); + assertThat(location.getSpec(), is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT")); } @Test(dependsOnMethods = { "testGetASpecificLocation" }) http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java index 7c946dd..ad56bfe 100644 --- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java +++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java @@ -29,6 +29,9 @@ import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.util.collections.Jsonya; + +import com.google.common.annotations.Beta; import com.google.common.collect.Iterables; public class Yamls { @@ -71,6 +74,7 @@ public class Yamls { * * @see #getAt(Object, List) */ + @Beta public static Object getAt(String yaml, List<String> path) { Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml); Object current = result.iterator().next(); @@ -85,8 +89,11 @@ public class Yamls { * <li>A string in the form like "[0]" is assumed to be an index into a list * </ul> * - * Returns {@code null} if that path does not exist. + * Also see {@link Jsonya}, such as {@code Jsonya.of(current).at(path).get()}. + * + * @return The object at the given path, or {@code null} if that path does not exist. */ + @Beta @SuppressWarnings("unchecked") public static Object getAtPreParsed(Object current, List<String> path) { for (String pathPart : path) {
