You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/11/30 12:50:24 UTC

[brooklyn-server] 02/02: fix bug where some feeds were not started because they were not persisted

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit a25cf3115033076550e489b4c54c0b703f915ccb
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Wed Nov 30 11:58:43 2022 +0000

    fix bug where some feeds were not started because they were not persisted
    
    previously we started them prior to the entity properly being up; now we delay that,
    but don't start if they weren't added as a persistable feeds.  most things should be
    persistable feeds, and now are.  other things we take more care to ensure started.
---
 .../org/apache/brooklyn/core/feed/AbstractFeed.java     |  5 +++++
 .../brooklyn/core/sensor/DurationSinceSensor.java       |  6 ++----
 .../brooklyn/core/sensor/function/FunctionSensor.java   |  6 ++----
 .../apache/brooklyn/core/workflow/WorkflowSensor.java   |  4 +---
 .../brooklyn/entity/group/DynamicClusterImpl.java       |  2 +-
 .../brooklyn/entity/group/DynamicMultiGroupImpl.java    |  2 +-
 .../apache/brooklyn/entity/stock/DataEntityImpl.java    |  2 +-
 .../org/apache/brooklyn/feed/function/FunctionFeed.java |  7 ++++++-
 .../java/org/apache/brooklyn/feed/http/HttpFeed.java    |  8 +++++---
 .../brooklyn/core/mgmt/rebind/RebindFeedTest.java       |  6 +++---
 .../brooklyn/entity/group/DynamicClusterTest.java       |  1 +
 .../apache/brooklyn/feed/function/FunctionFeedTest.java | 12 ++++++------
 .../brooklyn/container/entity/helm/HelmEntityImpl.java  | 14 +++++++-------
 .../brooklyn/policy/enricher/HttpLatencyDetector.java   | 17 ++++++++++++++---
 .../brooklyn/policy/enricher/RebindEnricherTest.java    | 11 ++++++++++-
 .../brooklyn/rest/resources/SensorResourceTest.java     |  2 +-
 .../entity/brooklynnode/BrooklynClusterImpl.java        |  4 ++--
 .../entity/brooklynnode/BrooklynEntityMirrorImpl.java   |  2 +-
 .../entity/software/base/SoftwareProcessImpl.java       |  2 +-
 .../apache/brooklyn/tasks/kubectl/ContainerSensor.java  |  4 ++--
 .../base/SoftwareProcessEntityFeedRebindTest.java       |  7 +++----
 .../software/base/lifecycle/ScriptHelperTest.java       |  2 +-
 22 files changed, 76 insertions(+), 50 deletions(-)

