@@ -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(); | |||
@@ -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; | |||
@@ -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); | |||
} |
@@ -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)); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 | |||
@@ -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); | |||
} | |||
} |
@@ -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" | |||