From 7d44e90c8df13146918c66cebdbb2573e1dec4ce Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Nov 2015 17:15:57 +0000 Subject: [PATCH] Fix event UUIDS missing on event bus The fixing of CLOUDSTACK-8816 introduced a regression that removed the first class entities in the event bus description property. This is because everything was changed to use the Class as a key... Everything but the populateFirstClassEntities method in ActionEventUtils. --- .../src/com/cloud/event/ActionEventUtils.java | 3 +- .../com/cloud/event/ActionEventUtilsTest.java | 213 ++++++++++++++++++ 2 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 server/test/com/cloud/event/ActionEventUtilsTest.java diff --git a/server/src/com/cloud/event/ActionEventUtils.java b/server/src/com/cloud/event/ActionEventUtils.java index 0ca41cc3a73..b1dd8fdbaa1 100644 --- a/server/src/com/cloud/event/ActionEventUtils.java +++ b/server/src/com/cloud/event/ActionEventUtils.java @@ -293,8 +293,7 @@ public class ActionEventUtils { for(Map.Entry entry : contextMap.entrySet()){ try{ - Object key = entry.getKey(); - Class clz = Class.forName((String)key); + Class clz = (Class)entry.getKey(); if(clz != null && Identity.class.isAssignableFrom(clz)){ String uuid = getEntityUuid(clz, entry.getValue()); eventDescription.put(ReflectUtil.getEntityName(clz), uuid); diff --git a/server/test/com/cloud/event/ActionEventUtilsTest.java b/server/test/com/cloud/event/ActionEventUtilsTest.java new file mode 100644 index 00000000000..a7fcf53f61d --- /dev/null +++ b/server/test/com/cloud/event/ActionEventUtilsTest.java @@ -0,0 +1,213 @@ +package com.cloud.event; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; + +import org.apache.cloudstack.framework.events.Event; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.events.EventBus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + + +import com.cloud.configuration.Config; +import com.cloud.event.dao.EventDao; +import com.cloud.network.IpAddress; +import com.cloud.projects.dao.ProjectDao; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.VirtualMachine; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(ComponentContext.class) +public class ActionEventUtilsTest { + //Predictable constants used throughout this test. + public static final long EVENT_ID = 1; + public static final long USER_ID = 1; + public static final long ACCOUNT_ID = 1; + + //Keep track of the static field values between tests. + //A horrid abuse of reflection required due to the strange + //static/inject pattern found in ActionEventUtils. + protected Map staticFieldValues = new HashMap<>(); + + //List of events published on the event bus. Handled via a mocked method. + //Cleared on every run. + protected List publishedEvents = new ArrayList<>(); + + //Mock fields. These are injected into ActionEventUtils by the setup() method. + @Mock + protected EventDao eventDao; + + @Mock + protected AccountDao accountDao; + + @Mock + protected UserDao userDao; + + @Mock + protected ProjectDao projectDao; + + @Mock + protected EntityManager entityMgr; + + @Mock + protected ConfigurationDao configDao; + + @Mock + protected EventBus eventBus; + + /** + * This setup method injects the mocked beans into the ActionEventUtils class. + * Because ActionEventUtils has static methods, we must also remember these fields + * and restore them later, as otherwise strange behavior can result in other unit + * tests due to the way the JVM handles static fields. + * @throws Exception + */ + @Before + public void setup() throws Exception { + publishedEvents = new ArrayList<>(); + staticFieldValues = new HashMap<>(); + setupCommonMocks(); + + ActionEventUtils utils = new ActionEventUtils(); + + for (Field field : ActionEventUtils.class.getDeclaredFields()) { + if (field.getAnnotation(Inject.class) != null) { + field.setAccessible(true); + + try { + //Inject the mocked field from this class into the ActionEventUtils + //and keep track of its original value. + Field mockField = this.getClass().getDeclaredField(field.getName()); + field.set(utils, mockField.get(this)); + Field staticField = ActionEventUtils.class.getDeclaredField("s_" + field.getName()); + staticFieldValues.put(field.getName(), staticField.get(null)); + } + catch (Exception e) { + // ignore missing fields + } + } + } + + utils.init(); + } + + /** + * Set up the common specialized mocks that are needed to make the ActionEventUtils class behave in a + * predictable way. This method only mocks things that are common to all the tests. Each individual test + * also mocks some other methods (e.g. find user/account) by itself. + */ + public void setupCommonMocks() throws Exception { + //Some basic mocks. + Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true"); + PowerMockito.mockStatic(ComponentContext.class); + Mockito.when(ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus); + + //Needed for persist to actually set an ID that can be returned from the ActionEventUtils + //methods. + Mockito.when(eventDao.persist(Mockito.any(EventVO.class))).thenAnswer(new Answer() { + @Override + public EventVO answer(InvocationOnMock invocation) throws Throwable { + EventVO event = (EventVO)invocation.getArguments()[0]; + Field id = event.getClass().getDeclaredField("id"); + id.setAccessible(true); + id.set(event, EVENT_ID); + return event; + } + }); + + //Needed to record events published on the bus. + Mockito.doAnswer(new Answer() { + @Override public Void answer(InvocationOnMock invocation) throws Throwable { + Event event = (Event)invocation.getArguments()[0]; + publishedEvents.add(event); + return null; + } + + }).when(eventBus).publish(Mockito.any(Event.class)); + } + + /** + * This teardown method restores the ActionEventUtils static field values to their original values, + * keeping the mocked mess inside this class. + */ + @After + public void teardown() { + ActionEventUtils utils = new ActionEventUtils(); + + for (String fieldName : staticFieldValues.keySet()) { + try { + Field field = ActionEventUtils.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(utils, staticFieldValues.get(fieldName)); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + utils.init(); + } + + @Test + public void testPopulateFirstClassEntities() { + AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid"); + account.setId(ACCOUNT_ID); + UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + + Mockito.when(accountDao.findById(ACCOUNT_ID)).thenReturn(account); + Mockito.when(userDao.findById(USER_ID)).thenReturn(user); + + CallContext.register(user, account); + + //Inject some entity UUIDs into the call context + String instanceUuid = UUID.randomUUID().toString(); + String ipUuid = UUID.randomUUID().toString(); + CallContext.current().putContextParameter(VirtualMachine.class, instanceUuid); + CallContext.current().putContextParameter(IpAddress.class, ipUuid); + + ActionEventUtils.onActionEvent(USER_ID, ACCOUNT_ID, account.getDomainId(), "StaticNat", "Test event"); + + //Assertions + Assert.assertNotEquals(publishedEvents.size(), 0); + Assert.assertEquals(publishedEvents.size(), 1); + + Event event = publishedEvents.get(0); + Assert.assertNotNull(event.getDescription()); + + JsonObject json = new JsonParser().parse(event.getDescription()).getAsJsonObject(); + + Assert.assertTrue(json.has("VirtualMachine")); + Assert.assertTrue(json.has("IpAddress")); + Assert.assertEquals(json.get("VirtualMachine").getAsString(), instanceUuid); + Assert.assertEquals(json.get("IpAddress").getAsString(), ipUuid); + + CallContext.unregister(); + } +}