//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2016 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dan Larkin-York //////////////////////////////////////////////////////////////////////////////// #include "BackupFeature.h" #include #include #include #include #include #include "Basics/StaticStrings.h" #include "Basics/StringUtils.h" #include "Basics/VelocyPackHelper.h" #include "Basics/FileUtils.h" #include "Logger/Logger.h" #include "ProgramOptions/ProgramOptions.h" #include "SimpleHttpClient/SimpleHttpClient.h" #include "SimpleHttpClient/SimpleHttpResult.h" #include "Shell/ClientFeature.h" #include "StorageEngine/HotBackupCommon.h" namespace { /// @brief name of the feature to report to application server constexpr auto FeatureName = "Backup"; constexpr auto OperationCreate = "create"; constexpr auto OperationDelete = "delete"; constexpr auto OperationList = "list"; constexpr auto OperationRestore = "restore"; #ifdef USE_ENTERPRISE constexpr auto OperationUpload = "upload"; constexpr auto OperationDownload = "download"; #endif std::unordered_set const Operations = {OperationCreate, OperationDelete, OperationList, OperationRestore, #ifdef USE_ENTERPRISE OperationUpload, OperationDownload, #endif }; /// @brief generic error for if server returns bad/unexpected json const arangodb::Result ErrorMalformedJsonResponse = { TRI_ERROR_INTERNAL, "got malformed JSON response from server"}; /// @brief check whether HTTP response is valid, complete, and not an error arangodb::Result checkHttpResponse(arangodb::httpclient::SimpleHttpClient& client, std::unique_ptr const& response) { using arangodb::basics::StringUtils::itoa; if (response == nullptr || !response->isComplete()) { return {TRI_ERROR_INTERNAL, "got invalid response from server: " + client.getErrorMessage()}; } if (response->wasHttpError()) { int errorNum = TRI_ERROR_INTERNAL; std::string errorMsg = response->getHttpReturnMessage(); std::shared_ptr bodyBuilder; // Handle case of no body: try { bodyBuilder = response->getBodyVelocyPack(); arangodb::velocypack::Slice error = bodyBuilder->slice(); if (!error.isNone() && error.hasKey(arangodb::StaticStrings::ErrorMessage)) { errorNum = error.get(arangodb::StaticStrings::ErrorNum).getNumericValue(); errorMsg = error.get(arangodb::StaticStrings::ErrorMessage).copyString(); } } catch(...) { } return {errorNum, "got invalid response from server: HTTP " + itoa(response->getHttpReturnCode()) + ": " + errorMsg}; } return {TRI_ERROR_NO_ERROR}; } arangodb::Result getUptime(arangodb::httpclient::SimpleHttpClient& client, double& uptime) { arangodb::Result result; std::string const url = "/_admin/statistics"; std::unique_ptr response( client.request(arangodb::rest::RequestType::GET, url, nullptr, 0)); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } VPackSlice const resBody = parsedBody->slice(); if (!resBody.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected reponse to be an object"); return result; } TRI_ASSERT(resBody.isObject()); VPackSlice const serverObject = resBody.get("server"); if (!serverObject.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'server' to be an object"); return result; } TRI_ASSERT(serverObject.isObject()); VPackSlice const uptimeSlice = serverObject.get("uptime"); if (!uptimeSlice.isNumber()) { result.reset(TRI_ERROR_INTERNAL, "expected 'server.uptime' to be numeric"); return result; } TRI_ASSERT(uptimeSlice.isNumber()); uptime = uptimeSlice.getNumericValue(); return result; } arangodb::Result waitForRestart(arangodb::ClientManager& clientManager, double originalUptime, double maxWaitForRestart) { arangodb::Result result; auto start = std::chrono::high_resolution_clock::now(); auto timeSinceStart = [start]() -> double { auto now = std::chrono::high_resolution_clock::now(); auto duration = now - start; return (static_cast(duration.count()) / decltype(duration)::period::den* decltype(duration)::period::num); }; LOG_TOPIC("0dfda", INFO, arangodb::Logger::BACKUP) << "Waiting for server to restart..."; // sleep once to allow shutdown to start std::this_thread::sleep_for(std::chrono::seconds(1)); while (timeSinceStart() < maxWaitForRestart) { std::unique_ptr client; try { result = clientManager.getConnectedClient(client, true, false, false, true); if (result.ok() && client != nullptr) { double uptime = 0.0; result = ::getUptime(*client, uptime); if (result.ok() && uptime < originalUptime) { LOG_TOPIC("5caac", INFO, arangodb::Logger::BACKUP) << "...server back up and running!"; return result; } // server still down, or still up prior to restart; fall through, wait, retry } } catch (arangodb::basics::Exception const& ex) { return {ex.code(), ex.what()}; } catch (std::exception const& ex) { return {TRI_ERROR_INTERNAL, std::string("Caught exception: ") + ex.what()}; } catch (...) { return {TRI_ERROR_INTERNAL, "Caught unknown exception."}; } std::this_thread::sleep_for(std::chrono::seconds(1)); } // okay, we timed out result.reset( TRI_ERROR_HTTP_SERVICE_UNAVAILABLE, "Server failed to respond to requests in the expected timeframe."); return result; } arangodb::Result executeList(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options) { arangodb::Result result; std::string const url = "/_admin/backup/list"; std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, nullptr, 0)); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } VPackSlice const resBody = parsedBody->slice(); if (!resBody.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected response to be an object"); return result; } TRI_ASSERT(resBody.isObject()); VPackSlice const resultObject = resBody.get("result"); if (!resultObject.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result' to be an object"); return result; } TRI_ASSERT(resultObject.isObject()); VPackSlice const backups = resultObject.get("list"); if (!backups.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result.list' to be an object"); return result; } TRI_ASSERT(backups.isObject()); if (backups.isEmptyObject()) { LOG_TOPIC("efc76", INFO, arangodb::Logger::BACKUP) << "There are no backups available."; } else { LOG_TOPIC("e0356", INFO, arangodb::Logger::BACKUP) << "The following backups are available:"; for (auto const& backup : VPackObjectIterator(backups)) { LOG_TOPIC("9e6b6", INFO, arangodb::Logger::BACKUP) << " - " << backup.key.copyString(); arangodb::ResultT meta = arangodb::BackupMeta::fromSlice(backup.value); if (meta.ok()) { LOG_TOPIC("0f208", INFO, arangodb::Logger::BACKUP) << " version: " << meta.get()._version; LOG_TOPIC("55af7", INFO, arangodb::Logger::BACKUP) << " date/time: " << meta.get()._datetime; LOG_TOPIC("43522", INFO, arangodb::Logger::BACKUP) << " size in bytes: " << meta.get()._sizeInBytes; LOG_TOPIC("12532", INFO, arangodb::Logger::BACKUP) << " number of files: " << meta.get()._nrFiles; LOG_TOPIC("43212", INFO, arangodb::Logger::BACKUP) << " number of DBServers: " << meta.get()._nrDBServers; LOG_TOPIC("43213", INFO, arangodb::Logger::BACKUP) << " number of available pieces: " << meta.get()._nrPiecesPresent; if (!meta.get()._serverId.empty()) { LOG_TOPIC("11112", INFO, arangodb::Logger::BACKUP) << " serverId: " << meta.get()._serverId; } if (meta.get()._potentiallyInconsistent) { LOG_TOPIC("56241", INFO, arangodb::Logger::BACKUP) << " potentiallyInconsistent: true"; } else { LOG_TOPIC("56242", INFO, arangodb::Logger::BACKUP) << " potentiallyInconsistent: false"; } if (meta.get()._isAvailable) { LOG_TOPIC("56244", INFO, arangodb::Logger::BACKUP) << " available: true"; } else { LOG_TOPIC("56246", INFO, arangodb::Logger::BACKUP) << " available: false"; } } } } return result; } arangodb::Result executeCreate(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options) { arangodb::Result result; std::string const url = "/_admin/backup/create"; VPackBuilder bodyBuilder; { VPackObjectBuilder guard(&bodyBuilder); bodyBuilder.add("timeout", VPackValue(options.maxWaitForLock)); bodyBuilder.add("allowInconsistent", VPackValue(options.force)); if (!options.label.empty()) { bodyBuilder.add("label", VPackValue(options.label)); } } std::string const body = bodyBuilder.slice().toJson(); std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, body.c_str(), body.size())); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } VPackSlice const resBody = parsedBody->slice(); if (!resBody.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected response to be an object"); return result; } TRI_ASSERT(resBody.isObject()); VPackSlice const resultObject = resBody.get("result"); if (!resultObject.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result'' to be an object"); return result; } TRI_ASSERT(resultObject.isObject()); VPackSlice const identifier = resultObject.get("id"); if (!identifier.isString()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result.directory' to be a string"); return result; } TRI_ASSERT(identifier.isString()); VPackSlice const forced = resultObject.get("potentiallyInconsistent"); if (forced.isTrue()) { LOG_TOPIC("f448b", WARN, arangodb::Logger::BACKUP) << "Failed to get write lock before proceeding with backup. Backup may " "contain some inconsistencies."; } else if (!forced.isBoolean() && !forced.isNone()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result.potentiallyInconsistent' to be an boolean"); return result; } LOG_TOPIC("c4d37", INFO, arangodb::Logger::BACKUP) << "Backup succeeded. Generated identifier '" << identifier.copyString() << "'"; VPackSlice sizeInBytes = resultObject.get("sizeInBytes"); VPackSlice nrFiles = resultObject.get("nrFiles"); if (sizeInBytes.isInteger() && nrFiles.isInteger()) { uint64_t size = sizeInBytes.getNumber(); uint64_t nr = nrFiles.getNumber(); LOG_TOPIC("ce423", INFO, arangodb::Logger::BACKUP) << "Total size of backup: " << size << ", number of files: " << nr; } return result; } arangodb::Result executeRestore(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options, arangodb::ClientManager& clientManager) { arangodb::Result result; double originalUptime = 0.0; if (options.maxWaitForRestart > 0.0) { result = ::getUptime(client, originalUptime); if (result.fail()) { return result; } } std::string const url = "/_admin/backup/restore"; VPackBuilder bodyBuilder; { VPackObjectBuilder guard(&bodyBuilder); bodyBuilder.add("id", VPackValue(options.identifier)); if (options.force) { bodyBuilder.add("ignoreVersion", VPackValue(true)); } } std::string const body = bodyBuilder.slice().toJson(); std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, body.c_str(), body.size())); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } LOG_TOPIC("b6d4c", INFO, arangodb::Logger::BACKUP) << "Successfully restored '" << options.identifier << "'"; bool cluster = false; // In a cluster, we do not have to wait for completion: VPackSlice resBody = parsedBody->slice(); TRI_ASSERT(resBody.isObject()); VPackSlice resultAttr = resBody.get("result"); if (resultAttr.isObject()) { VPackSlice isCluster = resultAttr.get("isCluster"); if (isCluster.isBool() && isCluster.isTrue()) { cluster = true; } } if (!cluster && options.maxWaitForRestart > 0.0) { result = ::waitForRestart(clientManager, originalUptime, options.maxWaitForRestart); } return result; } arangodb::Result executeDelete(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options) { arangodb::Result result; std::string const url = "/_admin/backup/delete"; VPackBuilder bodyBuilder; { VPackObjectBuilder guard(&bodyBuilder); bodyBuilder.add("id", VPackValue(options.identifier)); } std::string const body = bodyBuilder.slice().toJson(); std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, body.c_str(), body.size())); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } LOG_TOPIC("a23cb", INFO, arangodb::Logger::BACKUP) << "Successfully deleted '" << options.identifier << "'"; return result; } #ifdef USE_ENTERPRISE struct TransfereType { enum type { UPLOAD, DOWNLOAD }; static std::string asString(type t) { switch (t) { case UPLOAD: return "upload"; case DOWNLOAD: return "download"; default: TRI_ASSERT(false); } return ""; // just to please some compilers } static std::string asAdminPath(type t) { switch (t) { case UPLOAD: return "/_admin/backup/upload"; case DOWNLOAD: return "/_admin/backup/download"; default: TRI_ASSERT(false); } return ""; // just to please some compilers } static std::string asJsonId(type t) { switch (t) { case UPLOAD: return "uploadId"; case DOWNLOAD: return "downloadId"; default: TRI_ASSERT(false); } return ""; // just to please some compilers } }; arangodb::Result executeStatusQuery(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options, TransfereType::type type) { arangodb::Result result; std::string const url = TransfereType::asAdminPath(type); VPackBuilder bodyBuilder; { VPackObjectBuilder guard(&bodyBuilder); bodyBuilder.add(TransfereType::asJsonId(type), VPackValue(options.statusId)); if (options.abort) { bodyBuilder.add("abort", VPackValue(true)); } } std::string const body = bodyBuilder.slice().toJson(); std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, body.c_str(), body.size())); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // extract vpack body from response std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } VPackSlice const resBody = parsedBody->slice(); VPackSlice const resultObject = resBody.get("result"); if (!resultObject.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result' to be an object"); return result; } TRI_ASSERT(resultObject.isObject()); // Display status if (!options.abort) { VPackSlice const dbserversObject = resultObject.get("DBServers"); for (auto const& server : VPackObjectIterator(dbserversObject)) { std::string statusMessage = server.key.copyString(); if (server.value.hasKey("Status")) { statusMessage += " Status: " + server.value.get("Status").copyString(); } LOG_TOPIC("24d75", INFO, arangodb::Logger::BACKUP) << statusMessage; if (server.value.hasKey("Progress")) { VPackSlice const progressSlice = server.value.get("Progress"); LOG_TOPIC("68cc8", INFO, arangodb::Logger::BACKUP) << "Last progress update " << progressSlice.get("Time").copyString() << ": " << progressSlice.get("Done").getInt() << "/" << progressSlice.get("Total").getInt() << " files done"; } if (server.value.hasKey("Error")) { LOG_TOPIC("036de", ERR, arangodb::Logger::BACKUP) << "Error: " << server.value.get("Error").getInt(); } if (server.value.hasKey("ErrorMessage")) { LOG_TOPIC("3c3c4", ERR, arangodb::Logger::BACKUP) << "ErrorMessage: " << server.value.get("ErrorMessage").copyString(); } } } else { if (resBody.hasKey("error") && resBody.get("error").getBoolean()) { LOG_TOPIC("f3add", ERR, arangodb::Logger::BACKUP) << "error: " << resBody.get("errorMessage").copyString(); } else { LOG_TOPIC("c7c73", INFO, arangodb::Logger::BACKUP) << "aborting transfer"; } } return result; } arangodb::Result executeInitiateTransfere(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options, TransfereType::type type) { arangodb::Result result; // Load configuration file std::shared_ptr configFile; result = arangodb::basics::catchVoidToResult([&options, &configFile](){ std::string configFileSource = arangodb::basics::FileUtils::slurp(options.rcloneConfigFile); auto configFileParsed = arangodb::velocypack::Parser::fromJson(configFileSource); configFile.swap(configFileParsed); }); if (result.fail()) { return result; } // Initiate transfere std::string const url = TransfereType::asAdminPath(type); VPackBuilder bodyBuilder; { VPackObjectBuilder guard(&bodyBuilder); bodyBuilder.add("id", VPackValue(options.identifier)); bodyBuilder.add("remoteRepository", VPackValue(options.remoteDirectory)); bodyBuilder.add("config", configFile->slice()); } std::string const body = bodyBuilder.slice().toJson(); std::unique_ptr response( client.request(arangodb::rest::RequestType::POST, url, body.c_str(), body.size())); result = ::checkHttpResponse(client, response); if (result.fail()) { return result; } // Print command for checking status std::shared_ptr parsedBody; try { parsedBody = response->getBodyVelocyPack(); } catch (...) { result.reset(::ErrorMalformedJsonResponse); return result; } VPackSlice const resBody = parsedBody->slice(); VPackSlice const resultObject = resBody.get("result"); if (!resultObject.isObject()) { result.reset(TRI_ERROR_INTERNAL, "expected 'result' to be an object"); return result; } TRI_ASSERT(resultObject.isObject()); std::string transfereId = resultObject.get(TransfereType::asJsonId(type)).copyString(); LOG_TOPIC("a9597", INFO, arangodb::Logger::BACKUP) << "Backup initiated, use "; LOG_TOPIC("4c459", INFO, arangodb::Logger::BACKUP) << " arangobackup " << TransfereType::asString(type) << " --status-id=" << transfereId; LOG_TOPIC("5cd70", INFO, arangodb::Logger::BACKUP) << " to query progress."; return result; } arangodb::Result executeTransfere(arangodb::httpclient::SimpleHttpClient& client, arangodb::BackupFeature::Options const& options, TransfereType::type type) { if (!options.statusId.empty()) { // This is a status query return executeStatusQuery(client, options, type); } else { // This is a transfere return executeInitiateTransfere(client, options, type); } } #endif } // namespace namespace arangodb { BackupFeature::BackupFeature(application_features::ApplicationServer& server, int& exitCode) : ApplicationFeature(server, BackupFeature::featureName()), _clientManager{Logger::BACKUP}, _exitCode{exitCode} { requiresElevatedPrivileges(false); setOptional(false); startsAfter("Client"); }; std::string BackupFeature::featureName() { return ::FeatureName; } std::string BackupFeature::operationList(std::string const& separator) { TRI_ASSERT(::Operations.size() > 0); // construct an ordered list for sorted output std::vector operations(::Operations.begin(), ::Operations.end()); std::sort(operations.begin(), operations.end()); return basics::StringUtils::join(operations, separator); } void BackupFeature::collectOptions(std::shared_ptr options) { using arangodb::options::BooleanParameter; using arangodb::options::DiscreteValuesParameter; using arangodb::options::DoubleParameter; using arangodb::options::Flags; using arangodb::options::StringParameter; using arangodb::options::UInt32Parameter; using arangodb::options::UInt64Parameter; using arangodb::options::VectorParameter; options->addOption("--operation", "operation to perform (may be specified as positional " "argument without '--operation')", new DiscreteValuesParameter(&_options.operation, ::Operations), static_cast::type>(Flags::Hidden)); options->addOption("--allow-inconsistent", "whether to attempt to continue in face of errors; " "may result in inconsistent backup state (create operation)", new BooleanParameter(&_options.force)); options->addOption("--identifier", "a unique identifier for a backup " "(restore/upload/download operation)", new StringParameter(&_options.identifier)); // options->addOption("--include-search", "whether to include ArangoSearch data", // new BooleanParameter(&_options.includeSearch)); options->addOption( "--label", "an additional label to add to the backup identifier (create operation)", new StringParameter(&_options.label)); options->addOption("--max-wait-for-lock", "maximum time to wait in seconds to aquire a lock on " "all necessary resources (create operation)", new DoubleParameter(&_options.maxWaitForLock)); options->addOption( "--max-wait-for-restart", "maximum time to wait in seconds for the server to restart after a " "restore operation before reporting an error; if zero, arangobackup will " "not wait to check that the server restarts and will simply return the " "result of the restore request (restore operation)", new DoubleParameter(&_options.maxWaitForRestart)); #ifdef USE_ENTERPRISE options->addOption("--status-id", "returns the status of a transfer process " "(upload/download operation)", new StringParameter(&_options.statusId)); options->addOption("--rclone-config-file", "filename of the rclone configuration file used for" "file transfer (upload/download operation)", new StringParameter(&_options.rcloneConfigFile)); options->addOption("--remote-path", "remote rclone path of directory used to store or " "receive backups (upload/download operation)", new StringParameter(&_options.remoteDirectory)); options->addOption("--abort", "abort transfer with given status-id " "(upload/download operation)", new BooleanParameter(&_options.abort)); #endif /* options->addSection( "remote", "Options detailing a remote connection to use for operations"); options->addOption("--remote.credentials", "the credentials used for the remote endpoint", new StringParameter(&_options.credentials)); options->addOption("--remote.endpoint", "the remote endpoint", new StringParameter(&_options.endpoint)); */ } void BackupFeature::validateOptions(std::shared_ptr options) { using namespace arangodb::application_features; auto const& positionals = options->processingResult()._positionals; auto client = ApplicationServer::getFeature("Client"); if (client->databaseName() != "_system") { LOG_TOPIC("6b53c", FATAL, Logger::BACKUP) << "hot backups are global and must be performed on the _system database with super user privileges"; FATAL_ERROR_EXIT(); } if (1 == positionals.size()) { _options.operation = positionals[0]; } else { LOG_TOPIC("48e32", FATAL, Logger::BACKUP) << "expected exactly one operation of create|delete|download|list|restore|upload, got '" << basics::StringUtils::join(positionals, ", ") << "'"; FATAL_ERROR_EXIT(); } auto const it = ::Operations.find(_options.operation); if (it == ::Operations.end()) { LOG_TOPIC("138ed", FATAL, Logger::BACKUP) << "expected operation to be one of: " << operationList(", "); FATAL_ERROR_EXIT(); } if (_options.operation == ::OperationCreate) { if (!_options.label.empty()) { std::regex re = std::regex("^([a-zA-Z0-9\\._\\-]+)$", std::regex::ECMAScript); if (!std::regex_match(_options.label, re)) { LOG_TOPIC("7829b", FATAL, Logger::BACKUP) << "--label value may only contain numbers, letters, periods, " "dashes, and underscores"; FATAL_ERROR_EXIT(); } } if (_options.maxWaitForLock < 0.0) { LOG_TOPIC("6caeb", FATAL, Logger::BACKUP) << "expected --max-wait-for-lock to be a non-negative number, got '" << _options.maxWaitForLock << "'"; FATAL_ERROR_EXIT(); } } if (_options.operation == ::OperationDelete || _options.operation == ::OperationRestore) { if (_options.identifier.empty()) { LOG_TOPIC("e83ef", FATAL, Logger::BACKUP) << "must specify a backup via --identifier"; FATAL_ERROR_EXIT(); } } if (_options.operation == ::OperationRestore) { if (_options.maxWaitForRestart < 0.0) { LOG_TOPIC("efa20", FATAL, Logger::BACKUP) << "expected --max-wait-for-restart to " "be a non-negative number, got '" << _options.maxWaitForRestart << "'"; FATAL_ERROR_EXIT(); } } #ifdef USE_ENTERPRISE if (_options.operation == ::OperationUpload || _options.operation == ::OperationDownload) { if (_options.statusId.empty() == _options.identifier.empty()) { // Either both or none are set LOG_TOPIC("2d0fa", FATAL, Logger::BACKUP) << "either --status-id or --identifier" " must be set"; FATAL_ERROR_EXIT(); } if (_options.abort == true && (_options.statusId.empty() || !_options.identifier.empty())) { LOG_TOPIC("62375", FATAL, Logger::BACKUP) << "--abort true expects --status-id to be set"; FATAL_ERROR_EXIT(); } if (!_options.identifier.empty()) { if (_options.rcloneConfigFile.empty() || _options.remoteDirectory.empty()) { LOG_TOPIC("6063d", FATAL, Logger::BACKUP) << "for data transfer --rclone-config-file" " and --remote-path must be set"; FATAL_ERROR_EXIT(); } } } #endif } void BackupFeature::start() { Result result; std::unique_ptr client = _clientManager.getConnectedClient(false, true, true); if (_options.operation == OperationList) { result = ::executeList(*client, _options); } else if (_options.operation == OperationCreate) { result = ::executeCreate(*client, _options); } else if (_options.operation == OperationRestore) { result = ::executeRestore(*client, _options, _clientManager); } else if (_options.operation == OperationDelete) { result = ::executeDelete(*client, _options); #ifdef USE_ENTERPRISE } else if (_options.operation == OperationUpload) { result = ::executeTransfere(*client, _options, TransfereType::type::UPLOAD); } else if (_options.operation == OperationDownload) { result = ::executeTransfere(*client, _options, TransfereType::type::DOWNLOAD); #endif } if (result.fail()) { LOG_TOPIC("8bde3", ERR, Logger::BACKUP) << "Error during backup operation '" << _options.operation << "': " << result.errorMessage(); //FATAL_ERROR_EXIT(); _exitCode = EXIT_FAILURE; } _exitCode = EXIT_SUCCESS; } } // namespace arangodb