From aee164ab290a5b197a0b1772ac2f123e34382bc0 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 7 Jun 2017 00:25:55 +0100 Subject: [PATCH 1/6] Add policy to create location dynamically from entity --- .../policy/location/CreateLocationPolicy.java | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java new file mode 100644 index 0000000000..ada80c0d77 --- /dev/null +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.policy.location; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.SensorEvent; +import org.apache.brooklyn.api.sensor.SensorEventListener; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.policy.AbstractPolicy; +import org.apache.brooklyn.util.collections.MutableMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +/** + * Policy that is attached to an entity to create a location configured + * using its sensor data. The created lifecycle is added to the catalog + * and will be removed when the entity is no longer running. + */ +public class CreateLocationPolicy extends AbstractPolicy { + + private static final Logger LOG = LoggerFactory.getLogger(CreateLocationPolicy.class); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String id; + private String name; + private AttributeSensor status; + private Map> configuration; + private String catalogId; + private Class type; + private Set tags; + private String displayName; + + public Builder id(String val) { + this.id = val; return this; + } + public Builder name(String val) { + this.name = val; return this; + } + public Builder status(AttributeSensor val) { + this.status = val; return this; + } + public Builder configuration(Map> val) { + this.configuration = val; return this; + } + public Builder catalogId(String val) { + this.catalogId = val; return this; + } + public Builder type(Class val) { + this.type = val; return this; + } + public Builder tags(Set val) { + this.tags = val; return this; + } + public Builder displayName(String val) { + this.displayName = val; return this; + } + public CreateLocationPolicy build() { + return new CreateLocationPolicy(toFlags()); + } + public PolicySpec buildSpec() { + return PolicySpec.create(CreateLocationPolicy.class) + .configure(toFlags()); + } + private Map toFlags() { + return MutableMap.builder() + .putIfNotNull("id", id) + .putIfNotNull("name", name) + .putIfNotNull("location.status", status) + .putIfNotNull("location.configuration", configuration) + .putIfNotNull("location.catalogId", catalogId) + .putIfNotNull("location.type", type) + .putIfNotNull("location.tags", tags) + .putIfNotNull("location.displayName", displayName) + .build(); + } + } + + @SuppressWarnings("serial") + public static final ConfigKey> LOCATION_STATUS = ConfigKeys.builder(new TypeToken>() {}) + .name("location.status") + .description("Sensor on the entity to trigger location creation; defaults to service.isUp") + .defaultValue(Startable.SERVICE_UP) + .build(); + + @SuppressWarnings("serial") + public static final ConfigKey>> LOCATION_CONFIG = ConfigKeys.builder(new TypeToken>>() {}) + .name("location.config") + .description("Map of location configuration keys to sensors on the entity that provide their values") + .defaultValue(ImmutableMap.of()) + .build(); + + public static final ConfigKey LOCATION_DISPLAY_NAME = ConfigKeys.builder(String.class) + .name("location.displayName") + .description("The display name for the created location") + .constraint(Predicates.notNull()) + .build(); + + public static final ConfigKey LOCATION_CATALOG_ID = ConfigKeys.builder(String.class) + .name("location.catalogId") + .description("The catalog item ID to use for the created location") + .constraint(Predicates.notNull()) + .build(); + + @SuppressWarnings("serial") + public static final ConfigKey> LOCATION_TYPE = ConfigKeys.builder(new TypeToken>() {}) + .name("location.type") + .description("The type of location to create") + .constraint(Predicates.notNull()) + .build(); + + @SuppressWarnings("serial") + public static final ConfigKey> LOCATION_TAGS = ConfigKeys.builder(new TypeToken>() {}) + .name("location.tags") + .description("Tags for the created location") + .defaultValue(ImmutableSet.of()) + .build(); + + private AtomicBoolean created = new AtomicBoolean(false); + private Location location; + private Object mutex = new Object[0]; + + public CreateLocationPolicy() { + this(MutableMap.of()); + } + + public CreateLocationPolicy(Map props) { + super(props); + } + + @Override + public void init() { } + + @Override + public void rebind() { } + + protected AttributeSensor getStatusSensor() { + return config().get(LOCATION_STATUS); + } + + protected Class getLocationType() { + return config().get(LOCATION_TYPE); + } + + protected Map> getLocationConfiguration() { + return config().get(LOCATION_CONFIG); + } + + protected String getLocationCatalogItemId() { + return config().get(LOCATION_CATALOG_ID); + } + + protected String getLocationDisplayName() { + return config().get(LOCATION_DISPLAY_NAME); + } + + protected Set getLocationTags() { + return config().get(LOCATION_TAGS); + } + + @Override + protected void doReconfigureConfig(ConfigKey key, T val) { + throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this); + } + + private final SensorEventListener lifecycleEventHandler = new SensorEventListener() { + @Override + public void onEvent(SensorEvent event) { + synchronized (mutex) { + Boolean status = event.getValue(); + if (status) { + if (created.compareAndSet(false, true)) { + LocationSpec spec = LocationSpec.create(getLocationType()); + Map> configuration = getLocationConfiguration(); + for (String configKey : configuration.keySet()) { + AttributeSensor sensor = configuration.get(configKey); + Object value = entity.sensors().get(sensor); + spec.configure(configKey, value); + } + spec.catalogItemId(getLocationCatalogItemId()); + spec.displayName(getLocationDisplayName()); + spec.tags(getLocationTags()); + + location = getManagementContext().getLocationManager().createLocation(spec); + } + } else { + if (created.compareAndSet(true, false) && location != null) { + getManagementContext().getLocationManager().unmanage(location); + location = null; + } + } + } + } + }; + + @Override + public void setEntity(EntityLocal entity) { + subscriptions().subscribe(entity, getStatusSensor(), lifecycleEventHandler); + } +} From b9bf8da36ceda0239af7abb2cb2f706518ce922d Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Wed, 7 Jun 2017 14:31:50 +0100 Subject: [PATCH 2/6] Tidy up and add logging --- .../policy/location/CreateLocationPolicy.java | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java index ada80c0d77..7b6e1d3363 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -48,22 +48,22 @@ * and will be removed when the entity is no longer running. */ public class CreateLocationPolicy extends AbstractPolicy { - + private static final Logger LOG = LoggerFactory.getLogger(CreateLocationPolicy.class); public static Builder builder() { return new Builder(); } - + public static class Builder { private String id; private String name; private AttributeSensor status; private Map> configuration; private String catalogId; + private String displayName; private Class type; private Set tags; - private String displayName; public Builder id(String val) { this.id = val; return this; @@ -80,15 +80,15 @@ public Builder configuration(Map> val) { public Builder catalogId(String val) { this.catalogId = val; return this; } + public Builder displayName(String val) { + this.displayName = val; return this; + } public Builder type(Class val) { this.type = val; return this; } public Builder tags(Set val) { this.tags = val; return this; } - public Builder displayName(String val) { - this.displayName = val; return this; - } public CreateLocationPolicy build() { return new CreateLocationPolicy(toFlags()); } @@ -101,11 +101,11 @@ private Map toFlags() { .putIfNotNull("id", id) .putIfNotNull("name", name) .putIfNotNull("location.status", status) - .putIfNotNull("location.configuration", configuration) + .putIfNotNull("location.config", configuration) .putIfNotNull("location.catalogId", catalogId) + .putIfNotNull("location.displayName", displayName) .putIfNotNull("location.type", type) .putIfNotNull("location.tags", tags) - .putIfNotNull("location.displayName", displayName) .build(); } } @@ -124,18 +124,18 @@ private Map toFlags() { .defaultValue(ImmutableMap.of()) .build(); - public static final ConfigKey LOCATION_DISPLAY_NAME = ConfigKeys.builder(String.class) - .name("location.displayName") - .description("The display name for the created location") - .constraint(Predicates.notNull()) - .build(); - public static final ConfigKey LOCATION_CATALOG_ID = ConfigKeys.builder(String.class) .name("location.catalogId") .description("The catalog item ID to use for the created location") .constraint(Predicates.notNull()) .build(); + public static final ConfigKey LOCATION_DISPLAY_NAME = ConfigKeys.builder(String.class) + .name("location.displayName") + .description("The display name for the created location") + .constraint(Predicates.notNull()) + .build(); + @SuppressWarnings("serial") public static final ConfigKey> LOCATION_TYPE = ConfigKeys.builder(new TypeToken>() {}) .name("location.type") @@ -162,20 +162,10 @@ public CreateLocationPolicy(Map props) { super(props); } - @Override - public void init() { } - - @Override - public void rebind() { } - protected AttributeSensor getStatusSensor() { return config().get(LOCATION_STATUS); } - protected Class getLocationType() { - return config().get(LOCATION_TYPE); - } - protected Map> getLocationConfiguration() { return config().get(LOCATION_CONFIG); } @@ -188,6 +178,10 @@ protected String getLocationDisplayName() { return config().get(LOCATION_DISPLAY_NAME); } + protected Class getLocationType() { + return config().get(LOCATION_TYPE); + } + protected Set getLocationTags() { return config().get(LOCATION_TAGS); } @@ -204,6 +198,7 @@ public void onEvent(SensorEvent event) { Boolean status = event.getValue(); if (status) { if (created.compareAndSet(false, true)) { + LOG.info("Creating new location {} of type {}", getLocationCatalogItemId(), getLocationType().getSimpleName()); LocationSpec spec = LocationSpec.create(getLocationType()); Map> configuration = getLocationConfiguration(); for (String configKey : configuration.keySet()) { @@ -219,6 +214,7 @@ public void onEvent(SensorEvent event) { } } else { if (created.compareAndSet(true, false) && location != null) { + LOG.info("Deleting location {} (id {})", getLocationCatalogItemId(), location.getId()); getManagementContext().getLocationManager().unmanage(location); location = null; } From f58239db85391af96661b919516fe501ba0d1bf3 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Tue, 13 Jun 2017 11:43:53 -0400 Subject: [PATCH 3/6] Add call to super.setEntity in create location policy --- .../apache/brooklyn/policy/location/CreateLocationPolicy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java index 7b6e1d3363..3bc897fa9d 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -225,6 +225,7 @@ public void onEvent(SensorEvent event) { @Override public void setEntity(EntityLocal entity) { + super.setEntity(entity); subscriptions().subscribe(entity, getStatusSensor(), lifecycleEventHandler); } } From e18fd55e7f264c447bbf5d7c6bc3a2e5e49fa405 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Thu, 15 Jun 2017 11:58:42 +0200 Subject: [PATCH 4/6] use Catalog instead of LocationRegistry - add example to javadoc --- .../policy/location/CreateLocationPolicy.java | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java index 3bc897fa9d..18845820d4 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -22,9 +22,9 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.policy.PolicySpec; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.SensorEvent; @@ -37,15 +37,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Joiner; import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.reflect.TypeToken; /** * Policy that is attached to an entity to create a location configured * using its sensor data. The created lifecycle is added to the catalog * and will be removed when the entity is no longer running. + * + * + * in YAML blueprints: + *
{@code
+ * name: My App
+ * brooklyn.policies:
+ * - type: org.apache.brooklyn.policy.location.CreateLocationPolicy
+ *   brooklyn.config:
+ *     location.catalogId: my-cloud
+ *     location.displayName: My Cloud
+ *     location.type: jclouds:aws-ec2
+ * }
+ * */ public class CreateLocationPolicy extends AbstractPolicy { @@ -137,9 +153,9 @@ private Map toFlags() { .build(); @SuppressWarnings("serial") - public static final ConfigKey> LOCATION_TYPE = ConfigKeys.builder(new TypeToken>() {}) + public static final ConfigKey LOCATION_TYPE = ConfigKeys.builder(String.class) .name("location.type") - .description("The type of location to create") + .description("The type of location to create, i.e.: 'jclouds:aws-ec2'") .constraint(Predicates.notNull()) .build(); @@ -151,8 +167,8 @@ private Map toFlags() { .build(); private AtomicBoolean created = new AtomicBoolean(false); - private Location location; private Object mutex = new Object[0]; + private Iterable> catalogItems; public CreateLocationPolicy() { this(MutableMap.of()); @@ -178,7 +194,7 @@ protected String getLocationDisplayName() { return config().get(LOCATION_DISPLAY_NAME); } - protected Class getLocationType() { + protected String getLocationType() { return config().get(LOCATION_TYPE); } @@ -198,25 +214,35 @@ public void onEvent(SensorEvent event) { Boolean status = event.getValue(); if (status) { if (created.compareAndSet(false, true)) { - LOG.info("Creating new location {} of type {}", getLocationCatalogItemId(), getLocationType().getSimpleName()); - LocationSpec spec = LocationSpec.create(getLocationType()); - Map> configuration = getLocationConfiguration(); - for (String configKey : configuration.keySet()) { - AttributeSensor sensor = configuration.get(configKey); - Object value = entity.sensors().get(sensor); - spec.configure(configKey, value); + LOG.info("Creating new location {} of type {}", getLocationCatalogItemId(), getLocationType()); + + ImmutableList.Builder builder = ImmutableList.builder().add( + "brooklyn.catalog:", + " id: " + getLocationCatalogItemId(), + " itemType: location", + " item:", + " type: " + getLocationType(), + " brooklyn.config:", + " displayName: " + getDisplayName()); + + if (getLocationConfiguration().size() > 0) { + for (Map.Entry> entry : getLocationConfiguration().entrySet()) { + AttributeSensor sensor = getLocationConfiguration().get(entry.getKey()); + Object value = entity.sensors().get(sensor); + builder.add(" " + entry.getKey() + ": " + value); + } } - spec.catalogItemId(getLocationCatalogItemId()); - spec.displayName(getLocationDisplayName()); - spec.tags(getLocationTags()); + String locationBlueprint = Joiner.on("\n").join(builder.build()); + catalogItems = getManagementContext().getCatalog().addItems(locationBlueprint); - location = getManagementContext().getLocationManager().createLocation(spec); } } else { - if (created.compareAndSet(true, false) && location != null) { - LOG.info("Deleting location {} (id {})", getLocationCatalogItemId(), location.getId()); - getManagementContext().getLocationManager().unmanage(location); - location = null; + if (created.compareAndSet(true, false) && !Iterables.isEmpty(catalogItems)) { + CatalogItem catalogItem = Iterables.getOnlyElement(catalogItems); + LOG.info("Deleting location {} (id {})", getLocationCatalogItemId(), catalogItem.getId()); + String symbolicName = catalogItem.getSymbolicName(); + String version = catalogItem.getVersion(); + getManagementContext().getCatalog().deleteCatalogItem(symbolicName, version); } } } From 1aae33136281a29e613a7bbb46ebd5dd1f7c8406 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Thu, 15 Jun 2017 12:06:36 +0200 Subject: [PATCH 5/6] mark as Beta --- .../apache/brooklyn/policy/location/CreateLocationPolicy.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java index 18845820d4..44e64af557 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -37,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.Beta; import com.google.common.base.Joiner; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; @@ -63,6 +64,7 @@ * } * */ +@Beta public class CreateLocationPolicy extends AbstractPolicy { private static final Logger LOG = LoggerFactory.getLogger(CreateLocationPolicy.class); From 143b9d5cda1c13122c44c049403a442d37eae9f4 Mon Sep 17 00:00:00 2001 From: Andrew Donald Kennedy Date: Sun, 18 Jun 2017 17:58:56 +0100 Subject: [PATCH 6/6] Update location YAML with tags and check return status --- .../policy/location/CreateLocationPolicy.java | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java index 44e64af557..5fbc749c99 100644 --- a/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java +++ b/policy/src/main/java/org/apache/brooklyn/policy/location/CreateLocationPolicy.java @@ -19,12 +19,13 @@ package org.apache.brooklyn.policy.location; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.policy.PolicySpec; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.SensorEvent; @@ -48,19 +49,28 @@ /** * Policy that is attached to an entity to create a location configured - * using its sensor data. The created lifecycle is added to the catalog - * and will be removed when the entity is no longer running. - * - * - * in YAML blueprints: + * using its sensor data. The created location is added to the catalog + * and will be removed when the entity is no longer running, based on + * the entity {@link Startable#SERVICE_UP} sensor status. + *

+ * The policy definition includes configuration for the id, name and + * type of the location, as well as the {@link #LOCATION_CONFIG} map + * of config keys and the sensors used to define them. + *

+ * The YAML below shows a policy that creates a {@code jclouds:aws-ec2} + * location with the region defined by a sensor on the entity it is + * attached to: *

{@code
  * name: My App
  * brooklyn.policies:
- * - type: org.apache.brooklyn.policy.location.CreateLocationPolicy
- *   brooklyn.config:
- *     location.catalogId: my-cloud
- *     location.displayName: My Cloud
- *     location.type: jclouds:aws-ec2
+ *   - type: org.apache.brooklyn.policy.location.CreateLocationPolicy
+ *     id: create-my-cloud-location
+ *     brooklyn.config:
+ *       location.catalogId: my-cloud
+ *       location.displayName: "My Cloud"
+ *       location.type: jclouds:aws-ec2
+ *       location.config:
+ *         region: $brooklyn:sensor("aws.region")
  * }
* */ @@ -80,7 +90,7 @@ public static class Builder { private Map> configuration; private String catalogId; private String displayName; - private Class type; + private String type; private Set tags; public Builder id(String val) { @@ -101,7 +111,7 @@ public Builder catalogId(String val) { public Builder displayName(String val) { this.displayName = val; return this; } - public Builder type(Class val) { + public Builder type(String val) { this.type = val; return this; } public Builder tags(Set val) { @@ -154,10 +164,9 @@ private Map toFlags() { .constraint(Predicates.notNull()) .build(); - @SuppressWarnings("serial") public static final ConfigKey LOCATION_TYPE = ConfigKeys.builder(String.class) .name("location.type") - .description("The type of location to create, i.e.: 'jclouds:aws-ec2'") + .description("The type of location to create, i.e. 'jclouds:aws-ec2'") .constraint(Predicates.notNull()) .build(); @@ -170,7 +179,8 @@ private Map toFlags() { private AtomicBoolean created = new AtomicBoolean(false); private Object mutex = new Object[0]; - private Iterable> catalogItems; + private CatalogItem catalogItem; + private AtomicInteger version = new AtomicInteger(0); public CreateLocationPolicy() { this(MutableMap.of()); @@ -221,30 +231,49 @@ public void onEvent(SensorEvent event) { ImmutableList.Builder builder = ImmutableList.builder().add( "brooklyn.catalog:", " id: " + getLocationCatalogItemId(), + " version: " + version.incrementAndGet(), " itemType: location", " item:", " type: " + getLocationType(), " brooklyn.config:", - " displayName: " + getDisplayName()); + " displayName: " + getLocationDisplayName()); - if (getLocationConfiguration().size() > 0) { - for (Map.Entry> entry : getLocationConfiguration().entrySet()) { - AttributeSensor sensor = getLocationConfiguration().get(entry.getKey()); - Object value = entity.sensors().get(sensor); - builder.add(" " + entry.getKey() + ": " + value); + for (Map.Entry> entry : getLocationConfiguration().entrySet()) { + AttributeSensor sensor = getLocationConfiguration().get(entry.getKey()); + Object value = entity.sensors().get(sensor); + builder.add(" " + entry.getKey() + ": " + value); + } + if (getLocationTags().size() > 0) { + builder.add(" tags:"); + for (String tag : getLocationTags()) { + builder.add(" - " + tag); } } - String locationBlueprint = Joiner.on("\n").join(builder.build()); - catalogItems = getManagementContext().getCatalog().addItems(locationBlueprint); + String blueprint = Joiner.on("\n").join(builder.build()); + LOG.debug("Creating location {} from YAML\n{}", getLocationCatalogItemId(), blueprint); + Iterable> catalogItems = getManagementContext().getCatalog().addItems(blueprint, true); + int items = Iterables.size(catalogItems); + if (items > 1) { + LOG.warn("Got {} catalog items for location {}, using first: {}", + new Object[] { items, getLocationCatalogItemId(), Iterables.transform(catalogItems, item -> item.getCatalogItemId()) }); + } else if (items == 0) { + throw new IllegalStateException("No catalog items returned for location: " + getLocationCatalogItemId()); + } + catalogItem = Iterables.get(catalogItems, 0); } } else { - if (created.compareAndSet(true, false) && !Iterables.isEmpty(catalogItems)) { - CatalogItem catalogItem = Iterables.getOnlyElement(catalogItems); - LOG.info("Deleting location {} (id {})", getLocationCatalogItemId(), catalogItem.getId()); + if (created.compareAndSet(true, false) && catalogItem != null) { String symbolicName = catalogItem.getSymbolicName(); - String version = catalogItem.getVersion(); - getManagementContext().getCatalog().deleteCatalogItem(symbolicName, version); + String catalogVersion = catalogItem.getVersion(); + try { + LOG.info("Deleting location id {}: {}:{}", + new Object[] { catalogItem.getId(), symbolicName, catalogVersion }); + getManagementContext().getCatalog().deleteCatalogItem(symbolicName, catalogVersion); + } catch (NoSuchElementException nsee) { + LOG.warn("Could not find location {}:{} in catalog to delete", symbolicName, catalogVersion); + } + catalogItem = null; } } }