View Javadoc
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   * Processes plugin upgrade operations.
31   * <p>
32   * Upgrades are triggered by the start lifecycle event, and plugin enabled.
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       * Notifies the plugin upgrade manager that a plugin update tasks has been registered.
62       * <p>
63       * This method does nothing but logging at the moment. Is is now deprecated since it could result in circular
64       * dependency when trying to bind already exposed update tasks to plugin manager that is being created.
65       *
66       * @param task  the upgrade task that is being bound
67       * @param props the set of properties that the upgrade task was registered with
68       * @deprecated as of 2.0.16 no longer used we now use the eventing framework to run upgrade tasks for plugins that
69       *             are 'enabled', see {@link #onPluginEnabled(com.atlassian.plugin.event.events.PluginEnabledEvent)}
70       */
71      @SuppressWarnings("unchecked")
72      @Deprecated
73      public void onBind(final PluginUpgradeTask task, final Map props) {
74          // Doing lots here....
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          // TODO 1: should do something useful with the messages
84          // TODO 2: we don't know what upgrade tasks these messages came from
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         // Check if the Application is fully started:
102         if (started) {
103             // Run upgrades for this plugin that as been enabled AFTER the onStart event.
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         // If onStart() has not occurred yet then ignore event - we need to wait until the App is started properly.
113     }
114 
115     /**
116      * @return map of all upgrade tasks (stored by pluginKey)
117      */
118     protected Map<String, List<PluginUpgradeTask>> getUpgradeTasks() {
119         final Map<String, List<PluginUpgradeTask>> pluginUpgrades = new HashMap<>();
120 
121         // Find all implementations of PluginUpgradeTask
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         // 1. get all upgrade tasks for all plugins
140         final Map<String, List<PluginUpgradeTask>> pluginUpgrades = getUpgradeTasks();
141 
142 
143         final ArrayList<Message> messages = new ArrayList<Message>();
144 
145         // 2. for each plugin, sort tasks by build number and execute them
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             // nothing to do
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 }