Make ServiceManager implement Startable 18/82518/3
authorJim Hahn <jrh3@att.com>
Sat, 16 Mar 2019 17:47:21 +0000 (13:47 -0400)
committerJim Hahn <jrh3@att.com>
Sat, 16 Mar 2019 20:03:42 +0000 (16:03 -0400)
Also added a "name" to the manager, for logger purposes.

Change-Id: Ide57cdec1fafc36c43b7f7584c0baad6261e8412
Issue-ID: POLICY-1542
Signed-off-by: Jim Hahn <jrh3@att.com>
utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java
utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java
utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java

index 8bf89d5..13cd6de 100644 (file)
@@ -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<Service> 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<Service> running) throws ServiceManagerException {
         Exception ex = null;
 
+        logger.info("{} stopping", name);
+
         // stop everything, in reverse order
         Iterator<Service> 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;
     }
 }
index 3daa441..ac37b6b 100644 (file)
@@ -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() {
index 49c0599..b7774a5 100644 (file)
@@ -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());
     }