From 6a23b1abd44f54f708a9cfdd46ceb5d341cf2223 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Sat, 14 Nov 2020 14:10:12 -0500 Subject: [PATCH] allow standalone bubbles to upgrade from jenkins --- .../bubble/resources/account/MeResource.java | 5 +- .../bubble/server/BubbleConfiguration.java | 2 + .../service/boot/JarUpgradeMonitor.java | 12 +++ .../boot/PublicUpgradeMonitorService.java | 80 +++++++++++++++++++ .../bubble/service/boot/SageHelloService.java | 38 ++++++++- .../service/boot/StandardSelfNodeService.java | 15 +++- .../upgrade/BubbleJarUpgradeService.java | 25 ++---- .../packer/roles/bubble/tasks/main.yml | 2 +- 8 files changed, 151 insertions(+), 28 deletions(-) create mode 100644 bubble-server/src/main/java/bubble/service/boot/JarUpgradeMonitor.java create mode 100644 bubble-server/src/main/java/bubble/service/boot/PublicUpgradeMonitorService.java diff --git a/bubble-server/src/main/java/bubble/resources/account/MeResource.java b/bubble-server/src/main/java/bubble/resources/account/MeResource.java index a5abf312..2d52c1f9 100644 --- a/bubble-server/src/main/java/bubble/resources/account/MeResource.java +++ b/bubble-server/src/main/java/bubble/resources/account/MeResource.java @@ -30,6 +30,7 @@ import bubble.service.account.StandardAuthenticatorService; import bubble.service.account.download.AccountDownloadService; import bubble.service.boot.BubbleModelSetupService; import bubble.service.boot.SageHelloService; +import bubble.service.boot.StandardSelfNodeService; import bubble.service.cloud.NodeLaunchMonitor; import bubble.service.upgrade.BubbleJarUpgradeService; import com.fasterxml.jackson.databind.JsonNode; @@ -426,7 +427,7 @@ public class MeResource { return ok(model); } - @Autowired private SageHelloService sageHelloService; + @Autowired private StandardSelfNodeService selfNodeService; @Autowired private BubbleJarUpgradeService jarUpgradeService; private final AtomicLong lastUpgradeCheck = new AtomicLong(0); @@ -442,7 +443,7 @@ public class MeResource { synchronized (lastUpgradeCheck) { if (now() - lastUpgradeCheck.get() > UPGRADE_CHECK_INTERVAL) { lastUpgradeCheck.set(now()); - sageHelloService.interrupt(); + selfNodeService.getJarUpgradeMonitorBean().interrupt(); } } return ok_empty(); diff --git a/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java b/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java index e14067cb..2bceb7fe 100644 --- a/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java +++ b/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java @@ -274,6 +274,8 @@ public class BubbleConfiguration extends PgRestServerConfiguration } public String getShortVersion () { return getVersionInfo().getShortVersion(); } + // For a Bubble node with a sage, this will be set in the hello_from_sage notification handler + // For a Bubble without a sage, this will be set in NodeInitializerListener @Getter private BubbleVersionInfo sageVersion; public void setSageVersion(BubbleVersionInfo version) { sageVersion = version; diff --git a/bubble-server/src/main/java/bubble/service/boot/JarUpgradeMonitor.java b/bubble-server/src/main/java/bubble/service/boot/JarUpgradeMonitor.java new file mode 100644 index 00000000..82af9ce5 --- /dev/null +++ b/bubble-server/src/main/java/bubble/service/boot/JarUpgradeMonitor.java @@ -0,0 +1,12 @@ +package bubble.service.boot; + +import bubble.model.cloud.BubbleVersionInfo; +import org.cobbzilla.util.daemon.SimpleDaemon; + +import java.io.File; + +public abstract class JarUpgradeMonitor extends SimpleDaemon { + + public abstract void downloadJar(File upgradeJar, BubbleVersionInfo sageVersion); + +} diff --git a/bubble-server/src/main/java/bubble/service/boot/PublicUpgradeMonitorService.java b/bubble-server/src/main/java/bubble/service/boot/PublicUpgradeMonitorService.java new file mode 100644 index 00000000..35cbb65e --- /dev/null +++ b/bubble-server/src/main/java/bubble/service/boot/PublicUpgradeMonitorService.java @@ -0,0 +1,80 @@ +package bubble.service.boot; + +import bubble.model.cloud.BubbleVersionInfo; +import bubble.server.BubbleConfiguration; +import lombok.Cleanup; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.daemon.SimpleDaemon; +import org.cobbzilla.util.io.Decompressors; +import org.cobbzilla.util.io.FileUtil; +import org.cobbzilla.util.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; + +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.cobbzilla.util.daemon.ZillaRuntime.die; +import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; +import static org.cobbzilla.util.http.HttpUtil.url2file; +import static org.cobbzilla.util.http.HttpUtil.url2string; +import static org.cobbzilla.util.io.FileUtil.abs; +import static org.cobbzilla.wizard.model.SemanticVersion.isNewerVersion; + +@Service @Slf4j +public class PublicUpgradeMonitorService extends JarUpgradeMonitor { + + public static final long MONITOR_INTERVAL = HOURS.toMillis(6); + public static final long MONITOR_START_DELAY = SECONDS.toMillis(30); + + @Override protected long getStartupDelay() { return MONITOR_START_DELAY; } + @Override protected long getSleepTime() { return MONITOR_INTERVAL; } + @Override protected boolean canInterruptSleep() { return true; } + + public static final String BUBBLE_BASE_URI = "https://jenkins.bubblev.org/public/releases/bubble/"; + public static final String RELEASE_VERSION_URL = BUBBLE_BASE_URI + "latest.txt"; + + public static final String VERSION_TOKEN = "@@VERSION@@"; + public static final String RELEASE_JAR_URL = BUBBLE_BASE_URI + VERSION_TOKEN + "/bubble.zip"; + public static final String RELEASE_SHA_URL = BUBBLE_BASE_URI + VERSION_TOKEN + "/bubble.zip.sha256"; + + @Autowired private BubbleConfiguration configuration; + + @Override protected void process() { + try { + final String fullVersion = url2string(RELEASE_VERSION_URL).replace("_", " "); + String currentVersion = configuration.getVersionInfo().getVersion(); + // only update our sage version if the new public version is both + // -- newer than ourselves + // -- newer than the current sageVersion (or the current sageVersion is null) + if (isNewerVersion(fullVersion, currentVersion) + && (configuration.getSageVersion() == null || isNewerVersion(fullVersion, configuration.getSageVersion().getVersion()))) { + log.info("process: found newer version: "+fullVersion+" (current version "+currentVersion+"), setting sage version on BubbleConfiguration"); + configuration.setSageVersion(new BubbleVersionInfo() + .setVersion(fullVersion) + .setShortVersion(fullVersion.substring(fullVersion.indexOf(" ")+1)) + .setSha256(url2string(RELEASE_SHA_URL.replace(VERSION_TOKEN, fullVersion)))); + } + } catch (Exception e) { + log.warn("process: error: "+shortError(e)); + } + } + + @Override public void downloadJar(File upgradeJar, BubbleVersionInfo sageVersion) { + try { + @Cleanup final TempDir temp = new TempDir(); + final File bubbleZip = new File(temp, "bubble.zip"); + url2file(RELEASE_JAR_URL.replace(VERSION_TOKEN, sageVersion.getVersion()), bubbleZip); + Decompressors.extract(bubbleZip, temp); + final File jarFile = new File(abs(temp) + "/bubble-" + sageVersion.getVersion() + "/bubble.jar"); + if (!jarFile.exists()) { + die("downloadJar: jar file not found in zip file: "+abs(jarFile)); + } + FileUtil.copyFile(jarFile, upgradeJar); + } catch (Exception e) { + die("downloadJar: "+shortError(e)); + } + } + +} diff --git a/bubble-server/src/main/java/bubble/service/boot/SageHelloService.java b/bubble-server/src/main/java/bubble/service/boot/SageHelloService.java index bd55edb9..e02767a9 100644 --- a/bubble-server/src/main/java/bubble/service/boot/SageHelloService.java +++ b/bubble-server/src/main/java/bubble/service/boot/SageHelloService.java @@ -8,26 +8,37 @@ import bubble.dao.account.message.AccountMessageDAO; import bubble.dao.cloud.BubbleNodeDAO; import bubble.model.account.message.AccountMessage; import bubble.model.cloud.BubbleNode; +import bubble.model.cloud.BubbleVersionInfo; import bubble.model.cloud.notify.NotificationReceipt; +import bubble.notify.upgrade.JarUpgradeNotification; import bubble.server.BubbleConfiguration; import bubble.service.notify.NotificationService; +import lombok.Cleanup; import lombok.extern.slf4j.Slf4j; -import org.cobbzilla.util.daemon.SimpleDaemon; +import org.cobbzilla.util.http.HttpUtil; +import org.cobbzilla.util.io.FileUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.InputStream; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import static bubble.ApiConstants.AUTH_ENDPOINT; +import static bubble.ApiConstants.EP_UPGRADE; import static bubble.model.cloud.notify.NotificationType.hello_to_sage; +import static bubble.model.cloud.notify.NotificationType.upgrade_request; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; +import static org.cobbzilla.util.io.FileUtil.*; import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.system.Sleep.sleep; @Service @Slf4j -public class SageHelloService extends SimpleDaemon { +public class SageHelloService extends JarUpgradeMonitor { public static final long HELLO_SAGE_INTERVAL = HOURS.toMillis(6); public static final long HELLO_SAGE_START_DELAY = SECONDS.toMillis(10); @@ -89,4 +100,27 @@ public class SageHelloService extends SimpleDaemon { if (getIsDone()) throw e; sleep(HELLO_SAGE_INTERVAL / 10, "hello_to_sage: awaiting next hello after error"); } + + @Override public void downloadJar(File upgradeJar, BubbleVersionInfo sageVersion) { + // ask the sage to allow us to download the upgrade + final String key = notificationService.notifySync(configuration.getSageNode(), upgrade_request, new JarUpgradeNotification(sageVersion)); + log.info("downloadJar: received upgrade key from sage: "+key); + + // request the jar from the sage + final String uri = AUTH_ENDPOINT + EP_UPGRADE + "/" + configuration.getThisNode().getUuid() + "/" + key; + final String url = configuration.nodeBaseUri(configuration.getSageNode()) + uri; + final File newJar; + try { + newJar = temp(".jar"); + @Cleanup final InputStream in = HttpUtil.getUrlInputStream(url); + FileUtil.toFile(newJar, in); + } catch (Exception e) { + log.error("downloadJar: error downloading jar: "+shortError(e)); + return; + } + + // move to upgrade location, should trigger upgrade monitor + log.info("downloadJar: writing upgradeJar: "+abs(upgradeJar)); + renameOrDie(newJar, upgradeJar); + } } \ No newline at end of file diff --git a/bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java b/bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java index 9e55a98d..809cc506 100644 --- a/bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java +++ b/bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java @@ -29,6 +29,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.util.cache.Refreshable; +import org.cobbzilla.util.daemon.SimpleDaemon; import org.cobbzilla.util.http.HttpSchemes; import org.cobbzilla.util.http.HttpUtil; import org.cobbzilla.util.io.FileUtil; @@ -93,6 +94,8 @@ public class StandardSelfNodeService implements SelfNodeService { @Autowired private RedisService redisService; @Getter(lazy=true) private final RedisService nodeConfig = redisService.prefixNamespace(getClass().getSimpleName()); + @Getter private JarUpgradeMonitor jarUpgradeMonitorBean; + @Override public boolean initThisNode(BubbleNode thisNode) { log.info("initThisNode: initializing with thisNode="+thisNode.id()); final BubbleConfiguration c = configuration; @@ -146,12 +149,14 @@ public class StandardSelfNodeService implements SelfNodeService { } } - // start hello sage and spare devices services, if we have a sage that is not ourselves + // start jar upgrade monitor and spare devices services, if we have a sage that is not ourselves if (!c.isSage()) { if (thisNode.node() && !c.isSelfSage()) { - log.info("onStart: starting SageHelloService"); - c.getBean(SageHelloService.class).start(); + jarUpgradeMonitorBean = c.getBean(SageHelloService.class); + } else { + jarUpgradeMonitorBean = c.getBean(PublicUpgradeMonitorService.class); } + jarUpgradeMonitorBean.start(); log.info("onStart: building spare devices for all account that are not root account"); background(() -> { @@ -164,6 +169,10 @@ public class StandardSelfNodeService implements SelfNodeService { deviceDAO.refreshVpnUsers(); } }, "StandardSelfNodeService.onStart.spareDevices"); + + } else { + jarUpgradeMonitorBean = c.getBean(PublicUpgradeMonitorService.class); + jarUpgradeMonitorBean.start(); } // start RefundService if payments are enabled and this is a SageLauncher diff --git a/bubble-server/src/main/java/bubble/service/upgrade/BubbleJarUpgradeService.java b/bubble-server/src/main/java/bubble/service/upgrade/BubbleJarUpgradeService.java index 918f74c9..d34708a8 100644 --- a/bubble-server/src/main/java/bubble/service/upgrade/BubbleJarUpgradeService.java +++ b/bubble-server/src/main/java/bubble/service/upgrade/BubbleJarUpgradeService.java @@ -11,6 +11,8 @@ import bubble.model.cloud.BubbleVersionInfo; import bubble.notify.upgrade.JarUpgradeNotification; import bubble.server.BubbleConfiguration; import bubble.service.backup.BackupService; +import bubble.service.boot.JarUpgradeMonitor; +import bubble.service.boot.StandardSelfNodeService; import bubble.service.notify.NotificationService; import lombok.Cleanup; import lombok.Getter; @@ -46,6 +48,7 @@ public class BubbleJarUpgradeService { @Autowired private BubbleBackupDAO backupDAO; @Autowired private NotificationService notificationService; @Autowired private RedisService redis; + @Autowired private StandardSelfNodeService selfNodeService; @Getter(lazy=true) private final RedisService nodeUpgradeRequests = redis.prefixNamespace(getClass().getName()); @@ -91,25 +94,7 @@ public class BubbleJarUpgradeService { return; } - // ask the sage to allow us to download the upgrade - final String key = notificationService.notifySync(configuration.getSageNode(), upgrade_request, new JarUpgradeNotification(sageVersion)); - log.info("upgrade: received upgrade key from sage: "+key); - - // request the jar from the sage - final String uri = AUTH_ENDPOINT + EP_UPGRADE + "/" + configuration.getThisNode().getUuid() + "/" + key; - final String url = configuration.nodeBaseUri(configuration.getSageNode()) + uri; - final File newJar; - try { - newJar = temp(".jar"); - @Cleanup final InputStream in = HttpUtil.getUrlInputStream(url); - FileUtil.toFile(newJar, in); - } catch (Exception e) { - log.error("upgrade: error downloading jar: "+shortError(e)); - return; - } - - // move to upgrade location, should trigger upgrade monitor - log.info("upgrade: writing upgradeJar: "+abs(upgradeJar)); - renameOrDie(newJar, upgradeJar); + final JarUpgradeMonitor jarUpgradeMonitor = selfNodeService.getJarUpgradeMonitorBean(); + jarUpgradeMonitor.downloadJar(upgradeJar, sageVersion); } } diff --git a/bubble-server/src/main/resources/packer/roles/bubble/tasks/main.yml b/bubble-server/src/main/resources/packer/roles/bubble/tasks/main.yml index 2ebcaa97..0adea149 100644 --- a/bubble-server/src/main/resources/packer/roles/bubble/tasks/main.yml +++ b/bubble-server/src/main/resources/packer/roles/bubble/tasks/main.yml @@ -115,7 +115,7 @@ src: supervisor_bubble_upgrade_monitor.conf dest: /etc/supervisor/conf.d/bubble_upgrade_monitor.conf -- name: Install log_namager monitor cron +- name: Install log_manager monitor cron cron: name: "Log flag check and manager" minute: "*/5"