/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.core.mgmt.rebind;
import static org.apache.brooklyn.test.EntityTestUtils.assertAttributeEquals;
import static org.apache.brooklyn.test.EntityTestUtils.assertConfigEquals;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.entity.ImplementedBy;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
import org.apache.brooklyn.api.mgmt.rebind.RebindContext;
import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoManifest;
import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.AttributeSensor.SensorPersistenceMode;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.core.entity.trait.Resizable;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.core.location.LocationConfigTest.MyLocation;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
import org.apache.brooklyn.core.sensor.BasicSensorEvent;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.core.test.entity.TestEntityImpl;
import org.apache.brooklyn.entity.group.AbstractGroupImpl;
import org.apache.brooklyn.entity.group.BasicGroup;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
import org.apache.brooklyn.util.time.Durations;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.base.Objects;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class RebindEntityTest extends RebindTestFixtureWithApp {
// FIXME Add test about dependent configuration serialization?!
// TODO Convert, so not calling entity constructors
@Test
public void testRestoresSimpleApp() throws Exception {
newApp = rebind();
assertNotSame(newApp, origApp);
assertEquals(newApp.getId(), origApp.getId());
}
@Test
public void testRestoresEntityHierarchy() throws Exception {
TestEntity origE = origApp.createAndManageChild(EntitySpec.create(TestEntity.class));
TestEntity origE2 = origE.createAndManageChild(EntitySpec.create(TestEntity.class));
newApp = rebind();
// Assert has expected config/fields
assertEquals(newApp.getId(), origApp.getId());
assertEquals(newApp.getChildren().size(), 1);
TestEntity newE = (TestEntity) Iterables.get(newApp.getChildren(), 0);
assertEquals(newE.getId(), origE.getId());
assertEquals(newE.getChildren().size(), 1);
TestEntity newE2 = (TestEntity) Iterables.get(newE.getChildren(), 0);
assertEquals(newE2.getId(), origE2.getId());
assertNotSame(origApp, newApp);
assertNotSame(origApp.getManagementContext(), newApp.getManagementContext());
assertNotSame(origE, newE);
assertNotSame(origE2, newE2);
}
@Test
public void testRestoresGroupMembers() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
MyEntity origE2 = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
BasicGroup origG = origApp.createAndManageChild(EntitySpec.create(BasicGroup.class));
origG.addMember(origE);
origG.addMember(origE2);
newApp = rebind();
BasicGroup newG = (BasicGroup) Iterables.find(newApp.getChildren(), Predicates.instanceOf(BasicGroup.class));
Iterable<Entity> newEs = Iterables.filter(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(ImmutableSet.copyOf(newG.getMembers()), ImmutableSet.copyOf(newEs));
}
@Test
public void testRestoresEntityConfig() throws Exception {
origApp.createAndManageChild(EntitySpec.create(MyEntity.class).configure("myconfig", "myval"));
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myval");
}
@Test
public void testRestoresEntityDependentConfigCompleted() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class)
.configure("myconfig", DependentConfiguration.attributeWhenReady(origApp, TestApplication.MY_ATTRIBUTE)));
origApp.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
origE.getConfig(MyEntity.MY_CONFIG); // wait for it to be done
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myval");
}
@Test(enabled=false) // not yet supported
public void testRestoresEntityDependentConfigUncompleted() throws Exception {
origApp.createAndManageChild(EntitySpec.create(MyEntity.class)
.configure("myconfig", DependentConfiguration.attributeWhenReady(origApp, TestApplication.MY_ATTRIBUTE)));
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
newApp.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myval");
}
@Test
public void testRestoresEntitySensors() throws Exception {
AttributeSensor<String> myCustomAttribute = Sensors.newStringSensor("my.custom.attribute");
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
origE.sensors().set(myCustomAttribute, "myval");
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(newE.getAttribute(myCustomAttribute), "myval");
}
@Test
public void testRestoresEntityLocationAndCleansUp() throws Exception {
MyLocation loc = origManagementContext.getLocationManager().createLocation(LocationSpec.create(MyLocation.class));
origApp.createAndManageChild(EntitySpec.create(MyEntity.class).location(loc));
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
Assert.assertEquals(newE.getLocations().size(), 1);
Location loc2 = Iterables.getOnlyElement(newE.getLocations());
Assert.assertEquals(loc, loc2);
Assert.assertFalse(loc==loc2);
newApp.stop();
// TODO how to trigger automatic unmanagement? see notes in RebindLocalhostLocationTest
newManagementContext.getLocationManager().unmanage(loc2);
switchOriginalToNewManagementContext();
RebindTestUtils.waitForPersisted(origManagementContext);
BrooklynMementoManifest mf = loadMementoManifest();
Assert.assertTrue(mf.getLocationIdToType().isEmpty(), "Expected no locations; had "+mf.getLocationIdToType());
}
@Test
public void testRestoresEntityIdAndDisplayName() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class)
.displayName("mydisplayname")
.configure("iconUrl", "file:///tmp/myicon.png"));
String eId = origE.getId();
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(newE.getId(), eId);
assertEquals(newE.getDisplayName(), "mydisplayname");
}
// Saw this fail during development (fixed now); but want at least one of these tests to be run
// many times for stress testing purposes
@Test(invocationCount=100, groups="Integration")
public void testRestoresEntityIdAndDisplayNameManyTimes() throws Exception {
testRestoresEntityIdAndDisplayName();
}
@Test
public void testCanCustomizeRebind() throws Exception {
MyEntity2 origE = origApp.createAndManageChild(EntitySpec.create(MyEntity2.class).configure("myfield", "myval"));
newApp = rebind();
MyEntity2 newE = (MyEntity2) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity2.class));
assertEquals(newE.getMyfield(), "myval");
Assert.assertEquals(newE, origE);
}
@Test
public void testRebindsSubscriptions() throws Exception {
MyEntity2 origE = origApp.createAndManageChild(EntitySpec.create(MyEntity2.class).configure("subscribe", true));
newApp = rebind();
MyEntity2 newE = (MyEntity2) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity2.class));
newApp.sensors().set(TestApplication.MY_ATTRIBUTE, "mysensorval");
Asserts.eventually(Suppliers.ofInstance(newE.getEvents()), Predicates.<List<String>>equalTo(ImmutableList.of("mysensorval")));
Assert.assertEquals(newE, origE);
}
@Test
public void testHandlesReferencingOtherEntities() throws Exception {
MyEntity origOtherE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
MyEntityReffingOthers origE = origApp.createAndManageChild(EntitySpec.create(MyEntityReffingOthers.class)
.configure("entityRef", origOtherE));
origE.sensors().set(MyEntityReffingOthers.ENTITY_REF_SENSOR, origOtherE);
newApp = rebind();
MyEntityReffingOthers newE = (MyEntityReffingOthers) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntityReffingOthers.class));
MyEntity newOtherE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertAttributeEquals(newE, MyEntityReffingOthers.ENTITY_REF_SENSOR, newOtherE);
assertConfigEquals(newE, MyEntityReffingOthers.ENTITY_REF_CONFIG, newOtherE);
}
@Test
public void testHandlesReferencingOtherEntitiesInPojoField() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
ReffingEntity reffer = new ReffingEntity();
reffer.obj = origE;
reffer.entity = origE;
reffer.myEntity = origE;
origApp.config().set(TestEntity.CONF_OBJECT, reffer);
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
ReffingEntity reffer2 = (ReffingEntity)newApp.getConfig(TestEntity.CONF_OBJECT);
assertEquals(reffer2.myEntity, newE);
assertEquals(reffer2.entity, newE);
assertEquals(reffer2.obj, newE);
}
// Where the same object is referenced from two different fields, using types that do not share a
// super type... then the object will just be deserialized once - at that point it must have *both*
// interfaces.
@Test(groups="WIP")
public void testHandlesReferencingOtherEntityInPojoFieldsOfOtherTypes() throws Exception {
MyEntityWithMultipleInterfaces origE = origApp.createAndManageChild(EntitySpec.create(MyEntityWithMultipleInterfaces.class));
ReffingEntity reffer = new ReffingEntity();
reffer.group = origE;
reffer.resizable = origE;
origApp.config().set(TestEntity.CONF_OBJECT, reffer);
newApp = rebind();
MyEntityWithMultipleInterfaces newE = (MyEntityWithMultipleInterfaces) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntityWithMultipleInterfaces.class));
ReffingEntity newReffer = (ReffingEntity)newApp.getConfig(TestEntity.CONF_OBJECT);
assertEquals(newReffer.group, newE);
assertEquals(newReffer.resizable, newE);
}
@Test
public void testEntityTags() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
origE.tags().addTag("foo");
origE.tags().addTag(origApp);
newApp = rebind();
MyEntity newE = Iterables.getOnlyElement( Entities.descendants(newApp, MyEntity.class) );
assertTrue(newE.tags().containsTag("foo"), "tags are "+newE.tags().getTags());
assertFalse(newE.tags().containsTag("bar"));
assertTrue(newE.tags().containsTag(newE.getParent()));
assertTrue(newE.tags().containsTag(origApp));
assertEquals(newE.tags().getTags(), MutableSet.of("foo", newE.getParent()));
}
public static class ReffingEntity {
public Group group;
public Resizable resizable;
public MyEntity myEntity;
public Entity entity;
public Object obj;
@Override
public boolean equals(Object o) {
return (o instanceof ReffingEntity) && Objects.equal(entity, ((ReffingEntity)o).entity)
&& Objects.equal(obj, ((ReffingEntity)o).obj) && Objects.equal(group, ((ReffingEntity)o).group)
&& Objects.equal(resizable, ((ReffingEntity)o).resizable);
}
@Override
public int hashCode() {
return Objects.hashCode(entity, obj);
}
}
@Test
public void testHandlesReferencingOtherLocations() throws Exception {
MyLocation origLoc = new MyLocation();
MyEntityReffingOthers origE = origApp.createAndManageChild(EntitySpec.create(MyEntityReffingOthers.class)
.configure("locationRef", origLoc));
origE.sensors().set(MyEntityReffingOthers.LOCATION_REF_SENSOR, origLoc);
origApp.start(ImmutableList.of(origLoc));
newApp = rebind();
MyEntityReffingOthers newE = (MyEntityReffingOthers) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntityReffingOthers.class));
MyLocation newLoc = (MyLocation) Iterables.getOnlyElement(newApp.getLocations());
assertAttributeEquals(newE, MyEntityReffingOthers.LOCATION_REF_SENSOR, newLoc);
assertConfigEquals(newE, MyEntityReffingOthers.LOCATION_REF_CONFIG, newLoc);
}
@Test
public void testEntityManagementLifecycleAndVisibilityDuringRebind() throws Exception {
MyLatchingEntityImpl.latching = false;
MyLatchingEntity origE = origApp.createAndManageChild(EntitySpec.create(MyLatchingEntity.class));
MyLatchingEntityImpl.reset(); // after origE has been managed
MyLatchingEntityImpl.latching = true;
// Serialize and rebind, but don't yet manage the app
RebindTestUtils.waitForPersisted(origApp);
RebindTestUtils.checkCurrentMementoSerializable(origApp);
newManagementContext = RebindTestUtils.newPersistingManagementContextUnstarted(mementoDir, classLoader);
Thread thread = new Thread() {
public void run() {
try {
newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
};
try {
thread.start();
assertTrue(Durations.await(MyLatchingEntityImpl.reconstructStartedLatch, TIMEOUT_MS));
assertNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
assertNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
assertTrue(MyLatchingEntityImpl.managingStartedLatch.getCount() > 0);
MyLatchingEntityImpl.reconstructContinuesLatch.countDown();
assertTrue(Durations.await(MyLatchingEntityImpl.managingStartedLatch, TIMEOUT_MS));
assertNotNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
assertNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
assertTrue(MyLatchingEntityImpl.managedStartedLatch.getCount() > 0);
MyLatchingEntityImpl.managingContinuesLatch.countDown();
assertTrue(Durations.await(MyLatchingEntityImpl.managedStartedLatch, TIMEOUT_MS));
assertNotNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
assertNotNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
MyLatchingEntityImpl.managedContinuesLatch.countDown();
Durations.join(thread, TIMEOUT_MS);
assertFalse(thread.isAlive());
} finally {
thread.interrupt();
MyLatchingEntityImpl.reset();
}
}
@Test(groups="Integration") // takes more than 4 seconds, due to assertContinually calls
public void testSubscriptionAndPublishingOnlyActiveWhenEntityIsManaged() throws Exception {
MyLatchingEntityImpl.latching = false;
origApp.createAndManageChild(EntitySpec.create(MyLatchingEntity.class)
.configure("subscribe", TestApplication.MY_ATTRIBUTE)
.configure("publish", "myvaltopublish"));
MyLatchingEntityImpl.reset(); // after origE has been managed
MyLatchingEntityImpl.latching = true;
// Serialize and rebind, but don't yet manage the app
RebindTestUtils.waitForPersisted(origApp);
RebindTestUtils.checkCurrentMementoSerializable(origApp);
newManagementContext = new LocalManagementContext();
Thread thread = new Thread() {
public void run() {
try {
RebindTestUtils.rebind(RebindOptions.create()
.newManagementContext(newManagementContext)
.mementoDir(mementoDir)
.classLoader(RebindEntityTest.class.getClassLoader()));
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
};
try {
thread.start();
final List<Object> events = new CopyOnWriteArrayList<Object>();
newManagementContext.getSubscriptionManager().subscribe(null, MyLatchingEntityImpl.MY_SENSOR, new SensorEventListener<Object>() {
@Override public void onEvent(SensorEvent<Object> event) {
events.add(event.getValue());
}});
// In entity's reconstruct, publishes events are queued, and subscriptions don't yet take effect
assertTrue(Durations.await(MyLatchingEntityImpl.reconstructStartedLatch, TIMEOUT_MS));
newManagementContext.getSubscriptionManager().publish(new BasicSensorEvent<String>(TestApplication.MY_ATTRIBUTE, null, "myvaltooearly"));
Asserts.continually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(Collections.emptyList()));
Asserts.continually(Suppliers.ofInstance(events), Predicates.equalTo(Collections.emptyList()));
// When the entity is notified of "managing", then subscriptions take effect (but missed events not delivered);
// published events remain queued
MyLatchingEntityImpl.reconstructContinuesLatch.countDown();
assertTrue(MyLatchingEntityImpl.managingStartedLatch.getCount() > 0);
Asserts.continually(Suppliers.ofInstance(events), Predicates.equalTo(Collections.emptyList()));
Asserts.continually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(Collections.emptyList()));
newManagementContext.getSubscriptionManager().publish(new BasicSensorEvent<String>(TestApplication.MY_ATTRIBUTE, null, "myvaltoreceive"));
Asserts.eventually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.<List<Object>>equalTo(ImmutableList.of((Object)"myvaltoreceive")));
// When the entity is notified of "managed", its events are only then delivered
MyLatchingEntityImpl.managingContinuesLatch.countDown();
assertTrue(Durations.await(MyLatchingEntityImpl.managedStartedLatch, TIMEOUT_MS));
Asserts.eventually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.<List<Object>>equalTo(ImmutableList.of((Object)"myvaltoreceive")));
MyLatchingEntityImpl.managedContinuesLatch.countDown();
Durations.join(thread, TIMEOUT_MS);
assertFalse(thread.isAlive());
} finally {
thread.interrupt();
MyLatchingEntityImpl.reset();
}
}
@Test
public void testRestoresConfigKeys() throws Exception {
origApp.createAndManageChild(EntitySpec.create(TestEntity.class)
.configure(TestEntity.CONF_NAME, "nameval")
.configure(TestEntity.CONF_LIST_PLAIN, ImmutableList.of("val1", "val2"))
.configure(TestEntity.CONF_MAP_PLAIN, ImmutableMap.of("akey", "aval")));
newApp = rebind();
final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
assertEquals(newE.getConfig(TestEntity.CONF_NAME), "nameval");
assertEquals(newE.getConfig(TestEntity.CONF_LIST_PLAIN), ImmutableList.of("val1", "val2"));
assertEquals(newE.getConfig(TestEntity.CONF_MAP_PLAIN), ImmutableMap.of("akey", "aval"));
}
@Test // ListConfigKey deprecated, as order no longer guaranteed
public void testRestoresListConfigKey() throws Exception {
origApp.createAndManageChild(EntitySpec.create(TestEntity.class)
.configure(TestEntity.CONF_LIST_THING.subKey(), "val1")
.configure(TestEntity.CONF_LIST_THING.subKey(), "val2"));
newApp = rebind();
final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
//assertEquals(newE.getConfig(TestEntity.CONF_LIST_THING), ImmutableList.of("val1", "val2"));
assertEquals(ImmutableSet.copyOf(newE.getConfig(TestEntity.CONF_LIST_THING)), ImmutableSet.of("val1", "val2"));
}
@Test
public void testRestoresSetConfigKey() throws Exception {
origApp.createAndManageChild(EntitySpec.create(TestEntity.class)
.configure(TestEntity.CONF_SET_THING.subKey(), "val1")
.configure(TestEntity.CONF_SET_THING.subKey(), "val2"));
newApp = rebind();
final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
assertEquals(newE.getConfig(TestEntity.CONF_SET_THING), ImmutableSet.of("val1", "val2"));
}
@Test
public void testRestoresMapConfigKey() throws Exception {
origApp.createAndManageChild(EntitySpec.create(TestEntity.class)
.configure(TestEntity.CONF_MAP_THING.subKey("akey"), "aval")
.configure(TestEntity.CONF_MAP_THING.subKey("bkey"), "bval"));
newApp = rebind();
final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
assertEquals(newE.getConfig(TestEntity.CONF_MAP_THING), ImmutableMap.of("akey", "aval", "bkey", "bval"));
}
@Test
public void testRebindPreservesInheritedConfig() throws Exception {
origApp.config().set(MyEntity.MY_CONFIG, "myValInSuper");
origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
// rebind: inherited config is preserved
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
assertEquals(newApp.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
// This config should be inherited by dynamically-added children of app
MyEntity newE2 = newApp.createAndManageChild(EntitySpec.create(MyEntity.class));
assertEquals(newE2.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
}
@Test
public void testRebindPreservesGetConfigWithDefault() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
assertNull(origE.getConfig(MyEntity.MY_CONFIG));
assertEquals(origE.getConfigRaw(MyEntity.MY_CONFIG, true).or("mydefault"), "mydefault");
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertNull(newE.getConfig(MyEntity.MY_CONFIG));
assertEquals(newE.getConfigRaw(MyEntity.MY_CONFIG, true).or("mydefault"), "mydefault");
}
@Test
public void testRestoresUnmatchedConfig() throws Exception {
TestEntity origE = origApp.createAndManageChild(EntitySpec.create(TestEntity.class)
.configure("myunmatchedkey", "myunmatchedval"));
origE.createAndManageChild(EntitySpec.create(TestEntity.class));
newApp = rebind();
final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
final TestEntity newChildE = (TestEntity) Iterables.find(newE.getChildren(), Predicates.instanceOf(TestEntity.class));
assertEquals(newE.config().getBag().getStringKey("myunmatchedkey"), "myunmatchedval");
assertEquals(newE.config().getLocalBag().getStringKey("myunmatchedkey"), "myunmatchedval");
assertEquals(newChildE.config().getBag().getStringKey("myunmatchedkey"), "myunmatchedval");
assertFalse(newChildE.config().getLocalBag().containsKey("myunmatchedkey"));
}
@Test
public void testRebindPersistsNullAttribute() throws Exception {
MyEntity origE = origApp.createAndManageChild(EntitySpec.create(MyEntity.class));
origE.sensors().set(MyEntity.MY_SENSOR, null);
assertNull(origE.getAttribute(MyEntity.MY_SENSOR));
newApp = rebind();
MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
assertNull(newE.getAttribute(MyEntity.MY_SENSOR));
}
@Test
public void testRebindPersistsDynamicAttribute() throws Exception {
final String sensorName = "test.mydynamicsensor";
final String sensorDescription = "My description";
final AttributeSensor<String> MY_DYNAMIC_SENSOR = new BasicAttributeSensor<String>(
String.class, sensorName, sensorDescription);
origApp.sensors().set(MY_DYNAMIC_SENSOR, "myval");
assertEquals(origApp.getEntityType().getSensor(sensorName).getDescription(), sensorDescription);
newApp = rebind();
assertEquals(newApp.getAttribute(MY_DYNAMIC_SENSOR), "myval");
assertEquals(newApp.getEntityType().getSensor(sensorName).getDescription(), sensorDescription);
}
@Test
public void testRebindDoesNotPersistTransientAttribute() throws Exception {
final String sensorName = "test.mydynamicsensor";
final AttributeSensor<Object> MY_DYNAMIC_SENSOR = Sensors.builder(Object.class, sensorName)
.persistence(SensorPersistenceMode.NONE)
.build();
// Anonymous inner class: we will not be able to rebind that.
@SuppressWarnings("serial")
Semaphore unrebindableObject = new Semaphore(1) {
};
origApp.sensors().set(MY_DYNAMIC_SENSOR, unrebindableObject);
assertEquals(origApp.getAttribute(MY_DYNAMIC_SENSOR), unrebindableObject);
newApp = rebind();
assertNull(newApp.getAttribute(MY_DYNAMIC_SENSOR));
}
@Test
public void testRebindWhenPreviousAppDestroyedHasNoApp() throws Exception {
origApp.stop();
RebindTestUtils.waitForPersisted(origManagementContext);
newManagementContext = RebindTestUtils.newPersistingManagementContextUnstarted(mementoDir, classLoader);
List<Application> newApps = newManagementContext.getRebindManager().rebind(classLoader, null, ManagementNodeState.MASTER);
newManagementContext.getRebindManager().startPersistence();
assertEquals(newApps.size(), 0, "apps="+newApps);
assertEquals(newManagementContext.getApplications().size(), 0, "apps="+newManagementContext.getApplications());
}
@Test(invocationCount=100, groups="Integration")
public void testRebindWhenPreviousAppDestroyedHasNoAppRepeatedly() throws Exception {
testRebindWhenPreviousAppDestroyedHasNoApp();
}
/**
* @deprecated since 0.7; support for rebinding old-style entities is deprecated
*/
@Test
public void testHandlesOldStyleEntity() throws Exception {
MyOldStyleEntity origE = new MyOldStyleEntity(MutableMap.of("confName", "myval"), origApp);
Entities.manage(origE);
newApp = rebind();
MyOldStyleEntity newE = (MyOldStyleEntity) Iterables.find(newApp.getChildren(), EntityPredicates.idEqualTo(origE.getId()));
assertEquals(newE.getConfig(MyOldStyleEntity.CONF_NAME), "myval");
}
@Test
public void testIsRebinding() throws Exception {
origApp.createAndManageChild(EntitySpec.create(EntityChecksIsRebinding.class));
newApp = rebind();
final EntityChecksIsRebinding newE = (EntityChecksIsRebinding) Iterables.find(newApp.getChildren(), Predicates.instanceOf(EntityChecksIsRebinding.class));
assertTrue(newE.isRebindingValWhenRebinding());
assertFalse(newE.isRebinding());
}
@ImplementedBy(EntityChecksIsRebindingImpl.class)
public static interface EntityChecksIsRebinding extends TestEntity {
boolean isRebindingValWhenRebinding();
boolean isRebinding();
}
public static class EntityChecksIsRebindingImpl extends TestEntityImpl implements EntityChecksIsRebinding {
boolean isRebindingValWhenRebinding;
@Override public boolean isRebindingValWhenRebinding() {
return isRebindingValWhenRebinding;
}
@Override public boolean isRebinding() {
return super.isRebinding();
}
@Override public void rebind() {
super.rebind();
isRebindingValWhenRebinding = isRebinding();
}
}
public static class MyOldStyleEntity extends AbstractEntity {
@SetFromFlag("confName")
public static final ConfigKey<String> CONF_NAME = TestEntity.CONF_NAME;
@SuppressWarnings("deprecation")
public MyOldStyleEntity(Map<?,?> flags, Entity parent) {
super(flags, parent);
}
}
// TODO Don't want to extend EntityLocal, but tests want to call app.setAttribute
@ImplementedBy(MyEntityImpl.class)
public interface MyEntity extends Entity, Startable, EntityLocal {
@SetFromFlag("myconfig")
public static final ConfigKey<String> MY_CONFIG = new BasicConfigKey<String>(
String.class, "test.myentity.myconfig", "My test config");
public static final AttributeSensor<String> MY_SENSOR = new BasicAttributeSensor<String>(
String.class, "test.myentity.mysensor", "My test sensor");
}
public static class MyEntityImpl extends AbstractEntity implements MyEntity {
@SuppressWarnings("unused")
private final Object dummy = new Object(); // so not serializable
public MyEntityImpl() {
}
@Override
public void start(Collection<? extends Location> locations) {
addLocations(locations);
}
@Override
public void stop() {
}
@Override
public void restart() {
}
}
@ImplementedBy(MyEntityWithMultipleInterfacesImpl.class)
public interface MyEntityWithMultipleInterfaces extends Group, Resizable, EntityLocal {
@SetFromFlag("myconfig")
public static final ConfigKey<String> MY_CONFIG = new BasicConfigKey<String>(
String.class, "test.myentity.myconfig", "My test config");
public static final AttributeSensor<String> MY_SENSOR = new BasicAttributeSensor<String>(
String.class, "test.myentity.mysensor", "My test sensor");
}
public static class MyEntityWithMultipleInterfacesImpl extends AbstractGroupImpl implements MyEntityWithMultipleInterfaces {
@SuppressWarnings("unused")
private final Object dummy = new Object(); // so not serializable
public MyEntityWithMultipleInterfacesImpl() {
}
@Override
public Integer resize(Integer desiredSize) {
return 0;
}
}
// TODO Don't want to extend EntityLocal, but tests want to call app.setAttribute
@ImplementedBy(MyEntityReffingOthersImpl.class)
public interface MyEntityReffingOthers extends Entity, EntityLocal {
@SetFromFlag("entityRef")
public static final ConfigKey<Entity> ENTITY_REF_CONFIG = new BasicConfigKey<Entity>(
Entity.class, "test.config.entityref", "Ref to other entity");
@SetFromFlag("locationRef")
public static final ConfigKey<Location> LOCATION_REF_CONFIG = new BasicConfigKey<Location>(
Location.class, "test.config.locationref", "Ref to other location");
public static final AttributeSensor<Entity> ENTITY_REF_SENSOR = new BasicAttributeSensor<Entity>(
Entity.class, "test.attribute.entityref", "Ref to other entity");
public static final AttributeSensor<Location> LOCATION_REF_SENSOR = new BasicAttributeSensor<Location>(
Location.class, "test.attribute.locationref", "Ref to other location");
}
public static class MyEntityReffingOthersImpl extends AbstractEntity implements MyEntityReffingOthers {
@SetFromFlag("entityRef")
public static final ConfigKey<Entity> ENTITY_REF_CONFIG = new BasicConfigKey<Entity>(
Entity.class, "test.config.entityref", "Ref to other entity");
@SetFromFlag("locationRef")
public static final ConfigKey<Location> LOCATION_REF_CONFIG = new BasicConfigKey<Location>(
Location.class, "test.config.locationref", "Ref to other location");
public static final AttributeSensor<Entity> ENTITY_REF_SENSOR = new BasicAttributeSensor<Entity>(
Entity.class, "test.attribute.entityref", "Ref to other entity");
public static final AttributeSensor<Location> LOCATION_REF_SENSOR = new BasicAttributeSensor<Location>(
Location.class, "test.attribute.locationref", "Ref to other location");
@SuppressWarnings("unused")
private final Object dummy = new Object(); // so not serializable
public MyEntityReffingOthersImpl() {
}
}
@ImplementedBy(MyEntity2Impl.class)
public interface MyEntity2 extends Entity {
@SetFromFlag("myconfig")
public static final ConfigKey<String> MY_CONFIG = new BasicConfigKey<String>(
String.class, "test.myconfig", "My test config");
@SetFromFlag("subscribe")
public static final ConfigKey<Boolean> SUBSCRIBE = new BasicConfigKey<Boolean>(
Boolean.class, "test.subscribe", "Whether to do some subscriptions on re-bind", false);
public List<String> getEvents();
public String getMyfield();
}
public static class MyEntity2Impl extends AbstractEntity implements MyEntity2 {
@SetFromFlag
String myfield;
final List<String> events = new CopyOnWriteArrayList<String>();
@SuppressWarnings("unused")
private final Object dummy = new Object(); // so not serializable
public MyEntity2Impl() {
}
public List<String> getEvents() {
return events;
}
public String getMyfield() {
return myfield;
}
@Override
public void onManagementStarting() {
if (getConfig(SUBSCRIBE)) {
subscriptions().subscribe(getApplication(), TestApplication.MY_ATTRIBUTE, new SensorEventListener<String>() {
@Override public void onEvent(SensorEvent<String> event) {
events.add(event.getValue());
}
});
}
}
@Override
public RebindSupport<EntityMemento> getRebindSupport() {
return new BasicEntityRebindSupport(this) {
@Override public EntityMemento getMemento() {
// Note: using MutableMap so accepts nulls
return getMementoWithProperties(MutableMap.<String,Object>of("myfield", myfield));
}
@Override protected void doReconstruct(RebindContext rebindContext, EntityMemento memento) {
super.doReconstruct(rebindContext, memento);
myfield = (String) memento.getCustomField("myfield");
}
};
}
}
@ImplementedBy(MyLatchingEntityImpl.class)
public interface MyLatchingEntity extends Entity {
@SuppressWarnings({ "unchecked", "rawtypes" })
@SetFromFlag("subscribe")
public static final ConfigKey<AttributeSensor<?>> SUBSCRIBE = new BasicConfigKey(
AttributeSensor.class, "test.mylatchingentity.subscribe", "Sensor to subscribe to (or null means don't)", null);
@SetFromFlag("publish")
public static final ConfigKey<String> PUBLISH = new BasicConfigKey<String>(
String.class, "test.mylatchingentity.publish", "Value to publish (or null means don't)", null);
public static final AttributeSensor<String> MY_SENSOR = new BasicAttributeSensor<String>(
String.class, "test.mylatchingentity.mysensor", "My test sensor");
}
public static class MyLatchingEntityImpl extends AbstractEntity implements MyLatchingEntity {
static volatile CountDownLatch reconstructStartedLatch;
static volatile CountDownLatch reconstructContinuesLatch;
static volatile CountDownLatch managingStartedLatch;
static volatile CountDownLatch managingContinuesLatch;
static volatile CountDownLatch managedStartedLatch;
static volatile CountDownLatch managedContinuesLatch;
static volatile boolean latching = false;
static volatile List<Object> events;
static void reset() {
latching = false;
events = new CopyOnWriteArrayList<Object>();
reconstructStartedLatch = new CountDownLatch(1);
reconstructContinuesLatch = new CountDownLatch(1);
managingStartedLatch = new CountDownLatch(1);
managingContinuesLatch = new CountDownLatch(1);
managedStartedLatch = new CountDownLatch(1);
managedContinuesLatch = new CountDownLatch(1);
}
public MyLatchingEntityImpl() {
}
private void onReconstruct() {
if (getConfig(SUBSCRIBE) != null) {
getManagementSupport().getSubscriptionContext().subscribe(null, getConfig(SUBSCRIBE), new SensorEventListener<Object>() {
@Override public void onEvent(SensorEvent<Object> event) {
events.add(event.getValue());
}});
}
if (getConfig(PUBLISH) != null) {
sensors().set(MY_SENSOR, getConfig(PUBLISH));
}
if (latching) {
reconstructStartedLatch.countDown();
try {
reconstructContinuesLatch.await();
} catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
}
@Override
public void onManagementStarting() {
if (latching) {
managingStartedLatch.countDown();
try {
managingContinuesLatch.await();
} catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
}
@Override
public void onManagementStarted() {
if (latching) {
managedStartedLatch.countDown();
try {
managedContinuesLatch.await();
} catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
}
@Override
public RebindSupport<EntityMemento> getRebindSupport() {
return new BasicEntityRebindSupport(this) {
@Override protected void doReconstruct(RebindContext rebindContext, EntityMemento memento) {
MyLatchingEntityImpl.this.onReconstruct();
}
};
}
}
}