/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.policy.ha;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.trait.FailingEntity;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.apache.brooklyn.policy.ha.HASensors.FailureDescriptor;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
public class ServiceRestarterTest {
private static final int TIMEOUT_MS = 10*1000;
private ManagementContext managementContext;
private TestApplication app;
private TestEntity e1;
private ServiceRestarter policy;
private SensorEventListener<Object> eventListener;
private List<SensorEvent<?>> events;
@BeforeMethod(alwaysRun=true)
public void setUp() throws Exception {
managementContext = new LocalManagementContextForTests();
app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
e1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
events = Lists.newCopyOnWriteArrayList();
eventListener = new SensorEventListener<Object>() {
@Override public void onEvent(SensorEvent<Object> event) {
events.add(event);
}
};
}
@AfterMethod(alwaysRun=true)
public void tearDown() throws Exception {
if (managementContext != null) Entities.destroyAll(managementContext);
}
@Test
public void testRestartsOnFailure() throws Exception {
policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
e1.policies().add(policy);
e1.sensors().emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
Asserts.succeedsEventually(new Runnable() {
@Override public void run() {
assertEquals(e1.getCallHistory(), ImmutableList.of("restart"));
}});
}
@Test(groups="Integration") // Has a 1 second wait
public void testDoesNotRestartsWhenHealthy() throws Exception {
policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
e1.policies().add(policy);
e1.sensors().emit(HASensors.ENTITY_RECOVERED, new FailureDescriptor(e1, "not a failure"));
Asserts.succeedsContinually(new Runnable() {
@Override public void run() {
assertEquals(e1.getCallHistory(), ImmutableList.of());
}});
}
@Test
public void testEmitsFailureEventWhenRestarterFails() throws Exception {
final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
.configure(FailingEntity.FAIL_ON_RESTART, true));
app.subscriptions().subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
policy = new ServiceRestarter(new ConfigBag().configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
e2.policies().add(policy);
e2.sensors().emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
Asserts.succeedsEventually(new Runnable() {
@Override public void run() {
assertEquals(Iterables.getOnlyElement(events).getSensor(), ServiceRestarter.ENTITY_RESTART_FAILED, "events="+events);
assertEquals(Iterables.getOnlyElement(events).getSource(), e2, "events="+events);
assertEquals(((FailureDescriptor)Iterables.getOnlyElement(events).getValue()).getComponent(), e2, "events="+events);
}});
assertEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
}
@Test
public void testDoesNotSetOnFireOnFailure() throws Exception {
final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
.configure(FailingEntity.FAIL_ON_RESTART, true));
app.subscriptions().subscribe(e2, ServiceRestarter.ENTITY_RESTART_FAILED, eventListener);
policy = new ServiceRestarter(new ConfigBag()
.configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED)
.configure(ServiceRestarter.SET_ON_FIRE_ON_FAILURE, false));
e2.policies().add(policy);
e2.sensors().emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e2, "simulate failure"));
Asserts.succeedsContinually(new Runnable() {
@Override public void run() {
assertNotEquals(e2.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE);
}});
}
// Previously RestarterPolicy called entity.restart inside the event-listener thread.
// That caused all other events for that entity's subscriptions to be queued until that
// entity's single event handler thread was free again.
@Test
public void testRestartDoesNotBlockOtherSubscriptions() throws Exception {
final CountDownLatch inRestartLatch = new CountDownLatch(1);
final CountDownLatch continueRestartLatch = new CountDownLatch(1);
final FailingEntity e2 = app.createAndManageChild(EntitySpec.create(FailingEntity.class)
.configure(FailingEntity.FAIL_ON_RESTART, true)
.configure(FailingEntity.EXEC_ON_FAILURE, new Function<Object, Void>() {
@Override public Void apply(Object input) {
inRestartLatch.countDown();
try {
continueRestartLatch.await();
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
}
return null;
}}));
e2.policies().add(PolicySpec.create(ServiceRestarter.class)
.configure(ServiceRestarter.FAILURE_SENSOR_TO_MONITOR, HASensors.ENTITY_FAILED));
e2.subscriptions().subscribe(e2, TestEntity.SEQUENCE, eventListener);
// Cause failure, and wait for entity.restart to be blocking
e2.sensors().emit(HASensors.ENTITY_FAILED, new FailureDescriptor(e1, "simulate failure"));
assertTrue(inRestartLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// Expect other notifications to continue to get through
e2.sensors().set(TestEntity.SEQUENCE, 1);
Asserts.succeedsEventually(new Runnable() {
@Override public void run() {
assertEquals(Iterables.getOnlyElement(events).getValue(), 1);
}});
// Allow restart to finish
continueRestartLatch.countDown();
}
}