From d7ab5b5e26296bfcc1d235c4c3c72d13217e6503 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 5 Aug 2014 10:41:58 +0100 Subject: [PATCH 1/8] Persist and rebind feeds - Adds Feed interface - Adds abstractEntity.addFeed; if added like that then feed is persisted - Adds EntityInternal.getFeedSupport(), which has getFeeds(), addFeed(), removeFeed() and getAllFeeds() - AbstractFeed.poller: make private, with getter; and make transient --- api/src/main/java/brooklyn/entity/Feed.java | 59 +++++++++ .../brooklyn/entity/rebind/RebindSupport.java | 2 + .../java/brooklyn/mementos/EntityMemento.java | 6 + .../brooklyn/entity/basic/AbstractEntity.java | 49 ++++++++ .../brooklyn/entity/basic/EntityInternal.java | 25 ++++ .../AbstractBrooklynObjectRebindSupport.java | 6 + .../rebind/BasicEntityRebindSupport.java | 18 +++ .../rebind/BasicLocationRebindSupport.java | 5 + .../entity/rebind/RebindManagerImpl.java | 1 + .../entity/rebind/dto/BasicEntityMemento.java | 11 ++ .../entity/rebind/dto/MementosGenerators.java | 14 ++- .../brooklyn/event/feed/AbstractFeed.java | 45 ++++--- .../main/java/brooklyn/event/feed/Poller.java | 4 +- .../event/feed/function/FunctionFeed.java | 6 - .../brooklyn/event/feed/http/HttpFeed.java | 4 +- .../brooklyn/event/feed/shell/ShellFeed.java | 4 +- .../java/brooklyn/event/feed/ssh/SshFeed.java | 4 +- .../WindowsPerformanceCounterFeed.java | 7 +- .../entity/rebind/RebindFeedTest.java | 113 ++++++++++++++++++ .../entity/monitoring/zabbix/ZabbixFeed.java | 2 +- .../entity/chef/ChefAttributeFeed.java | 8 +- .../java/brooklyn/event/feed/jmx/JmxFeed.java | 3 +- 22 files changed, 353 insertions(+), 43 deletions(-) create mode 100644 api/src/main/java/brooklyn/entity/Feed.java create mode 100644 core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java diff --git a/api/src/main/java/brooklyn/entity/Feed.java b/api/src/main/java/brooklyn/entity/Feed.java new file mode 100644 index 0000000000..cf0430db54 --- /dev/null +++ b/api/src/main/java/brooklyn/entity/Feed.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.entity; + +import com.google.common.annotations.Beta; + +/** + * A sensor feed. + * These generally poll or subscribe to get sensor values for an entity. + * They make it easy to poll over http, jmx, etc. + * + * Assumes: + * + */ +@Beta +public interface Feed { + + /** + * True if everything has been _started_ (or it is starting) but not stopped, + * even if it is suspended; see also {@link #isActive()} + */ + boolean isActivated(); + + /** + * @eturn true iff the feed is running + */ + boolean isActive(); + + void start(); + + /** suspends this feed (stops the poller, or indicates that the feed should start in a state where the poller is stopped) */ + void suspend(); + + /** resumes this feed if it has been suspended and not stopped */ + void resume(); + + void stop(); +} diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java b/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java index e6bbbb4e05..435fbc66d2 100644 --- a/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java +++ b/api/src/main/java/brooklyn/entity/rebind/RebindSupport.java @@ -52,4 +52,6 @@ public interface RebindSupport { void addPolicies(RebindContext rebindContext, T Memento); void addEnrichers(RebindContext rebindContext, T Memento); + + void addFeeds(RebindContext rebindContext, T Memento); } diff --git a/api/src/main/java/brooklyn/mementos/EntityMemento.java b/api/src/main/java/brooklyn/mementos/EntityMemento.java index c0547d4fe2..fbe96ba663 100644 --- a/api/src/main/java/brooklyn/mementos/EntityMemento.java +++ b/api/src/main/java/brooklyn/mementos/EntityMemento.java @@ -24,6 +24,7 @@ import brooklyn.config.ConfigKey; import brooklyn.entity.Effector; +import brooklyn.entity.Feed; import brooklyn.entity.rebind.RebindSupport; import brooklyn.event.AttributeSensor; @@ -72,4 +73,9 @@ public interface EntityMemento extends Memento, TreeNode { * The ids of the enrichers of this entity. */ public Collection getEnrichers(); + + /** + * The sensor feeds attached to this entity. + */ + public Collection getFeeds(); } diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index ec2d5e96cf..031ceb4338 100644 --- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java +++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java @@ -38,6 +38,7 @@ import brooklyn.entity.Effector; import brooklyn.entity.Entity; import brooklyn.entity.EntityType; +import brooklyn.entity.Feed; import brooklyn.entity.Group; import brooklyn.entity.basic.ServiceStateLogic.ServiceNotUpLogic; import brooklyn.entity.proxying.EntitySpec; @@ -173,6 +174,7 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E Map presentationAttributes = Maps.newLinkedHashMap(); Collection policies = Lists.newCopyOnWriteArrayList(); Collection enrichers = Lists.newCopyOnWriteArrayList(); + Collection feeds = Lists.newCopyOnWriteArrayList(); // FIXME we do not currently support changing parents, but to implement a cluster that can shrink we need to support at least // orphaning (i.e. removing ownership). This flag notes if the entity has previously had a parent, and if an attempt is made to @@ -1213,6 +1215,53 @@ public boolean removeAllEnrichers() { return changed; } + // -------- FEEDS -------------------- + + /** + * Convenience, which calls {@link EntityInternal#getFeedSupport()} and {@link FeedSupport#addFeed(Feed)}. + */ + protected T addFeed(T feed) { + return getFeedSupport().addFeed(feed); + } + + public FeedSupport getFeedSupport() { + return new FeedSupport() { + @Override + public Collection getFeeds() { + return ImmutableList.copyOf(feeds); + } + + @Override + public T addFeed(T feed) { + feeds.add(feed); + + getManagementSupport().getEntityChangeListener().onChanged(); + return feed; + } + + @Override + public boolean removeFeed(Feed feed) { + feed.stop();destroy(); + boolean changed = feeds.remove(feed); + + if (changed) { + getManagementSupport().getEntityChangeListener().onChanged(); + } + return changed; + + } + + @Override + public boolean removeAllFeeds() { + boolean changed = false; + for (Feed feed : feeds) { + changed = removeFeed(feed) || changed; + } + return changed; + } + }; + } + // -------- SENSORS -------------------- @Override diff --git a/core/src/main/java/brooklyn/entity/basic/EntityInternal.java b/core/src/main/java/brooklyn/entity/basic/EntityInternal.java index 7fef8c5faa..ab1fd59d41 100644 --- a/core/src/main/java/brooklyn/entity/basic/EntityInternal.java +++ b/core/src/main/java/brooklyn/entity/basic/EntityInternal.java @@ -24,6 +24,7 @@ import brooklyn.basic.BrooklynObjectInternal; import brooklyn.config.ConfigKey; import brooklyn.entity.Effector; +import brooklyn.entity.Feed; import brooklyn.entity.rebind.RebindSupport; import brooklyn.entity.rebind.Rebindable; import brooklyn.event.AttributeSensor; @@ -132,6 +133,8 @@ public interface EntityInternal extends BrooklynObjectInternal, EntityLocal, Reb @Beta Effector getEffector(String effectorName); + FeedSupport getFeedSupport(); + Map toMetadataRecord(); @Override @@ -142,4 +145,26 @@ public interface EntityInternal extends BrooklynObjectInternal, EntityLocal, Reb * This persistence may happen asynchronously, or may not happen at all if persistence is disabled. */ void requestPersist(); + + public interface FeedSupport { + Collection getFeeds(); + + /** + * Adds the given feed to this entity. The feed will automatically be re-added on brooklyn restart. + */ + T addFeed(T feed); + + /** + * Removes the given feed from this entity. + * @return True if the feed existed at this entity; false otherwise + */ + boolean removeFeed(Feed feed); + + /** + * Removes all feeds from this entity. + * Use with caution as some entities automatically register feeds; this will remove those feeds as well. + * @return True if any feeds existed at this entity; false otherwise + */ + boolean removeAllFeeds(); + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java index 4549dd2e16..0d9dc81a2b 100644 --- a/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java +++ b/core/src/main/java/brooklyn/entity/rebind/AbstractBrooklynObjectRebindSupport.java @@ -23,6 +23,7 @@ import brooklyn.basic.AbstractBrooklynObject; import brooklyn.entity.rebind.dto.MementosGenerators; +import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.Memento; public abstract class AbstractBrooklynObjectRebindSupport implements RebindSupport { @@ -76,6 +77,11 @@ public void addEnrichers(RebindContext rebindContext, T Memento) { throw new UnsupportedOperationException(); } + @Override + public void addFeeds(RebindContext rebindContext, T Memento) { + throw new UnsupportedOperationException(); + } + /** * For overriding, to give custom reconstruct behaviour. * diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java index a6e1341065..36f5f8aed9 100644 --- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java +++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java @@ -30,6 +30,7 @@ import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.Effector; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.Group; import brooklyn.entity.basic.AbstractEntity; import brooklyn.entity.basic.EntityInternal; @@ -145,6 +146,23 @@ public void addEnrichers(RebindContext rebindContext, EntityMemento memento) { } } + @Override + public void addFeeds(RebindContext rebindContext, EntityMemento memento) { + for (Feed feed : memento.getFeeds()) { + if (feed != null) { + try { + feed.start(); + ((EntityInternal)entity).getFeedSupport().addFeed(feed); + } catch (Exception e) { + rebindContext.getExceptionHandler().onRebindFailed(BrooklynObjectType.ENTITY, entity, e); + } + } else { + LOG.warn("Feed not found; discarding feed of entity {}({})", + new Object[] {memento.getType(), memento.getId()}); + } + } + } + protected void addMembers(RebindContext rebindContext, EntityMemento memento) { if (memento.getMembers().size() > 0) { if (entity instanceof Group) { diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java index 50b4147c3f..69463e2a3a 100644 --- a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java +++ b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java @@ -109,6 +109,11 @@ protected void addCustoms(RebindContext rebindContext, LocationMemento memento) location.init(); // TODO deprecated calling init; will be deleted } + @Override + public void addFeeds(RebindContext rebindContext, LocationMemento Memento) { + throw new UnsupportedOperationException(); + } + protected void addChildren(RebindContext rebindContext, LocationMemento memento) { for (String childId : memento.getChildren()) { Location child = rebindContext.getLocation(childId); diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java index 2b4d4f8407..0056e8bed8 100644 --- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java @@ -532,6 +532,7 @@ protected List rebindImpl(final ClassLoader classLoader, final Rebi entityMemento.injectTypeClass(entity.getClass()); ((EntityInternal)entity).getRebindSupport().addPolicies(rebindContext, entityMemento); ((EntityInternal)entity).getRebindSupport().addEnrichers(rebindContext, entityMemento); + ((EntityInternal)entity).getRebindSupport().addFeeds(rebindContext, entityMemento); } catch (Exception e) { exceptionHandler.onRebindFailed(BrooklynObjectType.ENTITY, entity, e); } diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java index 30eb653d85..df72ee58d0 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java @@ -30,6 +30,7 @@ import brooklyn.config.ConfigKey; import brooklyn.entity.Effector; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.AbstractEntity; import brooklyn.entity.basic.Entities; import brooklyn.entity.rebind.RebindSupport; @@ -70,6 +71,7 @@ public static class Builder extends AbstractTreeNodeMemento.Builder { protected List enrichers = Lists.newArrayList(); protected List members = Lists.newArrayList(); protected List> effectors = Lists.newArrayList(); + protected List feeds = Lists.newArrayList(); public Builder from(EntityMemento other) { super.from((TreeNode)other); @@ -83,6 +85,7 @@ public Builder from(EntityMemento other) { enrichers.addAll(other.getEnrichers()); members.addAll(other.getMembers()); effectors.addAll(other.getEffectors()); + feeds.addAll(other.getFeeds()); tags.addAll(other.getTags()); return this; } @@ -103,6 +106,7 @@ public EntityMemento build() { private Map attributes; private List policies; private List enrichers; + private List feeds; // TODO can we move some of these to entity type, or remove/re-insert those which are final statics? private Map> configKeys; @@ -130,6 +134,7 @@ protected BasicEntityMemento(Builder builder) { locations = toPersistedList(builder.locations); policies = toPersistedList(builder.policies); enrichers = toPersistedList(builder.enrichers); + feeds = toPersistedList(builder.feeds); members = toPersistedList(builder.members); effectors = toPersistedList(builder.effectors); @@ -280,6 +285,12 @@ public List getLocations() { return fromPersistedList(locations); } + @Override + public List getFeeds() { + if (configByKey == null) postDeserialize(); + return fromPersistedList(feeds); + } + @Override protected ToStringHelper newVerboseStringHelper() { return super.newVerboseStringHelper() diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java index 328966558d..ebcea96b32 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java @@ -30,6 +30,7 @@ import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.Application; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.Group; import brooklyn.entity.basic.EntityDynamicType; import brooklyn.entity.basic.EntityInternal; @@ -124,7 +125,8 @@ public static EntityMemento newEntityMemento(Entity entity) { * @deprecated since 0.7.0; use {@link #newMemento(BrooklynObject)} instead */ @Deprecated - public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entity) { + public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entityRaw) { + EntityInternal entity = (EntityInternal) entityRaw; BasicEntityMemento.Builder builder = BasicEntityMemento.builder(); populateBrooklynObjectMementoBuilder(entity, builder); @@ -138,14 +140,14 @@ public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entity) builder.isTopLevelApp = (entity instanceof Application && entity.getParent() == null); - Map, Object> localConfig = ((EntityInternal)entity).getConfigMap().getLocalConfig(); + Map, Object> localConfig = entity.getConfigMap().getLocalConfig(); for (Map.Entry, Object> entry : localConfig.entrySet()) { ConfigKey key = checkNotNull(entry.getKey(), localConfig); Object value = configValueToPersistable(entry.getValue()); builder.config.put(key, value); } - Map localConfigUnmatched = MutableMap.copyOf(((EntityInternal)entity).getConfigMap().getLocalConfigBag().getAllConfig()); + Map localConfigUnmatched = MutableMap.copyOf(entity.getConfigMap().getLocalConfigBag().getAllConfig()); for (ConfigKey key : localConfig.keySet()) { localConfigUnmatched.remove(key.getName()); } @@ -157,7 +159,7 @@ public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entity) } @SuppressWarnings("rawtypes") - Map allAttributes = ((EntityInternal)entity).getAllAttributes(); + Map allAttributes = entity.getAllAttributes(); for (@SuppressWarnings("rawtypes") Map.Entry entry : allAttributes.entrySet()) { AttributeSensor key = checkNotNull(entry.getKey(), allAttributes); Object value = entry.getValue(); @@ -180,6 +182,10 @@ public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entity) builder.enrichers.add(enricher.getId()); } + for (Feed feed : entity.getFeedSupport().getFeeds()) { + builder.feeds.add(feed); + } + Entity parentEntity = entity.getParent(); builder.parent = (parentEntity != null) ? parentEntity.getId() : null; diff --git a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java index 1a7770f02d..11b5b3b185 100644 --- a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java +++ b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java @@ -23,29 +23,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.entity.Feed; import brooklyn.entity.basic.EntityLocal; /** * Captures common fields and processes for sensor feeds. * These generally poll or subscribe to get sensor values for an entity. * They make it easy to poll over http, jmx, etc. - * - * Assumes: - *
    - *
  • There will not be concurrent calls to start and stop. - *
  • There will only be one call to start and that will be done immediately after construction, - * in the same thread. - *
  • Once stopped, the feed will not be re-started. - *
