Преглед на файлове

Refactor backup download API calls

pull/42/head
Kristijan Mitrovic преди 4 години
родител
ревизия
034402bb84
променени са 5 файла, в които са добавени 276 реда и са изтрити 174 реда
  1. +38
    -4
      bubble-server/src/main/java/bubble/cloud/storage/StorageServiceDriver.java
  2. +10
    -83
      bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java
  3. +120
    -0
      bubble-server/src/main/java/bubble/resources/cloud/NetworkBackupKeysResource.java
  4. +99
    -2
      bubble-server/src/main/java/bubble/service/backup/NetworkKeysService.java
  5. +9
    -85
      bubble-server/src/main/java/bubble/service/backup/RestoreService.java

+ 38
- 4
bubble-server/src/main/java/bubble/cloud/storage/StorageServiceDriver.java Целия файл

@@ -10,22 +10,23 @@ import bubble.model.cloud.CloudService;
import bubble.model.cloud.StorageMetadata;
import bubble.notify.storage.StorageListing;
import lombok.Cleanup;
import lombok.NonNull;
import org.apache.commons.io.IOUtils;
import org.cobbzilla.util.error.ExceptionHandler;
import org.cobbzilla.util.string.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.util.Arrays;

import static bubble.ApiConstants.ROOT_NETWORK_UUID;
import static java.util.UUID.randomUUID;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.io.FileUtil.mkdirOrDie;
import static org.cobbzilla.util.io.StreamUtil.toStringOrDie;
import static org.cobbzilla.util.security.CryptStream.BUFFER_SIZE;
import static org.cobbzilla.util.system.Sleep.sleep;

public interface StorageServiceDriver extends CloudServiceDriver {
@@ -196,4 +197,37 @@ public interface StorageServiceDriver extends CloudServiceDriver {
StorageListing list(String fromNode, String prefix) throws IOException;
StorageListing listNext(String fromNode, String listingId) throws IOException;

@NonNull default void fetchFiles(@NonNull final String fromNodeUuid,
@NonNull final String fromPath,
@NonNull final String toDir)
throws IOException {
final var path = getPath(fromPath);
log.info("fetchFiles: downloading from path=" + path);

var listing = list(fromNodeUuid, path);
while (true) {
Arrays.stream(listing.getKeys())
.parallel()
.forEach(k -> {
final var logMsgPrefix = "fetchFiles [" + k + "]: ";
log.info(logMsgPrefix + "downloading file");
final var file = new File(toDir, k);
mkdirOrDie(file.getParentFile());
try {
@Cleanup final var in = read(fromNodeUuid, k);
try (var out = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE)) {
IOUtils.copyLarge(in, out);
}
log.info(logMsgPrefix + "successfully downloaded file");
} catch (Exception e) {
die(logMsgPrefix + "error downloading file", e);
}
});

if (!listing.isTruncated()) break;
listing = listNext(fromNodeUuid, listing.getListingId());
}
log.info("fetchFiles: full download successful");
}

}

+ 10
- 83
bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java Целия файл

@@ -5,51 +5,38 @@
package bubble.resources.cloud;

import bubble.dao.account.AccountPolicyDAO;
import bubble.dao.account.message.AccountMessageDAO;
import bubble.dao.bill.AccountPlanDAO;
import bubble.dao.cloud.BubbleBackupDAO;
import bubble.dao.cloud.BubbleDomainDAO;
import bubble.dao.cloud.BubbleNetworkDAO;
import bubble.dao.cloud.BubbleNodeDAO;
import bubble.model.account.Account;
import bubble.model.account.AccountPolicy;
import bubble.model.account.message.AccountAction;
import bubble.model.account.message.AccountMessage;
import bubble.model.account.message.AccountMessageType;
import bubble.model.account.message.ActionTarget;
import bubble.model.bill.AccountPlan;
import bubble.model.cloud.*;
import bubble.model.cloud.BubbleDomain;
import bubble.model.cloud.BubbleNetwork;
import bubble.model.cloud.BubbleNode;
import bubble.model.cloud.NetLocation;
import bubble.server.BubbleConfiguration;
import bubble.service.account.StandardAuthenticatorService;
import bubble.service.backup.NetworkKeysService;
import bubble.service.backup.RestoreService;
import bubble.service.cloud.NodeLaunchMonitor;
import bubble.service.cloud.NodeProgressMeterTick;
import bubble.service.cloud.StandardNetworkService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.wizard.stream.FileSendableResource;
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.server.ContainerRequest;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Nullable;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.util.List;

