Procházet zdrojové kódy

Add API calls for restoring from backup package

pull/47/head
Kristijan Mitrovic před 4 roky
rodič
revize
33a7a14bed
5 změnil soubory, kde provedl 101 přidání a 20 odebrání
  1. +22
    -0
      bubble-server/src/main/java/bubble/resources/account/AuthResource.java
  2. +6
    -0
      bubble-server/src/main/java/bubble/server/BubbleConfiguration.java
  3. +1
    -3
      bubble-server/src/main/java/bubble/service/backup/NetworkKeysService.java
  4. +71
    -16
      bubble-server/src/main/java/bubble/service/backup/RestoreService.java
  5. +1
    -1
      bubble-server/src/main/resources/messages

+ 22
- 0
bubble-server/src/main/java/bubble/resources/account/AuthResource.java Zobrazit soubor

@@ -37,6 +37,7 @@ import bubble.service.cloud.GeoService;
import bubble.service.notify.NotificationService;
import bubble.service.upgrade.BubbleJarUpgradeService;
import lombok.Cleanup;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.security.RsaMessage;
@@ -45,6 +46,7 @@ import org.cobbzilla.wizard.validation.ConstraintViolationBean;
import org.cobbzilla.wizard.validation.SimpleViolationException;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.server.ContainerRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -55,6 +57,8 @@ import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;

@@ -215,6 +219,24 @@ public class AuthResource {
return ok(receipt);
}

@PUT @Path(EP_RESTORE + EP_APPLY + "/{restoreKey}")
@Consumes(MULTIPART_FORM_DATA)
@NonNull public Response restoreFromPackage(@NonNull @Context final Request req,
@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("restoreKey") final String restoreKey,
@NonNull @FormDataParam("file") final InputStream in,
@NonNull @FormDataParam("password") final String password) {
authenticatorService.ensureAuthenticated(ctx);
if (empty(password)) return invalid("err.password.required");

try {
if (restoreService.restoreFromPackage(restoreKey, in, password)) return ok();
} catch (IOException e) {
log.error("Exception while restoring from package", e);
}
return invalid("err.restore.failed", "Restore failed");
}

@POST @Path(EP_REGISTER)
public Response register(@Context Request req,
@Context ContainerRequest ctx,


+ 6
- 0
bubble-server/src/main/java/bubble/server/BubbleConfiguration.java Zobrazit soubor

@@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.jknack.handlebars.Handlebars;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.map.DefaultedMap;
@@ -68,6 +69,7 @@ import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStream;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
import static org.cobbzilla.util.security.ShaUtil.sha256_file;
import static org.cobbzilla.util.security.ShaUtil.sha256_hex;
import static org.cobbzilla.util.system.CommandShell.totalSystemMemory;
import static org.cobbzilla.wizard.model.SemanticVersion.isNewerVersion;

@@ -419,4 +421,8 @@ public class BubbleConfiguration extends PgRestServerConfiguration

@JsonIgnore @Getter @Setter private List<String> testCloudModels = Collections.emptyList();

public String opensslCmd(boolean encrypt, @NonNull final String passphrase) {
return "openssl enc " + (encrypt ? "" : "-d")
+ " -aes-256-cbc -pbkdf2 -iter 10000 -pass pass:" + sha256_hex(passphrase);
}
}

+ 1
- 3
bubble-server/src/main/java/bubble/service/backup/NetworkKeysService.java Zobrazit soubor