*/ -public abstract class AbstractFeed { +public abstract class AbstractFeed implements Feed { private static final Logger log = LoggerFactory.getLogger(AbstractFeed.class); protected final EntityLocal entity; - protected final Poller poller; - private volatile boolean activated, suspended; - private final Object pollerStateMutex = new Object(); + protected final boolean onlyIfServiceUp; + private final Object pollerStateMutex = new Object(); + private transient volatile Poller poller; + private transient volatile boolean activated; + private transient volatile boolean suspended; public AbstractFeed(EntityLocal entity) { this(entity, false); @@ -53,16 +48,15 @@ public AbstractFeed(EntityLocal entity) { public AbstractFeed(EntityLocal entity, boolean onlyIfServiceUp) { this.entity = checkNotNull(entity, "entity"); - this.poller = new Poller(entity, onlyIfServiceUp); + this.onlyIfServiceUp = onlyIfServiceUp; } - /** true if everything has been _started_ (or it is starting) but not stopped, - * even if it is suspended; see also {@link #isActive()} */ + @Override public boolean isActivated() { return activated; } - /** true iff the feed is running */ + @Override public boolean isActive() { return activated && !suspended; } @@ -80,13 +74,18 @@ protected boolean isConnected() { return isActivated(); } - protected void start() { + @Override + public void start() { if (log.isDebugEnabled()) log.debug("Starting feed {} for {}", this, entity); if (activated) { throw new IllegalStateException(String.format("Attempt to start feed %s of entity %s when already running", this, entity)); } + if (poller != null) { + throw new IllegalStateException(String.format("Attempt to re-start feed %s of entity %s", this, entity)); + } + poller = new Poller(entity, onlyIfServiceUp); activated = true; preStart(); synchronized (pollerStateMutex) { @@ -97,7 +96,7 @@ protected void start() { } } - /** suspends this feed (stops the poller, or indicates that the feed should start in a state where the poller is stopped) */ + @Override public void suspend() { synchronized (pollerStateMutex) { if (activated && !suspended) { @@ -107,7 +106,7 @@ public void suspend() { } } - /** resumes this feed if it has been suspended and not stopped */ + @Override public void resume() { synchronized (pollerStateMutex) { if (activated && suspended) { @@ -117,6 +116,7 @@ public void resume() { } } + @Override public void stop() { if (!activated) { log.debug("Ignoring attempt to stop feed {} of entity {} when not running", this, entity); @@ -151,4 +151,11 @@ protected void preStop() { */ protected void postStop() { } + + /** + * For overriding, where sub-class can change return-type generics! + */ + protected Poller getPoller() { + return poller; + } } diff --git a/core/src/main/java/brooklyn/event/feed/Poller.java b/core/src/main/java/brooklyn/event/feed/Poller.java index 5ce336f8e5..34ec5ddfed 100644 --- a/core/src/main/java/brooklyn/event/feed/Poller.java +++ b/core/src/main/java/brooklyn/event/feed/Poller.java @@ -175,10 +175,10 @@ public void stop() { running = false; for (Task task : oneOffTasks) { - task.cancel(true); + if (task != null) task.cancel(true); } for (ScheduledTask task : tasks) { - task.cancel(); + if (task != null) task.cancel(); } oneOffTasks.clear(); tasks.clear(); diff --git a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java index 0690da9e78..94a866bf18 100644 --- a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java +++ b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java @@ -32,7 +32,6 @@ import brooklyn.event.feed.AbstractFeed; import brooklyn.event.feed.AttributePollHandler; import brooklyn.event.feed.DelegatingPollHandler; -import brooklyn.event.feed.Poller; import brooklyn.util.time.Duration; import com.google.common.base.Objects; @@ -180,9 +179,4 @@ protected void preStart() { minPeriod); } } - - @SuppressWarnings("unchecked") - private Poller getPoller() { - return (Poller) poller; - } } diff --git a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java index bea1d803b0..bff833bda3 100644 --- a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java +++ b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java @@ -354,7 +354,7 @@ private HttpClient createHttpClient(HttpPollIdentifier pollIdentifier) { } @SuppressWarnings("unchecked") - private Poller getPoller() { - return (Poller) poller; + protected Poller getPoller() { + return (Poller) super.getPoller(); } } diff --git a/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java b/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java index ec14f3e745..b90b2ea3d1 100644 --- a/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java +++ b/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java @@ -223,8 +223,8 @@ protected void preStart() { } @SuppressWarnings("unchecked") - private Poller getPoller() { - return (Poller) poller; + protected Poller getPoller() { + return (Poller) super.getPoller(); } /** diff --git a/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java b/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java index e8c514880a..14988ab5b7 100644 --- a/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java +++ b/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java @@ -235,8 +235,8 @@ public SshPollValue call() throws Exception { } @SuppressWarnings("unchecked") - private Poller getPoller() { - return (Poller) poller; + protected Poller getPoller() { + return (Poller) super.getPoller(); } private SshPollValue exec(String command, Map env) throws IOException { diff --git a/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java index dcb7ba6c87..7ccc9fc184 100644 --- a/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java +++ b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java @@ -182,12 +182,17 @@ public SshPollValue call() throws Exception { } }; - ((Poller) poller).scheduleAtFixedRate( + getPoller().scheduleAtFixedRate( new CallInEntityExecutionContext(entity, queryForCounterValues), new SendPerfCountersToSensors(entity, attributeSensors), periodUnits.toMillis(period)); } + @SuppressWarnings("unchecked") + protected Poller getPoller() { + return (Poller) super.getPoller(); + } + /** * A {@link java.util.concurrent.Callable} that wraps another {@link java.util.concurrent.Callable}, where the * inner {@link java.util.concurrent.Callable} is executed in the context of a diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java new file mode 100644 index 0000000000..056e440323 --- /dev/null +++ b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java @@ -0,0 +1,113 @@ +/* + * 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.entity.rebind; + +import static org.testng.Assert.assertEquals; + +import java.net.URL; +import java.util.Collection; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Feed; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.Sensors; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.test.EntityTestUtils; +import brooklyn.test.entity.TestEntity; +import brooklyn.test.entity.TestEntityImpl; +import brooklyn.util.http.BetterMockWebServer; + +import com.google.common.collect.Iterables; +import com.google.mockwebserver.MockResponse; + +public class RebindFeedTest extends RebindTestFixtureWithApp { + + final static AttributeSensor SENSOR_STRING = Sensors.newStringSensor("aString", ""); + final static AttributeSensor SENSOR_INT = Sensors.newIntegerSensor( "aLong", ""); + + private BetterMockWebServer server; + private URL baseUrl; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + server = BetterMockWebServer.newInstanceLocalhost(); + for (int i = 0; i < 100; i++) { + server.enqueue(new MockResponse().setResponseCode(200).addHeader("content-type: application/json").setBody("{\"foo\":\"myfoo\"}")); + } + server.play(); + baseUrl = server.getUrl("/"); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + if (server != null) server.shutdown(); + super.tearDown(); + } + + @Test + public void testHttpFeedRegisteredInInitIsPersisted() throws Exception { + TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityImpl.class) + .configure(MyEntityImpl.BASE_URL, baseUrl)); + EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_INT, (Integer)200); + EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_STRING, "{\"foo\":\"myfoo\"}"); + assertEquals(origEntity.getFeedSupport().getFeeds().size(), 1); + + newApp = rebind(false); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + + Collection newFeeds = newEntity.getFeedSupport().getFeeds(); + assertEquals(newFeeds.size(), 1); + + // Expect the feed to still be polling + newEntity.setAttribute(SENSOR_INT, null); + newEntity.setAttribute(SENSOR_STRING, null); + EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_INT, (Integer)200); + EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_STRING, "{\"foo\":\"myfoo\"}"); + } + + public static class MyEntityImpl extends TestEntityImpl { + public static final ConfigKey BASE_URL = ConfigKeys.newConfigKey(URL.class, "rebindFeedTest.baseUrl"); + + @Override + public void init() { + super.init(); + HttpFeed feed = addFeed(HttpFeed.builder() + .entity(this) + .baseUrl(getConfig(BASE_URL)) + .poll(HttpPollConfig.forSensor(SENSOR_INT) + .period(100) + .onSuccess(HttpValueFunctions.responseCode())) + .poll(HttpPollConfig.forSensor(SENSOR_STRING) + .period(100) + .onSuccess(HttpValueFunctions.stringContentsFunction())) + .build()); + } + } +} diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java index f50db7dc73..55645f41f1 100644 --- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java +++ b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java @@ -424,6 +424,6 @@ public HttpToolResponse call() throws Exception { @SuppressWarnings("unchecked") protected Poller getPoller() { - return (Poller) poller; + return (Poller) super.getPoller(); } } diff --git a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java index 17b295b3fb..de2edb4639 100644 --- a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java +++ b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java @@ -180,7 +180,6 @@ protected ChefAttributeFeed(Builder builder) { knifeTaskFactory = new KnifeNodeAttributeQueryTaskFactory(nodeName); } - @SuppressWarnings("unchecked") @Override protected void preStart() { final Callable getAttributesFromKnife = new Callable() { @@ -195,12 +194,17 @@ public SshPollValue call() throws Exception { } }; - ((Poller) poller).scheduleAtFixedRate( + getPoller().scheduleAtFixedRate( new CallInEntityExecutionContext(entity, getAttributesFromKnife), new SendChefAttributesToSensors(entity, chefAttributeSensors), periodUnits.toMillis(period)); } + @SuppressWarnings("unchecked") + protected Poller getPoller() { + return (Poller) super.getPoller(); + } + /** * An implementation of {@link KnifeTaskFactory} that queries for the attributes of a node. */ diff --git a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java index 026f000344..ab05914e48 100644 --- a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java +++ b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java @@ -187,10 +187,9 @@ public String getJmxUri() { return jmxUri; } - @VisibleForTesting @SuppressWarnings("unchecked") protected Poller getPoller() { - return (Poller) poller; + return (Poller) super.getPoller(); } @Override From 9fe95f9b697ca10b9f1f184e61ed5e9f1b961be5 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 5 Aug 2014 12:04:26 +0100 Subject: [PATCH 2/8] FunctionFeed: test persistence - and make easier to build a FunctionPollConfig --- .../brooklyn/entity/basic/DataEntityImpl.java | 2 +- .../feed/function/FunctionPollConfig.java | 33 ++++++++++++--- .../event/feed/http/HttpPollConfig.java | 16 ++++---- .../entity/rebind/RebindFeedTest.java | 41 +++++++++++++++++-- .../event/feed/function/FunctionFeedTest.java | 25 +++++++++++ 5 files changed, 98 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/brooklyn/entity/basic/DataEntityImpl.java b/core/src/main/java/brooklyn/entity/basic/DataEntityImpl.java index 8d1743f3de..140e48857a 100644 --- a/core/src/main/java/brooklyn/entity/basic/DataEntityImpl.java +++ b/core/src/main/java/brooklyn/entity/basic/DataEntityImpl.java @@ -62,7 +62,7 @@ protected void connectSensors() { if (map != null && map.size() > 0) { for (Map.Entry, Supplier> entry : map.entrySet()) { final AttributeSensor sensor = entry.getKey(); - final Supplier supplier = entry.getValue(); + final Supplier supplier = entry.getValue(); builder.poll(new FunctionPollConfig(sensor) .supplier(supplier) .onFailureOrException(Functions.constant(null))); diff --git a/core/src/main/java/brooklyn/event/feed/function/FunctionPollConfig.java b/core/src/main/java/brooklyn/event/feed/function/FunctionPollConfig.java index 7c4ef43b91..b2ff3f122b 100644 --- a/core/src/main/java/brooklyn/event/feed/function/FunctionPollConfig.java +++ b/core/src/main/java/brooklyn/event/feed/function/FunctionPollConfig.java @@ -24,6 +24,7 @@ import java.util.concurrent.Callable; import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.FeedConfig; import brooklyn.event.feed.PollConfig; import brooklyn.util.GroovyJavaMethods; @@ -33,6 +34,10 @@ public class FunctionPollConfig extends PollConfig callable; + public static FunctionPollConfig forSensor(AttributeSensor sensor) { + return new FunctionPollConfig(sensor); + } + public FunctionPollConfig(AttributeSensor sensor) { super(sensor); } @@ -46,20 +51,36 @@ public Callable getCallable() { return callable; } - public FunctionPollConfig callable(Callable val) { + /** + * The {@link Callable} to be invoked on each poll. + *

+ * Note this must use generics, otherwise the return type of subsequent chained + * calls will (e.g. to {@link FeedConfig#onException(com.google.common.base.Function)} will + * return the wrong type. + */ + @SuppressWarnings("unchecked") + public FunctionPollConfig callable(Callable val) { this.callable = checkNotNull(val, "callable"); - return this; + return (FunctionPollConfig) this; } - public FunctionPollConfig supplier(final Supplier val) { + /** + * Supplies the value to be returned by each poll. + *

+ * Note this must use generics, otherwise the return type of subsequent chained + * calls will (e.g. to {@link FeedConfig#onException(com.google.common.base.Function)} will + * return the wrong type. + */ + @SuppressWarnings("unchecked") + public FunctionPollConfig supplier(final Supplier val) { checkNotNull(val, "supplier"); - this.callable = new Callable() { + this.callable = new Callable() { @Override - public S call() throws Exception { + public newS call() throws Exception { return val.get(); } }; - return this; + return (FunctionPollConfig) this; } public FunctionPollConfig closure(Closure val) { diff --git a/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java b/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java index c8362fca6b..c091006cf5 100644 --- a/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java +++ b/core/src/main/java/brooklyn/event/feed/http/HttpPollConfig.java @@ -52,6 +52,14 @@ public boolean apply(@Nullable HttpToolResponse input) { return input != null && input.getResponseCode() >= 200 && input.getResponseCode() <= 399; }}; + public static HttpPollConfig forSensor(AttributeSensor sensor) { + return new HttpPollConfig(sensor); + } + + public static HttpPollConfig forMultiple() { + return new HttpPollConfig(PollConfig.NO_SENSOR); + } + public HttpPollConfig(AttributeSensor sensor) { super(sensor); super.checkSuccess(DEFAULT_SUCCESS); @@ -65,14 +73,6 @@ public HttpPollConfig(HttpPollConfig other) { headers = other.headers; } - public static HttpPollConfig forSensor(AttributeSensor sensor) { - return new HttpPollConfig(sensor); - } - - public static HttpPollConfig forMultiple() { - return new HttpPollConfig(PollConfig.NO_SENSOR); - } - public String getSuburl() { return suburl; } diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java index 056e440323..a0b5f04a9b 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java @@ -33,6 +33,8 @@ import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.AttributeSensor; import brooklyn.event.basic.Sensors; +import brooklyn.event.feed.function.FunctionFeed; +import brooklyn.event.feed.function.FunctionPollConfig; import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; @@ -42,6 +44,7 @@ import brooklyn.util.http.BetterMockWebServer; import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.Callables; import com.google.mockwebserver.MockResponse; public class RebindFeedTest extends RebindTestFixtureWithApp { @@ -73,8 +76,8 @@ public void tearDown() throws Exception { @Test public void testHttpFeedRegisteredInInitIsPersisted() throws Exception { - TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityImpl.class) - .configure(MyEntityImpl.BASE_URL, baseUrl)); + TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityWithHttpFeedImpl.class) + .configure(MyEntityWithHttpFeedImpl.BASE_URL, baseUrl)); EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_INT, (Integer)200); EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_STRING, "{\"foo\":\"myfoo\"}"); assertEquals(origEntity.getFeedSupport().getFeeds().size(), 1); @@ -92,13 +95,30 @@ public void testHttpFeedRegisteredInInitIsPersisted() throws Exception { EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_STRING, "{\"foo\":\"myfoo\"}"); } - public static class MyEntityImpl extends TestEntityImpl { + @Test + public void testFunctionFeedRegisteredInInitIsPersisted() throws Exception { + TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityWithFunctionFeedImpl.class)); + EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_INT, (Integer)1); + assertEquals(origEntity.getFeedSupport().getFeeds().size(), 1); + + newApp = rebind(false); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + + Collection newFeeds = newEntity.getFeedSupport().getFeeds(); + assertEquals(newFeeds.size(), 1); + + // Expect the feed to still be polling + newEntity.setAttribute(SENSOR_INT, null); + EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_INT, (Integer)1); + } + + public static class MyEntityWithHttpFeedImpl extends TestEntityImpl { public static final ConfigKey BASE_URL = ConfigKeys.newConfigKey(URL.class, "rebindFeedTest.baseUrl"); @Override public void init() { super.init(); - HttpFeed feed = addFeed(HttpFeed.builder() + addFeed(HttpFeed.builder() .entity(this) .baseUrl(getConfig(BASE_URL)) .poll(HttpPollConfig.forSensor(SENSOR_INT) @@ -110,4 +130,17 @@ public void init() { .build()); } } + + public static class MyEntityWithFunctionFeedImpl extends TestEntityImpl { + @Override + public void init() { + super.init(); + addFeed(FunctionFeed.builder() + .entity(this) + .poll(FunctionPollConfig.forSensor(SENSOR_INT) + .period(100) + .callable(Callables.returning(1))) + .build()); + } + } } diff --git a/core/src/test/java/brooklyn/event/feed/function/FunctionFeedTest.java b/core/src/test/java/brooklyn/event/feed/function/FunctionFeedTest.java index 59db32914a..90a0435fe8 100644 --- a/core/src/test/java/brooklyn/event/feed/function/FunctionFeedTest.java +++ b/core/src/test/java/brooklyn/event/feed/function/FunctionFeedTest.java @@ -48,6 +48,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicates; +import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Callables; @@ -191,6 +192,30 @@ public void run() { }}); } + @Test + @SuppressWarnings("unused") + public void testFunctionPollConfigBuilding() throws Exception { + FunctionPollConfig typeFromCallable = FunctionPollConfig.forSensor(SENSOR_INT) + .period(1) + .callable(Callables.returning(1)) + .onSuccess(Functions.constant(-1)); + + FunctionPollConfig typeFromSupplier = FunctionPollConfig.forSensor(SENSOR_INT) + .period(1) + .supplier(Suppliers.ofInstance(1)) + .onSuccess(Functions.constant(-1)); + + FunctionPollConfig usingConstructor = new FunctionPollConfig(SENSOR_INT) + .period(1) + .supplier(Suppliers.ofInstance(1)) + .onSuccess(Functions.constant(-1)); + + FunctionPollConfig usingConstructorWithFailureOrException = new FunctionPollConfig(SENSOR_INT) + .period(1) + .supplier(Suppliers.ofInstance(1)) + .onFailureOrException(Functions.constant(null)); + } + private static class IncrementingCallable implements Callable { private final AtomicInteger next = new AtomicInteger(0); From 05df686fe8e1f9b9ea768fd9502337e810c1f906 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 5 Aug 2014 12:04:50 +0100 Subject: [PATCH 3/8] Test rebinding SshFeed --- .../entity/rebind/RebindFeedTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java index a0b5f04a9b..744f9488a9 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java @@ -38,11 +38,18 @@ import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.event.feed.ssh.SshFeed; +import brooklyn.event.feed.ssh.SshPollConfig; +import brooklyn.event.feed.ssh.SshValueFunctions; +import brooklyn.location.Location; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.location.basic.SshMachineLocation; import brooklyn.test.EntityTestUtils; import brooklyn.test.entity.TestEntity; import brooklyn.test.entity.TestEntityImpl; import brooklyn.util.http.BetterMockWebServer; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Callables; import com.google.mockwebserver.MockResponse; @@ -112,6 +119,30 @@ public void testFunctionFeedRegisteredInInitIsPersisted() throws Exception { EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_INT, (Integer)1); } + @Test(groups="Integration") + public void testSshFeedRegisteredInStartIsPersisted() throws Exception { + LocalhostMachineProvisioningLocation origLoc = origApp.newLocalhostProvisioningLocation(); + SshMachineLocation origMachine = origLoc.obtain(); + + TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityWithSshFeedImpl.class) + .location(origMachine)); + + origApp.start(ImmutableList.of()); + + EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_INT, (Integer)0); + assertEquals(origEntity.getFeedSupport().getFeeds().size(), 1); + + newApp = rebind(false); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + + Collection newFeeds = newEntity.getFeedSupport().getFeeds(); + assertEquals(newFeeds.size(), 1); + + // Expect the feed to still be polling + newEntity.setAttribute(SENSOR_INT, null); + EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_INT, (Integer)0); + } + public static class MyEntityWithHttpFeedImpl extends TestEntityImpl { public static final ConfigKey BASE_URL = ConfigKeys.newConfigKey(URL.class, "rebindFeedTest.baseUrl"); @@ -143,4 +174,18 @@ public void init() { .build()); } } + + public static class MyEntityWithSshFeedImpl extends TestEntityImpl { + @Override + public void start(Collection locs) { + // TODO Auto-generated method stub + super.start(locs); + addFeed(SshFeed.builder() + .entity(this) + .poll(new SshPollConfig(SENSOR_INT) + .command("true") + .onSuccess(SshValueFunctions.exitStatus())) + .build()); + } + } } From 1953546c8fcfaf89eabfcce6cf143e1dd966a95b Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Wed, 6 Aug 2014 13:56:47 +0100 Subject: [PATCH 4/8] Persist JmxFeed: testing and transient fields --- .../brooklyn/event/feed/jmx/JmxHelper.java | 10 +- .../event/feed/jmx/RebindJmxFeedTest.java | 146 ++++++++++++++++++ 2 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 software/base/src/test/java/brooklyn/event/feed/jmx/RebindJmxFeedTest.java diff --git a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxHelper.java b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxHelper.java index b9353c3455..4a8445a612 100644 --- a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxHelper.java +++ b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxHelper.java @@ -153,11 +153,11 @@ public static String toJmxmpUrl(String host, Integer jmxmpPort) { final String user; final String password; - private volatile JMXConnector connector; - private volatile MBeanServerConnection connection; - private boolean triedConnecting; - private boolean failedReconnecting; - private long failedReconnectingTime; + private volatile transient JMXConnector connector; + private volatile transient MBeanServerConnection connection; + private transient boolean triedConnecting; + private transient boolean failedReconnecting; + private transient long failedReconnectingTime; private int minTimeBetweenReconnectAttempts = 1000; private final AtomicBoolean terminated = new AtomicBoolean(); diff --git a/software/base/src/test/java/brooklyn/event/feed/jmx/RebindJmxFeedTest.java b/software/base/src/test/java/brooklyn/event/feed/jmx/RebindJmxFeedTest.java new file mode 100644 index 0000000000..3c48c8eafe --- /dev/null +++ b/software/base/src/test/java/brooklyn/event/feed/jmx/RebindJmxFeedTest.java @@ -0,0 +1,146 @@ +/* + * 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.event.feed.jmx; + +import static org.testng.Assert.assertEquals; + +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Feed; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.java.UsesJmx; +import brooklyn.entity.java.UsesJmx.JmxAgentModes; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindTestFixtureWithApp; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.Sensors; +import brooklyn.event.feed.ConfigToAttributes; +import brooklyn.location.Location; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.location.basic.PortRanges; +import brooklyn.test.EntityTestUtils; +import brooklyn.test.GeneralisedDynamicMBean; +import brooklyn.test.JmxService; +import brooklyn.test.entity.TestEntity; +import brooklyn.test.entity.TestEntityImpl; +import brooklyn.util.collections.MutableMap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public class RebindJmxFeedTest extends RebindTestFixtureWithApp { + + private static final Logger log = LoggerFactory.getLogger(RebindJmxFeedTest.class); + + private static final String LOCALHOST_NAME = "localhost"; + + static final AttributeSensor SENSOR_STRING = Sensors.newStringSensor("aString", ""); + static final AttributeSensor SENSOR_INT = Sensors.newIntegerSensor( "aLong", ""); + + static final String JMX_ATTRIBUTE_NAME = "myattr"; + static final String OBJECT_NAME = "Brooklyn:type=MyTestMBean,name=myname"; + + private JmxService jmxService; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + + // Create an entity and configure it with the above JMX service + //jmxService = newJmxServiceRetrying(LOCALHOST_NAME, 5); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + if (jmxService != null) jmxService.shutdown(); + super.tearDown(); + } + + @Test + public void testJmxFeedIsPersisted() throws Exception { + runJmxFeedIsPersisted(false); + } + + @Test + public void testJmxFeedIsPersistedWithPreCreatedJmxHelper() throws Exception { + runJmxFeedIsPersisted(true); + } + + protected void runJmxFeedIsPersisted(boolean preCreateJmxHelper) throws Exception { + TestEntity origEntity = origApp.createAndManageChild(EntitySpec.create(TestEntity.class).impl(MyEntityWithJmxFeedImpl.class) + .configure(MyEntityWithJmxFeedImpl.PRE_CREATE_JMX_HELPER, preCreateJmxHelper)); + origApp.start(ImmutableList.of()); + + jmxService = new JmxService(origEntity); + GeneralisedDynamicMBean mbean = jmxService.registerMBean(MutableMap.of(JMX_ATTRIBUTE_NAME, "myval"), OBJECT_NAME); + + EntityTestUtils.assertAttributeEqualsEventually(origEntity, SENSOR_STRING, "myval"); + assertEquals(origEntity.getFeedSupport().getFeeds().size(), 1); + + newApp = rebind(false); + TestEntity newEntity = (TestEntity) Iterables.getOnlyElement(newApp.getChildren()); + + Collection newFeeds = newEntity.getFeedSupport().getFeeds(); + assertEquals(newFeeds.size(), 1); + + // Expect the feed to still be polling + newEntity.setAttribute(SENSOR_STRING, null); + EntityTestUtils.assertAttributeEqualsEventually(newEntity, SENSOR_STRING, "myval"); + } + + public static class MyEntityWithJmxFeedImpl extends TestEntityImpl { + public static final ConfigKey PRE_CREATE_JMX_HELPER = ConfigKeys.newBooleanConfigKey("test.rebindjmx.preCreateJmxHelper", "", false); + + @Override + public void start(Collection locs) { + // TODO Auto-generated method stub + super.start(locs); + + setAttribute(Attributes.HOSTNAME, "localhost"); + setAttribute(UsesJmx.JMX_PORT, + LocalhostMachineProvisioningLocation.obtainPort(PortRanges.fromString("40123+"))); + // only supports no-agent, at the moment + setConfig(UsesJmx.JMX_AGENT_MODE, JmxAgentModes.NONE); + setAttribute(UsesJmx.RMI_REGISTRY_PORT, -1); // -1 means to use the JMX_PORT only + ConfigToAttributes.apply(this, UsesJmx.JMX_CONTEXT); + + JmxFeed.Builder feedBuilder = JmxFeed.builder() + .entity(this) + .pollAttribute(new JmxAttributePollConfig(SENSOR_STRING) + .objectName(OBJECT_NAME) + .period(50) + .attributeName(JMX_ATTRIBUTE_NAME)); + if (getConfig(PRE_CREATE_JMX_HELPER)) { + JmxHelper jmxHelper = new JmxHelper(this); + feedBuilder.helper(jmxHelper); + } + addFeed(feedBuilder.build()); + } + } +} From 7ba8c805daa83eb83867cddb446707d98da8ef4b Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Wed, 6 Aug 2014 14:29:03 +0100 Subject: [PATCH 5/8] Experimenting with Feed as EntityAdjunct --- api/src/main/java/brooklyn/entity/Feed.java | 4 +- .../brooklyn/event/feed/AbstractFeed.java | 47 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/brooklyn/entity/Feed.java b/api/src/main/java/brooklyn/entity/Feed.java index cf0430db54..055e880bcd 100644 --- a/api/src/main/java/brooklyn/entity/Feed.java +++ b/api/src/main/java/brooklyn/entity/Feed.java @@ -51,7 +51,9 @@ public interface Feed { /** suspends this feed (stops the poller, or indicates that the feed should start in a state where the poller is stopped) */ void suspend(); - + + boolean isSuspended(); + /** resumes this feed if it has been suspended and not stopped */ void resume(); diff --git a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java index 11b5b3b185..9f27f3243e 100644 --- a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java +++ b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java @@ -23,34 +23,43 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; import brooklyn.entity.Feed; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.rebind.BasicPolicyRebindSupport; +import brooklyn.entity.rebind.RebindSupport; +import brooklyn.mementos.PolicyMemento; +import brooklyn.policy.basic.AbstractEntityAdjunct; /** * Captures common fields and processes for sensor feeds. * These generally poll or subscribe to get sensor values for an entity. * They make it easy to poll over http, jmx, etc. */ -public abstract class AbstractFeed implements Feed { +public abstract class AbstractFeed extends AbstractEntityAdjunct implements Feed { private static final Logger log = LoggerFactory.getLogger(AbstractFeed.class); + + public static final ConfigKey ONLY_IF_SERVICE_UP = ConfigKeys.newBooleanConfigKey("feed.onlyIfServiceUp", "", false); - protected final EntityLocal entity; - protected final boolean onlyIfServiceUp; private final Object pollerStateMutex = new Object(); private transient volatile Poller poller; private transient volatile boolean activated; private transient volatile boolean suspended; + public AbstractFeed() { + } + public AbstractFeed(EntityLocal entity) { this(entity, false); } public AbstractFeed(EntityLocal entity, boolean onlyIfServiceUp) { this.entity = checkNotNull(entity, "entity"); - this.onlyIfServiceUp = onlyIfServiceUp; + setConfig(ONLY_IF_SERVICE_UP, onlyIfServiceUp); } - + @Override public boolean isActivated() { return activated; @@ -85,7 +94,7 @@ public void start() { throw new IllegalStateException(String.format("Attempt to re-start feed %s of entity %s", this, entity)); } - poller = new Poller(entity, onlyIfServiceUp); + poller = new Poller(entity, getConfig(ONLY_IF_SERVICE_UP)); activated = true; preStart(); synchronized (pollerStateMutex) { @@ -116,6 +125,11 @@ public void resume() { } } + @Override + public void destroy() { + stop(); + } + @Override public void stop() { if (!activated) { @@ -132,6 +146,27 @@ public void stop() { } } postStop(); + super.destroy(); + } + + @Override + public boolean isSuspended() { + return suspended; + } + + @Override + public boolean isRunning() { + return !isSuspended() && !isDestroyed(); + } + + @Override + public RebindSupport getRebindSupport() { + return new BasicFeedRebindSupport(this); + } + + @Override + protected void onChanged() { + // TODO Auto-generated method stub } /** From 49c797737433d09bb028d16a2d74cb538327e815 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Wed, 6 Aug 2014 14:29:11 +0100 Subject: [PATCH 6/8] Tidy ChefAttributeFeed --- .../main/java/brooklyn/entity/chef/ChefAttributeFeed.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java index de2edb4639..f842db2374 100644 --- a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java +++ b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java @@ -163,7 +163,6 @@ protected void finalize() { } } - private final EntityLocal entity; private final String nodeName; private final long period; private final TimeUnit periodUnits; @@ -171,8 +170,7 @@ protected void finalize() { private final KnifeTaskFactory knifeTaskFactory; protected ChefAttributeFeed(Builder builder) { - super(checkNotNull(builder.entity, "builder.entity"), builder.onlyIfServiceUp); - entity = builder.entity; + super(checkNotNull(builder.entity, "entity"), builder.onlyIfServiceUp); nodeName = checkNotNull(builder.nodeName, "builder.nodeName"); period = builder.period; periodUnits = builder.periodUnits; @@ -366,5 +364,4 @@ public String getDescription() { return ""+chefAttributeSensors; } } - } From 159745bf2c3c4bc945365dd6eea6fcea6eae9826 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Wed, 3 Sep 2014 09:43:48 +0100 Subject: [PATCH 7/8] Convert Feed to extend EntityAdjunct --- api/src/main/java/brooklyn/entity/Entity.java | 5 + api/src/main/java/brooklyn/entity/Feed.java | 15 ++- .../entity/rebind/BrooklynObjectType.java | 1 + .../brooklyn/entity/rebind/RebindContext.java | 3 + .../entity/rebind/RebindExceptionHandler.java | 3 + .../brooklyn/mementos/BrooklynMemento.java | 6 + .../mementos/BrooklynMementoManifest.java | 2 + .../mementos/BrooklynMementoPersister.java | 2 + .../java/brooklyn/mementos/EntityMemento.java | 4 +- .../java/brooklyn/mementos/FeedMemento.java | 33 ++++++ .../brooklyn/entity/basic/AbstractEntity.java | 21 +++- .../entity/basic/EntityFunctions.java | 20 ++++ .../entity/basic/EntitySuppliers.java | 47 ++++++++ .../proxying/InternalPolicyFactory.java | 8 ++ .../rebind/BasicEntityRebindSupport.java | 17 ++- .../entity/rebind/BasicFeedRebindSupport.java | 48 ++++++++ .../rebind/PeriodicDeltaChangeListener.java | 18 ++- .../entity/rebind/PersisterDeltaImpl.java | 17 +++ .../entity/rebind/RebindContextImpl.java | 19 +++ .../rebind/RebindExceptionHandlerImpl.java | 22 ++++ .../entity/rebind/RebindManagerImpl.java | 70 ++++++++++- .../rebind/dto/BasicEnricherMemento.java | 2 +- .../entity/rebind/dto/BasicEntityMemento.java | 9 +- .../entity/rebind/dto/BasicFeedMemento.java | 89 ++++++++++++++ .../entity/rebind/dto/BasicPolicyMemento.java | 2 +- .../rebind/dto/BrooklynMementoImpl.java | 29 ++++- .../dto/BrooklynMementoManifestImpl.java | 14 +++ .../entity/rebind/dto/MementosGenerators.java | 35 +++++- .../rebind/dto/MutableBrooklynMemento.java | 34 ++++++ ...BrooklynMementoPersisterToObjectStore.java | 48 ++++++-- .../brooklyn/event/feed/AbstractFeed.java | 19 ++- .../event/feed/function/FunctionFeed.java | 21 +++- .../brooklyn/event/feed/http/HttpFeed.java | 21 +++- .../brooklyn/event/feed/shell/ShellFeed.java | 26 ++++- .../java/brooklyn/event/feed/ssh/SshFeed.java | 79 ++++++++----- .../WindowsPerformanceCounterFeed.java | 96 +++++++++------ .../WindowsPerformanceCounterPollConfig.java | 54 +++++++++ .../internal/BrooklynFeatureEnablement.java | 3 + .../internal/EntityChangeListener.java | 7 ++ .../internal/EntityManagementSupport.java | 11 ++ .../entity/basic/EntityFunctionsTest.java | 12 ++ .../entity/basic/EntitySuppliersTest.java | 70 +++++++++++ .../entity/rebind/RebindFeedTest.java | 1 - ...WindowsPerformanceCounterFeedLiveTest.java | 32 +---- .../WindowsPerformanceCounterFeedTest.java | 4 +- .../entity/monitoring/zabbix/ZabbixFeed.java | 77 +++++++++---- .../entity/chef/ChefAttributeFeed.java | 96 ++++++++++----- .../entity/chef/ChefAttributePollConfig.java | 54 +++++++++ .../java/brooklyn/event/feed/jmx/JmxFeed.java | 109 +++++++++++++----- 49 files changed, 1216 insertions(+), 219 deletions(-) create mode 100644 api/src/main/java/brooklyn/mementos/FeedMemento.java create mode 100644 core/src/main/java/brooklyn/entity/basic/EntitySuppliers.java create mode 100644 core/src/main/java/brooklyn/entity/rebind/BasicFeedRebindSupport.java create mode 100644 core/src/main/java/brooklyn/entity/rebind/dto/BasicFeedMemento.java create mode 100644 core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterPollConfig.java create mode 100644 core/src/test/java/brooklyn/entity/basic/EntitySuppliersTest.java create mode 100644 software/base/src/main/java/brooklyn/entity/chef/ChefAttributePollConfig.java diff --git a/api/src/main/java/brooklyn/entity/Entity.java b/api/src/main/java/brooklyn/entity/Entity.java index 9a0939229b..f0a2501f73 100644 --- a/api/src/main/java/brooklyn/entity/Entity.java +++ b/api/src/main/java/brooklyn/entity/Entity.java @@ -245,6 +245,11 @@ public interface Entity extends BrooklynObject { */ boolean removeEnricher(Enricher enricher); + /** + * Adds the given feed to this entity. Also calls feed.setEntity if available. + */ + T addFeed(T feed); + /** * @since 0.7 * @deprecated since 0.7; see {@link #getTagSupport()} diff --git a/api/src/main/java/brooklyn/entity/Feed.java b/api/src/main/java/brooklyn/entity/Feed.java index 055e880bcd..d24be0d0fc 100644 --- a/api/src/main/java/brooklyn/entity/Feed.java +++ b/api/src/main/java/brooklyn/entity/Feed.java @@ -18,6 +18,13 @@ */ package brooklyn.entity; +import brooklyn.basic.BrooklynObject; +import brooklyn.entity.rebind.RebindSupport; +import brooklyn.entity.rebind.Rebindable; +import brooklyn.entity.trait.Configurable; +import brooklyn.mementos.FeedMemento; +import brooklyn.policy.EntityAdjunct; + import com.google.common.annotations.Beta; /** @@ -34,7 +41,7 @@ * */ @Beta -public interface Feed { +public interface Feed extends EntityAdjunct, Rebindable { /** * True if everything has been _started_ (or it is starting) but not stopped, @@ -58,4 +65,10 @@ public interface Feed { void resume(); void stop(); + + /** + * This method will likely move out of this interface, into somewhere internal; users should not call this directly. + */ + @Override + RebindSupport getRebindSupport(); } diff --git a/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java b/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java index a027867879..4530e661b1 100644 --- a/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java +++ b/api/src/main/java/brooklyn/entity/rebind/BrooklynObjectType.java @@ -26,5 +26,6 @@ public enum BrooklynObjectType { LOCATION, POLICY, ENRICHER, + FEED, UNKNOWN; } \ No newline at end of file diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java index 42d5512fa6..7ac0aa265f 100644 --- a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java +++ b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java @@ -19,6 +19,7 @@ package brooklyn.entity.rebind; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.location.Location; import brooklyn.policy.Enricher; import brooklyn.policy.Policy; @@ -42,6 +43,8 @@ public interface RebindContext { Enricher getEnricher(String id); + Feed getFeed(String id); + Class loadClass(String typeName) throws ClassNotFoundException; RebindExceptionHandler getExceptionHandler(); diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java index 1c99044bfa..d6518ac8bd 100644 --- a/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java +++ b/api/src/main/java/brooklyn/entity/rebind/RebindExceptionHandler.java @@ -20,6 +20,7 @@ import brooklyn.basic.BrooklynObject; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.EntityLocal; import brooklyn.location.Location; import brooklyn.policy.Enricher; @@ -67,6 +68,8 @@ public interface RebindExceptionHandler { void onAddEnricherFailed(EntityLocal entity, Enricher enricher, Exception e); + void onAddFeedFailed(EntityLocal entity, Feed feed, Exception e); + void onManageFailed(BrooklynObjectType type, BrooklynObject instance, Exception e); void onDone(); diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMemento.java b/api/src/main/java/brooklyn/mementos/BrooklynMemento.java index 6340300add..fa63d46233 100644 --- a/api/src/main/java/brooklyn/mementos/BrooklynMemento.java +++ b/api/src/main/java/brooklyn/mementos/BrooklynMemento.java @@ -45,6 +45,8 @@ public interface BrooklynMemento extends Serializable { public EnricherMemento getEnricherMemento(String id); + public FeedMemento getFeedMemento(String id); + public Collection getApplicationIds(); public Collection getTopLevelLocationIds(); @@ -57,6 +59,8 @@ public interface BrooklynMemento extends Serializable { public Collection getEnricherIds(); + public Collection getFeedIds(); + public Map getEntityMementos(); public Map getLocationMementos(); @@ -64,4 +68,6 @@ public interface BrooklynMemento extends Serializable { public Map getPolicyMementos(); public Map getEnricherMementos(); + + public Map getFeedMementos(); } diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoManifest.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoManifest.java index 9fbaff3b07..1c4f0b52d9 100644 --- a/api/src/main/java/brooklyn/mementos/BrooklynMementoManifest.java +++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoManifest.java @@ -36,6 +36,8 @@ public interface BrooklynMementoManifest extends Serializable { public Map getEnricherIdToType(); + public Map getFeedIdToType(); + public boolean isEmpty(); } diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java index 4b4adcde24..46afbb5437 100644 --- a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java +++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java @@ -78,10 +78,12 @@ public interface Delta { Collection entities(); Collection policies(); Collection enrichers(); + Collection feeds(); Collection removedLocationIds(); Collection removedEntityIds(); Collection removedPolicyIds(); Collection removedEnricherIds(); + Collection removedFeedIds(); } } diff --git a/api/src/main/java/brooklyn/mementos/EntityMemento.java b/api/src/main/java/brooklyn/mementos/EntityMemento.java index fbe96ba663..20ccd1ec2d 100644 --- a/api/src/main/java/brooklyn/mementos/EntityMemento.java +++ b/api/src/main/java/brooklyn/mementos/EntityMemento.java @@ -75,7 +75,7 @@ public interface EntityMemento extends Memento, TreeNode { public Collection getEnrichers(); /** - * The sensor feeds attached to this entity. + * The ids of the sensor feeds attached to this entity. */ - public Collection getFeeds(); + public Collection getFeeds(); } diff --git a/api/src/main/java/brooklyn/mementos/FeedMemento.java b/api/src/main/java/brooklyn/mementos/FeedMemento.java new file mode 100644 index 0000000000..42db6a4a42 --- /dev/null +++ b/api/src/main/java/brooklyn/mementos/FeedMemento.java @@ -0,0 +1,33 @@ +/* + * 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.mementos; + +import java.util.Map; + +import brooklyn.entity.rebind.RebindSupport; + +/** + * Represents the state of a feed, so that it can be reconstructed (e.g. after restarting brooklyn). + * + * @see RebindSupport + */ +public interface FeedMemento extends Memento { + + Map getConfig(); +} diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java index 031ceb4338..61d0ecd22b 100644 --- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java +++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java @@ -51,6 +51,7 @@ import brooklyn.event.basic.AttributeMap; import brooklyn.event.basic.AttributeSensorAndConfigKey; import brooklyn.event.basic.BasicNotificationSensor; +import brooklyn.event.feed.AbstractFeed; import brooklyn.event.feed.ConfigToAttributes; import brooklyn.internal.storage.BrooklynStorage; import brooklyn.internal.storage.Reference; @@ -1220,7 +1221,7 @@ public boolean removeAllEnrichers() { /** * Convenience, which calls {@link EntityInternal#getFeedSupport()} and {@link FeedSupport#addFeed(Feed)}. */ - protected T addFeed(T feed) { + public T addFeed(T feed) { return getFeedSupport().addFeed(feed); } @@ -1233,9 +1234,19 @@ public Collection getFeeds() { @Override public T addFeed(T feed) { - feeds.add(feed); + Feed old = findApparentlyEqualAndWarnIfNotSameUniqueTag(feeds, feed); + if (old != null) { + LOG.debug("Removing "+old+" when adding "+feed+" to "+this); + removeFeed(old); + } - getManagementSupport().getEntityChangeListener().onChanged(); + feeds.add(feed); + ((AbstractFeed)feed).setEntity(AbstractEntity.this); + + getManagementContext().getRebindManager().getChangeListener().onManaged(feed); + getManagementSupport().getEntityChangeListener().onFeedAdded(feed); + // TODO Could add equivalent of AbstractEntity.POLICY_ADDED for enrichers; no use-case for that yet + return feed; } @@ -1245,10 +1256,10 @@ public boolean removeFeed(Feed feed) { boolean changed = feeds.remove(feed); if (changed) { - getManagementSupport().getEntityChangeListener().onChanged(); + getManagementContext().getRebindManager().getChangeListener().onUnmanaged(feed); + getManagementSupport().getEntityChangeListener().onFeedRemoved(feed); } return changed; - } @Override diff --git a/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java b/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java index 7ad41f62ac..4b8d8cd4f1 100644 --- a/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java +++ b/core/src/main/java/brooklyn/entity/basic/EntityFunctions.java @@ -28,13 +28,16 @@ import brooklyn.entity.Entity; import brooklyn.entity.trait.Identifiable; import brooklyn.event.AttributeSensor; +import brooklyn.location.Location; import brooklyn.management.ManagementContext; import brooklyn.util.flags.TypeCoercions; import brooklyn.util.guava.Functionals; import com.google.common.base.Function; +import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; public class EntityFunctions { @@ -128,4 +131,21 @@ public Collection get() { } return new AppsSupplier(); } + + public static Function locationMatching(Predicate filter) { + return new LocationMatching(filter); + } + + private static class LocationMatching implements Function { + private Predicate filter; + + private LocationMatching() { /* for xstream */ + } + public LocationMatching(Predicate filter) { + this.filter = filter; + } + @Override public Location apply(Entity input) { + return Iterables.find(input.getLocations(), filter); + } + } } diff --git a/core/src/main/java/brooklyn/entity/basic/EntitySuppliers.java b/core/src/main/java/brooklyn/entity/basic/EntitySuppliers.java new file mode 100644 index 0000000000..4ec779bf3d --- /dev/null +++ b/core/src/main/java/brooklyn/entity/basic/EntitySuppliers.java @@ -0,0 +1,47 @@ +/* + * 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.entity.basic; + +import brooklyn.entity.Entity; +import brooklyn.location.basic.Machines; +import brooklyn.location.basic.SshMachineLocation; + +import com.google.common.base.Supplier; + +public class EntitySuppliers { + + public static Supplier uniqueSshMachineLocation(Entity entity) { + return new UniqueSshMchineLocation(entity); + } + + private static class UniqueSshMchineLocation implements Supplier { + private Entity entity; + + private UniqueSshMchineLocation() { /* for xstream */ + } + + private UniqueSshMchineLocation(Entity entity) { + this.entity = entity; + } + + @Override public SshMachineLocation get() { + return Machines.findUniqueSshMachineLocation(entity.getLocations()).get(); + } + } +} diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java index 9a84c3b096..a02e8740c1 100644 --- a/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java +++ b/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java @@ -22,6 +22,7 @@ import brooklyn.config.ConfigKey; import brooklyn.enricher.basic.AbstractEnricher; +import brooklyn.entity.Feed; import brooklyn.management.ManagementContext; import brooklyn.management.internal.ManagementContextInternal; import brooklyn.policy.Enricher; @@ -178,4 +179,11 @@ public T constructPolicy(Class clazz) { public T constructEnricher(Class clazz) { return super.constructNewStyle(clazz); } + + /** + * Constructs a new-style feed (fails if no no-arg constructor). + */ + public T constructFeed(Class clazz) { + return super.constructNewStyle(clazz); + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java index 36f5f8aed9..624d3dd50f 100644 --- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java +++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java @@ -30,13 +30,13 @@ import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.Effector; import brooklyn.entity.Entity; -import brooklyn.entity.Feed; import brooklyn.entity.Group; import brooklyn.entity.basic.AbstractEntity; import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.rebind.dto.MementosGenerators; import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.AbstractFeed; import brooklyn.location.Location; import brooklyn.mementos.EntityMemento; import brooklyn.policy.basic.AbstractPolicy; @@ -148,11 +148,22 @@ public void addEnrichers(RebindContext rebindContext, EntityMemento memento) { @Override public void addFeeds(RebindContext rebindContext, EntityMemento memento) { - for (Feed feed : memento.getFeeds()) { + for (String feedId : memento.getFeeds()) { + AbstractFeed feed = (AbstractFeed) rebindContext.getFeed(feedId); if (feed != null) { try { - feed.start(); ((EntityInternal)entity).getFeedSupport().addFeed(feed); + } catch (Exception e) { + rebindContext.getExceptionHandler().onAddFeedFailed(entity, feed, e); + } + } else { + LOG.warn("Feed not found; discarding feed {} of entity {}({})", + new Object[] {feedId, memento.getType(), memento.getId()}); + } + + if (feed != null) { + try { + feed.start(); } catch (Exception e) { rebindContext.getExceptionHandler().onRebindFailed(BrooklynObjectType.ENTITY, entity, e); } diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicFeedRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicFeedRebindSupport.java new file mode 100644 index 0000000000..35088f68ad --- /dev/null +++ b/core/src/main/java/brooklyn/entity/rebind/BasicFeedRebindSupport.java @@ -0,0 +1,48 @@ +/* + * 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.entity.rebind; + +import brooklyn.event.feed.AbstractFeed; +import brooklyn.mementos.FeedMemento; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.flags.FlagUtils; + +public class BasicFeedRebindSupport extends AbstractBrooklynObjectRebindSupport { + + private final AbstractFeed feed; + + public BasicFeedRebindSupport(AbstractFeed feed) { + super(feed); + this.feed = feed; + } + + @Override + protected void addConfig(RebindContext rebindContext, FeedMemento memento) { + // TODO entity does config-lookup differently; the memento contains the config keys. + // BasicEntityMemento.postDeserialize uses the injectTypeClass to call EntityTypes.getDefinedConfigKeys(clazz) + ConfigBag configBag = ConfigBag.newInstance(memento.getConfig()); + FlagUtils.setFieldsFromFlags(feed, configBag); + FlagUtils.setAllConfigKeys(feed, configBag, false); + } + + @Override + protected void addCustoms(RebindContext rebindContext, FeedMemento memento) { + // no-op + } +} diff --git a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java index 7613c2a510..6831704d32 100644 --- a/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java +++ b/core/src/main/java/brooklyn/entity/rebind/PeriodicDeltaChangeListener.java @@ -30,6 +30,7 @@ import brooklyn.basic.BrooklynObject; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.EntityInternal; import brooklyn.internal.BrooklynFeatureEnablement; import brooklyn.location.Location; @@ -71,14 +72,18 @@ private static class DeltaCollector { Set entities = Sets.newLinkedHashSet(); Set policies = Sets.newLinkedHashSet(); Set enrichers = Sets.newLinkedHashSet(); + Set feeds = Sets.newLinkedHashSet(); Set removedLocationIds = Sets.newLinkedHashSet(); Set removedEntityIds = Sets.newLinkedHashSet(); Set removedPolicyIds = Sets.newLinkedHashSet(); Set removedEnricherIds = Sets.newLinkedHashSet(); + Set removedFeedIds = Sets.newLinkedHashSet(); public boolean isEmpty() { - return locations.isEmpty() && entities.isEmpty() && policies.isEmpty() && enrichers.isEmpty() && - removedEntityIds.isEmpty() && removedLocationIds.isEmpty() && removedPolicyIds.isEmpty() && removedEnricherIds.isEmpty(); + return locations.isEmpty() && entities.isEmpty() && policies.isEmpty() && + enrichers.isEmpty() && feeds.isEmpty() && + removedEntityIds.isEmpty() && removedLocationIds.isEmpty() && removedPolicyIds.isEmpty() && + removedEnricherIds.isEmpty() && removedFeedIds.isEmpty(); } } @@ -250,6 +255,13 @@ public void persistNow() { exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.ENRICHER, enricher, e); } } + for (Feed feed : prevDeltaCollector.feeds) { + try { + persisterDelta.feeds.add(feed.getRebindSupport().getMemento()); + } catch (Exception e) { + exceptionHandler.onGenerateMementoFailed(BrooklynObjectType.FEED, feed, e); + } + } persisterDelta.removedLocationIds = prevDeltaCollector.removedLocationIds; persisterDelta.removedEntityIds = prevDeltaCollector.removedEntityIds; persisterDelta.removedPolicyIds = prevDeltaCollector.removedPolicyIds; @@ -346,6 +358,8 @@ public synchronized void onChanged(BrooklynObject instance) { deltaCollector.policies.add((Policy) instance); } else if (instance instanceof Enricher) { deltaCollector.enrichers.add((Enricher) instance); + } else if (instance instanceof Feed) { + deltaCollector.feeds.add((Feed) instance); } else { throw new IllegalStateException("Unexpected brooklyn type: "+instance); } diff --git a/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java b/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java index 94ba759e93..53ec9068f8 100644 --- a/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/PersisterDeltaImpl.java @@ -23,6 +23,7 @@ import brooklyn.mementos.BrooklynMementoPersister.Delta; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.PolicyMemento; @@ -53,6 +54,10 @@ public Builder enrichers(Collection vals) { delta.enrichers.addAll(vals); return this; } + public Builder feeds(Collection vals) { + delta.feeds.addAll(vals); + return this; + } public Builder removedLocationIds(Collection vals) { delta.removedLocationIds.addAll(vals); return this; @@ -78,10 +83,12 @@ public Delta build() { Collection entities = Sets.newLinkedHashSet(); Collection policies = Sets.newLinkedHashSet(); Collection enrichers = Sets.newLinkedHashSet(); + Collection feeds = Sets.newLinkedHashSet(); Collection removedLocationIds = Sets.newLinkedHashSet(); Collection removedEntityIds = Sets.newLinkedHashSet(); Collection removedPolicyIds = Sets.newLinkedHashSet(); Collection removedEnricherIds = Sets.newLinkedHashSet(); + Collection removedFeedIds = Sets.newLinkedHashSet(); @Override public Collection locations() { @@ -103,6 +110,11 @@ public Collection enrichers() { return enrichers; } + @Override + public Collection feeds() { + return feeds; + } + @Override public Collection removedLocationIds() { return removedLocationIds; @@ -122,4 +134,9 @@ public Collection removedPolicyIds() { public Collection removedEnricherIds() { return removedEnricherIds; } + + @Override + public Collection removedFeedIds() { + return removedFeedIds; + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java index d1546e5a3e..ab077b7c8b 100644 --- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java @@ -24,6 +24,7 @@ import java.util.Map; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.location.Location; import brooklyn.policy.Enricher; import brooklyn.policy.Policy; @@ -36,6 +37,7 @@ public class RebindContextImpl implements RebindContext { private final Map locations = Maps.newLinkedHashMap(); private final Map policies = Maps.newLinkedHashMap(); private final Map enrichers = Maps.newLinkedHashMap(); + private final Map feeds = Maps.newLinkedHashMap(); private final ClassLoader classLoader; private final RebindExceptionHandler exceptionHandler; @@ -60,6 +62,10 @@ public void registerEnricher(String id, Enricher enricher) { enrichers.put(id, enricher); } + public void registerFeed(String id, Feed feed) { + feeds.put(id, feed); + } + public void unregisterPolicy(Policy policy) { policies.remove(policy.getId()); } @@ -68,6 +74,10 @@ public void unregisterEnricher(Enricher enricher) { enrichers.remove(enricher.getId()); } + public void unregisterFeed(Feed feed) { + feeds.remove(feed.getId()); + } + @Override public Entity getEntity(String id) { return entities.get(id); @@ -88,6 +98,11 @@ public Enricher getEnricher(String id) { return enrichers.get(id); } + @Override + public Feed getFeed(String id) { + return feeds.get(id); + } + @Override public Class loadClass(String className) throws ClassNotFoundException { return classLoader.loadClass(className); @@ -113,4 +128,8 @@ protected Collection getPolicies() { protected Collection getEnrichers() { return enrichers.values(); } + + protected Collection getFeeds() { + return feeds.values(); + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java index a13aa80f3b..cdb8e22c14 100644 --- a/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/RebindExceptionHandlerImpl.java @@ -29,6 +29,7 @@ import brooklyn.basic.BrooklynObject; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.rebind.RebindManager.RebindFailureMode; import brooklyn.location.Location; @@ -102,6 +103,7 @@ public void onLoadMementoFailed(BrooklynObjectType type, String msg, Exception e String errmsg = "problem loading memento: "+msg; switch (type) { + case FEED: case POLICY: case ENRICHER: switch (loadPolicyFailureMode) { @@ -193,6 +195,7 @@ public void onRebindFailed(BrooklynObjectType type, BrooklynObject instance, Exc String errmsg = "problem rebinding "+type+" "+instance.getId()+" ("+instance+")"; switch (type) { + case FEED: case ENRICHER: case POLICY: switch (addPolicyFailureMode) { @@ -254,6 +257,25 @@ public void onAddEnricherFailed(EntityLocal entity, Enricher enricher, Exception } } + @Override + public void onAddFeedFailed(EntityLocal entity, Feed feed, Exception e) { + Exceptions.propagateIfFatal(e); + String errmsg = "problem adding feed "+feed.getId()+" ("+feed+") to entity "+entity.getId()+" ("+entity+")"; + + switch (addPolicyFailureMode) { + case FAIL_FAST: + throw new IllegalStateException("Rebind: aborting due to "+errmsg, e); + case FAIL_AT_END: + addPolicyFailures.add(new IllegalStateException(errmsg, e)); + break; + case CONTINUE: + LOG.warn(errmsg+"; continuing", e); + break; + default: + throw new IllegalStateException("Unexpected state '"+addPolicyFailureMode+"' for addPolicyFailureMode"); + } + } + @Override public void onManageFailed(BrooklynObjectType type, BrooklynObject instance, Exception e) { Exceptions.propagateIfFatal(e); diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java index 0056e8bed8..4e517a13cf 100644 --- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java @@ -35,6 +35,7 @@ import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.Application; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.AbstractApplication; import brooklyn.entity.basic.AbstractEntity; import brooklyn.entity.basic.ConfigKeys; @@ -44,6 +45,7 @@ import brooklyn.entity.proxying.InternalFactory; import brooklyn.entity.proxying.InternalLocationFactory; import brooklyn.entity.proxying.InternalPolicyFactory; +import brooklyn.event.feed.AbstractFeed; import brooklyn.internal.BrooklynFeatureEnablement; import brooklyn.location.Location; import brooklyn.location.basic.AbstractLocation; @@ -56,6 +58,7 @@ import brooklyn.mementos.BrooklynMementoPersister.LookupContext; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.PolicyMemento; import brooklyn.mementos.TreeNode; @@ -115,6 +118,7 @@ public class RebindManagerImpl implements RebindManager { private final boolean persistPoliciesEnabled; private final boolean persistEnrichersEnabled; + private final boolean persistFeedsEnabled; private RebindFailureMode danglingRefFailureMode; private RebindFailureMode rebindFailureMode; private RebindFailureMode addPolicyFailureMode; @@ -149,6 +153,7 @@ public RebindManagerImpl(ManagementContextInternal managementContext) { this.persistPoliciesEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_POLICY_PERSISTENCE_PROPERTY); this.persistEnrichersEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_ENRICHER_PERSISTENCE_PROPERTY); + this.persistFeedsEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_FEED_PERSISTENCE_PROPERTY); danglingRefFailureMode = managementContext.getConfig().getConfig(DANGLING_REFERENCE_FAILURE_MODE); rebindFailureMode = managementContext.getConfig().getConfig(REBIND_FAILURE_MODE); @@ -429,7 +434,23 @@ protected List rebindImpl(final ClassLoader classLoader, final Rebi LOG.debug("Not rebinding enrichers; feature disabled: {}", memento.getEnricherIds()); } - + // Instantiate feeds + if (persistFeedsEnabled) { + LOG.debug("RebindManager instantiating feeds: {}", memento.getFeedIds()); + for (FeedMemento feedMemento : memento.getFeedMementos().values()) { + if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating feed {}", feedMemento); + + try { + Feed feed = newFeed(feedMemento, reflections); + rebindContext.registerFeed(feedMemento.getId(), feed); + } catch (Exception e) { + exceptionHandler.onCreateFailed(BrooklynObjectType.FEED, feedMemento.getId(), feedMemento.getType(), e); + } + } + } else { + LOG.debug("Not rebinding feeds; feature disabled: {}", memento.getFeedIds()); + } + // // PHASE THREE // @@ -494,6 +515,28 @@ protected List rebindImpl(final ClassLoader classLoader, final Rebi } } + // Reconstruct feeds + if (persistFeedsEnabled) { + LOG.debug("RebindManager reconstructing feeds"); + for (FeedMemento feedMemento : memento.getFeedMementos().values()) { + Feed feed = rebindContext.getFeed(feedMemento.getId()); + if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing feed {}", feedMemento); + + if (feed == null) { + // usually because of creation-failure, when not using fail-fast + exceptionHandler.onNotFound(BrooklynObjectType.FEED, feedMemento.getId()); + } else { + try { + feed.getRebindSupport().reconstruct(rebindContext, feedMemento); + } catch (Exception e) { + exceptionHandler.onRebindFailed(BrooklynObjectType.FEED, feed, e); + rebindContext.unregisterFeed(feed); + } + } + + } + } + // Reconstruct entities LOG.debug("RebindManager reconstructing entities"); for (EntityMemento entityMemento : sortParentFirst(memento.getEntityMementos()).values()) { @@ -578,12 +621,13 @@ protected List rebindImpl(final ClassLoader classLoader, final Rebi exceptionHandler.onDone(); if (!isEmpty) { - LOG.info("Rebind complete: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}", new Object[]{ + LOG.info("Rebind complete: {} app{}, {} entit{}, {} location{}, {} polic{}, {} enricher{}, {} feed{}", new Object[] { apps.size(), Strings.s(apps), rebindContext.getEntities().size(), Strings.ies(rebindContext.getEntities()), rebindContext.getLocations().size(), Strings.s(rebindContext.getLocations()), rebindContext.getPolicies().size(), Strings.ies(rebindContext.getPolicies()), - rebindContext.getEnrichers().size(), Strings.s(rebindContext.getEnrichers()) }); + rebindContext.getEnrichers().size(), Strings.s(rebindContext.getEnrichers()), + rebindContext.getFeeds().size(), Strings.s(rebindContext.getFeeds()) }); } // Return the top-level applications @@ -761,7 +805,27 @@ private Enricher newEnricher(EnricherMemento memento, Reflections reflections) { return (Enricher) invokeConstructor(reflections, enricherClazz, new Object[] {flags}); } + } + /** + * Constructs a new enricher, passing to its constructor the enricher id and all of memento.getConfig(). + */ + private Feed newFeed(FeedMemento memento, Reflections reflections) { + String id = memento.getId(); + String feedType = checkNotNull(memento.getType(), "feed type of %s must not be null in memento", id); + Class feedClazz = (Class) reflections.loadClass(feedType); + + if (InternalFactory.isNewStyle(feedClazz)) { + InternalPolicyFactory policyFactory = managementContext.getPolicyFactory(); + Feed feed = policyFactory.constructFeed(feedClazz); + FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", id), feed); + ((AbstractFeed)feed).setManagementContext(managementContext); + + return feed; + + } else { + throw new IllegalStateException("rebind of feed without no-arg constructor unsupported: id="+id+"; type="+feedType); + } } private T invokeConstructor(Reflections reflections, Class clazz, Object[]... possibleArgs) { diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEnricherMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEnricherMemento.java index 074651ac8b..fcfaad5d31 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEnricherMemento.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEnricherMemento.java @@ -28,7 +28,7 @@ import com.google.common.collect.Maps; /** - * The persisted state of a location. + * The persisted state of an enricher. * * @author aled */ diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java index df72ee58d0..2f9c0ae9d0 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BasicEntityMemento.java @@ -69,9 +69,9 @@ public static class Builder extends AbstractTreeNodeMemento.Builder { protected List locations = Lists.newArrayList(); protected List policies = Lists.newArrayList(); protected List enrichers = Lists.newArrayList(); + protected List feeds = Lists.newArrayList(); protected List members = Lists.newArrayList(); protected List> effectors = Lists.newArrayList(); - protected List feeds = Lists.newArrayList(); public Builder from(EntityMemento other) { super.from((TreeNode)other); @@ -83,9 +83,9 @@ public Builder from(EntityMemento other) { locations.addAll(other.getLocations()); policies.addAll(other.getPolicies()); enrichers.addAll(other.getEnrichers()); + feeds.addAll(other.getFeeds()); members.addAll(other.getMembers()); effectors.addAll(other.getEffectors()); - feeds.addAll(other.getFeeds()); tags.addAll(other.getTags()); return this; } @@ -106,7 +106,7 @@ public EntityMemento build() { private Map attributes; private List policies; private List enrichers; - private List feeds; + private List feeds; // TODO can we move some of these to entity type, or remove/re-insert those which are final statics? private Map> configKeys; @@ -286,8 +286,7 @@ public List getLocations() { } @Override - public List getFeeds() { - if (configByKey == null) postDeserialize(); + public List getFeeds() { return fromPersistedList(feeds); } diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BasicFeedMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/BasicFeedMemento.java new file mode 100644 index 0000000000..12327884db --- /dev/null +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BasicFeedMemento.java @@ -0,0 +1,89 @@ +/* + * 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.entity.rebind.dto; + +import java.io.Serializable; +import java.util.Map; + +import brooklyn.entity.basic.Entities; +import brooklyn.mementos.FeedMemento; + +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.collect.Maps; + +/** + * The persisted state of a feed. + * + * @author aled + */ +public class BasicFeedMemento extends AbstractMemento implements FeedMemento, Serializable { + + private static final long serialVersionUID = -2887448240614023137L; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractMemento.Builder { + protected Map config = Maps.newLinkedHashMap(); + + public Builder from(FeedMemento other) { + super.from(other); + config.putAll(other.getConfig()); + return this; + } + public Builder config(Map vals) { + config.putAll(vals); return this; + } + public FeedMemento build() { + return new BasicFeedMemento(this); + } + } + + private Map config; + private Map fields; + + // Trusts the builder to not mess around with mutability after calling build() + protected BasicFeedMemento(Builder builder) { + super(builder); + config = toPersistedMap(builder.config); + } + + @Deprecated + @Override + protected void setCustomFields(Map fields) { + this.fields = toPersistedMap(fields); + } + + @Deprecated + @Override + public Map getCustomFields() { + return fromPersistedMap(fields); + } + + @Override + public Map getConfig() { + return fromPersistedMap(config); + } + + @Override + protected ToStringHelper newVerboseStringHelper() { + return super.newVerboseStringHelper().add("config", Entities.sanitize(getConfig())); + } +} diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BasicPolicyMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/BasicPolicyMemento.java index 51c4831469..5a1454f341 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BasicPolicyMemento.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BasicPolicyMemento.java @@ -28,7 +28,7 @@ import com.google.common.collect.Maps; /** - * The persisted state of a location. + * The persisted state of a policy. * * @author aled */ diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoImpl.java b/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoImpl.java index 732b7ce706..5ed0bc3b0c 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoImpl.java @@ -28,6 +28,7 @@ import brooklyn.mementos.BrooklynMemento; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.Memento; import brooklyn.mementos.PolicyMemento; @@ -52,6 +53,7 @@ public static class Builder { protected final Map locations = Maps.newConcurrentMap(); protected final Map policies = Maps.newConcurrentMap(); protected final Map enrichers = Maps.newConcurrentMap(); + protected final Map feeds = Maps.newConcurrentMap(); public Builder brooklynVersion(String val) { brooklynVersion = val; return this; @@ -74,6 +76,8 @@ public void memento(Memento memento) { policy((PolicyMemento)memento); } else if (memento instanceof EnricherMemento) { enricher((EnricherMemento)memento); + } else if (memento instanceof FeedMemento) { + feed((FeedMemento)memento); } else { throw new IllegalStateException("Unexpected memento type :"+memento); } @@ -90,6 +94,9 @@ public Builder policy(PolicyMemento val) { public Builder enricher(EnricherMemento val) { enrichers.put(val.getId(), val); return this; } + public Builder feed(FeedMemento val) { + feeds.put(val.getId(), val); return this; + } public Builder entity(EntityMemento val) { entities.put(val.getId(), val); if (val.isTopLevelApp()) { @@ -103,9 +110,12 @@ public Builder location(LocationMemento val) { public Builder policies(Map vals) { policies.putAll(vals); return this; } - public Builder enricheres(Map vals) { + public Builder enrichers(Map vals) { enrichers.putAll(vals); return this; } + public Builder feeds(Map vals) { + feeds.putAll(vals); return this; + } public BrooklynMemento build() { return new BrooklynMementoImpl(this); } @@ -119,6 +129,7 @@ public BrooklynMemento build() { private Map locations; private Map policies; private Map enrichers; + private Map feeds; private BrooklynMementoImpl(Builder builder) { brooklynVersion = builder.brooklynVersion; @@ -128,6 +139,7 @@ private BrooklynMementoImpl(Builder builder) { locations = builder.locations; policies = builder.policies; enrichers = builder.enrichers; + feeds = builder.feeds; } @Override @@ -150,6 +162,11 @@ public EnricherMemento getEnricherMemento(String id) { return enrichers.get(id); } + @Override + public FeedMemento getFeedMemento(String id) { + return feeds.get(id); + } + @Override public Collection getApplicationIds() { return ImmutableList.copyOf(applicationIds); @@ -175,6 +192,11 @@ public Collection getEnricherIds() { return Collections.unmodifiableSet(enrichers.keySet()); } + @Override + public Collection getFeedIds() { + return Collections.unmodifiableSet(feeds.keySet()); + } + @Override public Collection getTopLevelLocationIds() { return Collections.unmodifiableList(topLevelLocationIds); @@ -198,4 +220,9 @@ public Map getPolicyMementos() { public Map getEnricherMementos() { return Collections.unmodifiableMap(enrichers); } + + @Override + public Map getFeedMementos() { + return Collections.unmodifiableMap(feeds); + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoManifestImpl.java b/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoManifestImpl.java index ca765f34ad..8a67f504c7 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoManifestImpl.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/BrooklynMementoManifestImpl.java @@ -40,6 +40,7 @@ public static class Builder { protected final Map locationIdToType = Maps.newConcurrentMap(); protected final Map policyIdToType = Maps.newConcurrentMap(); protected final Map enricherIdToType = Maps.newConcurrentMap(); + protected final Map feedIdToType = Maps.newConcurrentMap(); public Builder brooklynVersion(String val) { brooklynVersion = val; return this; @@ -68,6 +69,12 @@ public Builder enricher(String id, String type) { public Builder enrichers(Map vals) { enricherIdToType.putAll(vals); return this; } + public Builder feed(String id, String type) { + feedIdToType.put(id, type); return this; + } + public Builder feed(Map vals) { + feedIdToType.putAll(vals); return this; + } public BrooklynMementoManifest build() { return new BrooklynMementoManifestImpl(this); } @@ -77,12 +84,14 @@ public BrooklynMementoManifest build() { private final Map locationIdToType; private final Map policyIdToType; private final Map enricherIdToType; + private final Map feedIdToType; private BrooklynMementoManifestImpl(Builder builder) { entityIdToType = builder.entityIdToType; locationIdToType = builder.locationIdToType; policyIdToType = builder.policyIdToType; enricherIdToType = builder.enricherIdToType; + feedIdToType = builder.feedIdToType; } @Override @@ -105,6 +114,11 @@ public Map getEnricherIdToType() { return Collections.unmodifiableMap(enricherIdToType); } + @Override + public Map getFeedIdToType() { + return Collections.unmodifiableMap(feedIdToType); + } + @Override public boolean isEmpty() { return entityIdToType.isEmpty() && locationIdToType.isEmpty() && policyIdToType.isEmpty() && enricherIdToType.isEmpty(); diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java index ebcea96b32..d6fb7331a3 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/MementosGenerators.java @@ -36,6 +36,7 @@ import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.rebind.TreeUtils; import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.AbstractFeed; import brooklyn.location.Location; import brooklyn.location.basic.AbstractLocation; import brooklyn.location.basic.LocationInternal; @@ -44,6 +45,7 @@ import brooklyn.mementos.BrooklynMemento; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.Memento; import brooklyn.mementos.PolicyMemento; @@ -73,6 +75,8 @@ public static Memento newMemento(BrooklynObject instance) { return newPolicyMemento((Policy)instance); } else if (instance instanceof Enricher) { return newEnricherMemento((Enricher)instance); + } else if (instance instanceof Feed) { + return newFeedMemento((Feed)instance); } else { throw new IllegalArgumentException("Unexpected brooklyn type: "+(instance == null ? "null" : instance.getClass())+" ("+instance+")"); } @@ -183,7 +187,7 @@ public static BasicEntityMemento.Builder newEntityMementoBuilder(Entity entityRa } for (Feed feed : entity.getFeedSupport().getFeeds()) { - builder.feeds.add(feed); + builder.feeds.add(feed.getId()); } Entity parentEntity = entity.getParent(); @@ -330,6 +334,27 @@ public static EnricherMemento newEnricherMemento(Enricher enricher) { return builder.build(); } + /** + * Given a feed, extracts its state for serialization. + */ + public static FeedMemento newFeedMemento(Feed feed) { + BasicFeedMemento.Builder builder = BasicFeedMemento.builder(); + populateBrooklynObjectMementoBuilder(feed, builder); + + // TODO persist config keys as well? Or only support those defined on policy class; + // current code will lose the ConfigKey type on rebind for anything not defined on class. + // Whereas entities support that. + // TODO Do we need the "nonPersistableFlagNames" that locations use? + Map, Object> config = ((AbstractFeed)feed).getConfigMap().getAllConfig(); + for (Map.Entry, Object> entry : config.entrySet()) { + ConfigKey key = checkNotNull(entry.getKey(), "config=%s", config); + Object value = configValueToPersistable(entry.getValue()); + builder.config.put(key.getName(), value); + } + + return builder.build(); + } + private static void populateBrooklynObjectMementoBuilder(BrooklynObject instance, AbstractMemento.Builder builder) { builder.id = instance.getId(); builder.displayName = instance.getDisplayName(); @@ -365,4 +390,12 @@ public EnricherMemento apply(Enricher input) { }; } + public static Function feedMementoFunction() { + return new Function() { + @Override + public FeedMemento apply(Feed input) { + return MementosGenerators.newFeedMemento(input); + } + }; + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/dto/MutableBrooklynMemento.java b/core/src/main/java/brooklyn/entity/rebind/dto/MutableBrooklynMemento.java index 6c338053d5..152ca980ab 100644 --- a/core/src/main/java/brooklyn/entity/rebind/dto/MutableBrooklynMemento.java +++ b/core/src/main/java/brooklyn/entity/rebind/dto/MutableBrooklynMemento.java @@ -30,6 +30,7 @@ import brooklyn.mementos.BrooklynMemento; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.PolicyMemento; @@ -56,6 +57,7 @@ public class MutableBrooklynMemento implements BrooklynMemento { private final Map locations = Maps.newLinkedHashMap(); private final Map policies = Maps.newLinkedHashMap(); private final Map enrichers = Maps.newLinkedHashMap(); + private final Map feeds = Maps.newLinkedHashMap(); public MutableBrooklynMemento() { } @@ -91,6 +93,10 @@ public void updateEnricherMemento(EnricherMemento memento) { updateEnricherMementos(ImmutableSet.of(memento)); } + public void updateFeedMemento(FeedMemento memento) { + updateFeedMementos(ImmutableSet.of(memento)); + } + public void updateEntityMementos(Collection mementos) { for (EntityMemento memento : mementos) { entities.put(memento.getId(), memento); @@ -123,6 +129,12 @@ public void updateEnricherMementos(Collection mementos) { } } + public void updateFeedMementos(Collection mementos) { + for (FeedMemento memento : mementos) { + feeds.put(memento.getId(), memento); + } + } + /** * Removes the entities with the given ids. */ @@ -153,6 +165,13 @@ public void removeEnrichers(Collection ids) { enrichers.keySet().removeAll(ids); } + /** + * Removes the feeds with the given ids. + */ + public void removeFeeds(Collection ids) { + feeds.keySet().removeAll(ids); + } + @Override public EntityMemento getEntityMemento(String id) { return entities.get(id); @@ -174,6 +193,11 @@ public EnricherMemento getEnricherMemento(String id) { return enrichers.get(id); } + @Override + public FeedMemento getFeedMemento(String id) { + return feeds.get(id); + } + @Override public Collection getApplicationIds() { return ImmutableList.copyOf(applicationIds); @@ -200,6 +224,11 @@ public Collection getEnricherIds() { return Collections.unmodifiableSet(enrichers.keySet()); } + @Override + public Collection getFeedIds() { + return Collections.unmodifiableSet(feeds.keySet()); + } + @Override public Collection getTopLevelLocationIds() { return Collections.unmodifiableCollection(topLevelLocationIds); @@ -224,4 +253,9 @@ public Map getPolicyMementos() { public Map getEnricherMementos() { return ImmutableMap.copyOf(enrichers); } + + @Override + public Map getFeedMementos() { + return ImmutableMap.copyOf(feeds); + } } diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java index 0a62332f35..98607e20bb 100644 --- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java +++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterToObjectStore.java @@ -52,6 +52,7 @@ import brooklyn.mementos.BrooklynMementoPersister; import brooklyn.mementos.EnricherMemento; import brooklyn.mementos.EntityMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.mementos.LocationMemento; import brooklyn.mementos.Memento; import brooklyn.mementos.PolicyMemento; @@ -73,7 +74,7 @@ * {@link PersistenceObjectStore} such as a file system or a jclouds object store */ public class BrooklynMementoPersisterToObjectStore implements BrooklynMementoPersister { - // TODO Crazy amount of duplication between handling entity, location, policy + enricher; + // TODO Crazy amount of duplication between handling entity, location, policy, enricher + feed; // Need to remove that duplication. // TODO Should stop() take a timeout, and shutdown the executor gracefully? @@ -118,6 +119,7 @@ public BrooklynMementoPersisterToObjectStore(PersistenceObjectStore objectStore, objectStore.createSubPath("locations"); objectStore.createSubPath("policies"); objectStore.createSubPath("enrichers"); + objectStore.createSubPath("feeds"); // FIXME does it belong here or to ManagementPlaneSyncRecordPersisterToObjectStore ? objectStore.createSubPath("plane"); @@ -173,11 +175,13 @@ public BrooklynMementoManifest loadMementoManifest(final RebindExceptionHandler List locationSubPathList; List policySubPathList; List enricherSubPathList; + List feedSubPathList; try { entitySubPathList = objectStore.listContentsWithSubPath("entities"); locationSubPathList = objectStore.listContentsWithSubPath("locations"); policySubPathList = objectStore.listContentsWithSubPath("policies"); enricherSubPathList = objectStore.listContentsWithSubPath("enrichers"); + feedSubPathList = objectStore.listContentsWithSubPath("feeds"); } catch (Exception e) { Exceptions.propagateIfFatal(e); exceptionHandler.onLoadMementoFailed(BrooklynObjectType.UNKNOWN, "Failed to list files", e); @@ -186,9 +190,9 @@ public BrooklynMementoManifest loadMementoManifest(final RebindExceptionHandler Stopwatch stopwatch = Stopwatch.createStarted(); - LOG.debug("Scanning persisted state: {} entities, {} locations, {} policies, {} enrichers, from {}", new Object[]{ + LOG.debug("Scanning persisted state: {} entities, {} locations, {} policies, {} enrichers, {} feeds from {}", new Object[]{ entitySubPathList.size(), locationSubPathList.size(), policySubPathList.size(), enricherSubPathList.size(), - objectStore.getSummaryName() }); + feedSubPathList.size(), objectStore.getSummaryName() }); final BrooklynMementoManifestImpl.Builder builder = BrooklynMementoManifestImpl.builder(); @@ -250,6 +254,20 @@ public void run() { } }})); } + for (final String subPath : feedSubPathList) { + futures.add(executor.submit(new Runnable() { + public void run() { + try { + String contents = read(subPath); + String id = (String) XmlUtil.xpath(contents, "/feed/id"); + String type = (String) XmlUtil.xpath(contents, "/feed/type"); + builder.feed(id, type); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + exceptionHandler.onLoadMementoFailed(BrooklynObjectType.FEED, "Memento "+subPath, e); + } + }})); + } try { // Wait for all, failing fast if any exceptions. @@ -283,10 +301,10 @@ public void run() { BrooklynMementoManifest result = builder.build(); if (LOG.isDebugEnabled()) { - LOG.debug("Loaded memento manifest; took {}; {} entities, {} locations, {} policies, {} enrichers, from {}", new Object[]{ + LOG.debug("Loaded memento manifest; took {}; {} entities, {} locations, {} policies, {} enrichers, {} feeds, from {}", new Object[]{ Time.makeTimeStringRounded(stopwatch.elapsed(TimeUnit.MILLISECONDS)), result.getEntityIdToType().size(), result.getLocationIdToType().size(), result.getPolicyIdToType().size(), result.getEnricherIdToType().size(), - objectStore.getSummaryName() }); + result.getFeedIdToType().size(), objectStore.getSummaryName() }); } if (result.getEntityIdToType().size() != entitySubPathList.size()) { @@ -307,20 +325,22 @@ public BrooklynMemento loadMemento(LookupContext lookupContext, final RebindExce List locationSubPathList; List policySubPathList; List enricherSubPathList; + List feedSubPathList; try { entitySubPathList = objectStore.listContentsWithSubPath("entities"); locationSubPathList = objectStore.listContentsWithSubPath("locations"); policySubPathList = objectStore.listContentsWithSubPath("policies"); enricherSubPathList = objectStore.listContentsWithSubPath("enrichers"); + feedSubPathList = objectStore.listContentsWithSubPath("feeds"); } catch (Exception e) { Exceptions.propagateIfFatal(e); exceptionHandler.onLoadMementoFailed(BrooklynObjectType.UNKNOWN, "Failed to list files", e); throw new IllegalStateException("Failed to list memento files in "+objectStore+": "+e, e); } - LOG.debug("Loading persisted state: {} entities, {} locations, {} policies, {} enrichers, from {}", new Object[]{ + LOG.debug("Loading persisted state: {} entities, {} locations, {} policies, {} enrichers, {} feeds, from {}", new Object[]{ entitySubPathList.size(), locationSubPathList.size(), policySubPathList.size(), enricherSubPathList.size(), - objectStore.getSummaryName() }); + feedSubPathList.size(), objectStore.getSummaryName() }); final BrooklynMementoImpl.Builder builder = BrooklynMementoImpl.builder(); serializer.setLookupContext(lookupContext); @@ -361,6 +381,9 @@ public void run() { for (final String subPath : enricherSubPathList) { futures.add(executor.submit(new MementoLoader(subPath, BrooklynObjectType.ENRICHER))); } + for (final String subPath : feedSubPathList) { + futures.add(executor.submit(new MementoLoader(subPath, BrooklynObjectType.FEED))); + } try { // Wait for all, failing fast if any exceptions. @@ -398,10 +421,10 @@ public void run() { BrooklynMemento result = builder.build(); if (LOG.isDebugEnabled()) { - LOG.debug("Loaded memento; took {}; {} entities, {} locations, {} policies, {} enrichers, from {}", new Object[]{ + LOG.debug("Loaded memento; took {}; {} entities, {} locations, {} policies, {} enrichers, {} feeds, from {}", new Object[]{ Time.makeTimeStringRounded(stopwatch.elapsed(TimeUnit.MILLISECONDS)), result.getEntityIds().size(), result.getLocationIds().size(), result.getPolicyIds().size(), result.getEnricherIds().size(), - objectStore.getSummaryName() }); + result.getFeedIds().size(), objectStore.getSummaryName() }); } return result; @@ -419,6 +442,7 @@ public void checkpoint(BrooklynMemento newMemento, PersistenceExceptionHandler e .locations(newMemento.getLocationMementos().values()) .policies(newMemento.getPolicyMementos().values()) .enrichers(newMemento.getEnricherMementos().values()) + .feeds(newMemento.getFeedMementos().values()) .build(); Stopwatch stopwatch = deltaImpl(delta, exceptionHandler); @@ -470,6 +494,9 @@ private Stopwatch deltaImpl(Delta delta, PersistenceExceptionHandler exceptionHa for (EnricherMemento enricher : delta.enrichers()) { futures.add(asyncPersist("enrichers", enricher, exceptionHandler)); } + for (FeedMemento feed : delta.feeds()) { + futures.add(asyncPersist("feeds", feed, exceptionHandler)); + } for (String id : delta.removedEntityIds()) { futures.add(asyncDelete("entities", id, exceptionHandler)); @@ -483,6 +510,9 @@ private Stopwatch deltaImpl(Delta delta, PersistenceExceptionHandler exceptionHa for (String id : delta.removedEnricherIds()) { futures.add(asyncDelete("enrichers", id, exceptionHandler)); } + for (String id : delta.removedFeedIds()) { + futures.add(asyncDelete("feeds", id, exceptionHandler)); + } try { // Wait for all the tasks to complete or fail, rather than aborting on the first failure. diff --git a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java index 9f27f3243e..a2c630146e 100644 --- a/core/src/main/java/brooklyn/event/feed/AbstractFeed.java +++ b/core/src/main/java/brooklyn/event/feed/AbstractFeed.java @@ -27,9 +27,9 @@ import brooklyn.entity.Feed; import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityLocal; -import brooklyn.entity.rebind.BasicPolicyRebindSupport; +import brooklyn.entity.rebind.BasicFeedRebindSupport; import brooklyn.entity.rebind.RebindSupport; -import brooklyn.mementos.PolicyMemento; +import brooklyn.mementos.FeedMemento; import brooklyn.policy.basic.AbstractEntityAdjunct; /** @@ -51,15 +51,30 @@ public abstract class AbstractFeed extends AbstractEntityAdjunct implements Feed public AbstractFeed() { } + /** + * @deprecated since 0.7.0; use no-arg constructor; call {@link #setEntity(EntityLocal)} + */ + @Deprecated public AbstractFeed(EntityLocal entity) { this(entity, false); } + /** + * @deprecated since 0.7.0; use no-arg constructor; call {@link #setEntity(EntityLocal)} and {@code setConfig(ONLY_IF_SERVICE_UP, onlyIfServiceUp)} + */ + @Deprecated public AbstractFeed(EntityLocal entity, boolean onlyIfServiceUp) { this.entity = checkNotNull(entity, "entity"); setConfig(ONLY_IF_SERVICE_UP, onlyIfServiceUp); } + // Ensure idempotent, as called in builders (in case not registered with entity), and also called + // when registering with entity + @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + } + @Override public boolean isActivated() { return activated; diff --git a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java index 94a866bf18..410fcf45b6 100644 --- a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java +++ b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java @@ -28,6 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.feed.AbstractFeed; import brooklyn.event.feed.AttributePollHandler; @@ -39,6 +41,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** * Provides a feed of attribute values, by periodically invoking functions. @@ -79,6 +82,11 @@ public class FunctionFeed extends AbstractFeed { private static final Logger log = LoggerFactory.getLogger(FunctionFeed.class); + // Treat as immutable once built + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static Builder builder() { return new Builder(); } @@ -118,6 +126,7 @@ public Builder poll(FunctionPollConfig config) { public FunctionFeed build() { built = true; FunctionFeed result = new FunctionFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -145,12 +154,16 @@ public boolean equals(Object other) { } } - // Treat as immutable once built - private final SetMultimap> polls = HashMultimap.>create(); + /** + * For rebind; do not call directly; use builder + */ + public FunctionFeed() { + } protected FunctionFeed(Builder builder) { - super(builder.entity, builder.onlyIfServiceUp); + setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp); + SetMultimap> polls = HashMultimap.>create(); for (FunctionPollConfig config : builder.polls) { @SuppressWarnings({ "rawtypes", "unchecked" }) FunctionPollConfig configCopy = new FunctionPollConfig(config); @@ -158,11 +171,13 @@ protected FunctionFeed(Builder builder) { Callable job = config.getCallable(); polls.put(new FunctionPollIdentifier(job), configCopy); } + setConfig(POLLS, polls); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected void preStart() { + SetMultimap> polls = getConfig(POLLS); for (final FunctionPollIdentifier pollInfo : polls.keySet()) { Set> configs = polls.get(pollInfo); long minPeriod = Integer.MAX_VALUE; diff --git a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java index bff833bda3..f931d01fa8 100644 --- a/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java +++ b/core/src/main/java/brooklyn/event/feed/http/HttpFeed.java @@ -34,6 +34,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.feed.AbstractFeed; @@ -55,6 +57,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** * Provides a feed of attribute values, by polling over http. @@ -104,6 +107,10 @@ public class HttpFeed extends AbstractFeed { public static final Logger log = LoggerFactory.getLogger(HttpFeed.class); + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static Builder builder() { return new Builder(); } @@ -198,6 +205,7 @@ public Builder credentialsIfNotNull(String username, String password) { public HttpFeed build() { built = true; HttpFeed result = new HttpFeed(this); + result.setEntity(checkNotNull(entity, "entity")); if (suspended) result.suspend(); result.start(); return result; @@ -253,13 +261,17 @@ public boolean equals(Object other) { } } - // Treat as immutable once built - private final SetMultimap> polls = HashMultimap.>create(); + /** + * For rebind; do not call directly; use builder + */ + public HttpFeed() { + } protected HttpFeed(Builder builder) { - super(builder.entity, builder.onlyIfServiceUp); + setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp); Map baseHeaders = ImmutableMap.copyOf(checkNotNull(builder.headers, "headers")); + SetMultimap> polls = HashMultimap.>create(); for (HttpPollConfig config : builder.polls) { @SuppressWarnings({ "unchecked", "rawtypes" }) HttpPollConfig configCopy = new HttpPollConfig(config); @@ -286,10 +298,13 @@ protected HttpFeed(Builder builder) { polls.put(new HttpPollIdentifier(method, baseUriProvider, headers, body, credentials, connectionTimeout, socketTimeout), configCopy); } + setConfig(POLLS, polls); } @Override protected void preStart() { + SetMultimap> polls = getConfig(POLLS); + for (final HttpPollIdentifier pollInfo : polls.keySet()) { // Though HttpClients are thread safe and can take advantage of connection pooling // and authentication caching, the httpcomponents documentation says: diff --git a/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java b/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java index b90b2ea3d1..df088b8e5e 100644 --- a/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java +++ b/core/src/main/java/brooklyn/event/feed/shell/ShellFeed.java @@ -30,6 +30,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.feed.AbstractFeed; @@ -50,6 +52,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** * Provides a feed of attribute values, by executing shell commands (on the local machine where @@ -96,6 +99,11 @@ public class ShellFeed extends AbstractFeed { public static final Logger log = LoggerFactory.getLogger(ShellFeed.class); + @SuppressWarnings("serial") + private static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static Builder builder() { return new Builder(); } @@ -126,6 +134,7 @@ public Builder poll(ShellPollConfig config) { public ShellFeed build() { built = true; ShellFeed result = new ShellFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -171,12 +180,16 @@ public boolean equals(Object other) { } } - // Treat as immutable once built - private final SetMultimap> polls = HashMultimap.>create(); - + /** + * For rebind; do not call directly; use builder + */ + public ShellFeed() { + } + protected ShellFeed(Builder builder) { - super(builder.entity); - + super(); + + SetMultimap> polls = HashMultimap.>create(); for (ShellPollConfig config : builder.polls) { @SuppressWarnings({ "unchecked", "rawtypes" }) ShellPollConfig configCopy = new ShellPollConfig(config); @@ -190,10 +203,13 @@ protected ShellFeed(Builder builder) { polls.put(new ShellPollIdentifier(command, env, dir, input, context, timeout), configCopy); } + setConfig(POLLS, polls); } @Override protected void preStart() { + SetMultimap> polls = getConfig(POLLS); + for (final ShellPollIdentifier pollInfo : polls.keySet()) { Set> configs = polls.get(pollInfo); long minPeriod = Integer.MAX_VALUE; diff --git a/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java b/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java index 14988ab5b7..5b41d1952d 100644 --- a/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java +++ b/core/src/main/java/brooklyn/event/feed/ssh/SshFeed.java @@ -31,7 +31,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; import brooklyn.entity.Entity; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.feed.AbstractFeed; import brooklyn.event.feed.AttributePollHandler; @@ -52,6 +54,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** * Provides a feed of attribute values, by polling over ssh. @@ -90,7 +93,19 @@ public class SshFeed extends AbstractFeed { public static final Logger log = LoggerFactory.getLogger(SshFeed.class); - + + @SuppressWarnings("serial") + public static final ConfigKey> MACHINE = ConfigKeys.newConfigKey( + new TypeToken>() {}, + "machine"); + + public static final ConfigKey EXEC_AS_COMMAND = ConfigKeys.newBooleanConfigKey("execAsCommand"); + + @SuppressWarnings("serial") + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static Builder builder() { return new Builder(); } @@ -99,8 +114,7 @@ public static class Builder { private EntityLocal entity; private boolean onlyIfServiceUp = false; private Supplier machine; - private long period = 500; - private TimeUnit periodUnits = TimeUnit.MILLISECONDS; + private Duration period = Duration.of(500, TimeUnit.MILLISECONDS); private List> polls = Lists.newArrayList(); private boolean execAsCommand = false; private volatile boolean built; @@ -122,15 +136,14 @@ public Builder machine(Supplier val) { return this; } public Builder period(Duration period) { - return period(period.toMilliseconds(), TimeUnit.MILLISECONDS); + this.period = period; + return this; } public Builder period(long millis) { - return period(millis, TimeUnit.MILLISECONDS); + return period(Duration.of(millis, TimeUnit.MILLISECONDS)); } public Builder period(long val, TimeUnit units) { - this.period = val; - this.periodUnits = units; - return this; + return period(Duration.of(val, units)); } public Builder poll(SshPollConfig config) { polls.add(config); @@ -147,6 +160,7 @@ public Builder execAsScript() { public SshFeed build() { built = true; SshFeed result = new SshFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -181,39 +195,46 @@ public boolean equals(Object other) { } } - private final Supplier machine; - private final boolean execAsCommand; - - // Treat as immutable once built - private final SetMultimap> polls = HashMultimap.>create(); - /** @deprecated since 0.7.0, use static convenience on {@link Locations} */ @Deprecated public static SshMachineLocation getMachineOfEntity(Entity entity) { return Machines.findUniqueSshMachineLocation(entity.getLocations()).orNull(); } + /** + * For rebind; do not call directly; use builder + */ + public SshFeed() { + } + protected SshFeed(final Builder builder) { - super(builder.entity, builder.onlyIfServiceUp); - machine = builder.machine != null ? builder.machine : - new Supplier() { - @Override - public SshMachineLocation get() { - return Locations.findUniqueSshMachineLocation(entity.getLocations()).get(); - } - }; - execAsCommand = builder.execAsCommand; + setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp); + setConfig(MACHINE, builder.machine != null ? builder.machine : null); + setConfig(EXEC_AS_COMMAND, builder.execAsCommand); + SetMultimap> polls = HashMultimap.>create(); for (SshPollConfig config : builder.polls) { @SuppressWarnings({ "unchecked", "rawtypes" }) SshPollConfig configCopy = new SshPollConfig(config); - if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); + if (configCopy.getPeriod() < 0) configCopy.period(builder.period); polls.put(new SshPollIdentifier(config.getCommandSupplier(), config.getEnvSupplier()), configCopy); } + setConfig(POLLS, polls); } + protected SshMachineLocation getMachine() { + Supplier supplier = getConfig(MACHINE); + if (supplier != null) { + return supplier.get(); + } else { + return Locations.findUniqueSshMachineLocation(entity.getLocations()).get(); + } + } + @Override protected void preStart() { + SetMultimap> polls = getConfig(POLLS); + for (final SshPollIdentifier pollInfo : polls.keySet()) { Set> configs = polls.get(pollInfo); long minPeriod = Integer.MAX_VALUE; @@ -240,6 +261,8 @@ protected Poller getPoller() { } private SshPollValue exec(String command, Map env) throws IOException { + SshMachineLocation machine = getMachine(); + Boolean execAsCommand = getConfig(EXEC_AS_COMMAND); if (log.isTraceEnabled()) log.trace("Ssh polling for {}, executing {} with env {}", new Object[] {machine, command, env}); ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); @@ -249,14 +272,14 @@ private SshPollValue exec(String command, Map env) throws IOExcep .configure(SshTool.PROP_NO_EXTRA_OUTPUT, true) .configure(SshTool.PROP_OUT_STREAM, stdout) .configure(SshTool.PROP_ERR_STREAM, stderr); - if (execAsCommand) { - exitStatus = machine.get().execCommands(flags.getAllConfig(), + if (Boolean.TRUE.equals(execAsCommand)) { + exitStatus = machine.execCommands(flags.getAllConfig(), "ssh-feed", ImmutableList.of(command), env); } else { - exitStatus = machine.get().execScript(flags.getAllConfig(), + exitStatus = machine.execScript(flags.getAllConfig(), "ssh-feed", ImmutableList.of(command), env); } - return new SshPollValue(machine.get(), exitStatus, new String(stdout.toByteArray()), new String(stderr.toByteArray())); + return new SshPollValue(machine, exitStatus, new String(stdout.toByteArray()), new String(stderr.toByteArray())); } } diff --git a/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java index 7ccc9fc184..c3bfeb32a8 100644 --- a/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java +++ b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeed.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.SortedSet; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -36,6 +37,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.effector.EffectorTasks; @@ -60,7 +63,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** * A sensor feed that retrieves performance counters from a Windows host and posts the values to sensors. @@ -95,44 +99,52 @@ public class WindowsPerformanceCounterFeed extends AbstractFeed { protected static final Pattern lineWithPerfData = Pattern.compile("^\"[\\d:/\\-. ]+\",\".*\"$", Pattern.MULTILINE); private static final Joiner JOINER_ON_SPACE = Joiner.on(' '); + @SuppressWarnings("serial") + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static Builder builder() { return new Builder(); } public static class Builder { private EntityLocal entity; - private SortedMap sensors = Maps.newTreeMap(Ordering.natural()); - private long period = 30; - private TimeUnit periodUnits = TimeUnit.SECONDS; + private Set> polls = Sets.newLinkedHashSet(); + private Duration period = Duration.of(30, TimeUnit.SECONDS); private volatile boolean built; public Builder entity(EntityLocal val) { this.entity = checkNotNull(val, "entity"); return this; } - public Builder addSensor(String performanceCounterName, AttributeSensor sensor) { - sensors.put(checkNotNull(performanceCounterName, "performanceCounterName"), - checkNotNull(sensor, "sensor")); + public Builder addSensor(WindowsPerformanceCounterPollConfig config) { + polls.add(config); return this; } + public Builder addSensor(String performanceCounterName, AttributeSensor sensor) { + return addSensor(new WindowsPerformanceCounterPollConfig(sensor).performanceCounterName(checkNotNull(performanceCounterName, "performanceCounterName"))); + } public Builder addSensors(Map sensors) { - sensors.putAll(checkNotNull(sensors, "sensors")); + for (Map.Entry entry : sensors.entrySet()) { + addSensor(entry.getKey(), entry.getValue()); + } return this; } public Builder period(Duration period) { - return period(checkNotNull(period, "period").toMilliseconds(), TimeUnit.MILLISECONDS); + period = checkNotNull(period, "period"); + return this; } public Builder period(long millis) { return period(millis, TimeUnit.MILLISECONDS); } public Builder period(long val, TimeUnit units) { - this.period = val; - this.periodUnits = checkNotNull(units, "units"); - return this; + return period(Duration.of(val, units)); } public WindowsPerformanceCounterFeed build() { built = true; WindowsPerformanceCounterFeed result = new WindowsPerformanceCounterFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -142,34 +154,48 @@ protected void finalize() { } } - private final EntityLocal entity; - private final long period; - private final TimeUnit periodUnits; - private final SortedMap attributeSensors; - private final ProcessTaskFactory taskFactory; + /** + * For rebind; do not call directly; use builder + */ + public WindowsPerformanceCounterFeed() { + } protected WindowsPerformanceCounterFeed(Builder builder) { - super(checkNotNull(builder.entity, "builder.entity")); - entity = builder.entity; - period = builder.period; - periodUnits = builder.periodUnits; - attributeSensors = builder.sensors; + Set> polls = Sets.newLinkedHashSet(); + for (WindowsPerformanceCounterPollConfig config : builder.polls) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + WindowsPerformanceCounterPollConfig configCopy = new WindowsPerformanceCounterPollConfig(config); + if (configCopy.getPeriod() < 0) configCopy.period(builder.period); + polls.add(configCopy); + } + setConfig(POLLS, polls); + } + + @Override + protected void preStart() { + Set> polls = getConfig(POLLS); + + long minPeriod = Integer.MAX_VALUE; + SortedSet performanceCounterNames = Sets.newTreeSet(); + for (WindowsPerformanceCounterPollConfig config : polls) { + minPeriod = Math.min(minPeriod, config.getPeriod()); + performanceCounterNames.add(config.getPerformanceCounterName()); + } + SshMachineLocation machine = EffectorTasks.getSshMachine(getEntity()); Iterable allParams = ImmutableList.builder() .add("typeperf") - .addAll(Iterables.transform(attributeSensors.keySet(), QuoteStringFunction.INSTANCE)) + .addAll(Iterables.transform(performanceCounterNames, QuoteStringFunction.INSTANCE)) .add("-sc") .add("1") .build(); String command = JOINER_ON_SPACE.join(allParams); log.debug("Windows performance counter poll command will be: {}", command); - taskFactory = SshTasks.newSshExecTaskFactory(machine, command) + + final ProcessTaskFactory taskFactory = SshTasks.newSshExecTaskFactory(machine, command) .allowingNonZeroExitCode() .runAsCommand(); - } - @Override - protected void preStart() { final Callable queryForCounterValues = new Callable() { public SshPollValue call() throws Exception { ProcessTaskWrapper taskWrapper = taskFactory.newTask(); @@ -184,8 +210,8 @@ public SshPollValue call() throws Exception { getPoller().scheduleAtFixedRate( new CallInEntityExecutionContext(entity, queryForCounterValues), - new SendPerfCountersToSensors(entity, attributeSensors), - periodUnits.toMillis(period)); + new SendPerfCountersToSensors(entity, polls), + minPeriod); } @SuppressWarnings("unchecked") @@ -227,12 +253,16 @@ private static class SendPerfCountersToSensors implements PollHandler> INTEGER_TYPES = ImmutableSet.of(Integer.class, Long.class, Byte.class, Short.class, BigInteger.class); - private SortedMap sensorMap; - private EntityLocal entity; + private final EntityLocal entity; + private final SortedMap sensorMap; - public SendPerfCountersToSensors(EntityLocal entity, SortedMap sensorMap) { - this.sensorMap = sensorMap; + public SendPerfCountersToSensors(EntityLocal entity, Set> polls) { this.entity = entity; + + sensorMap = Maps.newTreeMap(); + for (WindowsPerformanceCounterPollConfig config : polls) { + sensorMap.put(config.getPerformanceCounterName(), config.getSensor()); + } } @Override diff --git a/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterPollConfig.java b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterPollConfig.java new file mode 100644 index 0000000000..fe4f8977ac --- /dev/null +++ b/core/src/main/java/brooklyn/event/feed/windows/WindowsPerformanceCounterPollConfig.java @@ -0,0 +1,54 @@ +/* + * 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.event.feed.windows; + +import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.PollConfig; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +public class WindowsPerformanceCounterPollConfig extends PollConfig>{ + + private String performanceCounterName; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public WindowsPerformanceCounterPollConfig(AttributeSensor sensor) { + super(sensor); + onSuccess((Function)Functions.identity()); + } + + public WindowsPerformanceCounterPollConfig(WindowsPerformanceCounterPollConfig other) { + super(other); + this.performanceCounterName = other.performanceCounterName; + } + + public String getPerformanceCounterName() { + return performanceCounterName; + } + + public WindowsPerformanceCounterPollConfig performanceCounterName(String val) { + this.performanceCounterName = val; return this; + } + + @Override + public String toString() { + return "windowsPerformanceCounter["+performanceCounterName+"]"; + } +} diff --git a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java index c16c948a5b..d04b734b2d 100644 --- a/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java +++ b/core/src/main/java/brooklyn/internal/BrooklynFeatureEnablement.java @@ -41,6 +41,8 @@ public class BrooklynFeatureEnablement { public static final String FEATURE_ENRICHER_PERSISTENCE_PROPERTY = "brooklyn.experimental.feature.enricherPersistence"; + public static final String FEATURE_FEED_PERSISTENCE_PROPERTY = "brooklyn.experimental.feature.feedPersistence"; + private static final Map FEATURE_ENABLEMENTS = Maps.newLinkedHashMap(); private static final Object MUTEX = new Object(); @@ -52,6 +54,7 @@ static void setDefaults() { setDefault(FEATURE_POLICY_PERSISTENCE_PROPERTY, true); setDefault(FEATURE_ENRICHER_PERSISTENCE_PROPERTY, true); + setDefault(FEATURE_FEED_PERSISTENCE_PROPERTY, true); } static { diff --git a/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java b/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java index 8e1dbe6092..811092a476 100644 --- a/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java +++ b/core/src/main/java/brooklyn/management/internal/EntityChangeListener.java @@ -20,6 +20,7 @@ import brooklyn.config.ConfigKey; import brooklyn.entity.Effector; +import brooklyn.entity.Feed; import brooklyn.event.AttributeSensor; import brooklyn.policy.Enricher; import brooklyn.policy.Policy; @@ -39,6 +40,8 @@ public interface EntityChangeListener { @Override public void onPolicyRemoved(Policy policy) {} @Override public void onEnricherAdded(Enricher enricher) {} @Override public void onEnricherRemoved(Enricher enricher) {} + @Override public void onFeedAdded(Feed feed) {} + @Override public void onFeedRemoved(Feed feed) {} @Override public void onEffectorStarting(Effector effector) {} @Override public void onEffectorCompleted(Effector effector) {} }; @@ -65,6 +68,10 @@ public interface EntityChangeListener { void onEnricherRemoved(Enricher enricher); + void onFeedAdded(Feed feed); + + void onFeedRemoved(Feed feed); + void onEffectorStarting(Effector effector); void onEffectorCompleted(Effector effector); diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java index e7a4bbe815..b820cdc376 100644 --- a/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java +++ b/core/src/main/java/brooklyn/management/internal/EntityManagementSupport.java @@ -30,6 +30,7 @@ import brooklyn.entity.Application; import brooklyn.entity.Effector; import brooklyn.entity.Entity; +import brooklyn.entity.Feed; import brooklyn.entity.basic.AbstractEntity; import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityInternal; @@ -379,6 +380,11 @@ public void onEnricherAdded(Enricher enricher) { getManagementContext().getRebindManager().getChangeListener().onManaged(enricher); } @Override + public void onFeedAdded(Feed feed) { + getManagementContext().getRebindManager().getChangeListener().onChanged(entity); + getManagementContext().getRebindManager().getChangeListener().onManaged(feed); + } + @Override public void onPolicyRemoved(Policy policy) { getManagementContext().getRebindManager().getChangeListener().onChanged(entity); getManagementContext().getRebindManager().getChangeListener().onUnmanaged(policy); @@ -389,6 +395,11 @@ public void onEnricherRemoved(Enricher enricher) { getManagementContext().getRebindManager().getChangeListener().onUnmanaged(enricher); } @Override + public void onFeedRemoved(Feed feed) { + getManagementContext().getRebindManager().getChangeListener().onChanged(entity); + getManagementContext().getRebindManager().getChangeListener().onUnmanaged(feed); + } + @Override public void onAttributeChanged(AttributeSensor attribute) { // TODO Could make this more efficient by inspecting the attribute to decide if needs persisted // immediately, or not important, or transient (e.g. do we really need to persist diff --git a/core/src/test/java/brooklyn/entity/basic/EntityFunctionsTest.java b/core/src/test/java/brooklyn/entity/basic/EntityFunctionsTest.java index 106d3b3ad1..02fcd9b4ae 100644 --- a/core/src/test/java/brooklyn/entity/basic/EntityFunctionsTest.java +++ b/core/src/test/java/brooklyn/entity/basic/EntityFunctionsTest.java @@ -26,17 +26,23 @@ import brooklyn.entity.BrooklynAppUnitTestSupport; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; import brooklyn.test.entity.TestEntity; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; + public class EntityFunctionsTest extends BrooklynAppUnitTestSupport { private TestEntity entity; + private Location loc; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { super.setUp(); entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("mydisplayname")); + loc = app.getManagementContext().getLocationRegistry().resolve("localhost"); } @Test @@ -62,4 +68,10 @@ public void testDisplayName() throws Exception { public void testId() throws Exception { assertEquals(EntityFunctions.id().apply(entity), entity.getId()); } + + @Test + public void testLocationMatching() throws Exception { + entity.addLocations(ImmutableList.of(loc)); + assertEquals(EntityFunctions.locationMatching(Predicates.alwaysTrue()).apply(entity), loc); + } } diff --git a/core/src/test/java/brooklyn/entity/basic/EntitySuppliersTest.java b/core/src/test/java/brooklyn/entity/basic/EntitySuppliersTest.java new file mode 100644 index 0000000000..ba41199b35 --- /dev/null +++ b/core/src/test/java/brooklyn/entity/basic/EntitySuppliersTest.java @@ -0,0 +1,70 @@ +/* + * 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.entity.basic; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.BrooklynAppUnitTestSupport; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.location.MachineProvisioningLocation; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.test.entity.TestEntity; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class EntitySuppliersTest extends BrooklynAppUnitTestSupport { + + private TestEntity entity; + private Location loc; + private SshMachineLocation machine; + + @BeforeMethod(alwaysRun=true) + @SuppressWarnings("unchecked") + @Override + public void setUp() throws Exception { + super.setUp(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).displayName("mydisplayname")); + loc = app.getManagementContext().getLocationRegistry().resolve("localhost"); + machine = ((MachineProvisioningLocation)loc).obtain(ImmutableMap.of()); + } + + @Test + public void testUniqueSshMachineLocation() throws Exception { + entity.addLocations(ImmutableList.of(machine)); + assertEquals(EntitySuppliers.uniqueSshMachineLocation(entity).get(), machine); + } + + @Test + public void testUniqueSshMachineLocationWhenNoLocation() throws Exception { + Supplier supplier = EntitySuppliers.uniqueSshMachineLocation(entity); + try { + supplier.get(); + fail(); + } catch (IllegalStateException e) { + // expected: success + } + } +} diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java index 744f9488a9..b4e2a9977e 100644 --- a/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java +++ b/core/src/test/java/brooklyn/entity/rebind/RebindFeedTest.java @@ -178,7 +178,6 @@ public void init() { public static class MyEntityWithSshFeedImpl extends TestEntityImpl { @Override public void start(Collection locs) { - // TODO Auto-generated method stub super.start(locs); addFeed(SshFeed.builder() .entity(this) diff --git a/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedLiveTest.java b/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedLiveTest.java index 34ee43b8a2..9a9210fb56 100644 --- a/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedLiveTest.java +++ b/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedLiveTest.java @@ -20,13 +20,10 @@ import java.util.Map; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import brooklyn.entity.basic.ApplicationBuilder; -import brooklyn.entity.basic.Entities; -import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.BrooklynAppLiveTestSupport; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.AttributeSensor; @@ -34,9 +31,7 @@ import brooklyn.location.Location; import brooklyn.location.MachineLocation; import brooklyn.location.MachineProvisioningLocation; -import brooklyn.management.ManagementContext; import brooklyn.test.EntityTestUtils; -import brooklyn.test.entity.TestApplication; import brooklyn.test.entity.TestEntity; import brooklyn.util.collections.MutableMap; @@ -64,29 +59,20 @@ * use a jclouds location, as adding a dependency on brooklyn-locations-jclouds would cause a * cyclic dependency. */ -public class WindowsPerformanceCounterFeedLiveTest { +public class WindowsPerformanceCounterFeedLiveTest extends BrooklynAppLiveTestSupport { - final static AttributeSensor CPU_IDLE_TIME = - Sensors.newDoubleSensor("cpu.idleTime", ""); - final static AttributeSensor TELEPHONE_LINES = - Sensors.newIntegerSensor("telephone.lines", ""); + final static AttributeSensor CPU_IDLE_TIME = Sensors.newDoubleSensor("cpu.idleTime", ""); + final static AttributeSensor TELEPHONE_LINES = Sensors.newIntegerSensor("telephone.lines", ""); private static final String LOCATION_SPEC = "named:WindowsLiveTest"; - private ManagementContext mgmt; - private TestApplication app; private Location loc; private EntityLocal entity; @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { - if (mgmt!=null) { - app = ApplicationBuilder.newManagedApp(TestApplication.class, mgmt); - } else { - app = ApplicationBuilder.newManagedApp(TestApplication.class); - mgmt = ((EntityInternal)app).getManagementContext(); - } - + super.setUp(); + Map allFlags = MutableMap.builder() .put("tags", ImmutableList.of(getClass().getName())) .build(); @@ -99,12 +85,6 @@ public void setUp() throws Exception { app.start(ImmutableList.of(loc)); } - @AfterMethod(alwaysRun=true) - public void tearDown() throws Exception { - if (mgmt != null) Entities.destroyAllCatching(mgmt); - mgmt = null; - } - @Test(groups={"Live","Disabled"}, enabled=false) public void testRetrievesPerformanceCounters() throws Exception { // We can be pretty sure that a Windows instance in the cloud will have zero telephone lines... diff --git a/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedTest.java b/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedTest.java index 61304abf6f..4b2ed9199a 100644 --- a/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedTest.java +++ b/core/src/test/java/brooklyn/event/feed/windows/WindowsPerformanceCounterFeedTest.java @@ -32,7 +32,7 @@ public class WindowsPerformanceCounterFeedTest { @Test public void testIteratorWithSingleValue() { - Iterator iterator = new WindowsPerformanceCounterFeed + Iterator iterator = new WindowsPerformanceCounterFeed .PerfCounterValueIterator("\"10/14/2013 15:28:24.406\",\"0.000000\""); assertTrue(iterator.hasNext()); assertEquals(iterator.next(), "0.000000"); @@ -41,7 +41,7 @@ public void testIteratorWithSingleValue() { @Test public void testIteratorWithMultipleValues() { - Iterator iterator = new WindowsPerformanceCounterFeed + Iterator iterator = new WindowsPerformanceCounterFeed .PerfCounterValueIterator("\"10/14/2013 15:35:50.582\",\"8803.000000\",\"405622.000000\""); assertTrue(iterator.hasNext()); assertEquals(iterator.next(), "8803.000000"); diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java index 55645f41f1..e1b022d2c0 100644 --- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java +++ b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java @@ -35,8 +35,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; import brooklyn.entity.Entity; import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.EntityFunctions; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.feed.AbstractFeed; import brooklyn.event.feed.AttributePollHandler; @@ -52,6 +55,7 @@ import brooklyn.util.net.Cidr; import com.google.common.base.Function; +import com.google.common.base.Functions; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -63,6 +67,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.net.HostAndPort; +import com.google.common.reflect.TypeToken; import com.google.gson.JsonObject; public class ZabbixFeed extends AbstractFeed { @@ -88,6 +93,25 @@ public class ZabbixFeed extends AbstractFeed { private static final AtomicInteger id = new AtomicInteger(0); + @SuppressWarnings("serial") + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + + @SuppressWarnings("serial") + public static final ConfigKey> BASE_URI_PROVIDER = ConfigKeys.newConfigKey( + new TypeToken>() {}, + "baseUriProvider"); + + public static final ConfigKey GROUP_ID = ConfigKeys.newIntegerConfigKey("groupId"); + + public static final ConfigKey TEMPLATE_ID = ConfigKeys.newIntegerConfigKey("templateId"); + + @SuppressWarnings("serial") + public static final ConfigKey> UNIQUE_HOSTNAME_GENERATOR = ConfigKeys.newConfigKey( + new TypeToken>() {}, + "uniqueHostnameGenerator"); + public static Builder builder() { return new ConcreteBuilder(); } @@ -110,11 +134,9 @@ public static class Builder> { private Integer sessionTimeout; private Integer groupId; private Integer templateId; - private Function uniqueHostnameGenerator = new Function() { - @Override public String apply(EntityLocal entity) { - Location loc = Iterables.find(entity.getLocations(), Predicates.instanceOf(MachineLocation.class)); - return loc.getId(); - }}; + private Function uniqueHostnameGenerator = Functions.compose( + EntityFunctions.id(), + EntityFunctions.locationMatching(Predicates.instanceOf(MachineLocation.class))); @SuppressWarnings("unchecked") protected B self() { @@ -217,6 +239,7 @@ public T build() { } // Now create feed T result = (T) new ZabbixFeed(this); + result.setEntity(checkNotNull(entity, "entity")); built = true; if (suspended) result.suspend(); result.start(); @@ -250,44 +273,48 @@ public boolean equals(Object other) { } } - // Treat as immutable once built - protected final Set> polls = Sets.newLinkedHashSet(); - - protected Supplier baseUriProvider; - protected Integer groupId, templateId; - // Flag set when the Zabbix agent is registered for a host protected final AtomicBoolean registered = new AtomicBoolean(false); - private final Function uniqueHostnameGenerator; - + /** + * For rebind; do not call directly; use builder + */ + public ZabbixFeed() { + } + protected ZabbixFeed(final Builder builder) { - super(builder.entity); - - baseUriProvider = builder.baseUriProvider; - if (builder.baseUri!=null) { - if (baseUriProvider!=null) + setConfig(BASE_URI_PROVIDER, builder.baseUriProvider); + if (builder.baseUri != null) { + if (builder.baseUriProvider != null) { throw new IllegalStateException("Not permitted to supply baseUri and baseUriProvider"); - URI uri = builder.baseUri; - baseUriProvider = Suppliers.ofInstance(uri); + } + setConfig(BASE_URI_PROVIDER, Suppliers.ofInstance(builder.baseUri)); + } else { + setConfig(BASE_URI_PROVIDER, checkNotNull(builder.baseUriProvider, "baseUriProvider and baseUri")); } - checkNotNull(baseUriProvider); - groupId = checkNotNull(builder.groupId, "Zabbix groupId must be set"); - templateId = checkNotNull(builder.templateId, "Zabbix templateId must be set"); + setConfig(GROUP_ID, checkNotNull(builder.groupId, "Zabbix groupId must be set")); + setConfig(TEMPLATE_ID, checkNotNull(builder.templateId, "Zabbix templateId must be set")); + setConfig(UNIQUE_HOSTNAME_GENERATOR, checkNotNull(builder.uniqueHostnameGenerator, "uniqueHostnameGenerator")); + Set> polls = Sets.newLinkedHashSet(); for (ZabbixPollConfig config : builder.polls) { @SuppressWarnings({ "unchecked", "rawtypes" }) ZabbixPollConfig configCopy = new ZabbixPollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); polls.add(configCopy); } - - uniqueHostnameGenerator = checkNotNull(builder.uniqueHostnameGenerator, "uniqueHostnameGenerator"); + setConfig(POLLS, polls); } @Override protected void preStart() { + final Supplier baseUriProvider = getConfig(BASE_URI_PROVIDER); + final Function uniqueHostnameGenerator = getConfig(UNIQUE_HOSTNAME_GENERATOR); + final Integer groupId = getConfig(GROUP_ID); + final Integer templateId = getConfig(TEMPLATE_ID); + final Set> polls = getConfig(POLLS); + log.info("starting zabbix feed for {}", entity); // TODO if supplier returns null, we may wish to defer initialization until url available? diff --git a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java index f842db2374..e27b391245 100644 --- a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java +++ b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributeFeed.java @@ -24,12 +24,16 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.SortedSet; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.basic.EntityLocal; import brooklyn.event.AttributeSensor; @@ -50,6 +54,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -87,13 +93,20 @@ */ public class ChefAttributeFeed extends AbstractFeed { + private static final Logger log = LoggerFactory.getLogger(ChefAttributeFeed.class); + /** * Prefix for attribute sensor names. */ public static final String CHEF_ATTRIBUTE_PREFIX = "chef.attribute."; - private static final Logger log = LoggerFactory.getLogger(ChefAttributeFeed.class); + @SuppressWarnings("serial") + public static final ConfigKey>> POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "polls"); + public static final ConfigKey NODE_NAME = ConfigKeys.newStringConfigKey("nodeName"); + public static Builder builder() { return new Builder(); } @@ -103,9 +116,8 @@ public static class Builder { private EntityLocal entity; private boolean onlyIfServiceUp = false; private String nodeName; - private Map> sensors = Maps.newHashMap(); - private long period = 30; - private TimeUnit periodUnits = TimeUnit.SECONDS; + private Set polls = Sets.newLinkedHashSet(); + private Duration period = Duration.of(30, TimeUnit.SECONDS); private volatile boolean built; public Builder entity(EntityLocal val) { @@ -121,12 +133,17 @@ public Builder nodeName(String nodeName) { this.nodeName = checkNotNull(nodeName, "nodeName"); return this; } - public Builder addSensor(String chefAttributePath, AttributeSensor sensor) { - sensors.put(checkNotNull(chefAttributePath, "chefAttributePath"), checkNotNull(sensor, "sensor")); + public Builder addSensor(ChefAttributePollConfig config) { + polls.add(config); return this; } + public Builder addSensor(String chefAttributePath, AttributeSensor sensor) { + return addSensor(new ChefAttributePollConfig(sensor).chefAttributePath(chefAttributePath)); + } public Builder addSensors(Map sensors) { - sensors.putAll(checkNotNull(sensors, "sensors")); + for (Map.Entry entry : sensors.entrySet()) { + addSensor(entry.getKey(), entry.getValue()); + } return this; } public Builder addSensors(AttributeSensor[] sensors) { @@ -141,19 +158,19 @@ public Builder addSensors(Iterable sensors) { return this; } public Builder period(Duration period) { - return period(checkNotNull(period, "period").toMilliseconds(), TimeUnit.MILLISECONDS); + this.period = period; + return this; } public Builder period(long millis) { - return period(millis, TimeUnit.MILLISECONDS); + return period(Duration.of(millis, TimeUnit.MILLISECONDS)); } public Builder period(long val, TimeUnit units) { - this.period = val; - this.periodUnits = checkNotNull(units, "units"); - return this; + return period(Duration.of(val, units)); } public ChefAttributeFeed build() { built = true; ChefAttributeFeed result = new ChefAttributeFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -163,23 +180,41 @@ protected void finalize() { } } - private final String nodeName; - private final long period; - private final TimeUnit periodUnits; - private final Map> chefAttributeSensors; - private final KnifeTaskFactory knifeTaskFactory; + private KnifeTaskFactory knifeTaskFactory; + /** + * For rebind; do not call directly; use builder + */ + public ChefAttributeFeed() { + } + protected ChefAttributeFeed(Builder builder) { - super(checkNotNull(builder.entity, "entity"), builder.onlyIfServiceUp); - nodeName = checkNotNull(builder.nodeName, "builder.nodeName"); - period = builder.period; - periodUnits = builder.periodUnits; - chefAttributeSensors = builder.sensors; - knifeTaskFactory = new KnifeNodeAttributeQueryTaskFactory(nodeName); + setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp); + setConfig(NODE_NAME, checkNotNull(builder.nodeName, "builder.nodeName")); + + Set> polls = Sets.newLinkedHashSet(); + for (ChefAttributePollConfig config : builder.polls) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + ChefAttributePollConfig configCopy = new ChefAttributePollConfig(config); + if (configCopy.getPeriod() < 0) configCopy.period(builder.period); + polls.add(configCopy); + } + setConfig(POLLS, polls); } @Override protected void preStart() { + final String nodeName = getConfig(NODE_NAME); + final Set> polls = getConfig(POLLS); + + long minPeriod = Integer.MAX_VALUE; + SortedSet performanceCounterNames = Sets.newTreeSet(); + for (ChefAttributePollConfig config : polls) { + minPeriod = Math.min(minPeriod, config.getPeriod()); + } + + knifeTaskFactory = new KnifeNodeAttributeQueryTaskFactory(nodeName); + final Callable getAttributesFromKnife = new Callable() { public SshPollValue call() throws Exception { ProcessTaskWrapper taskWrapper = knifeTaskFactory.newTask(); @@ -194,8 +229,8 @@ public SshPollValue call() throws Exception { getPoller().scheduleAtFixedRate( new CallInEntityExecutionContext(entity, getAttributesFromKnife), - new SendChefAttributesToSensors(entity, chefAttributeSensors), - periodUnits.toMillis(period)); + new SendChefAttributesToSensors(entity, polls), + minPeriod); } @SuppressWarnings("unchecked") @@ -250,12 +285,15 @@ private static class SendChefAttributesToSensors implements PollHandler PREFIXES = ImmutableList.of("", "automatic", "force_override", "override", "normal", "force_default", "default"); private static final Splitter SPLITTER = Splitter.on('.'); - private Map> chefAttributeSensors; - private EntityLocal entity; + private final EntityLocal entity; + private final Map> chefAttributeSensors; - public SendChefAttributesToSensors(EntityLocal entity, Map> chefAttributeSensors) { - this.chefAttributeSensors = chefAttributeSensors; + public SendChefAttributesToSensors(EntityLocal entity, Set> polls) { this.entity = entity; + chefAttributeSensors = Maps.newLinkedHashMap(); + for (ChefAttributePollConfig config : polls) { + chefAttributeSensors.put(config.getChefAttributePath(), config.getSensor()); + } } @Override diff --git a/software/base/src/main/java/brooklyn/entity/chef/ChefAttributePollConfig.java b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributePollConfig.java new file mode 100644 index 0000000000..b76b6ad939 --- /dev/null +++ b/software/base/src/main/java/brooklyn/entity/chef/ChefAttributePollConfig.java @@ -0,0 +1,54 @@ +/* + * 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.entity.chef; + +import brooklyn.event.AttributeSensor; +import brooklyn.event.feed.PollConfig; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +public class ChefAttributePollConfig extends PollConfig>{ + + private String chefAttributePath; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ChefAttributePollConfig(AttributeSensor sensor) { + super(sensor); + onSuccess((Function)Functions.identity()); + } + + public ChefAttributePollConfig(ChefAttributePollConfig other) { + super(other); + this.chefAttributePath = other.chefAttributePath; + } + + public String getChefAttributePath() { + return chefAttributePath; + } + + public ChefAttributePollConfig chefAttributePath(String val) { + this.chefAttributePath = val; return this; + } + + @Override + public String toString() { + return "chef["+chefAttributePath+"]"; + } +} diff --git a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java index ab05914e48..6ea49334fd 100644 --- a/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java +++ b/software/base/src/main/java/brooklyn/event/feed/jmx/JmxFeed.java @@ -18,6 +18,8 @@ */ package brooklyn.event.feed.jmx; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.List; import java.util.Map; import java.util.Set; @@ -32,6 +34,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.event.feed.AbstractFeed; @@ -41,12 +45,12 @@ import brooklyn.event.feed.Poller; import brooklyn.util.time.Duration; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; /** @@ -90,6 +94,26 @@ public class JmxFeed extends AbstractFeed { public static final long JMX_CONNECTION_TIMEOUT_MS = 120*1000; + public static final ConfigKey HELPER = ConfigKeys.newConfigKey(JmxHelper.class, "helper"); + public static final ConfigKey OWN_HELPER = ConfigKeys.newBooleanConfigKey("ownHelper"); + public static final ConfigKey JMX_URI = ConfigKeys.newStringConfigKey("jmxUri"); + public static final ConfigKey JMX_CONNECTION_TIMEOUT = ConfigKeys.newLongConfigKey("jmxConnectionTimeout"); + + @SuppressWarnings("serial") + public static final ConfigKey>> ATTRIBUTE_POLLS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "attributePolls"); + + @SuppressWarnings("serial") + public static final ConfigKey, JmxOperationPollConfig>> OPERATION_POLLS = ConfigKeys.newConfigKey( + new TypeToken, JmxOperationPollConfig>>() {}, + "operationPolls"); + + @SuppressWarnings("serial") + public static final ConfigKey>> NOTIFICATION_SUBSCRIPTIONS = ConfigKeys.newConfigKey( + new TypeToken>>() {}, + "notificationPolls"); + public static Builder builder() { return new Builder(); } @@ -139,6 +163,7 @@ public Builder subscribeToNotification(JmxNotificationSubscriptionConfig conf public JmxFeed build() { built = true; JmxFeed result = new JmxFeed(this); + result.setEntity(checkNotNull(entity, "entity")); result.start(); return result; } @@ -148,43 +173,66 @@ protected void finalize() { } } - private final JmxHelper helper; - private final boolean ownHelper; - private final String jmxUri; - private final long jmxConnectionTimeout; - - // Treat as immutable; never modified after constructor - private final SetMultimap> attributePolls = HashMultimap.>create(); - private final SetMultimap, JmxOperationPollConfig> operationPolls = HashMultimap.,JmxOperationPollConfig>create(); - private final SetMultimap> notificationSubscriptions = HashMultimap.create(); private final SetMultimap notificationListeners = HashMultimap.create(); + /** + * For rebind; do not call directly; use builder + */ + public JmxFeed() { + } + protected JmxFeed(Builder builder) { - super(builder.entity); - this.helper = (builder.helper != null) ? builder.helper : new JmxHelper(entity); - this.ownHelper = (builder.helper == null); - this.jmxUri = helper.getUrl(); - this.jmxConnectionTimeout = builder.jmxConnectionTimeout; + super(); + if (builder.helper != null) { + JmxHelper helper = builder.helper; + setConfig(HELPER, helper); + setConfig(OWN_HELPER, false); + setConfig(JMX_URI, helper.getUrl()); + } + setConfig(JMX_CONNECTION_TIMEOUT, builder.jmxConnectionTimeout); + SetMultimap> attributePolls = HashMultimap.>create(); for (JmxAttributePollConfig config : builder.attributePolls) { @SuppressWarnings({ "rawtypes", "unchecked" }) JmxAttributePollConfig configCopy = new JmxAttributePollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); attributePolls.put(configCopy.getObjectName().getCanonicalName() + configCopy.getAttributeName(), configCopy); } + setConfig(ATTRIBUTE_POLLS, attributePolls); + + SetMultimap, JmxOperationPollConfig> operationPolls = HashMultimap.,JmxOperationPollConfig>create(); for (JmxOperationPollConfig config : builder.operationPolls) { @SuppressWarnings({ "rawtypes", "unchecked" }) JmxOperationPollConfig configCopy = new JmxOperationPollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); operationPolls.put(configCopy.buildOperationIdentity(), configCopy); } + setConfig(OPERATION_POLLS, operationPolls); + + SetMultimap> notificationSubscriptions = HashMultimap.create(); for (JmxNotificationSubscriptionConfig config : builder.notificationSubscriptions) { notificationSubscriptions.put(config.getNotificationFilter(), config); } + setConfig(NOTIFICATION_SUBSCRIPTIONS, notificationSubscriptions); } + @Override + public void setEntity(EntityLocal entity) { + if (getConfig(HELPER) == null) { + JmxHelper helper = new JmxHelper(entity); + setConfig(HELPER, helper); + setConfig(OWN_HELPER, true); + setConfig(JMX_URI, helper.getUrl()); + } + super.setEntity(entity); + } + public String getJmxUri() { - return jmxUri; + return getConfig(JMX_URI); + } + + protected JmxHelper getHelper() { + return getConfig(HELPER); } @SuppressWarnings("unchecked") @@ -194,7 +242,7 @@ protected Poller getPoller() { @Override protected boolean isConnected() { - return super.isConnected() && helper.isConnected(); + return super.isConnected() && getHelper().isConnected(); } @Override @@ -217,13 +265,16 @@ protected void preStart() { * ... * at brooklyn.entity.rebind.RebindManagerImpl.rebind(RebindManagerImpl.java:184) */ + final SetMultimap> notificationSubscriptions = getConfig(NOTIFICATION_SUBSCRIPTIONS); + final SetMultimap, JmxOperationPollConfig> operationPolls = getConfig(OPERATION_POLLS); + final SetMultimap> attributePolls = getConfig(ATTRIBUTE_POLLS); getPoller().submit(new Callable() { public Void call() { - helper.connect(jmxConnectionTimeout); + getHelper().connect(getConfig(JMX_CONNECTION_TIMEOUT)); return null; } - @Override public String toString() { return "Connect JMX "+helper.getUrl(); } + @Override public String toString() { return "Connect JMX "+getHelper().getUrl(); } }); for (final NotificationFilter filter : notificationSubscriptions.keySet()) { @@ -254,7 +305,7 @@ public Void call() { @Override protected void preStop() { super.preStop(); - + for (Map.Entry entry : notificationListeners.entries()) { unregisterNotificationListener(entry.getKey(), entry.getValue()); } @@ -264,6 +315,8 @@ protected void preStop() { @Override protected void postStop() { super.postStop(); + JmxHelper helper = getHelper(); + Boolean ownHelper = getConfig(OWN_HELPER); if (helper != null && ownHelper) helper.terminate(); } @@ -287,11 +340,11 @@ private void registerOperationPoller(Set> configs) { getPoller().scheduleAtFixedRate( new Callable() { public Object call() throws Exception { - if (log.isDebugEnabled()) log.debug("jmx operation polling for {} sensors at {} -> {}", new Object[] {getEntity(), jmxUri, operationName}); + if (log.isDebugEnabled()) log.debug("jmx operation polling for {} sensors at {} -> {}", new Object[] {getEntity(), getJmxUri(), operationName}); if (signature.size() == params.size()) { - return helper.operation(objectName, operationName, signature, params); + return getHelper().operation(objectName, operationName, signature, params); } else { - return helper.operation(objectName, operationName, params.toArray()); + return getHelper().operation(objectName, operationName, params.toArray()); } } }, @@ -317,8 +370,8 @@ private void registerAttributePoller(Set> configs) { getPoller().scheduleAtFixedRate( new Callable() { public Object call() throws Exception { - if (log.isTraceEnabled()) log.trace("jmx attribute polling for {} sensors at {} -> {}", new Object[] {getEntity(), jmxUri, jmxAttributeName}); - return helper.getAttribute(objectName, jmxAttributeName); + if (log.isTraceEnabled()) log.trace("jmx attribute polling for {} sensors at {} -> {}", new Object[] {getEntity(), getJmxUri(), jmxAttributeName}); + return getHelper().getAttribute(objectName, jmxAttributeName); } }, new DelegatingPollHandler(handlers), minPeriod); @@ -355,14 +408,14 @@ private NotificationListener registerNotificationListener(Set Date: Fri, 5 Sep 2014 11:45:38 +0100 Subject: [PATCH 8/8] Adds IllegalStateExceptionSupplier.EMPTY_EXCEPTION MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - don’t create a new instance every time a Maybe.absent is constructed --- .../java/brooklyn/util/guava/IllegalStateExceptionSupplier.java | 2 ++ utils/common/src/main/java/brooklyn/util/guava/Maybe.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/common/src/main/java/brooklyn/util/guava/IllegalStateExceptionSupplier.java b/utils/common/src/main/java/brooklyn/util/guava/IllegalStateExceptionSupplier.java index 4fc85462ac..14ee6391e6 100644 --- a/utils/common/src/main/java/brooklyn/util/guava/IllegalStateExceptionSupplier.java +++ b/utils/common/src/main/java/brooklyn/util/guava/IllegalStateExceptionSupplier.java @@ -22,6 +22,8 @@ public class IllegalStateExceptionSupplier implements Supplier { + public static final IllegalStateExceptionSupplier EMPTY_EXCEPTION = new IllegalStateExceptionSupplier(); + protected final String message; protected final Throwable cause; diff --git a/utils/common/src/main/java/brooklyn/util/guava/Maybe.java b/utils/common/src/main/java/brooklyn/util/guava/Maybe.java index fb60bf8a9d..05bf2bd366 100644 --- a/utils/common/src/main/java/brooklyn/util/guava/Maybe.java +++ b/utils/common/src/main/java/brooklyn/util/guava/Maybe.java @@ -153,7 +153,7 @@ public static class Absent extends Maybe { private static final long serialVersionUID = -757170462010887057L; private final Supplier exception; public Absent() { - this(new IllegalStateExceptionSupplier()); + this(IllegalStateExceptionSupplier.EMPTY_EXCEPTION); } public Absent(Supplier exception) { this.exception = exception;