@@ -30,6 +30,7 @@ import bubble.service.account.StandardAuthenticatorService; | |||||
import bubble.service.account.download.AccountDownloadService; | import bubble.service.account.download.AccountDownloadService; | ||||
import bubble.service.boot.BubbleModelSetupService; | import bubble.service.boot.BubbleModelSetupService; | ||||
import bubble.service.boot.SageHelloService; | import bubble.service.boot.SageHelloService; | ||||
import bubble.service.boot.StandardSelfNodeService; | |||||
import bubble.service.cloud.NodeLaunchMonitor; | import bubble.service.cloud.NodeLaunchMonitor; | ||||
import bubble.service.upgrade.BubbleJarUpgradeService; | import bubble.service.upgrade.BubbleJarUpgradeService; | ||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
@@ -426,7 +427,7 @@ public class MeResource { | |||||
return ok(model); | return ok(model); | ||||
} | } | ||||
@Autowired private SageHelloService sageHelloService; | |||||
@Autowired private StandardSelfNodeService selfNodeService; | |||||
@Autowired private BubbleJarUpgradeService jarUpgradeService; | @Autowired private BubbleJarUpgradeService jarUpgradeService; | ||||
private final AtomicLong lastUpgradeCheck = new AtomicLong(0); | private final AtomicLong lastUpgradeCheck = new AtomicLong(0); | ||||
@@ -442,7 +443,7 @@ public class MeResource { | |||||
synchronized (lastUpgradeCheck) { | synchronized (lastUpgradeCheck) { | ||||
if (now() - lastUpgradeCheck.get() > UPGRADE_CHECK_INTERVAL) { | if (now() - lastUpgradeCheck.get() > UPGRADE_CHECK_INTERVAL) { | ||||
lastUpgradeCheck.set(now()); | lastUpgradeCheck.set(now()); | ||||
sageHelloService.interrupt(); | |||||
selfNodeService.getJarUpgradeMonitorBean().interrupt(); | |||||
} | } | ||||
} | } | ||||
return ok_empty(); | return ok_empty(); | ||||
@@ -274,6 +274,8 @@ public class BubbleConfiguration extends PgRestServerConfiguration | |||||
} | } | ||||
public String getShortVersion () { return getVersionInfo().getShortVersion(); } | 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; | @Getter private BubbleVersionInfo sageVersion; | ||||
public void setSageVersion(BubbleVersionInfo version) { | public void setSageVersion(BubbleVersionInfo version) { | ||||
sageVersion = 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.dao.cloud.BubbleNodeDAO; | ||||
import bubble.model.account.message.AccountMessage; | import bubble.model.account.message.AccountMessage; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.model.cloud.BubbleVersionInfo; | |||||
import bubble.model.cloud.notify.NotificationReceipt; | import bubble.model.cloud.notify.NotificationReceipt; | ||||
import bubble.notify.upgrade.JarUpgradeNotification; | |||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.notify.NotificationService; | import bubble.service.notify.NotificationService; | ||||
import lombok.Cleanup; | |||||
import lombok.extern.slf4j.Slf4j; | 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.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import java.io.File; | |||||
import java.io.InputStream; | |||||
import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||
import java.util.concurrent.atomic.AtomicReference; | 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.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.HOURS; | ||||
import static java.util.concurrent.TimeUnit.SECONDS; | 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.COMPACT_MAPPER; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.system.Sleep.sleep; | import static org.cobbzilla.util.system.Sleep.sleep; | ||||
@Service @Slf4j | @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_INTERVAL = HOURS.toMillis(6); | ||||
public static final long HELLO_SAGE_START_DELAY = SECONDS.toMillis(10); | public static final long HELLO_SAGE_START_DELAY = SECONDS.toMillis(10); | ||||
@@ -89,4 +100,27 @@ public class SageHelloService extends SimpleDaemon { | |||||
if (getIsDone()) throw e; | if (getIsDone()) throw e; | ||||
sleep(HELLO_SAGE_INTERVAL / 10, "hello_to_sage: awaiting next hello after error"); | 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.NonNull; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.cache.Refreshable; | import org.cobbzilla.util.cache.Refreshable; | ||||
import org.cobbzilla.util.daemon.SimpleDaemon; | |||||
import org.cobbzilla.util.http.HttpSchemes; | import org.cobbzilla.util.http.HttpSchemes; | ||||
import org.cobbzilla.util.http.HttpUtil; | import org.cobbzilla.util.http.HttpUtil; | ||||
import org.cobbzilla.util.io.FileUtil; | import org.cobbzilla.util.io.FileUtil; | ||||
@@ -93,6 +94,8 @@ public class StandardSelfNodeService implements SelfNodeService { | |||||
@Autowired private RedisService redisService; | @Autowired private RedisService redisService; | ||||
@Getter(lazy=true) private final RedisService nodeConfig = redisService.prefixNamespace(getClass().getSimpleName()); | @Getter(lazy=true) private final RedisService nodeConfig = redisService.prefixNamespace(getClass().getSimpleName()); | ||||
@Getter private JarUpgradeMonitor jarUpgradeMonitorBean; | |||||
@Override public boolean initThisNode(BubbleNode thisNode) { | @Override public boolean initThisNode(BubbleNode thisNode) { | ||||
log.info("initThisNode: initializing with thisNode="+thisNode.id()); | log.info("initThisNode: initializing with thisNode="+thisNode.id()); | ||||
final BubbleConfiguration c = configuration; | 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 (!c.isSage()) { | ||||
if (thisNode.node() && !c.isSelfSage()) { | 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"); | log.info("onStart: building spare devices for all account that are not root account"); | ||||
background(() -> { | background(() -> { | ||||
@@ -164,6 +169,10 @@ public class StandardSelfNodeService implements SelfNodeService { | |||||
deviceDAO.refreshVpnUsers(); | deviceDAO.refreshVpnUsers(); | ||||
} | } | ||||
}, "StandardSelfNodeService.onStart.spareDevices"); | }, "StandardSelfNodeService.onStart.spareDevices"); | ||||
} else { | |||||
jarUpgradeMonitorBean = c.getBean(PublicUpgradeMonitorService.class); | |||||
jarUpgradeMonitorBean.start(); | |||||
} | } | ||||
// start RefundService if payments are enabled and this is a SageLauncher | // 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.notify.upgrade.JarUpgradeNotification; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.backup.BackupService; | import bubble.service.backup.BackupService; | ||||
import bubble.service.boot.JarUpgradeMonitor; | |||||
import bubble.service.boot.StandardSelfNodeService; | |||||
import bubble.service.notify.NotificationService; | import bubble.service.notify.NotificationService; | ||||
import lombok.Cleanup; | import lombok.Cleanup; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
@@ -46,6 +48,7 @@ public class BubbleJarUpgradeService { | |||||
@Autowired private BubbleBackupDAO backupDAO; | @Autowired private BubbleBackupDAO backupDAO; | ||||
@Autowired private NotificationService notificationService; | @Autowired private NotificationService notificationService; | ||||
@Autowired private RedisService redis; | @Autowired private RedisService redis; | ||||
@Autowired private StandardSelfNodeService selfNodeService; | |||||
@Getter(lazy=true) private final RedisService nodeUpgradeRequests = redis.prefixNamespace(getClass().getName()); | @Getter(lazy=true) private final RedisService nodeUpgradeRequests = redis.prefixNamespace(getClass().getName()); | ||||
@@ -91,25 +94,7 @@ public class BubbleJarUpgradeService { | |||||
return; | 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 | src: supervisor_bubble_upgrade_monitor.conf | ||||
dest: /etc/supervisor/conf.d/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: | cron: | ||||
name: "Log flag check and manager" | name: "Log flag check and manager" | ||||
minute: "*/5" | minute: "*/5" | ||||