import static bubble.ApiConstants.*;
import static bubble.model.account.Account.validatePassword;
import static bubble.model.cloud.BubbleNetwork.TAG_ALLOW_REGISTRATION;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_OCTET_STREAM;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@Consumes(APPLICATION_JSON)
@@ -60,16 +47,12 @@ public class NetworkActionsResource {
@Autowired private BubbleNodeDAO nodeDAO;
@Autowired private StandardNetworkService networkService;
@Autowired private NodeLaunchMonitor launchMonitor;
@Autowired private AccountMessageDAO messageDAO;
@Autowired private AccountPolicyDAO policyDAO;
@Autowired private NetworkKeysService keysService;
@Autowired private BubbleDomainDAO domainDAO;
@Autowired private BubbleNetworkDAO networkDAO;
@Autowired private AccountPlanDAO accountPlanDAO;
@Autowired private BubbleConfiguration configuration;
@Autowired private StandardAuthenticatorService authenticatorService;
@Autowired private BubbleBackupDAO backupDAO;
@Autowired private RestoreService restoreService;

private Account account;
private BubbleNetwork network;
@@ -121,80 +104,24 @@ public class NetworkActionsResource {
return tick == null ? notFound(uuid) : ok(tick);
}

@GET @Path(EP_KEYS)
public Response requestNetworkKeys(@Context Request req,
@Context ContainerRequest ctx) {
@Path(EP_KEYS)
@NonNull public NetworkBackupKeysResource getBackupKeys(@NonNull @Context final ContainerRequest ctx) {
final Account caller = userPrincipal(ctx);
if (!caller.admin()) return forbidden();
if (!caller.admin()) throw forbiddenEx();

// must request from the network you are on
if (!network.getUuid().equals(configuration.getThisNetwork().getUuid())) {
return invalid("err.networkKeys.mustRequestFromSameNetwork");
throw invalidEx("err.networkKeys.mustRequestFromSameNetwork");
}

final AccountPolicy policy = policyDAO.findSingleByAccount(caller.getUuid());
if (policy == null || !policy.hasVerifiedAccountContacts()) {
return invalid("err.networkKeys.noVerifiedContacts");
throw invalidEx("err.networkKeys.noVerifiedContacts");
}
messageDAO.create(new AccountMessage()
.setMessageType(AccountMessageType.request)
.setAction(AccountAction.password)
.setTarget(ActionTarget.network)
.setAccount(caller.getUuid())
.setNetwork(configuration.getThisNetwork().getUuid())
.setName(network.getUuid())
.setRemoteHost(getRemoteHost(req)));
return ok();
}

@NonNull private Pair<String, NetworkKeys> checkNetworkBackupRequest(@NonNull final ContainerRequest ctx,
@NonNull final String keysCode,
@Nullable final NameAndValue enc) {
final Account caller = userPrincipal(ctx);
if (!caller.admin()) throw forbiddenEx();

final var thisNetworkUuid = configuration.getThisNetwork().getUuid();
if (!thisNetworkUuid.equals(network.getUuid())) throw forbiddenEx();

authenticatorService.ensureAuthenticated(ctx, ActionTarget.network);

final String encryptionKey = enc == null ? null : enc.getValue();
final ConstraintViolationBean error = validatePassword(encryptionKey);
if (error != null) throw new SimpleViolationException(error);

final NetworkKeys keys = keysService.retrieveKeys(keysCode);
if (empty(keys)) throw invalidEx("err.retrieveNetworkKeys.notFound");

return Pair.of(encryptionKey, keys);
}

@POST @Path(EP_KEYS+"/{keysCode}")
public Response retrieveNetworkKeys(@NonNull @Context final Request req,
@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode,
@Nullable final NameAndValue enc) {
final var keys = checkNetworkBackupRequest(ctx, keysCode, enc);
return ok(keys.getRight().encrypt(keys.getLeft()));
}

@POST @Path(EP_KEYS + "/{keysCode}" + EP_BACKUPS + "/{id}" + EP_DOWNLOAD) @Produces(APPLICATION_OCTET_STREAM)
@NonNull public Response downloadBackup(@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode,
@NonNull @PathParam("id") final String backupId,
@Nullable final NameAndValue enc) {
final var passphrase = checkNetworkBackupRequest(ctx, keysCode, enc).getLeft();
final var thisNetworkUuid = configuration.getThisNetwork().getUuid();

final var backup = backupDAO.findByNetworkAndId(thisNetworkUuid, backupId);
if (backup == null) throw notFoundEx(backupId);

final var backupArchive = restoreService.buildRestorePackage(thisNetworkUuid, backup, passphrase);
final var outFileName = network.getNickname() + "." + backupId + ".tgz.enc";

return send(new FileSendableResource(backupArchive).setContentType(APPLICATION_OCTET_STREAM)
.setContentLength(backupArchive.length())
.setForceDownload(true)
.setName(outFileName));
return configuration.subResource(NetworkBackupKeysResource.class, caller, network);
}

@POST @Path(EP_STOP)


+ 120
- 0
bubble-server/src/main/java/bubble/resources/cloud/NetworkBackupKeysResource.java Целия файл

@@ -0,0 +1,120 @@
package bubble.resources.cloud;

import bubble.dao.account.message.AccountMessageDAO;
import bubble.dao.cloud.BubbleBackupDAO;
import bubble.model.account.Account;
import bubble.model.account.message.AccountAction;
import bubble.model.account.message.AccountMessage;
import bubble.model.account.message.AccountMessageType;
import bubble.model.account.message.ActionTarget;
import bubble.model.cloud.BubbleNetwork;
import bubble.service.backup.NetworkKeysService;
import lombok.NonNull;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.wizard.stream.FileSendableResource;
import org.cobbzilla.wizard.validation.ConstraintViolationBean;
import org.cobbzilla.wizard.validation.SimpleViolationException;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.jersey.server.ContainerRequest;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Nullable;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.io.File;

import static bubble.ApiConstants.*;
import static bubble.model.account.Account.validatePassword;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_OCTET_STREAM;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

/**
* Ensure that only network admin can access these calls, and only for current network. Such admin should have verified
* account contact, and should be authenticated as in `authenticatorService.ensureAuthenticated`.
*/
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public class NetworkBackupKeysResource {
@Autowired private AccountMessageDAO messageDAO;
@Autowired private BubbleBackupDAO backupDAO;
@Autowired private NetworkKeysService keysService;

private final Account adminCaller;
private final BubbleNetwork thisNetwork;

public NetworkBackupKeysResource(@NonNull final Account adminCaller, @NonNull final BubbleNetwork thisNetwork) {
this.adminCaller = adminCaller;
this.thisNetwork = thisNetwork;
}

@GET
@NonNull public Response requestNetworkKeys(@NonNull @Context final Request req,
@NonNull @Context final ContainerRequest ctx) {
messageDAO.create(new AccountMessage().setMessageType(AccountMessageType.request)
.setAction(AccountAction.password)
.setTarget(ActionTarget.network)
.setAccount(adminCaller.getUuid())
.setNetwork(thisNetwork.getUuid())
.setName(thisNetwork.getUuid())
.setRemoteHost(getRemoteHost(req)));
return ok();
}

@NonNull private String fetchAndCheckEncryptionKey(@Nullable final NameAndValue enc) {
final String encryptionKey = enc == null ? null : enc.getValue();
final ConstraintViolationBean error = validatePassword(encryptionKey);
if (error != null) throw new SimpleViolationException(error);
return encryptionKey;
}

@POST @Path("/{keysCode}")
@NonNull public Response retrieveNetworkKeys(@NonNull @Context final Request req,
@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode,
@Nullable final NameAndValue enc) {
final var networkKeys = keysService.retrieveKeys(keysCode);
final var encryptionKey = fetchAndCheckEncryptionKey(enc);
return ok(networkKeys.encrypt(encryptionKey));
}

@POST @Path("/{keysCode}" + EP_BACKUPS + EP_START)
@NonNull public Response backupDownloadStart(@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode,
@NonNull @QueryParam("backupId") final String backupId,
@Nullable final NameAndValue enc) {
keysService.retrieveKeys(keysCode);
final var passphrase = fetchAndCheckEncryptionKey(enc);

final var backup = backupDAO.findByNetworkAndId(thisNetwork.getUuid(), backupId);
if (backup == null) throw notFoundEx(backupId);

keysService.startBackupDownload(thisNetwork.getUuid(), backup, keysCode, passphrase);
keysService.backupDownloadStatus(keysCode);
return ok();
}

@GET @Path("/{keysCode}" + EP_BACKUPS + EP_STATUS)
@NonNull public Response backupDownloadStatus(@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode) {
// not checking keys code now here. However, such key will be required in preparing/prepared backup downloads'
// mapping within restoreService.
return ok(keysService.backupDownloadStatus(keysCode));
}

@GET @Path("/{keysCode}" + EP_BACKUPS + EP_DOWNLOAD)
@NonNull public Response backupDownload(@NonNull @Context final ContainerRequest ctx,
@NonNull @PathParam("keysCode") final String keysCode) {
final var status = keysService.backupDownloadStatus(keysCode);
if (!status.isDone()) return accepted();

keysService.clearBackupDownloadKey(keysCode);
final var outFileName = "backup-" + thisNetwork.getNickname() + ".tgz.enc";
final var backupArchiveFile = new File(status.getPackagePath());
return send(new FileSendableResource(backupArchiveFile).setContentType(APPLICATION_OCTET_STREAM)
.setContentLength(backupArchiveFile.length())
.setForceDownload(true)
.setName(outFileName));
}
}

+ 99
- 2
bubble-server/src/main/java/bubble/service/backup/NetworkKeysService.java Целия файл

@@ -5,20 +5,38 @@
package bubble.service.backup;

import bubble.dao.cloud.CloudServiceDAO;
import bubble.model.cloud.BubbleBackup;
import bubble.model.cloud.CloudService;
import bubble.model.cloud.NetworkKeys;
import bubble.server.BubbleConfiguration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.cache.redis.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static bubble.model.cloud.NetworkKeys.PARAM_STORAGE;
import static bubble.model.cloud.NetworkKeys.PARAM_STORAGE_CREDENTIALS;
import static java.io.File.createTempFile;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.cobbzilla.util.daemon.ZillaRuntime.background;
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;
import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx;

@Service @Slf4j
public class NetworkKeysService {
@@ -31,6 +49,9 @@ public class NetworkKeysService {
@Autowired private RedisService redis;
@Getter(lazy=true) private final RedisService networkPasswordTokens = redis.prefixNamespace(getClass().getSimpleName());

// keysCode -> status object
private static final Map<String, BackupPackagingStatus> activeBackupDownloads = new ConcurrentHashMap<>();

public void registerView(String uuid) {
final NetworkKeys keys = new NetworkKeys();
final CloudService storage = cloudDAO.findByUuid(configuration.getThisNetwork().getStorage());
@@ -43,11 +64,87 @@ public class NetworkKeysService {
getNetworkPasswordTokens().set(uuid, json(keys), EX, KEY_EXPIRATION);
}

public NetworkKeys retrieveKeys(String uuid) {
@NonNull public NetworkKeys retrieveKeys(@NonNull final String uuid) {
final String json = getNetworkPasswordTokens().get(uuid);
if (json == null) return null;
if (empty(json)) throw invalidEx("err.retrieveNetworkKeys.notFound");
getNetworkPasswordTokens().del(uuid);
return json(json, NetworkKeys.class);
}

public void startBackupDownload(@NonNull final String nodeUuid, @NonNull final BubbleBackup backup,
@NonNull final String keysCode, @NonNull final String passphrase) {
final var storageServiceUuid = configuration.getThisNetwork().getStorage();
final var storageService = cloudDAO.findByUuid(storageServiceUuid);
if (storageService == null) throw notFoundEx(storageServiceUuid);
final var storageDriver = storageService.getStorageDriver(configuration);

final var status = new BackupPackagingStatus(backup.getUuid());
if (activeBackupDownloads.putIfAbsent(keysCode, status) != null) {
throw invalidEx("err.download.error", "Already started");
}

background(() -> {
File backupDir = null;
try {
backupDir = createTempDir("backup-");
final var backupDirAbs = abs(backupDir);
storageDriver.fetchFiles(nodeUuid, backup.getPath(), backupDirAbs);
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);
status.ok(backupPackageAbs);
} catch (Exception e) {
status.fail(e.getMessage());
} finally {
try {
if (backupDir != null) deleteDirectory(backupDir);
} catch (IOException e) {
log.error("Cannot delete tmp backup folder " + backupDir, e);
}
}
});
}

@NonNull public BackupPackagingStatus backupDownloadStatus(@NonNull final String keysCode) {
final var status = activeBackupDownloads.get(keysCode);
if (status == null) throw notFoundEx(keysCode);
if (status.hasError()) throw invalidEx("err.download.error", status.getError());
return status;
}

public void clearBackupDownloadKey(@NonNull final String keysCode) {
activeBackupDownloads.remove(keysCode);
}

public static class BackupPackagingStatus {
@Getter private boolean done;
@Getter private final String backupUuid;
@JsonIgnore @Getter private String packagePath;
@Getter private String error;
public boolean hasError() { return !empty(error); }

private BackupPackagingStatus(@NonNull final String backupUuid) {
this.done = false;
this.backupUuid = backupUuid;
this.packagePath = null;
this.error = null;
}

private BackupPackagingStatus ok(@NonNull final String packagePath) {
this.done = true;
this.packagePath = packagePath;
this.error = null;
return this;
}

private BackupPackagingStatus fail(@NonNull final String error) {
this.done = true;
this.packagePath = null;
this.error = error;
return this;
}
}

}

+ 9
- 85
bubble-server/src/main/java/bubble/service/backup/RestoreService.java Целия файл

@@ -4,43 +4,31 @@
*/
package bubble.service.backup;

import bubble.cloud.storage.StorageServiceDriver;
import bubble.dao.cloud.CloudServiceDAO;
import bubble.model.cloud.BubbleBackup;
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.apache.commons.io.IOUtils;
import org.cobbzilla.util.collection.NameAndValue;
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 java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

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.io.File.createTempFile;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.io.FileUtil.*;
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.security.CryptStream.BUFFER_SIZE;
import static org.cobbzilla.util.security.ShaUtil.sha256_hex;
import static org.cobbzilla.util.system.CommandShell.*;
import static org.cobbzilla.wizard.cache.redis.RedisService.EX;

@Service @Slf4j
@@ -102,16 +90,18 @@ public class RestoreService {
}

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 " + abs(RESTORE_DIR) + ", cannot restore");
log.error("restore: files already exist in " + restoreDirAbs + ", cannot restore");
return false;
}

final CloudService storageService = json(storageJson, CloudService.class)
.setCredentials(json(credentialsJson, CloudCredentials.class));
final var storageDriver = json(storageJson, CloudService.class)
.setCredentials(json(credentialsJson, CloudCredentials.class))
.getStorageDriver(configuration);
try {
fetchBackupFiles(thisNodeUuid, storageService, backup, RESTORE_DIR);
log.info("restore: notifying system to restore from backup at: "+abs(RESTORE_DIR));
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) {
@@ -124,70 +114,4 @@ public class RestoreService {
}
}
}

/**
* Build temporal ZIP with (specified) backup files for specified network/node.
* @return Created tar file with backup.
*/
@NonNull public File buildRestorePackage(@NonNull final String nodeUuid, @NonNull final BubbleBackup backup,
@NonNull final String passphrase) {
final var storageService = cloudDAO.findByUuid(configuration.getThisNetwork().getStorage());
if (storageService == null) return die("buildRestorePackage: cannot find network storage for this network");

File backupDir = null;
try {
backupDir = fetchBackupFiles(nodeUuid, storageService, backup, createTempDir("backup-"));
final var backupPackage = createTempFile("backup-", ".tgz.enc");
execScript("cd " + backupDir.getAbsolutePath()
+ " && tar -cz *"
+ " | openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -pass pass:" + sha256_hex(passphrase)
+ " > " + backupPackage.getAbsolutePath());
return backupPackage;
} catch (Exception e) {
return die("buildRestorePackage: error downloading backup files ", e);
} finally {
try {
deleteDirectory(backupDir);
} catch (IOException e) {
log.error("Cannot delete temporarily created backup folder " + backupDir.getAbsolutePath(), e);
}
}
}

@NonNull private File fetchBackupFiles(@NonNull final String thisNodeUuid,
@NonNull final CloudService storageService,
@NonNull final BubbleBackup backup,
@NonNull final File destDir)
throws IOException {
final var path = StorageServiceDriver.getPath(backup.getPath());
log.info("fetchBackupFiles: downloading backup from path=" + path);
final var storageDriver = storageService.getStorageDriver(configuration);
final var destDirAbs = abs(destDir);

var listing = storageDriver.list(thisNodeUuid, path);
while (true) {
Arrays.stream(listing.getKeys())
.parallel()
.forEach(k -> {
final var logMsgPrefix = "fetchBackupFiles [" + k + "]: ";
log.info(logMsgPrefix + "downloading file");
final var file = new File(destDirAbs, k);
mkdirOrDie(file.getParentFile());
try {
@Cleanup final var in = storageDriver.read(thisNodeUuid, k);
try (var out = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE)) {
IOUtils.copyLarge(in, out);
}
log.info(logMsgPrefix + "successfully downloaded file");
} catch (Exception e) {
die(logMsgPrefix + "error downloading file", e);
}
});

if (!listing.isTruncated()) break;
listing = storageDriver.listNext(thisNodeUuid, listing.getListingId());
}
log.info("fetchBackupFiles: full download successful");
return destDir;
}
}

Зареждане…
Отказ
Запис