diff --git a/core/src/main/java/org/apache/brooklyn/core/feed/AbstractFeed.java b/core/src/main/java/org/apache/brooklyn/core/feed/AbstractFeed.java
index 43c4f7a69b..c4d4165b7f 100644
--- a/core/src/main/java/org/apache/brooklyn/core/feed/AbstractFeed.java
+++ b/core/src/main/java/org/apache/brooklyn/core/feed/AbstractFeed.java
@@ -64,7 +64,12 @@ public abstract class AbstractFeed extends AbstractEntityAdjunct implements Feed
 
     @Beta
     public static <T extends AbstractFeed> T initAndMaybeStart(T feed, Entity entity) {
+        return initAndMaybeStart(feed, entity, false);
+    }
+    @Beta
+    public static <T extends AbstractFeed> T initAndMaybeStart(T feed, Entity entity, boolean registerOnEntity) {
         feed.setEntity(checkNotNull((EntityInternal)entity, "entity"));
+        if (registerOnEntity) ((EntityInternal) entity).feeds().add(feed);
         if (Entities.isManagedActive(entity)) {
             // start it is entity is already managed (dynamic addition); otherwise rely on EntityManagementSupport to start us (initializer-based addition and after rebind)
             feed.start();
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/DurationSinceSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/DurationSinceSensor.java
index 2002d05088..a406c5f779 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/DurationSinceSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/DurationSinceSensor.java
@@ -74,13 +74,11 @@ public class DurationSinceSensor extends AddSensorInitializer<Duration> {
             entity.sensors().set(epochSensor, epoch);
         }
 
-        FunctionFeed feed = FunctionFeed.builder()
+        FunctionFeed.builder()
                 .entity(entity)
                 .poll(new FunctionPollConfig<>(sensor).callable(new UpdateTimeSince(entity, epochSensor, initParam(TIME_SUPPLIER))))
                 .period(initParam(SENSOR_PERIOD))
-                .build();
-
-        ((EntityInternal)entity).feeds().add(feed);
+                .build(true);
     }
 
     private static class CurrentTimeSupplier implements Supplier<Long> {
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java
index 16c7c05777..98611e1d5f 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java
@@ -83,10 +83,8 @@ public final class FunctionSensor<T> extends AbstractAddSensorFeed<T> {
                 .logWarningGraceTime(logWarningGraceTime)
                 .period(initParam(SENSOR_PERIOD));
 
-        FunctionFeed feed = FunctionFeed.builder().entity(entity)
+        FunctionFeed.builder().entity(entity)
                 .poll(pollConfig)
-                .build();
-
-        entity.addFeed(feed);
+                .build(true);
     }
 }
diff --git a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowSensor.java b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowSensor.java
index fd12e4cdab..9afd99dfd8 100644
--- a/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/workflow/WorkflowSensor.java
@@ -104,9 +104,7 @@ public final class WorkflowSensor<T> extends AbstractAddTriggerableSensor<T> imp
                 .onlyIfServiceUp(Maybe.ofDisallowingNull(EntityInitializers.resolve(params, ONLY_IF_SERVICE_UP)).or(false))
                 .poll(pollConfig);
 
-        FunctionFeed feed = feedBuilder.build();
-        wc.init(feed);
-        entity.addFeed(feed);
+        wc.init(feedBuilder.build(true));
     }
 
     @Override
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
index 69b8ca5ade..f19c9e0985 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
@@ -282,7 +282,7 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
                 .poll(new FunctionPollConfig<Boolean, Boolean>(CLUSTER_ONE_AND_ALL_MEMBERS_UP)
                         .onException(Functions.constant(Boolean.FALSE))
                         .callable(new ClusterOneAndAllMembersUpCallable(this)))
-                .build();
+                .build(true);
     }
 
     private static class ClusterOneAndAllMembersUpCallable implements Callable<Boolean> {
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroupImpl.java
index 7b187df98d..5f652a6039 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroupImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroupImpl.java
@@ -138,7 +138,7 @@ public class DynamicMultiGroupImpl extends DynamicGroupImpl implements DynamicMu
                                         return null;
                                     }
                                 }))
-                    .build();
+                    .build(true);
         }
     }
 
diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/DataEntityImpl.java b/core/src/main/java/org/apache/brooklyn/entity/stock/DataEntityImpl.java
index cc6d8d7ce7..4ae54337c5 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/stock/DataEntityImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/stock/DataEntityImpl.java
@@ -71,7 +71,7 @@ public class DataEntityImpl extends AbstractEntity implements DataEntity {
             }
         }
 
