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.PluginEventListener;
8 import com.atlassian.plugin.event.PluginEventManager;
9 import com.atlassian.plugin.event.events.PluginEnabledEvent;
10 import com.atlassian.sal.api.lifecycle.LifecycleAware;
11 import com.atlassian.sal.api.message.Message;
12 import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
13 import com.atlassian.sal.api.transaction.TransactionCallback;
14 import com.atlassian.sal.api.transaction.TransactionTemplate;
15 import com.atlassian.sal.api.upgrade.PluginUpgradeManager;
16 import com.atlassian.sal.api.upgrade.PluginUpgradeTask;
17 import com.atlassian.sal.core.message.DefaultMessage;
18 import com.google.common.collect.ImmutableList;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21 import org.springframework.beans.factory.InitializingBean;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.TimeUnit;
28
29
30
31
32
33
34 public class DefaultPluginUpgradeManager implements PluginUpgradeManager, LifecycleAware, InitializingBean {
35 private static final Logger log = LoggerFactory.getLogger(DefaultPluginUpgradeManager.class);
36
37 protected static final String LOCK_TIMEOUT_PROPERTY = "sal.upgrade.task.lock.timeout";
38 protected static final int LOCK_TIMEOUT_SECONDS = Integer.getInteger(LOCK_TIMEOUT_PROPERTY, 300000);
39
40 private volatile boolean started = false;
41
42 private final List<PluginUpgradeTask> upgradeTasks;
43 private final TransactionTemplate transactionTemplate;
44 private final PluginAccessor pluginAccessor;
45 private final PluginSettingsFactory pluginSettingsFactory;
46 private final PluginEventManager pluginEventManager;
47 private final ClusterLockService clusterLockService;
48
49 public DefaultPluginUpgradeManager(final List<PluginUpgradeTask> upgradeTasks, final TransactionTemplate transactionTemplate,
50 final PluginAccessor pluginAccessor, final PluginSettingsFactory pluginSettingsFactory,
51 final PluginEventManager pluginEventManager, final ClusterLockService clusterLockService) {
52 this.upgradeTasks = upgradeTasks;
53 this.transactionTemplate = transactionTemplate;
54 this.pluginAccessor = pluginAccessor;
55 this.pluginSettingsFactory = pluginSettingsFactory;
56 this.pluginEventManager = pluginEventManager;
57 this.clusterLockService = clusterLockService;
58 }
59
60
61
62
63
64
65
66
67
68
69
70
71 @SuppressWarnings("unchecked")
72 @Deprecated
73 public void onBind(final PluginUpgradeTask task, final Map props) {
74
75 log.debug("onbind task = [" + task.getPluginKey() + ", " + task.getBuildNumber() + "] ");
76 }
77
78 @Override
79 public void onStart() {
80 log.debug("onStart");
81 final List<Message> messages = upgrade();
82
83
84
85 if (messages != null) {
86 for (final Message msg : messages) {
87 log.error("Upgrade error: " + msg);
88 }
89 }
90
91 started = true;
92 }
93
94 @Override
95 public void onStop() {
96 pluginEventManager.unregister(this);
97 }
98
99 @PluginEventListener
100 public void onPluginEnabled(PluginEnabledEvent event) {
101
102 if (started) {
103
104 final List<Message> messages = upgradeInternal(event.getPlugin());
105 if (messages != null && messages.size() > 0) {
106 log.error("Error(s) encountered while upgrading plugin '" + event.getPlugin().getName() + "' on enable.");
107 for (final Message msg : messages) {
108 log.error("Upgrade error: " + msg);
109 }
110 }
111 }
112
113 }
114
115
116
117
118 protected Map<String, List<PluginUpgradeTask>> getUpgradeTasks() {
119 final Map<String, List<PluginUpgradeTask>> pluginUpgrades = new HashMap<>();
120
121
122 for (final PluginUpgradeTask upgradeTask : upgradeTasks) {
123 pluginUpgrades.computeIfAbsent(upgradeTask.getPluginKey(), k -> new ArrayList<>())
124 .add(upgradeTask);
125 }
126
127 return pluginUpgrades;
128 }
129
130
131 @SuppressWarnings("unchecked")
132 public List<Message> upgrade() {
133 return upgradeInternal();
134 }
135
136 public List<Message> upgradeInternal() {
137 log.info("Running plugin upgrade tasks...");
138
139
140 final Map<String, List<PluginUpgradeTask>> pluginUpgrades = getUpgradeTasks();
141
142
143 final ArrayList<Message> messages = new ArrayList<Message>();
144
145
146 for (final String pluginKey : pluginUpgrades.keySet()) {
147 final List<PluginUpgradeTask> upgrades = pluginUpgrades.get(pluginKey);
148
149 final List<Message> upgradeMessages = upgradePlugin(pluginKey, upgrades);
150 if (upgradeMessages != null) {
151 messages.addAll(upgradeMessages);
152 }
153 }
154
155 return messages;
156 }
157
158 public List<Message> upgradeInternal(Plugin plugin) {
159 final Map<String, List<PluginUpgradeTask>> pluginUpgrades = getUpgradeTasks();
160 final String pluginKey = plugin.getKey();
161 final List<PluginUpgradeTask> upgrades = pluginUpgrades.get(pluginKey);
162 if (upgrades == null) {
163
164 return null;
165 }
166 return upgradePlugin(pluginKey, upgrades);
167 }
168
169 private List<Message> upgradePlugin(final String pluginKey, final List<PluginUpgradeTask> upgrades) {
170 return transactionTemplate.execute(() -> {
171 final Plugin plugin = pluginAccessor.getPlugin(pluginKey);
172 if (plugin == null) {
173 throw new IllegalArgumentException("Invalid plugin key: " + pluginKey);
174 }
175
176 final PluginUpgrader pluginUpgrader = new PluginUpgrader(plugin, pluginSettingsFactory.createGlobalSettings(), upgrades);
177
178 final String lockName = "sal.upgrade." + pluginKey;
179 final ClusterLock lock = clusterLockService.getLockForName(lockName);
180 try {
181 if (!lock.tryLock(LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
182 final String timeoutMessage = "unable to acquire cluster lock named '" + lockName +
183 "' after waiting " + LOCK_TIMEOUT_SECONDS +
184 " seconds; note that this timeout may be adjusted via the system property '" +
185 LOCK_TIMEOUT_PROPERTY + "'";
186 log.error(timeoutMessage);
187 return ImmutableList.of(new DefaultMessage(timeoutMessage));
188 }
189 } catch (InterruptedException e) {
190 final String interruptedMessage = "interrupted while trying to acquire cluster lock named '" + lockName +
191 "' " + e.getMessage();
192 log.error(interruptedMessage);
193 return ImmutableList.of(new DefaultMessage(interruptedMessage));
194 }
195
196 try {
197 return pluginUpgrader.upgrade();
198 } finally {
199 lock.unlock();
200 }
201 });
202 }
203
204 public void afterPropertiesSet() throws Exception {
205 pluginEventManager.register(this);
206 }
207 }