1 package com.atlassian.sal.core.upgrade;
2
3 import com.atlassian.beehive.ClusterLock;
4 import com.atlassian.beehive.ClusterLockService;
5 import com.atlassian.plugin.Plugin;
6 import com.atlassian.plugin.PluginAccessor;
7 import com.atlassian.plugin.event.PluginEventManager;
8 import com.atlassian.sal.api.message.Message;
9 import com.atlassian.sal.api.pluginsettings.PluginSettings;
10 import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
11 import com.atlassian.sal.api.transaction.TransactionCallback;
12 import com.atlassian.sal.api.transaction.TransactionTemplate;
13 import com.atlassian.sal.api.upgrade.PluginUpgradeTask;
14 import com.atlassian.sal.core.message.DefaultMessage;
15 import org.junit.After;
16 import org.junit.Before;
17 import org.junit.Test;
18 import org.junit.runner.RunWith;
19 import org.mockito.ArgumentCaptor;
20 import org.mockito.Mock;
21 import org.mockito.junit.MockitoJUnitRunner;
22
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.concurrent.TimeUnit;
27
28 import static com.atlassian.sal.core.upgrade.PluginUpgrader.BUILD;
29 import static java.util.Collections.singletonList;
30 import static org.hamcrest.Matchers.contains;
31 import static org.hamcrest.Matchers.empty;
32 import static org.hamcrest.Matchers.is;
33 import static org.hamcrest.Matchers.not;
34 import static org.junit.Assert.assertThat;
35 import static org.mockito.ArgumentMatchers.anyString;
36 import static org.mockito.Mockito.never;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40
41 @RunWith(MockitoJUnitRunner.class)
42 public class DefaultPluginUpgradeManagerTest {
43 @Mock
44 private Plugin plugin;
45 @Mock
46 private Plugin secondPlugin;
47 @Mock
48 private PluginUpgradeTask pluginUpgradeTask;
49 @Mock
50 private PluginUpgradeTask secondPluginUpgradeTask;
51 @Mock
52 private PluginUpgradeTask thirdPluginUpgradeTask;
53 @Mock
54 private PluginAccessor pluginAccessor;
55 @Mock
56 private PluginSettingsFactory pluginSettingsFactory;
57 @Mock
58 private PluginEventManager pluginEventManager;
59 @Mock
60 private PluginSettings pluginSettings;
61 @Mock
62 private ClusterLockService clusterLockService;
63 @Mock
64 private ClusterLock clusterLock;
65
66 private static final String PLUGIN_KEY = "com.atlassian.plugin.test.upgrade-test";
67 private static final String SECOND_PLUGIN_KEY = "com.atlassian.plugin2.test.upgrade-test";
68
69 private DefaultPluginUpgradeManager pluginUpgradeManager;
70 private CountingTransactionTemplate transactionTemplate;
71
72 @Before
73 public void setUp() throws Exception {
74 transactionTemplate = new CountingTransactionTemplate();
75
76 when(plugin.getKey()).thenReturn(PLUGIN_KEY);
77 when(secondPlugin.getKey()).thenReturn(SECOND_PLUGIN_KEY);
78
79 when(pluginAccessor.getPlugin(PLUGIN_KEY)).thenReturn(plugin);
80 when(pluginAccessor.getPlugin(SECOND_PLUGIN_KEY)).thenReturn(secondPlugin);
81
82 when(pluginUpgradeTask.getPluginKey()).thenReturn(PLUGIN_KEY);
83 when(pluginUpgradeTask.getBuildNumber()).thenReturn(1);
84 when(pluginUpgradeTask.doUpgrade()).thenReturn(Collections.<Message>emptyList());
85
86 when(secondPluginUpgradeTask.getPluginKey()).thenReturn(PLUGIN_KEY);
87 when(secondPluginUpgradeTask.getBuildNumber()).thenReturn(2);
88 when(secondPluginUpgradeTask.doUpgrade()).thenReturn(Collections.<Message>emptyList());
89
90 when(thirdPluginUpgradeTask.getPluginKey()).thenReturn(SECOND_PLUGIN_KEY);
91 when(thirdPluginUpgradeTask.getBuildNumber()).thenReturn(1);
92 when(thirdPluginUpgradeTask.doUpgrade()).thenReturn(Collections.<Message>emptyList());
93
94 when(pluginSettingsFactory.createGlobalSettings()).thenReturn(pluginSettings);
95
96 when(clusterLockService.getLockForName(anyString())).thenReturn(clusterLock);
97 }
98
99 @After
100 public void tearDown() {
101 transactionTemplate.resetTransactionCount();
102 }
103
104 @Test
105 public void testUpgradeInternalIsSuccessful() throws Exception {
106 pluginUpgradeManager = new DefaultPluginUpgradeManager(singletonList(pluginUpgradeTask), transactionTemplate,
107 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
108
109 when(pluginSettings.get(plugin.getKey() + BUILD)).thenReturn(Integer.toString(0));
110 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(true);
111
112 List<Message> messages = pluginUpgradeManager.upgradeInternal(plugin);
113
114
115 verify(pluginSettings).put(PLUGIN_KEY + BUILD, "1");
116
117 assertThat(messages, empty());
118
119 verify(clusterLock).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
120 verify(clusterLock).unlock();
121 }
122
123 @Test
124 public void testUpgradeInternalFailsIfUpgradeFails() throws Exception {
125 pluginUpgradeManager = new DefaultPluginUpgradeManager(singletonList(pluginUpgradeTask), transactionTemplate,
126 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
127
128 when(pluginUpgradeTask.doUpgrade()).thenReturn(Collections.<Message>singletonList(new DefaultMessage("failed.upgrade")));
129 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(true);
130
131 List<Message> messages = pluginUpgradeManager.upgradeInternal(plugin);
132
133
134 verify(pluginSettings, never()).put(anyString(), anyString());
135
136 assertThat(messages, not(empty()));
137
138 ArgumentCaptor<String> lockName = ArgumentCaptor.forClass(String.class);
139 verify(clusterLockService).getLockForName(lockName.capture());
140 verify(clusterLock).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
141 verify(clusterLock).unlock();
142
143 messages = pluginUpgradeManager.upgradeInternal(plugin);
144
145 verify(clusterLockService, times(2)).getLockForName(lockName.getValue());
146 }
147
148 @Test
149 public void testThatOnlyOneTransactionTemplateIsExecutedPerPluginUpgrade() throws Exception {
150 pluginUpgradeManager = new DefaultPluginUpgradeManager(Arrays.asList(pluginUpgradeTask, secondPluginUpgradeTask), transactionTemplate,
151 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
152
153 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(true);
154
155 List<Message> messages = pluginUpgradeManager.upgradeInternal(plugin);
156
157 assertThat(messages, empty());
158 assertThat(transactionTemplate.getTransactionCount(), is(1));
159
160 verify(pluginSettings).put(PLUGIN_KEY + BUILD, "2");
161
162 verify(clusterLock).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
163 verify(clusterLock).unlock();
164 }
165
166 @Test
167 public void testThatOnlyOneTransactionTemplateIsExecutedPerPluginUpgradeWithMultiplePlugins() throws Exception {
168 pluginUpgradeManager = new DefaultPluginUpgradeManager(Arrays.asList(pluginUpgradeTask, secondPluginUpgradeTask, thirdPluginUpgradeTask), transactionTemplate,
169 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
170
171 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(true);
172
173 List<Message> messages = pluginUpgradeManager.upgradeInternal();
174
175 verify(pluginSettings).put(PLUGIN_KEY + BUILD, "2");
176 verify(pluginSettings).put(SECOND_PLUGIN_KEY + BUILD, "1");
177
178 assertThat(messages, empty());
179 assertThat(transactionTemplate.getTransactionCount(), is(2));
180
181 verify(clusterLock, times(2)).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
182 verify(clusterLock, times(2)).unlock();
183 }
184
185 @Test
186 public void testUpgradeLockTimeout() throws InterruptedException {
187 pluginUpgradeManager = new DefaultPluginUpgradeManager(singletonList(pluginUpgradeTask), transactionTemplate,
188 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
189
190 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenReturn(false);
191
192 List<Message> messages = pluginUpgradeManager.upgradeInternal(plugin);
193
194 final Message expectedMessage = new DefaultMessage("unable to acquire cluster lock named 'sal.upgrade.com.atlassian.plugin.test.upgrade-test' after waiting " + DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS + " seconds; note that this timeout may be adjusted via the system property '" + DefaultPluginUpgradeManager.LOCK_TIMEOUT_PROPERTY + "'");
195 assertThat(messages, contains(expectedMessage));
196
197 verify(clusterLock).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
198 }
199
200 @Test
201 public void testUpgradeLockInterrupted() throws InterruptedException {
202 pluginUpgradeManager = new DefaultPluginUpgradeManager(singletonList(pluginUpgradeTask), transactionTemplate,
203 pluginAccessor, pluginSettingsFactory, pluginEventManager, clusterLockService);
204
205 when(clusterLock.tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).thenThrow(new InterruptedException("blargh"));
206
207 List<Message> messages = pluginUpgradeManager.upgradeInternal(plugin);
208
209 final Message expectedMessage = new DefaultMessage("interrupted while trying to acquire cluster lock named 'sal.upgrade.com.atlassian.plugin.test.upgrade-test' blargh");
210 assertThat(messages, contains(expectedMessage));
211
212 verify(clusterLock).tryLock(DefaultPluginUpgradeManager.LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
213 }
214
215 private static class CountingTransactionTemplate implements TransactionTemplate {
216 private int transactionCount = 0;
217
218 @Override
219 public <T> T execute(final TransactionCallback<T> action) {
220 transactionCount++;
221 return action.doInTransaction();
222 }
223
224 void resetTransactionCount() {
225 transactionCount = 0;
226 }
227
228 int getTransactionCount() {
229 return transactionCount;
230 }
231 }
232 }