-        feed = builder.build();
+        feed = builder.build(true);  // register because connect not called on rebing
     }
 
     protected void disconnectSensors() {
diff --git a/core/src/main/java/org/apache/brooklyn/feed/function/FunctionFeed.java b/core/src/main/java/org/apache/brooklyn/feed/function/FunctionFeed.java
index c9119d13c7..2750570f94 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/function/FunctionFeed.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/function/FunctionFeed.java
@@ -134,8 +134,13 @@ public class FunctionFeed extends AbstractFeed {
             return this;
         }
         public FunctionFeed build() {
+            return build(false);
+        }
+        /** builds the feed, optionally registering it on entity. that should be done unless either the caller does it with addFeed (but no hard in adding twice),
+         * or if it should not be persisted and it is guaranteed to be re-added on rebind, as is done with SoftwareProcess connectSensors */
+        public FunctionFeed build(boolean registerOnEntity) {
             built = true;
-            return AbstractFeed.initAndMaybeStart(new FunctionFeed(this), entity);
+            return AbstractFeed.initAndMaybeStart(new FunctionFeed(this), entity, registerOnEntity);
         }
         @Override
         protected void finalize() {
diff --git a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
index 0590450bbc..7cee560a45 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
@@ -259,14 +259,16 @@ public class HttpFeed extends AbstractFeed {
             }
         }
         public HttpFeed build() {
-            return build(null);
+            return build(false, null);
         }
-        /** normally no arg is required, but if feed is not attached to entity, it will need starting here */
-        public HttpFeed build(Boolean feedStart) {
+
+        /** normally no arg needed, but can pass true for first to register, and null for latter to auto-start at the right time, or true to force start */
+        public HttpFeed build(boolean registerOnEntity, Boolean feedStart) {
             built = true;
             HttpFeed result = new HttpFeed(this);
             result.setEntity(checkNotNull((EntityLocal)entity, "entity"));
             if (suspended) result.suspend();
+            if (registerOnEntity) entity.addFeed(result);
             if ((feedStart==null && Entities.isManagedActive(entity)) || Boolean.TRUE.equals(feedStart)) {
                 // this feed is used a lot without being attached to an entity, not ideal, but let's support it
                 result.start();
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFeedTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFeedTest.java
index 68e6f7826d..e38f50b9bd 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFeedTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFeedTest.java
@@ -295,18 +295,18 @@ public class RebindFeedTest extends RebindTestFixtureWithApp {
         @Override
         public void init() {
             super.init();
-            addFeed(FunctionFeed.builder()
+            FunctionFeed.builder()
                     .entity(this)
                     .poll(FunctionPollConfig.forSensor(SENSOR_INT)
                             .period(POLL_PERIOD)
                             .callable(Callables.returning(1)))
-                    .build());
+                    .build(true);
             addFeed(FunctionFeed.builder()
                     .entity(this)
                     .poll(FunctionPollConfig.forSensor(SENSOR_STRING)
                             .period(POLL_PERIOD)
                             .callable(Callables.returning("OK")))
-                    .build());
+                    .build());  // should be identical to above build(true)
         }
     }
     
diff --git a/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java b/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
index 1984a0fde5..51fc29b632 100644
--- a/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
+++ b/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
@@ -382,6 +382,7 @@ public class DynamicClusterTest extends AbstractDynamicClusterOrFabricTest {
         assertEquals(cluster.getCurrentSize(), (Integer)2);
         assertEquals(cluster.getMembers().size(), 2);
         assertEquals(cluster.getAttribute(Changeable.GROUP_SIZE), (Integer)2);
+        EntityAsserts.assertAttributeEqualsEventually(cluster, DynamicCluster.CLUSTER_ONE_AND_ALL_MEMBERS_UP, true);
     }
 
     @Test
diff --git a/core/src/test/java/org/apache/brooklyn/feed/function/FunctionFeedTest.java b/core/src/test/java/org/apache/brooklyn/feed/function/FunctionFeedTest.java
index 820757e409..e044756d9c 100644
--- a/core/src/test/java/org/apache/brooklyn/feed/function/FunctionFeedTest.java
+++ b/core/src/test/java/org/apache/brooklyn/feed/function/FunctionFeedTest.java
@@ -95,7 +95,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .callable(new IncrementingCallable())
                         //.onSuccess((Function<Object,Integer>)(Function)Functions.identity()))
                         )
-                .build();
+                .build(false);  // deliberately don't support rebind
         
         Asserts.succeedsEventually(new Runnable() {
             @Override
@@ -145,7 +145,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .period(1)
                         .callable(Callables.returning(123))
                         .onSuccess(new AddOneFunction()))
-                .build();
+                .build(true);  // recommended
 
         EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, 124);
     }
@@ -160,7 +160,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .period(1)
                         .callable(new ExceptionCallable(errMsg))
                         .onException(new ToStringFunction()))
-                .build();
+                .build(true);  // recommended
 
         Asserts.succeedsEventually(new Runnable() {
             @Override
@@ -181,7 +181,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .checkSuccess(Predicates.alwaysFalse())
                         .onSuccess(new AddOneFunction())
                         .onFailure(Functions.constant(-1)))
-                .build();
+                .build(true);  // recommended
 
         EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, -1);
     }
