/** * 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.hadoop.service; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.service.AbstractService; import org.apache.hadoop.service.LoggingStateChangeListener; import org.apache.hadoop.service.Service; import org.apache.hadoop.service.ServiceStateChangeListener; import org.apache.hadoop.service.ServiceStateException; import org.junit.Test; public class TestServiceLifecycle extends ServiceAssert { private static Log LOG = LogFactory.getLog(TestServiceLifecycle.class); /** * Walk the {@link BreakableService} through it's lifecycle, * more to verify that service's counters work than anything else * @throws Throwable if necessary */ @Test public void testWalkthrough() throws Throwable { BreakableService svc = new BreakableService(); assertServiceStateCreated(svc); assertStateCount(svc, Service.STATE.NOTINITED, 1); assertStateCount(svc, Service.STATE.INITED, 0); assertStateCount(svc, Service.STATE.STARTED, 0); assertStateCount(svc, Service.STATE.STOPPED, 0); svc.init(new Configuration()); assertServiceStateInited(svc); assertStateCount(svc, Service.STATE.INITED, 1); svc.start(); assertServiceStateStarted(svc); assertStateCount(svc, Service.STATE.STARTED, 1); svc.stop(); assertServiceStateStopped(svc); assertStateCount(svc, Service.STATE.STOPPED, 1); } /** * call init twice * @throws Throwable if necessary */ @Test public void testInitTwice() throws Throwable { BreakableService svc = new BreakableService(); Configuration conf = new Configuration(); conf.set("test.init","t"); svc.init(conf); svc.init(new Configuration()); assertStateCount(svc, Service.STATE.INITED, 1); assertServiceConfigurationContains(svc, "test.init"); } /** * Call start twice * @throws Throwable if necessary */ @Test public void testStartTwice() throws Throwable { BreakableService svc = new BreakableService(); svc.init(new Configuration()); svc.start(); svc.start(); assertStateCount(svc, Service.STATE.STARTED, 1); } /** * Verify that when a service is stopped more than once, no exception * is thrown. * @throws Throwable if necessary */ @Test public void testStopTwice() throws Throwable { BreakableService svc = new BreakableService(); svc.init(new Configuration()); svc.start(); svc.stop(); assertStateCount(svc, Service.STATE.STOPPED, 1); svc.stop(); assertStateCount(svc, Service.STATE.STOPPED, 1); } /** * Show that if the service failed during an init * operation, it stays in the created state, even after stopping it * @throws Throwable if necessary */ @Test public void testStopFailedInit() throws Throwable { BreakableService svc = new BreakableService(true, false, false); assertServiceStateCreated(svc); try { svc.init(new Configuration()); fail("Expected a failure, got " + svc); } catch (BreakableService.BrokenLifecycleEvent e) { //expected } //the service state wasn't passed assertServiceStateStopped(svc); assertStateCount(svc, Service.STATE.INITED, 1); assertStateCount(svc, Service.STATE.STOPPED, 1); //now try to stop svc.stop(); assertStateCount(svc, Service.STATE.STOPPED, 1); } /** * Show that if the service failed during an init * operation, it stays in the created state, even after stopping it * @throws Throwable if necessary */ @Test public void testStopFailedStart() throws Throwable { BreakableService svc = new BreakableService(false, true, false); svc.init(new Configuration()); assertServiceStateInited(svc); try { svc.start(); fail("Expected a failure, got " + svc); } catch (BreakableService.BrokenLifecycleEvent e) { //expected } //the service state wasn't passed assertServiceStateStopped(svc); } /** * verify that when a service fails during its stop operation, * its state does not change. * @throws Throwable if necessary */ @Test public void testFailingStop() throws Throwable { BreakableService svc = new BreakableService(false, false, true); svc.init(new Configuration()); svc.start(); try { svc.stop(); fail("Expected a failure, got " + svc); } catch (BreakableService.BrokenLifecycleEvent e) { //expected } assertStateCount(svc, Service.STATE.STOPPED, 1); } /** * verify that when a service that is not started is stopped, the * service enters the stopped state * @throws Throwable on a failure */ @Test public void testStopUnstarted() throws Throwable { BreakableService svc = new BreakableService(); svc.stop(); assertServiceStateStopped(svc); assertStateCount(svc, Service.STATE.INITED, 0); assertStateCount(svc, Service.STATE.STOPPED, 1); } /** * Show that if the service failed during an init * operation, stop was called. */ @Test public void testStopFailingInitAndStop() throws Throwable { BreakableService svc = new BreakableService(true, false, true); svc.registerServiceListener(new LoggingStateChangeListener()); try { svc.init(new Configuration()); fail("Expected a failure, got " + svc); } catch (BreakableService.BrokenLifecycleEvent e) { assertEquals(Service.STATE.INITED, e.state); } //the service state is stopped assertServiceStateStopped(svc); assertEquals(Service.STATE.INITED, svc.getFailureState()); Throwable failureCause = svc.getFailureCause(); assertNotNull("Null failure cause in " + svc, failureCause); BreakableService.BrokenLifecycleEvent cause = (BreakableService.BrokenLifecycleEvent) failureCause; assertNotNull("null state in " + cause + " raised by " + svc, cause.state); assertEquals(Service.STATE.INITED, cause.state); } @Test public void testInitNullConf() throws Throwable { BreakableService svc = new BreakableService(false, false, false); try { svc.init(null); LOG.warn("Null Configurations are permitted "); } catch (ServiceStateException e) { //expected } } @Test public void testServiceNotifications() throws Throwable { BreakableService svc = new BreakableService(false, false, false); BreakableStateChangeListener listener = new BreakableStateChangeListener(); svc.registerServiceListener(listener); svc.init(new Configuration()); assertEventCount(listener, 1); svc.start(); assertEventCount(listener, 2); svc.stop(); assertEventCount(listener, 3); svc.stop(); assertEventCount(listener, 3); } /** * Test that when a service listener is unregistered, it stops being invoked * @throws Throwable on a failure */ @Test public void testServiceNotificationsStopOnceUnregistered() throws Throwable { BreakableService svc = new BreakableService(false, false, false); BreakableStateChangeListener listener = new BreakableStateChangeListener(); svc.registerServiceListener(listener); svc.init(new Configuration()); assertEventCount(listener, 1); svc.unregisterServiceListener(listener); svc.start(); assertEventCount(listener, 1); svc.stop(); assertEventCount(listener, 1); svc.stop(); } /** * This test uses a service listener that unregisters itself during the callbacks. * This a test that verifies the concurrency logic on the listener management * code, that it doesn't throw any immutable state change exceptions * if you change list membership during the notifications. * The standard <code>AbstractService</code> implementation copies the list * to an array in a <code>synchronized</code> block then iterates through * the copy precisely to prevent this problem. * @throws Throwable on a failure */ @Test public void testServiceNotificationsUnregisterDuringCallback() throws Throwable { BreakableService svc = new BreakableService(false, false, false); BreakableStateChangeListener listener = new SelfUnregisteringBreakableStateChangeListener(); BreakableStateChangeListener l2 = new BreakableStateChangeListener(); svc.registerServiceListener(listener); svc.registerServiceListener(l2); svc.init(new Configuration()); assertEventCount(listener, 1); assertEventCount(l2, 1); svc.unregisterServiceListener(listener); svc.start(); assertEventCount(listener, 1); assertEventCount(l2, 2); svc.stop(); assertEventCount(listener, 1); svc.stop(); } private static class SelfUnregisteringBreakableStateChangeListener extends BreakableStateChangeListener { @Override public synchronized void stateChanged(Service service) { super.stateChanged(service); service.unregisterServiceListener(this); } } private void assertEventCount(BreakableStateChangeListener listener, int expected) { assertEquals(listener.toString(), expected, listener.getEventCount()); } @Test public void testServiceFailingNotifications() throws Throwable { BreakableService svc = new BreakableService(false, false, false); BreakableStateChangeListener listener = new BreakableStateChangeListener(); listener.setFailingState(Service.STATE.STARTED); svc.registerServiceListener(listener); svc.init(new Configuration()); assertEventCount(listener, 1); //start this; the listener failed but this won't show svc.start(); //counter went up assertEventCount(listener, 2); assertEquals(1, listener.getFailureCount()); //stop the service -this doesn't fail svc.stop(); assertEventCount(listener, 3); assertEquals(1, listener.getFailureCount()); svc.stop(); } /** * This test verifies that you can block waiting for something to happen * and use notifications to manage it * @throws Throwable on a failure */ @Test public void testListenerWithNotifications() throws Throwable { //this tests that a listener can get notified when a service is stopped AsyncSelfTerminatingService service = new AsyncSelfTerminatingService(2000); NotifyingListener listener = new NotifyingListener(); service.registerServiceListener(listener); service.init(new Configuration()); service.start(); assertServiceInState(service, Service.STATE.STARTED); long start = System.currentTimeMillis(); synchronized (listener) { listener.wait(20000); } long duration = System.currentTimeMillis() - start; assertEquals(Service.STATE.STOPPED, listener.notifyingState); assertServiceInState(service, Service.STATE.STOPPED); assertTrue("Duration of " + duration + " too long", duration < 10000); } @Test public void testSelfTerminatingService() throws Throwable { SelfTerminatingService service = new SelfTerminatingService(); BreakableStateChangeListener listener = new BreakableStateChangeListener(); service.registerServiceListener(listener); service.init(new Configuration()); assertEventCount(listener, 1); //start the service service.start(); //and expect an event count of exactly two assertEventCount(listener, 2); } @Test public void testStartInInitService() throws Throwable { Service service = new StartInInitService(); BreakableStateChangeListener listener = new BreakableStateChangeListener(); service.registerServiceListener(listener); service.init(new Configuration()); assertServiceInState(service, Service.STATE.STARTED); assertEventCount(listener, 1); } @Test public void testStopInInitService() throws Throwable { Service service = new StopInInitService(); BreakableStateChangeListener listener = new BreakableStateChangeListener(); service.registerServiceListener(listener); service.init(new Configuration()); assertServiceInState(service, Service.STATE.STOPPED); assertEventCount(listener, 1); } /** * Listener that wakes up all threads waiting on it */ private static class NotifyingListener implements ServiceStateChangeListener { public Service.STATE notifyingState = Service.STATE.NOTINITED; public synchronized void stateChanged(Service service) { notifyingState = service.getServiceState(); this.notifyAll(); } } /** * Service that terminates itself after starting and sleeping for a while */ private static class AsyncSelfTerminatingService extends AbstractService implements Runnable { final int timeout; private AsyncSelfTerminatingService(int timeout) { super("AsyncSelfTerminatingService"); this.timeout = timeout; } @Override protected void serviceStart() throws Exception { new Thread(this).start(); super.serviceStart(); } @Override public void run() { try { Thread.sleep(timeout); } catch (InterruptedException ignored) { } this.stop(); } } /** * Service that terminates itself in startup */ private static class SelfTerminatingService extends AbstractService { private SelfTerminatingService() { super("SelfTerminatingService"); } @Override protected void serviceStart() throws Exception { //start super.serviceStart(); //then stop stop(); } } /** * Service that starts itself in init */ private static class StartInInitService extends AbstractService { private StartInInitService() { super("StartInInitService"); } @Override protected void serviceInit(Configuration conf) throws Exception { super.serviceInit(conf); start(); } } /** * Service that starts itself in init */ private static class StopInInitService extends AbstractService { private StopInInitService() { super("StopInInitService"); } @Override protected void serviceInit(Configuration conf) throws Exception { super.serviceInit(conf); stop(); } } }