@@ -32,7 +32,6 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.FileUtil.createTempDir;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.security.ShaUtil.sha256_hex;
import static org.cobbzilla.util.system.CommandShell.execScript;
import static org.cobbzilla.wizard.cache.redis.RedisService.EX;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
@@ -92,8 +91,7 @@ public class NetworkKeysService {
final var backupPackageAbs = abs(createTempFile("backup-", ".tgz.enc"));
execScript("cd " + backupDirAbs
+ " && tar -cz *"
+ " | openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -pass pass:" + sha256_hex(passphrase)
+ " > " + backupPackageAbs);
+ " | " + configuration.opensslCmd(true, passphrase) + " > " + backupPackageAbs);
status.ok(backupPackageAbs);
} catch (Exception e) {
status.fail(e.getMessage());


+ 71
- 16
bubble-server/src/main/java/bubble/service/backup/RestoreService.java Zobrazit soubor

@@ -10,25 +10,33 @@ import bubble.model.cloud.CloudCredentials;
import bubble.model.cloud.CloudService;
import bubble.model.cloud.NetworkKeys;
import bubble.server.BubbleConfiguration;
import lombok.Cleanup;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.io.FileUtil;
import org.cobbzilla.util.io.TempDir;
import org.cobbzilla.util.system.Bytes;
import org.cobbzilla.wizard.cache.redis.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import static bubble.ApiConstants.HOME_DIR;
import static bubble.model.cloud.NetworkKeys.PARAM_STORAGE;
import static bubble.model.cloud.NetworkKeys.PARAM_STORAGE_CREDENTIALS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.FileUtil.touch;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.system.CommandShell.execScript;
import static org.cobbzilla.wizard.cache.redis.RedisService.EX;

@Service @Slf4j
@@ -69,18 +77,19 @@ public class RestoreService {
return getRestoreKeys().keys("*").size() > 0 || RESTORE_MARKER_FILE.exists();
}

public boolean restore(String restoreKey, BubbleBackup backup) {
public boolean restore(@NonNull final String restoreKey, @NonNull final BubbleBackup backup) {
final String thisNodeUuid = configuration.getThisNode().getUuid();
final String thisNetworkUuid = configuration.getThisNode().getNetwork();
String lock = null;
try {
lock = getRestoreKeys().lock(thisNetworkUuid, RESTORE_LOCK_TIMEOUT, RESTORE_DEADLOCK_TIMEOUT);

final String keyJson = getRestoreKeys().get(restoreKey);
if (keyJson == null) {
log.error("restore: restoreKey not found: " + restoreKey);
return false;
}
final var restoreDirAbs = checkAndGetRestoreDirPath();
if (restoreDirAbs == null) return false;

final var keyJson = checkAndGetKeyJson(restoreKey);
if (keyJson == null) return false;

final NetworkKeys networkKeys = json(keyJson, NetworkKeys.class);
final String storageJson = NameAndValue.find(networkKeys.getKeys(), PARAM_STORAGE);
final String credentialsJson = NameAndValue.find(networkKeys.getKeys(), PARAM_STORAGE_CREDENTIALS);
@@ -89,25 +98,71 @@ public class RestoreService {
return false;
}

final String[] existingFiles = RESTORE_DIR.list();
final var restoreDirAbs = abs(RESTORE_DIR);
if (existingFiles != null && existingFiles.length > 0) {
log.error("restore: files already exist in " + restoreDirAbs + ", cannot restore");
return false;
}

final var storageDriver = json(storageJson, CloudService.class)
.setCredentials(json(credentialsJson, CloudCredentials.class))
.getStorageDriver(configuration);
try {
storageDriver.fetchFiles(thisNodeUuid, backup.getPath(), restoreDirAbs);
log.info("restore: notifying system to restore from backup at: " + restoreDirAbs);
touch(RESTORE_MARKER_FILE);
return true;
} catch (IOException e) {
log.error("restore: error downloading backup ", e);
return false;
}

log.info("restore: notifying system to restore from backup at: " + restoreDirAbs);
touch(RESTORE_MARKER_FILE);
return true;
} finally {
if (lock != null) {
getRestoreKeys().unlock(thisNetworkUuid, lock);
}
}
}

@Nullable private String checkAndGetKeyJson(@NonNull final String restoreKey) {
final String keyJson = getRestoreKeys().get(restoreKey);
if (keyJson == null) {
log.error("restore: restoreKey not found: " + restoreKey);
return null;
}
return keyJson;
}

@Nullable private String checkAndGetRestoreDirPath() {
final var existingFiles = RESTORE_DIR.list();
final var restoreDirAbs = abs(RESTORE_DIR);

if (!empty(existingFiles)) {
log.error("restore: files already exist in " + restoreDirAbs + ", cannot restore");
return null;
}

return restoreDirAbs;
}

public boolean restoreFromPackage(@NonNull final String restoreKey,
@NonNull final InputStream backupPackageIS,
@NonNull final String passphrase) throws IOException {
final var thisNetworkUuid = configuration.getThisNode().getNetwork();
String lock = null;
try {
lock = getRestoreKeys().lock(thisNetworkUuid, RESTORE_LOCK_TIMEOUT, RESTORE_DEADLOCK_TIMEOUT);

final var restoreDirAbs = checkAndGetRestoreDirPath();
if (restoreDirAbs == null) return false;

if (checkAndGetKeyJson(restoreKey) == null) return false;

@Cleanup final var tempDir = new TempDir();
final var uploadedFile = new File(tempDir, "uploaded.tgz.enc");
FileUtil.toFileOrDie(uploadedFile, backupPackageIS);

execScript("cd " + abs(restoreDirAbs)
+ " && " + configuration.opensslCmd(false, passphrase) + " < " + abs(uploadedFile)
+ " | tar xzv");

log.info("restore: notifying system to restore from backup at: " + restoreDirAbs);
touch(RESTORE_MARKER_FILE);
return true;
} finally {
if (lock != null) {
getRestoreKeys().unlock(thisNetworkUuid, lock);


+ 1
- 1
bubble-server/src/main/resources/messages

@@ -1 +1 @@
Subproject commit 9fcd7bc0a768b124b09b7e8995c27fc94dedf9d8
Subproject commit 6700bd024ce27c63409db86f00df9bb47b0036b7

Načítá se…
Zrušit
Uložit