@@ -196,7 +196,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .checkSuccess(Predicates.alwaysFalse())
                         .onSuccess(new AddOneFunction())
                         .onException(Functions.constant(-1)))
-                .build();
+                .build(true);  // recommended
 
         EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, -1);
     }
@@ -225,7 +225,7 @@ public class FunctionFeedTest extends BrooklynAppUnitTestSupport {
                         .period(10)
                         .callable(incrementingCallable)
                         .onSuccess(new ToStringFunction()))
-                .build();
+                .build(true);  // recommended
 
         Asserts.succeedsEventually(new Runnable() {
             @Override
diff --git a/locations/container/src/main/java/org/apache/brooklyn/container/entity/helm/HelmEntityImpl.java b/locations/container/src/main/java/org/apache/brooklyn/container/entity/helm/HelmEntityImpl.java
index 8f3b889547..e73a4f3722 100644
--- a/locations/container/src/main/java/org/apache/brooklyn/container/entity/helm/HelmEntityImpl.java
+++ b/locations/container/src/main/java/org/apache/brooklyn/container/entity/helm/HelmEntityImpl.java
@@ -124,17 +124,17 @@ public class HelmEntityImpl extends AbstractEntity implements HelmEntity {
                 .configure(FunctionSensor.FUNCTION, status));
         initializer.apply(this);
 
-        addFeed(FunctionFeed.builder()
+        FunctionFeed.builder()
                 .entity(this)
                 .poll(new FunctionPollConfig<String, List<String>>(DEPLOYMENTS).callable(getKubeDeploymentsCallable()))
                 .period(Duration.TEN_SECONDS)
-                .build());
+                .build(true);
 
-        addFeed(FunctionFeed.builder()
+        FunctionFeed.builder()
                 .entity(this)
                 .poll(new FunctionPollConfig<String, List<String>>(SERVICES).callable(getKubeServicesCallable()))
                 .period(Duration.TEN_SECONDS)
-                .build());
+                .build(true);
     }
 
 
@@ -144,11 +144,11 @@ public class HelmEntityImpl extends AbstractEntity implements HelmEntity {
                 .callable(status)
                 ;
 
-        addFeed(FunctionFeed.builder()
+        FunctionFeed.builder()
                 .entity(this)
                 .poll(pollConfig)
                 .period(Duration.FIVE_SECONDS)
-                .build());
+                .build(true);
     }
 
     private void connectServiceUpIsRunning() {
@@ -160,7 +160,7 @@ public class HelmEntityImpl extends AbstractEntity implements HelmEntity {
                         .suppressDuplicates(true)
                         .onException(Functions.constant(Boolean.FALSE))
                         .callable(() -> isRunning()))
-                .build();
+                .build(true);  // only called at start so needs to be registered
     }
 
 
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/enricher/HttpLatencyDetector.java b/policy/src/main/java/org/apache/brooklyn/policy/enricher/HttpLatencyDetector.java
index d8a62740ba..d76c1fa2ea 100644
--- a/policy/src/main/java/org/apache/brooklyn/policy/enricher/HttpLatencyDetector.java
+++ b/policy/src/main/java/org/apache/brooklyn/policy/enricher/HttpLatencyDetector.java
@@ -148,17 +148,28 @@ public class HttpLatencyDetector extends AbstractEnricher implements Enricher {
                 .period(getConfig(PERIOD))
                 .baseUri(Suppliers.compose(Urls.stringToUriFunction(), AtomicReferences.supplier(url)))
                 .poll(new HttpPollConfig<Double>(REQUEST_LATENCY_IN_SECONDS_MOST_RECENT)
-                        .onResult(new ComputeLatencyAndRecordError())
+                        .onResult(new ComputeLatencyAndRecordError(entity))
                         .setOnException(null))
                 .suspended()
-                .build(true);
+
+                // don't need to register on entity because we are created whenever we restart; do force start though, because during rebind it needs to start when enricher starts
+//                .build(false, true);
+
+                // alternatively we can register on entity and use its default start logic
+                .build(true, null);
 
         if (getUniqueTag()==null) 
             uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+
                 (getConfig(URL)!=null ? getConfig(URL) : getConfig(URL_SENSOR));
     }
 
-    class ComputeLatencyAndRecordError implements Function<HttpToolResponse, Double> {
+    static class ComputeLatencyAndRecordError implements Function<HttpToolResponse, Double> {
+        final Entity entity;
+
+        ComputeLatencyAndRecordError(Entity entity) {
+            this.entity = entity;
+        }
+
         @Override
         public @Nullable Double apply(@Nullable HttpToolResponse input) {
             entity.sensors().set(Sensors.newSensor(Integer.class, "web.request.latencyDetector.lastCode"), input.getResponseCode());
diff --git a/policy/src/test/java/org/apache/brooklyn/policy/enricher/RebindEnricherTest.java b/policy/src/test/java/org/apache/brooklyn/policy/enricher/RebindEnricherTest.java
index 975fd9fbda..4690304823 100644
--- a/policy/src/test/java/org/apache/brooklyn/policy/enricher/RebindEnricherTest.java
+++ b/policy/src/test/java/org/apache/brooklyn/policy/enricher/RebindEnricherTest.java
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Dumper;
 import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
 import org.apache.brooklyn.core.sensor.Sensors;
@@ -85,12 +86,20 @@ public class RebindEnricherTest extends RebindTestFixtureWithApp {
                 .url(baseUrl)
                 .buildSpec());
         origApp.sensors().set(Attributes.SERVICE_UP, true);
-        
+
+        Time.sleep(2000);
+        Dumper.dumpInfo(origApp);
+        EntityAsserts.assertAttribute(origApp, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_MOST_RECENT, x -> x!=null);
+
         TestApplication newApp = rebind();
 
         newApp.sensors().set(HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_MOST_RECENT, null);
         newApp.sensors().set(HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW, null);
 
+        Time.sleep(2000);
+        Dumper.dumpInfo(newApp);
+        EntityAsserts.assertAttribute(newApp, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_MOST_RECENT, x -> x!=null);
+
         EntityAsserts.assertAttributeEventuallyNonNull(newApp, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_MOST_RECENT);
         EntityAsserts.assertAttributeEventuallyNonNull(newApp, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW);
     }
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
index 92676a5d79..d5f3e19112 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
@@ -355,7 +355,7 @@ public class SensorResourceTest extends BrooklynRestResourceTest {
     
     @Test
     public void testGetSensorValueOfTypeFeed() throws Exception {
-        Feed feed = entity.feeds().add(FunctionFeed.builder().entity(entity).build());
+        Feed feed = entity.feeds().add(FunctionFeed.builder().entity(entity).build(true));
         entity.sensors().set(Sensors.newSensor(Feed.class, "myFeed"), feed);
         doGetSensorTest("myFeed", Map.class, ImmutableMap.of("type", Feed.class.getName(), "id", feed.getId(), "entityId", entity.getId()));
     }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
index da42fe9338..d223040368 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynClusterImpl.java
@@ -56,12 +56,12 @@ public class BrooklynClusterImpl extends DynamicClusterImpl implements BrooklynC
         getMutableEntityType().addEffector(BrooklynClusterUpgradeEffectorBody.UPGRADE_CLUSTER);
 
         ServiceProblemsLogic.updateProblemsIndicator(this, MASTER_NODE, MSG_NO_MASTER);
-        addFeed(FunctionFeed.builder()
+        FunctionFeed.builder()
                 .entity(this)
                 .poll(new FunctionPollConfig<Object, BrooklynNode>(MASTER_NODE)
                         .period(Duration.ONE_SECOND)
                         .callable(new MasterChildFinder()))
-                .build());
+                .build(true);
         
         enrichers().add( Enrichers.builder().transforming(MASTER_NODE)
             .uniqueTag("master-node-web-uri")
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
index 7ff2c3a03c..1253255187 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java
@@ -140,7 +140,7 @@ public class BrooklynEntityMirrorImpl extends AbstractEntity implements Brooklyn
                     }
                 }))
             .poll(HttpPollConfig.forSensor(MIRROR_SUMMARY).onSuccess(new MirrorSummary()))
-            .build(true);
+            .build(false, true);  //connectSensors will re-run so don't add as feed (we probably could, but preserving old behavior)
 
         populateEffectors();
     }
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
index faaff23662..891a5fcd3f 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcessImpl.java
@@ -303,7 +303,7 @@ public abstract class SoftwareProcessImpl extends AbstractEntity implements Soft
                                 return getDriver().isRunning();
                             }
                         }))
