Skip to content

Commit 2ae3e0c

Browse files
committed
Merge release branch 4.6 to master
* 4.6: Fix event UUIDS missing on event bus Add select template dropdown when reinstall VM CLOUDSTACK-9068: Listing Port Forwarding Rules take too much time to load
2 parents d7859ad + 399d052 commit 2ae3e0c

4 files changed

Lines changed: 261 additions & 6 deletions

File tree

server/src/com/cloud/event/ActionEventUtils.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,7 @@ private static void populateFirstClassEntities(Map<String, String> eventDescript
293293

294294
for(Map.Entry<Object, Object> entry : contextMap.entrySet()){
295295
try{
296-
Object key = entry.getKey();
297-
Class<?> clz = Class.forName((String)key);
296+
Class<?> clz = (Class<?>)entry.getKey();
298297
if(clz != null && Identity.class.isAssignableFrom(clz)){
299298
String uuid = getEntityUuid(clz, entry.getValue());
300299
eventDescription.put(ReflectUtil.getEntityName(clz), uuid);
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package com.cloud.event;
2+
3+
import java.lang.reflect.Field;
4+
import java.util.ArrayList;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.UUID;
9+
10+
import javax.inject.Inject;
11+
12+
import org.apache.cloudstack.framework.events.Event;
13+
import org.apache.cloudstack.context.CallContext;
14+
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
15+
import org.apache.cloudstack.framework.events.EventBus;
16+
import org.junit.After;
17+
import org.junit.Assert;
18+
import org.junit.Before;
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.Mock;
22+
import org.mockito.Mockito;
23+
import org.mockito.invocation.InvocationOnMock;
24+
import org.mockito.stubbing.Answer;
25+
import org.powermock.api.mockito.PowerMockito;
26+
import org.powermock.core.classloader.annotations.PrepareForTest;
27+
import org.powermock.modules.junit4.PowerMockRunner;
28+
29+
30+
import com.cloud.configuration.Config;
31+
import com.cloud.event.dao.EventDao;
32+
import com.cloud.network.IpAddress;
33+
import com.cloud.projects.dao.ProjectDao;
34+
import com.cloud.user.AccountVO;
35+
import com.cloud.user.User;
36+
import com.cloud.user.UserVO;
37+
import com.cloud.user.dao.AccountDao;
38+
import com.cloud.user.dao.UserDao;
39+
import com.cloud.utils.component.ComponentContext;
40+
import com.cloud.utils.db.EntityManager;
41+
import com.cloud.vm.VirtualMachine;
42+
import com.google.gson.JsonObject;
43+
import com.google.gson.JsonParser;
44+
45+
@RunWith(PowerMockRunner.class)
46+
@PrepareForTest(ComponentContext.class)
47+
public class ActionEventUtilsTest {
48+
//Predictable constants used throughout this test.
49+
public static final long EVENT_ID = 1;
50+
public static final long USER_ID = 1;
51+
public static final long ACCOUNT_ID = 1;
52+
53+
//Keep track of the static field values between tests.
54+
//A horrid abuse of reflection required due to the strange
55+
//static/inject pattern found in ActionEventUtils.
56+
protected Map<String, Object> staticFieldValues = new HashMap<>();
57+
58+
//List of events published on the event bus. Handled via a mocked method.
59+
//Cleared on every run.
60+
protected List<Event> publishedEvents = new ArrayList<>();
61+
62+
//Mock fields. These are injected into ActionEventUtils by the setup() method.
63+
@Mock
64+
protected EventDao eventDao;
65+
66+
@Mock
67+
protected AccountDao accountDao;
68+
69+
@Mock
70+
protected UserDao userDao;
71+
72+
@Mock
73+
protected ProjectDao projectDao;
74+
75+
@Mock
76+
protected EntityManager entityMgr;
77+
78+
@Mock
79+
protected ConfigurationDao configDao;
80+
81+
@Mock
82+
protected EventBus eventBus;
83+
84+
/**
85+
* This setup method injects the mocked beans into the ActionEventUtils class.
86+
* Because ActionEventUtils has static methods, we must also remember these fields
87+
* and restore them later, as otherwise strange behavior can result in other unit
88+
* tests due to the way the JVM handles static fields.
89+
* @throws Exception
90+
*/
91+
@Before
92+
public void setup() throws Exception {
93+
publishedEvents = new ArrayList<>();
94+
staticFieldValues = new HashMap<>();
95+
setupCommonMocks();
96+
97+
ActionEventUtils utils = new ActionEventUtils();
98+
99+
for (Field field : ActionEventUtils.class.getDeclaredFields()) {
100+
if (field.getAnnotation(Inject.class) != null) {
101+
field.setAccessible(true);
102+
103+
try {
104+
//Inject the mocked field from this class into the ActionEventUtils
105+
//and keep track of its original value.
106+
Field mockField = this.getClass().getDeclaredField(field.getName());
107+
field.set(utils, mockField.get(this));
108+
Field staticField = ActionEventUtils.class.getDeclaredField("s_" + field.getName());
109+
staticFieldValues.put(field.getName(), staticField.get(null));
110+
}
111+
catch (Exception e) {
112+
// ignore missing fields
113+
}
114+
}
115+
}
116+
117+
utils.init();
118+
}
119+
120+
/**
121+
* Set up the common specialized mocks that are needed to make the ActionEventUtils class behave in a
122+
* predictable way. This method only mocks things that are common to all the tests. Each individual test
123+
* also mocks some other methods (e.g. find user/account) by itself.
124+
*/
125+
public void setupCommonMocks() throws Exception {
126+
//Some basic mocks.
127+
Mockito.when(configDao.getValue(Config.PublishActionEvent.key())).thenReturn("true");
128+
PowerMockito.mockStatic(ComponentContext.class);
129+
Mockito.when(ComponentContext.getComponent(EventBus.class)).thenReturn(eventBus);
130+
131+
//Needed for persist to actually set an ID that can be returned from the ActionEventUtils
132+
//methods.
133+
Mockito.when(eventDao.persist(Mockito.any(EventVO.class))).thenAnswer(new Answer<EventVO>() {
134+
@Override
135+
public EventVO answer(InvocationOnMock invocation) throws Throwable {
136+
EventVO event = (EventVO)invocation.getArguments()[0];
137+
Field id = event.getClass().getDeclaredField("id");
138+
id.setAccessible(true);
139+
id.set(event, EVENT_ID);
140+
return event;
141+
}
142+
});
143+
144+
//Needed to record events published on the bus.
145+
Mockito.doAnswer(new Answer<Void>() {
146+
@Override public Void answer(InvocationOnMock invocation) throws Throwable {
147+
Event event = (Event)invocation.getArguments()[0];
148+
publishedEvents.add(event);
149+
return null;
150+
}
151+
152+
}).when(eventBus).publish(Mockito.any(Event.class));
153+
}
154+
155+
/**
156+
* This teardown method restores the ActionEventUtils static field values to their original values,
157+
* keeping the mocked mess inside this class.
158+
*/
159+
@After
160+
public void teardown() {
161+
ActionEventUtils utils = new ActionEventUtils();
162+
163+
for (String fieldName : staticFieldValues.keySet()) {
164+
try {
165+
Field field = ActionEventUtils.class.getDeclaredField(fieldName);
166+
field.setAccessible(true);
167+
field.set(utils, staticFieldValues.get(fieldName));
168+
}
169+
catch (Exception e) {
170+
e.printStackTrace();
171+
}
172+
}
173+
174+
utils.init();
175+
}
176+
177+
@Test
178+
public void testPopulateFirstClassEntities() {
179+
AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid");
180+
account.setId(ACCOUNT_ID);
181+
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone",
182+
UUID.randomUUID().toString(), User.Source.UNKNOWN);
183+
184+
Mockito.when(accountDao.findById(ACCOUNT_ID)).thenReturn(account);
185+
Mockito.when(userDao.findById(USER_ID)).thenReturn(user);
186+
187+
CallContext.register(user, account);
188+
189+
//Inject some entity UUIDs into the call context
190+
String instanceUuid = UUID.randomUUID().toString();
191+
String ipUuid = UUID.randomUUID().toString();
192+
CallContext.current().putContextParameter(VirtualMachine.class, instanceUuid);
193+
CallContext.current().putContextParameter(IpAddress.class, ipUuid);
194+
195+
ActionEventUtils.onActionEvent(USER_ID, ACCOUNT_ID, account.getDomainId(), "StaticNat", "Test event");
196+
197+
//Assertions
198+
Assert.assertNotEquals(publishedEvents.size(), 0);
199+
Assert.assertEquals(publishedEvents.size(), 1);
200+
201+
Event event = publishedEvents.get(0);
202+
Assert.assertNotNull(event.getDescription());
203+
204+
JsonObject json = new JsonParser().parse(event.getDescription()).getAsJsonObject();
205+
206+
Assert.assertTrue(json.has("VirtualMachine"));
207+
Assert.assertTrue(json.has("IpAddress"));
208+
Assert.assertEquals(json.get("VirtualMachine").getAsString(), instanceUuid);
209+
Assert.assertEquals(json.get("IpAddress").getAsString(), ipUuid);
210+
211+
CallContext.unregister();
212+
}
213+
}

ui/scripts/instances.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,10 +879,56 @@
879879
return null;
880880
}
881881
},
882+
createForm: {
883+
title: 'label.reinstall.vm',
884+
desc: 'message.reinstall.vm',
885+
isWarning: true,
886+
fields: {
887+
template: {
888+
label: 'label.select.a.template',
889+
select: function(args) {
890+
var data = {
891+
templatefilter: 'featured'
892+
};
893+
$.ajax({
894+
url: createURL('listTemplates'),
895+
data: data,
896+
async: false,
897+
success: function(json) {
898+
var templates = json.listtemplatesresponse.template;
899+
var items = [{
900+
id: -1,
901+
description: ''
902+
}];
903+
$(templates).each(function() {
904+
items.push({
905+
id: this.id,
906+
description: this.name
907+
});
908+
});
909+
args.response.success({
910+
data: items
911+
});
912+
}
913+
});
914+
}
915+
}
916+
}
917+
},
882918

883919
action: function(args) {
920+
var dataObj = {
921+
virtualmachineid: args.context.instances[0].id
922+
};
923+
if (args.data.template != -1) {
924+
$.extend(dataObj, {
925+
templateid: args.data.template
926+
});
927+
}
928+
884929
$.ajax({
885-
url: createURL("restoreVirtualMachine&virtualmachineid=" + args.context.instances[0].id),
930+
url: createURL("restoreVirtualMachine"),
931+
data: dataObj,
886932
dataType: "json",
887933
async: true,
888934
success: function(json) {

ui/scripts/ui/widgets/multiEdit.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,6 @@
278278
$td.addClass('blank');
279279
}
280280

281-
// Align width to main header
282-
_medit.refreshItemWidths($multi);
283-
284281
if (data._hideFields &&
285282
$.inArray(fieldName, data._hideFields) > -1) {
286283
$td.addClass('disabled');

0 commit comments

Comments
 (0)