summaryrefslogtreecommitdiffstats
path: root/utils/src
diff options
context:
space:
mode:
authorLiam Fallon <liam.fallon@est.tech>2019-03-12 14:25:52 +0000
committerGerrit Code Review <gerrit@onap.org>2019-03-12 14:25:52 +0000
commit5f3a884d55e0e7e74df813f2b278bf12a31acbbb (patch)
treeb9e2e314dcf0ead59f20e8d69cc03a39d8050fe5 /utils/src
parent33ef186e1e34b74cea33a77a043c0c8100d5c467 (diff)
parent1162f4b61e6893c0f44d1f9d5d8abc81a94bed48 (diff)
Merge "Add ServiceManager class"
Diffstat (limited to 'utils/src')
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java190
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java44
-rw-r--r--utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java16
-rw-r--r--utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerExceptionTest.java63
-rw-r--r--utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java249
-rw-r--r--utils/src/test/resources/logback-test.xml37
6 files changed, 591 insertions, 8 deletions
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
new file mode 100644
index 00000000..8bf89d56
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java
@@ -0,0 +1,190 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import org.onap.policy.common.capabilities.Startable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages a series of services. The services are started in order, and stopped in reverse
+ * order.
+ */
+public class ServiceManager {
+ private static final Logger logger = LoggerFactory.getLogger(ServiceManager.class);
+
+ /**
+ * Services to be started/stopped.
+ */
+ private final Deque<Service> items = new LinkedList<>();
+
+ /**
+ * {@code True} if the services are currently running, {@code false} otherwise.
+ */
+ private boolean running;
+
+ /**
+ * Adds a pair of service actions to the manager.
+ *
+ * @param stepName name to be logged when the service is started/stopped
+ * @param starter function to start the service
+ * @param stopper function to stop the service
+ * @return this manager
+ */
+ public synchronized ServiceManager addAction(String stepName, RunnableWithEx starter, RunnableWithEx stopper) {
+ if (running) {
+ throw new IllegalStateException("services are already running; cannot add " + stepName);
+ }
+
+ items.add(new Service(stepName, starter, stopper));
+ return this;
+ }
+
+ /**
+ * Adds a service to the manager. The manager will invoke the service's
+ * {@link Startable#start()} and {@link Startable#stop()} methods.
+ *
+ * @param stepName name to be logged when the service is started/stopped
+ * @param service object to be started/stopped
+ * @return this manager
+ */
+ public synchronized ServiceManager addService(String stepName, Startable service) {
+ if (running) {
+ throw new IllegalStateException("services are 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 {
+ if (running) {
+ throw new IllegalStateException("services are already running");
+ }
+
+ // 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);
+ item.starter.run();
+ started.add(item);
+
+ } catch (Exception e) {
+ logger.error("failed to start {}; rewinding steps", item.stepName);
+ ex = e;
+ break;
+ }
+ }
+
+ if (ex == null) {
+ running = true;
+ return;
+ }
+
+ // one of the services failed to start - rewind those we've previously started
+ try {
+ rewind(started);
+
+ } catch (ServiceManagerException e) {
+ logger.error("rewind failed", 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 {
+ if (!running) {
+ throw new IllegalStateException("services are not running");
+ }
+
+ running = false;
+ rewind(items);
+ }
+
+ /**
+ * Rewinds a list of services, stopping them in reverse order. Stops all of the
+ * services, even if one of the "stop" functions throws an exception.
+ *
+ * @param running services that are running, in the order they were started
+ * @throws ServiceManagerException if a service fails to stop
+ */
+ private void rewind(Deque<Service> running) throws ServiceManagerException {
+ Exception ex = null;
+
+ // stop everything, in reverse order
+ Iterator<Service> it = running.descendingIterator();
+ while (it.hasNext()) {
+ Service item = it.next();
+ try {
+ logger.info("stopping {}", item.stepName);
+ item.stopper.run();
+ } catch (Exception e) {
+ logger.error("failed to stop {}", item.stepName);
+ ex = e;
+
+ // do NOT break or re-throw, as we must stop ALL remaining items
+ }
+ }
+
+ if (ex != null) {
+ throw new ServiceManagerException(ex);
+ }
+ }
+
+ /**
+ * Service information.
+ */
+ private static class Service {
+ private String stepName;
+ private RunnableWithEx starter;
+ private RunnableWithEx stopper;
+
+ public Service(String stepName, RunnableWithEx starter, RunnableWithEx stopper) {
+ this.stepName = stepName;
+ this.starter = starter;
+ this.stopper = stopper;
+ }
+ }
+
+ @FunctionalInterface
+ public static interface RunnableWithEx {
+ public 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
new file mode 100644
index 00000000..3daa441a
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+/**
+ * Exceptions thrown by the ServiceManager.
+ */
+public class ServiceManagerException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ServiceManagerException() {
+ super();
+ }
+
+ public ServiceManagerException(String message) {
+ super(message);
+ }
+
+ public ServiceManagerException(Throwable cause) {
+ super(cause);
+ }
+
+ public ServiceManagerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java b/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
index d1aa59d5..eb918d35 100644
--- a/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
+++ b/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
@@ -5,15 +5,15 @@
* Licensed 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.
- *
+ *
* SPDX-License-Identifier: Apache-2.0
* ============LICENSE_END=========================================================
*/
@@ -158,10 +158,10 @@ public class ResourceUtilsTest {
theUrl = ResourceUtils.getLocalFile("file:///");
assertNotNull(theUrl);
-
+
theUrl = ResourceUtils.getLocalFile("file:///testdir/testfile.xml");
assertNull(theUrl);
-
+
theUrl = ResourceUtils.getLocalFile(null);
assertNull(theUrl);
}
@@ -185,7 +185,7 @@ public class ResourceUtilsTest {
theStream = ResourceUtils.getResourceAsStream(jarFileResource);
assertNotNull(theStream);
-
+
theStream = ResourceUtils.getResourceAsStream(pathDirResource);
assertNotNull(theStream);
@@ -250,7 +250,7 @@ public class ResourceUtilsTest {
assertNull(theString);
theString = ResourceUtils.getResourceAsString("");
- assertEquals("org\ntestdir\n", theString);
+ assertEquals("logback-test.xml\norg\ntestdir\n", theString);
}
@Test
@@ -295,7 +295,7 @@ public class ResourceUtilsTest {
assertEquals("/something/else", ResourceUtils.getFilePath4Resource("/something/else"));
assertTrue(ResourceUtils.getFilePath4Resource("xml/example.xml").endsWith("xml/example.xml"));
}
-
+
/**
* Cleandown resource utils test.
*/
diff --git a/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerExceptionTest.java b/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerExceptionTest.java
new file mode 100644
index 00000000..5fe321e8
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerExceptionTest.java
@@ -0,0 +1,63 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+public class ServiceManagerExceptionTest {
+ private ServiceManagerException sme;
+
+ @Test
+ public void testServiceManagerException() {
+ sme = new ServiceManagerException();
+ assertNull(sme.getMessage());
+ assertNull(sme.getCause());
+ }
+
+ @Test
+ public void testServiceManagerExceptionString() {
+ sme = new ServiceManagerException("hello");
+ assertEquals("hello", sme.getMessage());
+ assertNull(sme.getCause());
+ }
+
+ @Test
+ public void testServiceManagerExceptionThrowable() {
+ Throwable thrown = new Throwable("expected exception");
+ sme = new ServiceManagerException(thrown);
+ assertNotNull(sme.getMessage());
+ assertSame(thrown, sme.getCause());
+ }
+
+ @Test
+ public void testServiceManagerExceptionStringThrowable() {
+ Throwable thrown = new Throwable("another expected exception");
+ sme = new ServiceManagerException("world", thrown);
+ assertEquals("world", sme.getMessage());
+ assertSame(thrown, sme.getCause());
+ }
+
+}
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
new file mode 100644
index 00000000..49c0599b
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/services/ServiceManagerTest.java
@@ -0,0 +1,249 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+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.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import org.junit.Before;
+import org.junit.Test;
+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 EXPECTED_EXCEPTION = "expected exception";
+
+ private ServiceManager svcmgr;
+
+ /**
+ * Initializes {@link #svcmgr}.
+ */
+ @Before
+ public void setUp() {
+ svcmgr = new ServiceManager();
+ }
+
+ @Test
+ public void testAddAction() throws Exception {
+ RunnableWithEx start1 = mock(RunnableWithEx.class);
+ RunnableWithEx stop1 = mock(RunnableWithEx.class);
+ svcmgr.addAction("first action", start1, stop1);
+
+ RunnableWithEx start2 = mock(RunnableWithEx.class);
+ RunnableWithEx stop2 = mock(RunnableWithEx.class);
+ svcmgr.addAction("second action", start2, stop2);
+
+ svcmgr.start();
+ verify(start1).run();
+ verify(start2).run();
+ verify(stop1, never()).run();
+ verify(stop2, never()).run();
+
+ // cannot add while running
+ assertThatIllegalStateException().isThrownBy(() -> svcmgr.addAction("fail action", start1, stop1))
+ .withMessage(ALREADY_RUNNING + "; cannot add fail action");
+
+ svcmgr.stop();
+ verify(start1).run();
+ verify(start2).run();
+ verify(stop1).run();
+ verify(stop2).run();
+ }
+
+ @Test
+ public void testAddStartable() throws Exception {
+ Startable start1 = mock(Startable.class);
+ svcmgr.addService("first startable", start1);
+
+ Startable start2 = mock(Startable.class);
+ svcmgr.addService("second startable", start2);
+
+ svcmgr.start();
+ verify(start1).start();
+ verify(start1, never()).stop();
+ verify(start2).start();
+ verify(start2, never()).stop();
+
+ // cannot add while running
+ assertThatIllegalStateException().isThrownBy(() -> svcmgr.addService("fail startable", start1))
+ .withMessage(ALREADY_RUNNING + "; cannot add fail startable");
+
+ svcmgr.stop();
+ verify(start1).start();
+ verify(start1).stop();
+ verify(start2).start();
+ verify(start2).stop();
+ }
+
+ @Test
+ public void testStart() throws Exception {
+ Startable start1 = mock(Startable.class);
+ svcmgr.addService("test start", start1);
+
+ svcmgr.start();
+ verify(start1).start();
+ verify(start1, never()).stop();
+
+ // cannot re-start
+ assertThatIllegalStateException().isThrownBy(() -> svcmgr.start())
+ .withMessage(ALREADY_RUNNING);
+
+ // verify that it didn't try to start the service again
+ verify(start1).start();
+ }
+
+ @Test
+ public void testStart_Ex() {
+ Startable start1 = mock(Startable.class);
+ svcmgr.addService("test start ex", start1);
+
+ Startable start2 = mock(Startable.class);
+ svcmgr.addService("second test start ex", start2);
+
+ // this one will throw an exception
+ Startable start3 = mock(Startable.class);
+ RuntimeException exception = new RuntimeException(EXPECTED_EXCEPTION);
+ when(start3.start()).thenThrow(exception);
+ svcmgr.addService("third test start ex", start3);
+
+ Startable start4 = mock(Startable.class);
+ svcmgr.addService("fourth test start ex", start4);
+
+ Startable start5 = mock(Startable.class);
+ svcmgr.addService("fifth test start ex", start5);
+
+ assertThatThrownBy(() -> svcmgr.start()).isInstanceOf(ServiceManagerException.class).hasCause(exception);
+
+ verify(start1).start();
+ verify(start2).start();
+ verify(start3).start();
+ verify(start4, never()).start();
+ verify(start5, never()).start();
+
+ verify(start1).stop();
+ verify(start2).stop();
+ verify(start3, never()).stop();
+ verify(start4, never()).stop();
+ verify(start5, never()).stop();
+ }
+
+ @Test
+ public void testStart_RewindEx() {
+ Startable start1 = mock(Startable.class);
+ svcmgr.addService("test start rewind", start1);
+
+ // this one will throw an exception during rewind
+ Startable start2 = mock(Startable.class);
+ RuntimeException exception2 = new RuntimeException(EXPECTED_EXCEPTION);
+ when(start2.stop()).thenThrow(exception2);
+ svcmgr.addService("second test start rewind", start2);
+
+ // this one will throw an exception
+ Startable start3 = mock(Startable.class);
+ RuntimeException exception = new RuntimeException(EXPECTED_EXCEPTION);
+ when(start3.start()).thenThrow(exception);
+ svcmgr.addService("third test start rewind", start3);
+
+ Startable start4 = mock(Startable.class);
+ svcmgr.addService("fourth test start rewind", start4);
+
+ Startable start5 = mock(Startable.class);
+ svcmgr.addService("fifth test start rewind", start5);
+
+ assertThatThrownBy(() -> svcmgr.start()).isInstanceOf(ServiceManagerException.class).hasCause(exception);
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ Startable start1 = mock(Startable.class);
+ svcmgr.addService("first stop", start1);
+
+ // cannot stop until started
+ assertThatIllegalStateException().isThrownBy(() -> svcmgr.stop())
+ .withMessage("services are not running");
+
+ // verify that it didn't try to stop the service
+ verify(start1, never()).stop();
+
+ // start it
+ svcmgr.start();
+
+ svcmgr.stop();
+ verify(start1).stop();
+ }
+
+ @Test
+ public void testStop_Ex() throws Exception {
+ RunnableWithEx start1 = mock(RunnableWithEx.class);
+ RunnableWithEx stop1 = mock(RunnableWithEx.class);
+ svcmgr.addAction("first stop ex", start1, stop1);
+
+ Startable start2 = mock(Startable.class);
+ svcmgr.addService("second stop ex", start2);
+
+ svcmgr.start();
+ verify(start1).run();
+ verify(stop1, never()).run();
+ verify(start2).start();
+ verify(start2, never()).stop();
+
+ svcmgr.stop();
+ verify(start1).run();
+ verify(stop1).run();
+ verify(start2).start();
+ verify(start2).stop();
+ }
+
+ @Test
+ public void testRewind() throws Exception {
+ RunnableWithEx starter = mock(RunnableWithEx.class);
+ LinkedList<String> lst = new LinkedList<>();
+
+ svcmgr.addAction("first rewind", starter, () -> lst.add("rewind1"));
+ svcmgr.addAction("second rewind", starter, () -> lst.add("rewind2"));
+
+ // this one will throw an exception during rewind
+ RuntimeException exception = new RuntimeException(EXPECTED_EXCEPTION);
+ svcmgr.addAction("third rewind", starter, () -> {
+ lst.add("rewind3");
+ throw exception;
+ });
+
+ svcmgr.addAction("fourth rewind", starter, () -> lst.add("rewind4"));
+ svcmgr.addAction("fifth rewind", starter, () -> lst.add("rewind5"));
+
+ svcmgr.start();
+
+ assertThatThrownBy(() -> svcmgr.stop()).isInstanceOf(ServiceManagerException.class).hasCause(exception);
+
+ // all of them should have been stopped, in reverse order
+ assertEquals(Arrays.asList("rewind5", "rewind4", "rewind3", "rewind2", "rewind1").toString(), lst.toString());
+ }
+
+}
diff --git a/utils/src/test/resources/logback-test.xml b/utils/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..01d5c861
--- /dev/null
+++ b/utils/src/test/resources/logback-test.xml
@@ -0,0 +1,37 @@
+<!--
+ ============LICENSE_START=======================================================
+ ONAP - Common Modules
+ ================================================================================
+ Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed 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.
+ ============LICENSE_END=========================================================
+ -->
+
+<!-- Controls the output of logs for JUnit tests -->
+
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <Pattern>
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M\(%line\) - %msg%n
+ </Pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+</configuration>