Mercurial > hg > release > thermostat-1.6
changeset 1906:686b04914043
Add polling backend. PR2995
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019218.html
author | Jie Kang <jkang@redhat.com> |
---|---|
date | Tue, 07 Jun 2016 11:14:49 -0400 |
parents | 5a3e91f53ee3 |
children | fe6cbf89e84e |
files | agent/core/src/main/java/com/redhat/thermostat/backend/HostPollingAction.java agent/core/src/main/java/com/redhat/thermostat/backend/HostPollingBackend.java agent/core/src/main/java/com/redhat/thermostat/backend/PollingBackend.java agent/core/src/main/java/com/redhat/thermostat/backend/VmPollingAction.java agent/core/src/main/java/com/redhat/thermostat/backend/VmPollingBackend.java agent/core/src/test/java/com/redhat/thermostat/backend/HostPollingBackendTest.java agent/core/src/test/java/com/redhat/thermostat/backend/PollingBackendTest.java agent/core/src/test/java/com/redhat/thermostat/backend/VmPollingBackendTest.java |
diffstat | 8 files changed, 939 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/backend/HostPollingAction.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +/** + * An action to be performed at a regular interval as part of a + * {@link HostPollingBackend} implementation. + */ +public interface HostPollingAction { + + /** + * Run the action. + */ + public void run(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/backend/HostPollingBackend.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ScheduledExecutorService; + +import com.redhat.thermostat.common.NotImplementedException; +import com.redhat.thermostat.common.Version; + +/** + * Convenience {@link Backend} class for implementations that will take some + * action at the Host or system level on a regular interval. Simply + * extend this class, implement any missing methods, and register one or + * more {@link HostPollingAction} implementations during instantiation. + */ +public abstract class HostPollingBackend extends PollingBackend { + + private final Set<HostPollingAction> actions; + + public HostPollingBackend(String name, String description, + String vendor, Version version, ScheduledExecutorService executor) { + super(name, description, vendor, version, executor); + actions = new CopyOnWriteArraySet<>(); + } + + final void doScheduledActions() { + for (HostPollingAction action : actions) { + action.run(); + } + } + + /** + * Register an action to be performed at each polling interval. It is + * recommended that implementations register all such actions during + * instantiation. + */ + protected final void registerAction(HostPollingAction action) { + actions.add(action); + } + + /** + * Unregister an action so that it will no longer be performed at each + * polling interval. If no such action has been registered, this + * method has no effect. Depending on thread timing issues, the action + * may be performed once even after this method has been called. + */ + protected final void unregisterAction(HostPollingAction action) { + actions.remove(action); + } + + @Override + public void setObserveNewJvm(boolean newValue) { + throw new NotImplementedException("This backend does not observe jvms!"); + } + + // Intentionally final do-nothing. + final void preActivate() {}; + final void postDeactivate() {}; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/backend/PollingBackend.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.redhat.thermostat.common.Version; + +/* + * Convenience {@link Backend} class for implementations that will take action + * on a fixed interval. This is package private, and plugins should instead + * extend child classes {@link HostPollingBackend} + * or {@link VmPollingBackend} as appropriate. + */ +abstract class PollingBackend extends BaseBackend { + + static final long DEFAULT_INTERVAL = 1000; // TODO make this configurable. + + private ScheduledExecutorService executor; + private boolean isActive; + + PollingBackend(String name, String description, String vendor, + Version version, + ScheduledExecutorService executor) { + super(name, description, vendor, + version.getVersionNumber(), true); + this.executor = executor; + } + + @Override + public final synchronized boolean activate() { + if (!isActive) { + preActivate(); + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + doScheduledActions(); + } + }, 0, DEFAULT_INTERVAL, TimeUnit.MILLISECONDS); + + isActive = true; + } + return isActive; + } + + @Override + public final synchronized boolean deactivate() { + if (isActive) { + executor.shutdown(); + postDeactivate(); + isActive = false; + } + return !isActive; + } + + @Override + public final boolean isActive() { + return isActive; + } + + // Test hook. + final void setActive(boolean active) { + isActive = active; + } + + // Give child classes a chance to specify what should happen at each polling interval. + abstract void doScheduledActions(); + + // An opportunity for child classes to do some setup upon activation. + // Will execute before any actions are scheduled. + void preActivate() {} + + // An opportunity for child classes to do some cleanup upon deactivation. + // Will execute after all actions are unscheduled. + void postDeactivate() {} + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/backend/VmPollingAction.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +/** + * An action to be performed at a regular interval as part of a + * {@link VmPollingBackend} implementation. + */ +public interface VmPollingAction { + + /** + * Run the action. + * + * @param vmId a String representation of the VmID on which to take action. + * @param pid the process ID of the JVM instance on which to take action. + */ + public void run(String vmId, int pid); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/backend/VmPollingBackend.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.agent.VmStatusListener; +import com.redhat.thermostat.agent.VmStatusListenerRegistrar; +import com.redhat.thermostat.common.Version; +import com.redhat.thermostat.common.utils.LoggingUtils; + +/** + * Convenience {@link Backend} class for implementations that will take some + * action for each monitored JVM process on a regular interval. Simply + * extend this class, implement any missing methods, and register one or + * more {@link VmPollingAction} implementations during instantiation. + */ +public abstract class VmPollingBackend extends PollingBackend implements VmStatusListener { + + private final Set<VmPollingAction> actions; + private final Map<Integer, String> pidsToMonitor = new ConcurrentHashMap<>(); + private final VmStatusListenerRegistrar registrar; + private static final Logger logger = LoggingUtils.getLogger(VmPollingBackend.class); + + public VmPollingBackend(String name, String description, + String vendor, Version version, ScheduledExecutorService executor, + VmStatusListenerRegistrar registrar) { + super(name, description, vendor, version, executor); + this.registrar = registrar; + actions = new CopyOnWriteArraySet<>(); + } + + @Override + final void preActivate() { + registrar.register(this); + } + + @Override + final void postDeactivate() { + registrar.unregister(this); + } + + @Override + final void doScheduledActions() { + for (Entry<Integer, String> entry : pidsToMonitor.entrySet()) { + int pid = entry.getKey(); + String vmId = entry.getValue(); + for (VmPollingAction action : actions) { + action.run(vmId, pid); + } + } + } + + /** + * Register an action to be performed at each polling interval. It is + * recommended that implementations register all such actions during + * instantiation. + */ + protected final void registerAction(VmPollingAction action) { + actions.add(action); + } + + /** + * Unregister an action so that it will no longer be performed at each + * polling interval. If no such action has been registered, this + * method has no effect. Depending on thread timing issues, the action + * may be performed once even after this method has been called. + */ + protected final void unregisterAction(VmPollingAction action) { + actions.remove(action); + } + + @Override + public void vmStatusChanged(Status newStatus, String vmId, int pid) { + switch (newStatus) { + case VM_STARTED: + /* fall-through */ + case VM_ACTIVE: + if (getObserveNewJvm()) { + pidsToMonitor.put(pid, vmId); + } else { + logger.log(Level.FINE, "skipping new vm " + pid); + } + break; + case VM_STOPPED: + pidsToMonitor.remove(pid); + break; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/test/java/com/redhat/thermostat/backend/HostPollingBackendTest.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import static org.junit.Assert.fail; +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.concurrent.ScheduledExecutorService; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.common.NotImplementedException; +import com.redhat.thermostat.common.Version; + +public class HostPollingBackendTest { + + private HostPollingBackend backend; + private ScheduledExecutorService mockExecutor; + + @Before + public void setUp() { + mockExecutor = mock(ScheduledExecutorService.class); + Version mockVersion = mock(Version.class); + when(mockVersion.getVersionNumber()).thenReturn("backend-version"); + backend = new HostPollingBackend("backend-name", "backend-description", + "backend-vendor", mockVersion, mockExecutor) { + @Override + public int getOrderValue() { + return 0; // Doesn't matter, not being tested. + } + }; + if (!backend.getObserveNewJvm()) { + /* At time of writing, default is true. This is + * inherited from parent PollingBackend. In case + * default changes: + */ + backend.setObserveNewJvm(true); + } + } + + @Test + public void verifySetObserveNewJvmThrowsException() { + try { + backend.setObserveNewJvm(true); + } catch (NotImplementedException e) { + return; // pass + } + fail("Should have thrown NotImplementedException"); + } + + @Test + public void verifyRegisteredActionPerformed() { + HostPollingAction action = mock(HostPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); + verify(action).run(); + } + + @Test + public void verifyMultipleRegisteredActionsPerformed() { + HostPollingAction action1 = mock(HostPollingAction.class); + HostPollingAction action2 = mock(HostPollingAction.class); + backend.registerAction(action1); + backend.registerAction(action2); + backend.doScheduledActions(); + verify(action1).run(); + verify(action2).run(); + } + + @Test + public void verifyUnregisteredActionNotPerformed() { + HostPollingAction action = mock(HostPollingAction.class); + backend.registerAction(action); + backend.unregisterAction(action); + backend.doScheduledActions(); + verify(action, never()).run(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/test/java/com/redhat/thermostat/backend/PollingBackendTest.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,166 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.redhat.thermostat.common.Version; + +public class PollingBackendTest { + + private PollingBackend backend; + private ScheduledExecutorService mockExecutor; + private CustomActivateTester mockActivate; + + @Before + public void setUp() { + mockExecutor = mock(ScheduledExecutorService.class); + mockActivate = mock(CustomActivateTester.class); + Version mockVersion = mock(Version.class); + when(mockVersion.getVersionNumber()).thenReturn("backend-version"); + backend = new PollingBackend("backend-name", "backend-description", + "backend-vendor", mockVersion, mockExecutor) { + + @Override + public int getOrderValue() { + return 0; // Doesn't matter, not being tested. + } + + @Override + void doScheduledActions() { + // Won't be called because mock executor. + } + + @Override + void preActivate() { + mockActivate.activate(); + } + + @Override + void postDeactivate() { + mockActivate.deactivate(); + }}; + } + + @After + public void tearDown() { + backend = null; + } + + @Test + public void verifyActivate() { + backend.activate(); + verify(mockExecutor).scheduleAtFixedRate(any(Runnable.class), eq( (long) 0), + eq( (long) 1000), eq(TimeUnit.MILLISECONDS)); + verify(mockActivate).activate(); + } + + @Test + public void verifyNoopActivateWhenAlreadyActive() { + backend.setActive(true); + backend.activate(); + verifyNoMoreInteractions(mockExecutor); + verifyNoMoreInteractions(mockActivate); + } + + @Test + public void verifyDeactivate() { + backend.setActive(true); + backend.deactivate(); + verify(mockActivate).deactivate(); + verify(mockExecutor).shutdown(); + } + + @Test + public void verifyNoopDeactivateWhenNotActive() { + backend.deactivate(); + verifyNoMoreInteractions(mockExecutor); + verifyNoMoreInteractions(mockActivate); + } + + @Test + public void verifyScheduledRunnableActuallyRunsScheduledAction() { + final ScheduledActionTester mockAction = mock(ScheduledActionTester.class); + Version mockVersion = mock(Version.class); + when(mockVersion.getVersionNumber()).thenReturn("backend-version"); + ScheduledExecutorService mockExecutor = mock(ScheduledExecutorService.class); + backend = new PollingBackend("backend-name", "backend-description", + "backend-vendor", mockVersion, mockExecutor) { + + @Override + public int getOrderValue() { + return 0; + } + + @Override + void doScheduledActions() { + mockAction.doAction(); + }}; + backend.activate(); + ArgumentCaptor<Runnable> scheduledRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mockExecutor).scheduleAtFixedRate(scheduledRunnableCaptor.capture(), eq( (long) 0), + eq( (long) 1000), eq(TimeUnit.MILLISECONDS)); + Runnable scheduledRunnable = scheduledRunnableCaptor.getValue(); + scheduledRunnable.run(); + verify(mockAction).doAction(); + backend.deactivate(); + verify(mockExecutor).shutdown(); + verifyNoMoreInteractions(mockAction); + } + + private interface CustomActivateTester { + public void activate(); + public void deactivate(); + } + + private interface ScheduledActionTester { + public void doAction(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/test/java/com/redhat/thermostat/backend/VmPollingBackendTest.java Tue Jun 07 11:14:49 2016 -0400 @@ -0,0 +1,215 @@ +/* + * Copyright 2012-2016 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.backend; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.concurrent.ScheduledExecutorService; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.agent.VmStatusListener.Status; +import com.redhat.thermostat.agent.VmStatusListenerRegistrar; +import com.redhat.thermostat.common.Version; + +public class VmPollingBackendTest { + + private VmPollingBackend backend; + private ScheduledExecutorService mockExecutor; + private VmStatusListenerRegistrar mockRegistrar; + + @Before + public void setUp() { + mockExecutor = mock(ScheduledExecutorService.class); + Version mockVersion = mock(Version.class); + when(mockVersion.getVersionNumber()).thenReturn("backend-version"); + mockRegistrar = mock(VmStatusListenerRegistrar.class); + backend = new VmPollingBackend("backend-name", "backend-description", + "backend-vendor", mockVersion, mockExecutor, mockRegistrar) { + @Override + public int getOrderValue() { + return 0; // Doesn't matter, not being tested. + } + }; + if (!backend.getObserveNewJvm()) { + /* At time of writing, default is true. This is + * inherited from parent PollingBackend. In case + * default changes: + */ + backend.setObserveNewJvm(true); + } + } + + @Test + public void verifyCustomActivateRegistersListener() { + backend.preActivate(); + verify(mockRegistrar).register(backend); + } + + @Test + public void verifyCustomDeactivateUnregistersListener() { + backend.postDeactivate(); + verify(mockRegistrar).unregister(backend); + } + + @Test + public void verifyRegisteredActionPerformed() { + String vmId = "test-vm-id"; + int pid = 123; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId, pid); + VmPollingAction action = mock(VmPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); + + verify(action).run(eq(vmId), eq(pid)); + } + + @Test + public void verifyMultipleRegisteredActionsPerformed() { + String vmId = "test-vm-id"; + int pid = 123; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId, pid); + VmPollingAction action1 = mock(VmPollingAction.class); + VmPollingAction action2 = mock(VmPollingAction.class); + backend.registerAction(action1); + backend.registerAction(action2); + backend.doScheduledActions(); + + verify(action1).run(eq(vmId), eq(pid)); + verify(action2).run(eq(vmId), eq(pid)); + } + + @Test + public void verifyActionsPerformedOnMultipleVms() { + String vmId1 = "test-vm-id1", vmId2 = "test-vm-id2"; + int pid1 = 123, pid2 = 456; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId1, pid1); + backend.vmStatusChanged(Status.VM_ACTIVE, vmId2, pid2); + VmPollingAction action = mock(VmPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); + + verify(action).run(eq(vmId1), eq(pid1)); + verify(action).run(eq(vmId2), eq(pid2)); + } + + @Test + public void verifyMultipleRegisteredActionsPerformedOnMultipleVms() { + String vmId1 = "test-vm-id1", vmId2 = "test-vm-id2"; + int pid1 = 123, pid2 = 456; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId1, pid1); + backend.vmStatusChanged(Status.VM_ACTIVE, vmId2, pid2); + VmPollingAction action1 = mock(VmPollingAction.class); + VmPollingAction action2 = mock(VmPollingAction.class); + backend.registerAction(action1); + backend.registerAction(action2); + backend.doScheduledActions(); + + verify(action1).run(eq(vmId1), eq(pid1)); + verify(action1).run(eq(vmId2), eq(pid2)); + verify(action2).run(eq(vmId1), eq(pid1)); + verify(action2).run(eq(vmId2), eq(pid2)); + } + + @Test + public void verifyUnregisteredActionNotPerformed() { + String vmId = "test-vm-id"; + int pid = 123; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId, pid); + VmPollingAction action1 = mock(VmPollingAction.class); + VmPollingAction action2 = mock(VmPollingAction.class); + backend.registerAction(action1); + backend.registerAction(action2); + backend.doScheduledActions(); // Triggers both + backend.unregisterAction(action1); + backend.doScheduledActions(); // Triggers only action2 + + verify(action1, times(1)).run(eq(vmId), eq(pid)); + verify(action2, times(2)).run(eq(vmId), eq(pid)); + } + + @Test + public void verifyVmStatusChangedStartedAndActiveResultInPolling() { + String vmId1 = "test-vm-id1", vmId2 = "test-vm-id2"; + int pid1 = 123, pid2 = 456; + backend.vmStatusChanged(Status.VM_STARTED, vmId1, pid1); + backend.vmStatusChanged(Status.VM_ACTIVE, vmId2, pid2); + VmPollingAction action = mock(VmPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); + + verify(action).run(eq(vmId1), eq(pid1)); + verify(action).run(eq(vmId2), eq(pid2)); + } + + @Test + public void verifyVmStatusChangedStopsResultsInNoMorePolling() { + String vmId1 = "test-vm-id1", vmId2 = "test-vm-id2"; + int pid1 = 123, pid2 = 456; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId1, pid1); + backend.vmStatusChanged(Status.VM_ACTIVE, vmId2, pid2); + VmPollingAction action = mock(VmPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); // Triggers for both vms + backend.vmStatusChanged(Status.VM_STOPPED, vmId1, pid1); + backend.doScheduledActions(); // Triggers only for vm2 + + verify(action, times(1)).run(eq(vmId1), eq(pid1)); + verify(action, times(2)).run(eq(vmId2), eq(pid2)); + } + + @Test + public void verifyGetSetObserveNewJvmWorksAsExpected() { + String vmId1 = "test-vm-id1", vmId2 = "test-vm-id2"; + int pid1 = 123, pid2 = 456; + backend.vmStatusChanged(Status.VM_ACTIVE, vmId1, pid1); + backend.setObserveNewJvm(false); + backend.vmStatusChanged(Status.VM_ACTIVE, vmId2, pid2); // Should be ignored. + VmPollingAction action = mock(VmPollingAction.class); + backend.registerAction(action); + backend.doScheduledActions(); + + verify(action).run(eq(vmId1), eq(pid1)); + verify(action, never()).run(eq(vmId2), eq(pid2)); + } +}