-                .build();
+                .build(false);
     }
 
     /**
diff --git a/software/base/src/main/java/org/apache/brooklyn/tasks/kubectl/ContainerSensor.java b/software/base/src/main/java/org/apache/brooklyn/tasks/kubectl/ContainerSensor.java
index 240550513b..54eeec6139 100644
--- a/software/base/src/main/java/org/apache/brooklyn/tasks/kubectl/ContainerSensor.java
+++ b/software/base/src/main/java/org/apache/brooklyn/tasks/kubectl/ContainerSensor.java
@@ -68,12 +68,12 @@ public class ContainerSensor<T> extends AbstractAddTriggerableSensor<T> implemen
                 .callable(new ContainerSensorCallable(entity, configBag, sensor));
         standardPollConfig(entity, configBag, poll);
 
-        ((EntityInternal) entity).feeds().add(FunctionFeed.builder()
+        FunctionFeed.builder()
                 .name("Container Sensor Feed: "+initParam(SENSOR_NAME))
                 .entity(entity)
                 .onlyIfServiceUp(Maybe.ofDisallowingNull(EntityInitializers.resolve(initParams(), ONLY_IF_SERVICE_UP)).or(false))
                 .poll(poll)
-                .build());
+                .build(true);
     }
 
     public static class ContainerSensorCallable implements Callable<Object> {
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityFeedRebindTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityFeedRebindTest.java
index 64fbbfd8c9..c7daa9a227 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityFeedRebindTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SoftwareProcessEntityFeedRebindTest.java
@@ -111,6 +111,7 @@ public class SoftwareProcessEntityFeedRebindTest extends RebindTestFixtureWithAp
             // It could happen that driver.isRunning() returns true, but the feed for SERVICE_PROCESS_IS_RUNNING
             // still didn't have the chance to execute so the sensor is still null.
             EntityAsserts.assertAttributeEqualsEventually(child, SoftwareProcess.SERVICE_PROCESS_IS_RUNNING, Boolean.TRUE);
+            EntityAsserts.assertAttributeChangesEventually(child, MyServiceWithFeeds.COUNTER);
         }
 
         LOG.info("Rebinding "+numEntities+" entities");
@@ -160,7 +161,6 @@ public class SoftwareProcessEntityFeedRebindTest extends RebindTestFixtureWithAp
         protected RecordingSensorEventListener<Lifecycle> stateListener;
         protected RecordingSensorEventListener<Boolean> upListener;
         protected RecordingSensorEventListener<Boolean> processRunningListener;
-        protected FunctionFeed functionFeed;
         protected boolean feedCalledWhenNotManaged;
         
         @Override
@@ -195,8 +195,7 @@ public class SoftwareProcessEntityFeedRebindTest extends RebindTestFixtureWithAp
         public void init() {
             super.init();
             
-            // By calling feeds().add(...), it will persist the feed, and rebind it
-            functionFeed = feeds().add(FunctionFeed.builder()
+            FunctionFeed.builder()
                     .entity(this)
                     .period(Duration.millis(10))
                     .uniqueTag("MyserviceWithFeeds-functionFeed")
@@ -214,7 +213,7 @@ public class SoftwareProcessEntityFeedRebindTest extends RebindTestFixtureWithAp
                                     return (oldVal == null ? 0 : oldVal) + 1;
                                 }
                             }))
-                    .build());
+                    .build(true); // persist the feed so it can be rebinded
             
             subscribeToServiceState();
         }
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java
index 3ce1e30e82..098bf640e7 100644
--- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/lifecycle/ScriptHelperTest.java
@@ -115,7 +115,7 @@ public class ScriptHelperTest extends BrooklynAppUnitTestSupport {
                             return getDriver().isRunning();
                         }
                     }))
-                .build();
+                .build(false);
         }
     }