From 1ea998182b78ee8e857aa2974c554ca952f1f68f Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Sat, 16 Mar 2019 13:47:21 -0400 Subject: Make ServiceManager implement Startable Also added a "name" to the manager, for logger purposes. Change-Id: Ide57cdec1fafc36c43b7f7584c0baad6261e8412 Issue-ID: POLICY-1542 Signed-off-by: Jim Hahn --- .../common/utils/services/ServiceManager.java | 86 +++++++++++++++------- .../utils/services/ServiceManagerException.java | 2 +- .../common/utils/services/ServiceManagerTest.java | 62 ++++++++++++++-- 3 files changed, 114 insertions(+), 36 deletions(-) (limited to 'utils/src') diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java index 8bf89d56..13cd6de8 100644 --- a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java +++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java @@ -31,9 +31,14 @@ import org.slf4j.LoggerFactory; * Manages a series of services. The services are started in order, and stopped in reverse * order. */ -public class ServiceManager { +public class ServiceManager implements Startable { private static final Logger logger = LoggerFactory.getLogger(ServiceManager.class); + /** + * Manager name. + */ + private final String name; + /** * Services to be started/stopped. */ @@ -44,6 +49,25 @@ public class ServiceManager { */ private boolean running; + /** + * Constructs the object, with a default name. + */ + public ServiceManager() { + this("service manager"); + } + + /** + * Constructs the object. + * @param name the manager's name, used for logging purposes + */ + public ServiceManager(String name) { + this.name = name; + } + + public String getName() { + return name; + } + /** * Adds a pair of service actions to the manager. * @@ -54,7 +78,7 @@ public class ServiceManager { */ public synchronized ServiceManager addAction(String stepName, RunnableWithEx starter, RunnableWithEx stopper) { if (running) { - throw new IllegalStateException("services are already running; cannot add " + stepName); + throw new IllegalStateException(name + " is already running; cannot add " + stepName); } items.add(new Service(stepName, starter, stopper)); @@ -71,44 +95,47 @@ public class ServiceManager { */ public synchronized ServiceManager addService(String stepName, Startable service) { if (running) { - throw new IllegalStateException("services are already running; cannot add " + stepName); + throw new IllegalStateException(name + " is already running; cannot add " + stepName); } items.add(new Service(stepName, () -> service.start(), () -> service.stop())); return this; } - /** - * Starts each service, in order. If a service throws an exception, then the - * previously started services are stopped, in reverse order. - * - * @throws ServiceManagerException if a service fails to start - */ - public synchronized void start() throws ServiceManagerException { + @Override + public synchronized boolean isAlive() { + return running; + } + + @Override + public synchronized boolean start() { if (running) { - throw new IllegalStateException("services are already running"); + throw new IllegalStateException(name + " is already running"); } + logger.info("{} starting", name); + // tracks the services that have been started so far Deque started = new LinkedList<>(); Exception ex = null; for (Service item : items) { try { - logger.info("starting {}", item.stepName); + logger.info("{} starting {}", name, item.stepName); item.starter.run(); started.add(item); } catch (Exception e) { - logger.error("failed to start {}; rewinding steps", item.stepName); + logger.error("{} failed to start {}; rewinding steps", name, item.stepName); ex = e; break; } } if (ex == null) { + logger.info("{} started", name); running = true; - return; + return true; } // one of the services failed to start - rewind those we've previously started @@ -116,26 +143,27 @@ public class ServiceManager { rewind(started); } catch (ServiceManagerException e) { - logger.error("rewind failed", e); + logger.error("{} rewind failed", name, e); } throw new ServiceManagerException(ex); } - /** - * Stops the services, in reverse order from which they were started. Stops all of the - * services, even if one of the "stop" functions throws an exception. Assumes that - * {@link #start()} has completed successfully. - * - * @throws ServiceManagerException if a service fails to stop - */ - public synchronized void stop() throws ServiceManagerException { + @Override + public synchronized boolean stop() { if (!running) { - throw new IllegalStateException("services are not running"); + throw new IllegalStateException(name + " is not running"); } running = false; rewind(items); + + return true; + } + + @Override + public void shutdown() { + stop(); } /** @@ -148,21 +176,25 @@ public class ServiceManager { private void rewind(Deque running) throws ServiceManagerException { Exception ex = null; + logger.info("{} stopping", name); + // stop everything, in reverse order Iterator it = running.descendingIterator(); while (it.hasNext()) { Service item = it.next(); try { - logger.info("stopping {}", item.stepName); + logger.info("{} stopping {}", name, item.stepName); item.stopper.run(); } catch (Exception e) { - logger.error("failed to stop {}", item.stepName); + logger.error("{} failed to stop {}", name, item.stepName); ex = e; // do NOT break or re-throw, as we must stop ALL remaining items } } + logger.info("{} stopped", name); + if (ex != null) { throw new ServiceManagerException(ex); } @@ -185,6 +217,6 @@ public class ServiceManager { @FunctionalInterface public static interface RunnableWithEx { - public void run() throws Exception; + void run() throws Exception; } } diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java index 3daa441a..ac37b6b4 100644 --- a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java +++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java @@ -23,7 +23,7 @@ package org.onap.policy.common.utils.services; /** * Exceptions thrown by the ServiceManager. */ -public class ServiceManagerException extends Exception { +public class ServiceManagerException extends RuntimeException { private static final long serialVersionUID = 1L; public ServiceManagerException() { diff --git a/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java b/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java index 49c0599b..b7774a5e 100644 --- a/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java +++ b/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java @@ -23,6 +23,8 @@ package org.onap.policy.common.utils.services; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -36,7 +38,8 @@ import org.onap.policy.common.capabilities.Startable; import org.onap.policy.common.utils.services.ServiceManager.RunnableWithEx; public class ServiceManagerTest { - private static final String ALREADY_RUNNING = "services are already running"; + private static final String MY_NAME = "my-name"; + private static final String ALREADY_RUNNING = MY_NAME + " is already running"; private static final String EXPECTED_EXCEPTION = "expected exception"; private ServiceManager svcmgr; @@ -46,7 +49,17 @@ public class ServiceManagerTest { */ @Before public void setUp() { - svcmgr = new ServiceManager(); + svcmgr = new ServiceManager(MY_NAME); + } + + @Test + public void testServiceName() { + assertEquals("service manager", new ServiceManager().getName()); + } + + @Test + public void testGetName() { + assertEquals(MY_NAME, svcmgr.getName()); } @Test @@ -106,16 +119,20 @@ public class ServiceManagerTest { Startable start1 = mock(Startable.class); svcmgr.addService("test start", start1); - svcmgr.start(); + assertTrue(svcmgr.start()); + + assertTrue(svcmgr.isAlive()); verify(start1).start(); verify(start1, never()).stop(); // cannot re-start - assertThatIllegalStateException().isThrownBy(() -> svcmgr.start()) - .withMessage(ALREADY_RUNNING); + assertThatIllegalStateException().isThrownBy(() -> svcmgr.start()).withMessage(ALREADY_RUNNING); // verify that it didn't try to start the service again verify(start1).start(); + + // still running + assertTrue(svcmgr.isAlive()); } @Test @@ -140,6 +157,8 @@ public class ServiceManagerTest { assertThatThrownBy(() -> svcmgr.start()).isInstanceOf(ServiceManagerException.class).hasCause(exception); + assertFalse(svcmgr.isAlive()); + verify(start1).start(); verify(start2).start(); verify(start3).start(); @@ -177,6 +196,8 @@ public class ServiceManagerTest { svcmgr.addService("fifth test start rewind", start5); assertThatThrownBy(() -> svcmgr.start()).isInstanceOf(ServiceManagerException.class).hasCause(exception); + + assertFalse(svcmgr.isAlive()); } @Test @@ -185,8 +206,7 @@ public class ServiceManagerTest { svcmgr.addService("first stop", start1); // cannot stop until started - assertThatIllegalStateException().isThrownBy(() -> svcmgr.stop()) - .withMessage("services are not running"); + assertThatIllegalStateException().isThrownBy(() -> svcmgr.stop()).withMessage(MY_NAME + " is not running"); // verify that it didn't try to stop the service verify(start1, never()).stop(); @@ -194,7 +214,9 @@ public class ServiceManagerTest { // start it svcmgr.start(); - svcmgr.stop(); + assertTrue(svcmgr.stop()); + + assertFalse(svcmgr.isAlive()); verify(start1).stop(); } @@ -218,6 +240,28 @@ public class ServiceManagerTest { verify(stop1).run(); verify(start2).start(); verify(start2).stop(); + + assertFalse(svcmgr.isAlive()); + } + + @Test + public void testShutdown() throws Exception { + Startable start1 = mock(Startable.class); + svcmgr.addService("first stop", start1); + + // cannot stop until started + assertThatIllegalStateException().isThrownBy(() -> svcmgr.shutdown()).withMessage(MY_NAME + " is not running"); + + // verify that it didn't try to stop the service + verify(start1, never()).stop(); + + // start it + svcmgr.start(); + + svcmgr.shutdown(); + + assertFalse(svcmgr.isAlive()); + verify(start1).stop(); } @Test @@ -242,6 +286,8 @@ public class ServiceManagerTest { assertThatThrownBy(() -> svcmgr.stop()).isInstanceOf(ServiceManagerException.class).hasCause(exception); + assertFalse(svcmgr.isAlive()); + // all of them should have been stopped, in reverse order assertEquals(Arrays.asList("rewind5", "rewind4", "rewind3", "rewind2", "rewind1").toString(), lst.toString()); } -- cgit 1.2.3-korg