/* * 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.entity.lifecycle; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.Enricher; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityAdjuncts; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceProblemsLogic; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.core.test.entity.TestEntityImpl.TestEntityWithoutEnrichers; import org.apache.brooklyn.entity.group.DynamicCluster; import org.apache.brooklyn.test.EntityTestUtils; import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @Test public class ServiceStateLogicTest extends BrooklynAppUnitTestSupport { private static final Logger log = LoggerFactory.getLogger(ServiceStateLogicTest.class); final static String INDICATOR_KEY_1 = "test-indicator-1"; final static String INDICATOR_KEY_2 = "test-indicator-2"; protected TestEntity entity; @Override protected void setUpApp() { super.setUpApp(); entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); } public void testManuallySettingIndicatorsOnEntities() { // if we set a not up indicator, entity service up should become false ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up"); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); // but state will not change unless we also set either a problem or expected state assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null); ServiceProblemsLogic.updateProblemsIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service state also"); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // and if we clear the not up indicator, service up becomes true, but there is a problem, so it shows on-fire ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // if we then clear the problem also, state goes to RUNNING ServiceProblemsLogic.clearProblemsIndicator(entity, INDICATOR_KEY_1); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); // now add not-up indicator again, and it reverts to up=false, state=stopped ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're again pretending to block service up"); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // but if we expect it to be running it will show on fire (because service is not up) ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // and if we again clear the not up indicator it will deduce up=true and state=running ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); } public void testAppStoppedAndEntityNullBeforeStarting() { // AbstractApplication has default logic to ensure service is not up if it hasn't been started, // (this can be removed by updating the problem indicator associated with the SERVICE_STATE_ACTUAL sensor) assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false); // and from that it imputes stopped state assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // TestEntity has no such indicators however assertAttributeEquals(entity, Attributes.SERVICE_UP, null); assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null); } public void testAllUpAndRunningAfterStart() { app.start(ImmutableList.<Location>of()); assertAttributeEquals(app, Attributes.SERVICE_UP, true); assertAttributeEquals(entity, Attributes.SERVICE_UP, true); // above should be immediate, entity should then derive RUNNING from expected state, and then so should app from children assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); } public void testStopsNicelyToo() { app.start(ImmutableList.<Location>of()); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); app.stop(); assertAttributeEquals(app, Attributes.SERVICE_UP, false); assertAttributeEquals(entity, Attributes.SERVICE_UP, false); // above should be immediate, app and entity should then derive STOPPED from the expected state assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); } public void testTwoIndicatorsAreBetterThanOne() { // if we set a not up indicator, entity service up should become false ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up"); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_2, "We're also pretending to block service up"); ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1); // clearing one indicator is not sufficient assertAttributeEquals(entity, Attributes.SERVICE_UP, false); // but it does not become true when both are cleared ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_2); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true); } @Test(invocationCount=100, groups="Integration") public void testManuallySettingIndicatorsOnApplicationsManyTimes() throws Exception { testManuallySettingIndicatorsOnApplications(); } public void testManuallySettingIndicatorsOnApplications() throws Exception { // indicators on application are more complicated because it is configured with additional indicators from its children // test a lot of situations, including reconfiguring some of the quorum config // to begin with, an entity has not reported anything, so the ComputeServiceIndicatorsFromChildren ignores it // but the AbstractApplication has emitted a not-up indicator because it has not been started // both as asserted by this other test: testAppStoppedAndEntityNullBeforeStarting(); // if we clear the not up indicator, the app will show as up, and as running, because it has no reporting children ServiceNotUpLogic.clearNotUpIndicator(app, Attributes.SERVICE_STATE_ACTUAL); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); // if we then put a not-up indicator on the TestEntity, it publishes false, but app is still up. State // won't propagate due to it's SERVICE_STATE_ACTUAL (null) being in IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're also pretending to block service up"); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true); assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); // the entity still has no opinion about its state assertAttributeEqualsContinually(entity, Attributes.SERVICE_STATE_ACTUAL, null); // switching the entity state to one not in IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES will propagate the up state ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING); assertAttributeEqualsContinually(entity, Attributes.SERVICE_UP, false); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // if the entity expects to be stopped, it will report stopped ServiceStateLogic.setExpectedState(entity, Lifecycle.STOPPED); assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // and the app will ignore the entity state, so becomes running assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); // if we clear the not-up indicator, both the entity and the app report service up (with the entity first) ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1); assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true); // but entity is still stopped because that is what is expected there, and that's okay even if service is apparently up assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // the app however is running, because the default state quorum check is "all are healthy" assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true); // if we change the state quorum check for the app to be "all are healthy and at least one running" *then* it shows stopped // (normally this would be done in `initEnrichers` of course) Enricher appChildrenBasedEnricher = EntityAdjuncts.tryFindWithUniqueTag(app.enrichers(), ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG).get(); appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumChecks.allAndAtLeastOne()); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // if entity is expected running, then it will show running because service is up; this is reflected at app and at entity ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); // now, when the entity is unmanaged, the app goes on fire because don't have "at least one running" Entities.unmanage(entity); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // but UP_QUORUM_CHECK is still the default atLeastOneUnlessEmpty; so serviceUp=true assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true); // if we change its up-quorum to atLeastOne then state becomes stopped (because there is no expected state; has not been started) appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOne()); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false); // if we now start it will successfully start (because unlike entities it does not wait for service up) // but will remain down and will go on fire app.start(ImmutableList.<Location>of()); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // restoring up-quorum to "atLeastOneUnlessEmpty" causes it to become RUNNING, because happy with empty appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty()); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); // but running-quorum is still allAndAtLeastOne, so remains on-fire assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // now add a child, it's still up and running because null values are ignored by default (i.e. we're still "empty") entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true); assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); // tell it not to ignore null values for children states, and it will go onfire (but still be service up) appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES, ImmutableSet.<Lifecycle>of()); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); assertAttributeEquals(app, Attributes.SERVICE_UP, true); // tell it not to ignore null values for service up and it will go service down appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_SERVICE_UP_NULL, false); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false); // set the entity to RUNNING and the app will be healthy again ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1); ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING); assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); assertAttributeEquals(entity, Attributes.SERVICE_UP, true); assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); } @Test public void testQuorumWithStringStates() { final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class) .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntityWithoutEnrichers.class)) .configure(DynamicCluster.INITIAL_SIZE, 1)); cluster.start(ImmutableList.of(app.newSimulatedLocation())); EntityTestUtils.assertGroupSizeEqualsEventually(cluster, 1); //manually set state to healthy as enrichers are disabled EntityInternal child = (EntityInternal) cluster.getMembers().iterator().next(); child.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); child.sensors().set(Attributes.SERVICE_UP, Boolean.TRUE); EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); //set untyped service state, the quorum check should be able to handle coercion AttributeSensor<Object> stateSensor = Sensors.newSensor(Object.class, Attributes.SERVICE_STATE_ACTUAL.getName()); child.sensors().set(stateSensor, "running"); EntityTestUtils.assertAttributeEqualsContinually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); } private static <T> void assertAttributeEqualsEventually(Entity x, AttributeSensor<T> sensor, T value) { try { EntityTestUtils.assertAttributeEqualsEventually(ImmutableMap.of("timeout", Duration.seconds(3)), x, sensor, value); } catch (Throwable e) { log.warn("Expected "+x+" eventually to have "+sensor+" = "+value+"; instead:"); Entities.dumpInfo(x); throw Exceptions.propagate(e); } } private static <T> void assertAttributeEqualsContinually(Entity x, AttributeSensor<T> sensor, T value) { try { EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", Duration.millis(25)), x, sensor, value); } catch (Throwable e) { log.warn("Expected "+x+" continually to have "+sensor+" = "+value+"; instead:"); Entities.dumpInfo(x); throw Exceptions.propagate(e); } } private static <T> void assertAttributeEquals(Entity x, AttributeSensor<T> sensor, T value) { try { EntityTestUtils.assertAttributeEquals(x, sensor, value); } catch (Throwable e) { log.warn("Expected "+x+" to have "+sensor+" = "+value+"; instead:"); Entities.dumpInfo(x); throw Exceptions.propagate(e); } } }