|
42 | 42 | import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate; |
43 | 43 | import org.apache.cloudstack.agent.lb.SetupMSListAnswer; |
44 | 44 | import org.apache.cloudstack.agent.lb.SetupMSListCommand; |
| 45 | +import org.apache.cloudstack.ca.PostCertificateRenewalCommand; |
45 | 46 | import org.apache.cloudstack.ca.SetupCertificateAnswer; |
46 | 47 | import org.apache.cloudstack.ca.SetupCertificateCommand; |
47 | 48 | import org.apache.cloudstack.ca.SetupKeyStoreCommand; |
|
68 | 69 | import com.cloud.agent.transport.Request; |
69 | 70 | import com.cloud.agent.transport.Response; |
70 | 71 | import com.cloud.exception.AgentControlChannelException; |
| 72 | +import com.cloud.host.Host; |
71 | 73 | import com.cloud.resource.ServerResource; |
72 | 74 | import com.cloud.utils.PropertiesUtil; |
73 | 75 | import com.cloud.utils.StringUtils; |
@@ -127,6 +129,7 @@ public int value() { |
127 | 129 | Long _id; |
128 | 130 |
|
129 | 131 | Timer _timer = new Timer("Agent Timer"); |
| 132 | + Timer certTimer; |
130 | 133 | Timer hostLBTimer; |
131 | 134 |
|
132 | 135 | List<WatchTask> _watchList = new ArrayList<WatchTask>(); |
@@ -140,9 +143,11 @@ public int value() { |
140 | 143 | long _startupWait = _startupWaitDefault; |
141 | 144 | boolean _reconnectAllowed = true; |
142 | 145 | //For time sentitive task, e.g. PingTask |
143 | | - private final ThreadPoolExecutor _ugentTaskPool; |
| 146 | + ThreadPoolExecutor _ugentTaskPool; |
144 | 147 | ExecutorService _executor; |
145 | 148 |
|
| 149 | + Thread _shutdownThread = new ShutdownThread(this); |
| 150 | + |
146 | 151 | private String _keystoreSetupPath; |
147 | 152 | private String _keystoreCertImportPath; |
148 | 153 |
|
@@ -153,7 +158,7 @@ public Agent(final IAgentShell shell) { |
153 | 158 |
|
154 | 159 | _connection = new NioClient("Agent", _shell.getNextHost(), _shell.getPort(), _shell.getWorkers(), this); |
155 | 160 |
|
156 | | - Runtime.getRuntime().addShutdownHook(new ShutdownThread(this)); |
| 161 | + Runtime.getRuntime().addShutdownHook(_shutdownThread); |
157 | 162 |
|
158 | 163 | _ugentTaskPool = |
159 | 164 | new ThreadPoolExecutor(shell.getPingRetries(), 2 * shell.getPingRetries(), 10, TimeUnit.MINUTES, new SynchronousQueue<Runnable>(), new NamedThreadFactory( |
@@ -192,7 +197,7 @@ public Agent(final IAgentShell shell, final int localAgentId, final ServerResour |
192 | 197 | // ((NioClient)_connection).setBindAddress(_shell.getPrivateIp()); |
193 | 198 |
|
194 | 199 | s_logger.debug("Adding shutdown hook"); |
195 | | - Runtime.getRuntime().addShutdownHook(new ShutdownThread(this)); |
| 200 | + Runtime.getRuntime().addShutdownHook(_shutdownThread); |
196 | 201 |
|
197 | 202 | _ugentTaskPool = |
198 | 203 | new ThreadPoolExecutor(shell.getPingRetries(), 2 * shell.getPingRetries(), 10, TimeUnit.MINUTES, new SynchronousQueue<Runnable>(), new NamedThreadFactory( |
@@ -239,20 +244,39 @@ public String getResourceName() { |
239 | 244 | return _resource.getClass().getSimpleName(); |
240 | 245 | } |
241 | 246 |
|
| 247 | + /** |
| 248 | + * In case of a software based agent restart, this method |
| 249 | + * can help to perform explicit garbage collection of any old |
| 250 | + * agent instances and its inner objects. |
| 251 | + */ |
| 252 | + private void scavengeOldAgentObjects() { |
| 253 | + _executor.submit(new Runnable() { |
| 254 | + @Override |
| 255 | + public void run() { |
| 256 | + try { |
| 257 | + Thread.sleep(2000L); |
| 258 | + } catch (final InterruptedException ignored) { |
| 259 | + } finally { |
| 260 | + System.gc(); |
| 261 | + } |
| 262 | + } |
| 263 | + }); |
| 264 | + } |
| 265 | + |
242 | 266 | public void start() { |
243 | 267 | if (!_resource.start()) { |
244 | 268 | s_logger.error("Unable to start the resource: " + _resource.getName()); |
245 | 269 | throw new CloudRuntimeException("Unable to start the resource: " + _resource.getName()); |
246 | 270 | } |
247 | 271 |
|
248 | | - _keystoreSetupPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreSetupScript); |
| 272 | + _keystoreSetupPath = Script.findScript("scripts/util/", KeyStoreUtils.KS_SETUP_SCRIPT); |
249 | 273 | if (_keystoreSetupPath == null) { |
250 | | - throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreSetupScript)); |
| 274 | + throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.KS_SETUP_SCRIPT)); |
251 | 275 | } |
252 | 276 |
|
253 | | - _keystoreCertImportPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreImportScript); |
| 277 | + _keystoreCertImportPath = Script.findScript("scripts/util/", KeyStoreUtils.KS_IMPORT_SCRIPT); |
254 | 278 | if (_keystoreCertImportPath == null) { |
255 | | - throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreImportScript)); |
| 279 | + throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.KS_IMPORT_SCRIPT)); |
256 | 280 | } |
257 | 281 |
|
258 | 282 | try { |
@@ -274,6 +298,7 @@ public void start() { |
274 | 298 | } |
275 | 299 | } |
276 | 300 | _shell.updateConnectedHost(); |
| 301 | + scavengeOldAgentObjects(); |
277 | 302 | } |
278 | 303 |
|
279 | 304 | public void stop(final String reason, final String detail) { |
@@ -298,14 +323,42 @@ public void stop(final String reason, final String detail) { |
298 | 323 | } |
299 | 324 | _connection.stop(); |
300 | 325 | _connection = null; |
| 326 | + _link = null; |
301 | 327 | } |
302 | 328 |
|
303 | 329 | if (_resource != null) { |
304 | 330 | _resource.stop(); |
305 | 331 | _resource = null; |
306 | 332 | } |
307 | 333 |
|
308 | | - _ugentTaskPool.shutdownNow(); |
| 334 | + if (_startup != null) { |
| 335 | + _startup = null; |
| 336 | + } |
| 337 | + |
| 338 | + if (_ugentTaskPool != null) { |
| 339 | + _ugentTaskPool.shutdownNow(); |
| 340 | + _ugentTaskPool = null; |
| 341 | + } |
| 342 | + |
| 343 | + if (_executor != null) { |
| 344 | + _executor.shutdown(); |
| 345 | + _executor = null; |
| 346 | + } |
| 347 | + |
| 348 | + if (_timer != null) { |
| 349 | + _timer.cancel(); |
| 350 | + _timer = null; |
| 351 | + } |
| 352 | + |
| 353 | + if (hostLBTimer != null) { |
| 354 | + hostLBTimer.cancel(); |
| 355 | + hostLBTimer = null; |
| 356 | + } |
| 357 | + |
| 358 | + if (certTimer != null) { |
| 359 | + certTimer.cancel(); |
| 360 | + certTimer = null; |
| 361 | + } |
309 | 362 | } |
310 | 363 |
|
311 | 364 | public Long getId() { |
@@ -318,6 +371,15 @@ public void setId(final Long id) { |
318 | 371 | _shell.setPersistentProperty(getResourceName(), "id", Long.toString(id)); |
319 | 372 | } |
320 | 373 |
|
| 374 | + private synchronized void scheduleServicesRestartTask() { |
| 375 | + if (certTimer != null) { |
| 376 | + certTimer.cancel(); |
| 377 | + certTimer.purge(); |
| 378 | + } |
| 379 | + certTimer = new Timer("Certificate Renewal Timer"); |
| 380 | + certTimer.schedule(new PostCertificateRenewalTask(this), 5000L); |
| 381 | + } |
| 382 | + |
321 | 383 | private synchronized void scheduleHostLBCheckerTask(final long checkInterval) { |
322 | 384 | if (hostLBTimer != null) { |
323 | 385 | hostLBTimer.cancel(); |
@@ -578,6 +640,9 @@ protected void processRequest(final Request request, final Link link) { |
578 | 640 | answer = setupAgentKeystore((SetupKeyStoreCommand) cmd); |
579 | 641 | } else if (cmd instanceof SetupCertificateCommand && ((SetupCertificateCommand) cmd).isHandleByAgent()) { |
580 | 642 | answer = setupAgentCertificate((SetupCertificateCommand) cmd); |
| 643 | + if (Host.Type.Routing.equals(_resource.getType())) { |
| 644 | + scheduleServicesRestartTask(); |
| 645 | + } |
581 | 646 | } else if (cmd instanceof SetupDirectDownloadCertificate) { |
582 | 647 | answer = setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd); |
583 | 648 | } else if (cmd instanceof SetupMSListCommand) { |
@@ -641,7 +706,7 @@ private Answer setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd |
641 | 706 | return new Answer(cmd, false, "Failed to find agent.properties file"); |
642 | 707 | } |
643 | 708 |
|
644 | | - final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile; |
| 709 | + final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; |
645 | 710 |
|
646 | 711 | String cerFile = agentFile.getParent() + "/" + certificateName + ".cer"; |
647 | 712 | Script.runSimpleBashScript(String.format("echo '%s' > %s", certificate, cerFile)); |
@@ -666,13 +731,13 @@ public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) { |
666 | 731 | if (agentFile == null) { |
667 | 732 | return new Answer(cmd, false, "Failed to find agent.properties file"); |
668 | 733 | } |
669 | | - final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile; |
670 | | - final String csrFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCsrFile; |
| 734 | + final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; |
| 735 | + final String csrFile = agentFile.getParent() + "/" + KeyStoreUtils.CSR_FILENAME; |
671 | 736 |
|
672 | | - String storedPassword = _shell.getPersistentProperty(null, KeyStoreUtils.passphrasePropertyName); |
| 737 | + String storedPassword = _shell.getPersistentProperty(null, KeyStoreUtils.KS_PASSPHRASE_PROPERTY); |
673 | 738 | if (Strings.isNullOrEmpty(storedPassword)) { |
674 | 739 | storedPassword = keyStorePassword; |
675 | | - _shell.setPersistentProperty(null, KeyStoreUtils.passphrasePropertyName, storedPassword); |
| 740 | + _shell.setPersistentProperty(null, KeyStoreUtils.KS_PASSPHRASE_PROPERTY, storedPassword); |
676 | 741 | } |
677 | 742 |
|
678 | 743 | Script script = new Script(true, _keystoreSetupPath, 60000, s_logger); |
@@ -706,10 +771,10 @@ private Answer setupAgentCertificate(final SetupCertificateCommand cmd) { |
706 | 771 | if (agentFile == null) { |
707 | 772 | return new Answer(cmd, false, "Failed to find agent.properties file"); |
708 | 773 | } |
709 | | - final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile; |
710 | | - final String certFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCertFile; |
711 | | - final String privateKeyFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultPrivateKeyFile; |
712 | | - final String caCertFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCaCertFile; |
| 774 | + final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; |
| 775 | + final String certFile = agentFile.getParent() + "/" + KeyStoreUtils.CERT_FILENAME; |
| 776 | + final String privateKeyFile = agentFile.getParent() + "/" + KeyStoreUtils.PKEY_FILENAME; |
| 777 | + final String caCertFile = agentFile.getParent() + "/" + KeyStoreUtils.CACERT_FILENAME; |
713 | 778 |
|
714 | 779 | try { |
715 | 780 | FileUtils.writeStringToFile(new File(certFile), certificate, Charset.defaultCharset()); |
@@ -722,7 +787,7 @@ private Answer setupAgentCertificate(final SetupCertificateCommand cmd) { |
722 | 787 | Script script = new Script(true, _keystoreCertImportPath, 60000, s_logger); |
723 | 788 | script.add(agentFile.getAbsolutePath()); |
724 | 789 | script.add(keyStoreFile); |
725 | | - script.add(KeyStoreUtils.agentMode); |
| 790 | + script.add(KeyStoreUtils.AGENT_MODE); |
726 | 791 | script.add(certFile); |
727 | 792 | script.add(""); |
728 | 793 | script.add(caCertFile); |
@@ -1072,6 +1137,60 @@ public void doTask(final Task task) throws TaskExecutionException { |
1072 | 1137 | } |
1073 | 1138 | } |
1074 | 1139 |
|
| 1140 | + /** |
| 1141 | + * Task stops the current agent and launches a new agent |
| 1142 | + * when there are no outstanding jobs in the agent's task queue |
| 1143 | + */ |
| 1144 | + public class PostCertificateRenewalTask extends ManagedContextTimerTask { |
| 1145 | + |
| 1146 | + private Agent agent; |
| 1147 | + |
| 1148 | + public PostCertificateRenewalTask(final Agent agent) { |
| 1149 | + this.agent = agent; |
| 1150 | + } |
| 1151 | + |
| 1152 | + @Override |
| 1153 | + protected void runInContext() { |
| 1154 | + while (true) { |
| 1155 | + try { |
| 1156 | + if (_inProgress.get() == 0) { |
| 1157 | + s_logger.debug("Running post certificate renewal task to restart services."); |
| 1158 | + |
| 1159 | + // Let the resource perform any post certificate renewal cleanups |
| 1160 | + _resource.executeRequest(new PostCertificateRenewalCommand()); |
| 1161 | + |
| 1162 | + IAgentShell shell = agent._shell; |
| 1163 | + ServerResource resource = agent._resource.getClass().newInstance(); |
| 1164 | + |
| 1165 | + // Stop current agent |
| 1166 | + agent.cancelTasks(); |
| 1167 | + agent._reconnectAllowed = false; |
| 1168 | + Runtime.getRuntime().removeShutdownHook(agent._shutdownThread); |
| 1169 | + agent.stop(ShutdownCommand.Requested, "Restarting due to new X509 certificates"); |
| 1170 | + |
| 1171 | + // Nullify references for GC |
| 1172 | + agent._shell = null; |
| 1173 | + agent._watchList = null; |
| 1174 | + agent._shutdownThread = null; |
| 1175 | + agent._controlListeners = null; |
| 1176 | + agent = null; |
| 1177 | + |
| 1178 | + // Start a new agent instance |
| 1179 | + shell.launchNewAgent(resource); |
| 1180 | + return; |
| 1181 | + } |
| 1182 | + if (s_logger.isTraceEnabled()) { |
| 1183 | + s_logger.debug("Other tasks are in progress, will retry post certificate renewal command after few seconds"); |
| 1184 | + } |
| 1185 | + Thread.sleep(5000); |
| 1186 | + } catch (final Exception e) { |
| 1187 | + s_logger.warn("Failed to execute post certificate renewal command:", e); |
| 1188 | + break; |
| 1189 | + } |
| 1190 | + } |
| 1191 | + } |
| 1192 | + } |
| 1193 | + |
1075 | 1194 | public class PreferredHostCheckerTask extends ManagedContextTimerTask { |
1076 | 1195 |
|
1077 | 1196 | @Override |
|