Improved error handling for cases where blob account credentials are either not found in the provided credentials sources and/or some of the credentials sources provided are not readable or parseable.
This commit is contained in:
parent
f8522248cb
commit
69425a303b
|
@ -221,21 +221,27 @@ ACTOR Future<Void> deleteRecursively_impl(Reference<BlobStoreEndpoint> b, std::s
|
||||||
state Future<Void> done = b->listBucketStream(bucket, resultStream, prefix, '/', std::numeric_limits<int>::max());
|
state Future<Void> done = b->listBucketStream(bucket, resultStream, prefix, '/', std::numeric_limits<int>::max());
|
||||||
// Wrap done in an actor which will send end_of_stream since listBucketStream() does not (so that many calls can write to the same stream)
|
// Wrap done in an actor which will send end_of_stream since listBucketStream() does not (so that many calls can write to the same stream)
|
||||||
done = map(done, [=](Void) {
|
done = map(done, [=](Void) {
|
||||||
resultStream.sendError(end_of_stream());
|
resultStream.sendError(end_of_stream());
|
||||||
return Void();
|
return Void();
|
||||||
});
|
});
|
||||||
|
|
||||||
state std::list<Future<Void>> deleteFutures;
|
state std::list<Future<Void>> deleteFutures;
|
||||||
try {
|
try {
|
||||||
loop {
|
loop {
|
||||||
BlobStoreEndpoint::ListResult list = waitNext(resultStream.getFuture());
|
choose {
|
||||||
for(auto &object : list.objects) {
|
// Throw if done throws, otherwise don't stop until end_of_stream
|
||||||
int *pNumDeletedCopy = pNumDeleted; // avoid capture of this
|
when(Void _ = wait(done)) {}
|
||||||
deleteFutures.push_back(map(b->deleteObject(bucket, object.name), [pNumDeletedCopy](Void) -> Void {
|
|
||||||
if(pNumDeletedCopy != nullptr)
|
when(BlobStoreEndpoint::ListResult list = waitNext(resultStream.getFuture())) {
|
||||||
++*pNumDeletedCopy;
|
for(auto &object : list.objects) {
|
||||||
return Void();
|
int *pNumDeletedCopy = pNumDeleted; // avoid capture of this
|
||||||
}));
|
deleteFutures.push_back(map(b->deleteObject(bucket, object.name), [pNumDeletedCopy](Void) -> Void {
|
||||||
|
if(pNumDeletedCopy != nullptr)
|
||||||
|
++*pNumDeletedCopy;
|
||||||
|
return Void();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is just a precaution to avoid having too many outstanding delete actors waiting to run
|
// This is just a precaution to avoid having too many outstanding delete actors waiting to run
|
||||||
|
@ -290,9 +296,12 @@ Future<int64_t> BlobStoreEndpoint::objectSize(std::string const &bucket, std::st
|
||||||
// Try to read a file, parse it as JSON, and return the resulting document.
|
// Try to read a file, parse it as JSON, and return the resulting document.
|
||||||
// It will NOT throw if any errors are encountered, it will just return an empty
|
// It will NOT throw if any errors are encountered, it will just return an empty
|
||||||
// JSON object and will log trace events for the errors encountered.
|
// JSON object and will log trace events for the errors encountered.
|
||||||
ACTOR Future<json_spirit::mObject> tryReadJSONFile(std::string path) {
|
ACTOR Future<Optional<json_spirit::mObject>> tryReadJSONFile(std::string path) {
|
||||||
state std::string content;
|
state std::string content;
|
||||||
|
|
||||||
|
// Event type to be logged in the event of an exception
|
||||||
|
state const char *errorEventType = "BlobCredentialFileError";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state Reference<IAsyncFile> f = wait(IAsyncFileSystem::filesystem()->open(path, IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED, 0));
|
state Reference<IAsyncFile> f = wait(IAsyncFileSystem::filesystem()->open(path, IAsyncFile::OPEN_NO_AIO | IAsyncFile::OPEN_READONLY | IAsyncFile::OPEN_UNCACHED, 0));
|
||||||
state int64_t size = wait(f->size());
|
state int64_t size = wait(f->size());
|
||||||
|
@ -300,25 +309,22 @@ ACTOR Future<json_spirit::mObject> tryReadJSONFile(std::string path) {
|
||||||
int r = wait(f->read(mutateString(buf), size, 0));
|
int r = wait(f->read(mutateString(buf), size, 0));
|
||||||
ASSERT(r == size);
|
ASSERT(r == size);
|
||||||
content = buf.toString();
|
content = buf.toString();
|
||||||
} catch(Error &e) {
|
|
||||||
if(e.code() != error_code_actor_cancelled)
|
|
||||||
TraceEvent(SevWarn, "BlobCredentialFileError").detail("File", path).error(e).suppressFor(60, true);
|
|
||||||
return json_spirit::mObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Any exceptions from hehre forward are parse failures
|
||||||
|
errorEventType = "BlobCredentialFileParseFailed";
|
||||||
json_spirit::mValue json;
|
json_spirit::mValue json;
|
||||||
json_spirit::read_string(content, json);
|
json_spirit::read_string(content, json);
|
||||||
if(json.type() == json_spirit::obj_type)
|
if(json.type() == json_spirit::obj_type)
|
||||||
return json.get_obj();
|
return json.get_obj();
|
||||||
else
|
else
|
||||||
TraceEvent(SevWarn, "BlobCredentialFileNotJSONObject").detail("File", path).suppressFor(60, true);
|
TraceEvent(SevWarn, "BlobCredentialFileNotJSONObject").detail("File", path).suppressFor(60, true);
|
||||||
|
|
||||||
} catch(Error &e) {
|
} catch(Error &e) {
|
||||||
if(e.code() != error_code_actor_cancelled)
|
if(e.code() != error_code_actor_cancelled)
|
||||||
TraceEvent(SevWarn, "BlobCredentialFileParseFailed").detail("File", path).error(e).suppressFor(60, true);
|
TraceEvent(SevWarn, errorEventType).detail("File", path).error(e).suppressFor(60, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_spirit::mObject();
|
return Optional<json_spirit::mObject>();
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTOR Future<Void> updateSecret_impl(Reference<BlobStoreEndpoint> b) {
|
ACTOR Future<Void> updateSecret_impl(Reference<BlobStoreEndpoint> b) {
|
||||||
|
@ -326,7 +332,7 @@ ACTOR Future<Void> updateSecret_impl(Reference<BlobStoreEndpoint> b) {
|
||||||
if(pFiles == nullptr)
|
if(pFiles == nullptr)
|
||||||
return Void();
|
return Void();
|
||||||
|
|
||||||
state std::vector<Future<json_spirit::mObject>> reads;
|
state std::vector<Future<Optional<json_spirit::mObject>>> reads;
|
||||||
for(auto &f : *pFiles)
|
for(auto &f : *pFiles)
|
||||||
reads.push_back(tryReadJSONFile(f));
|
reads.push_back(tryReadJSONFile(f));
|
||||||
|
|
||||||
|
@ -334,13 +340,22 @@ ACTOR Future<Void> updateSecret_impl(Reference<BlobStoreEndpoint> b) {
|
||||||
|
|
||||||
std::string key = b->key + "@" + b->host;
|
std::string key = b->key + "@" + b->host;
|
||||||
|
|
||||||
|
int invalid = 0;
|
||||||
|
|
||||||
for(auto &f : reads) {
|
for(auto &f : reads) {
|
||||||
JSONDoc doc(f.get());
|
// If value not present then the credentials file wasn't readable or valid. Continue to check other results.
|
||||||
|
if(!f.get().present()) {
|
||||||
|
++invalid;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONDoc doc(f.get().get());
|
||||||
if(doc.has("accounts") && doc.last().type() == json_spirit::obj_type) {
|
if(doc.has("accounts") && doc.last().type() == json_spirit::obj_type) {
|
||||||
JSONDoc accounts(doc.last().get_obj());
|
JSONDoc accounts(doc.last().get_obj());
|
||||||
if(accounts.has(key, false) && accounts.last().type() == json_spirit::obj_type) {
|
if(accounts.has(key, false) && accounts.last().type() == json_spirit::obj_type) {
|
||||||
JSONDoc account(accounts.last());
|
JSONDoc account(accounts.last());
|
||||||
std::string secret;
|
std::string secret;
|
||||||
|
// Once we find a matching account, use it.
|
||||||
if(account.tryGet("secret", secret)) {
|
if(account.tryGet("secret", secret)) {
|
||||||
b->secret = secret;
|
b->secret = secret;
|
||||||
return Void();
|
return Void();
|
||||||
|
@ -349,7 +364,12 @@ ACTOR Future<Void> updateSecret_impl(Reference<BlobStoreEndpoint> b) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Void();
|
// If any sources were invalid
|
||||||
|
if(invalid > 0)
|
||||||
|
throw backup_auth_unreadable();
|
||||||
|
|
||||||
|
// All sources were valid but didn't contain the desired info
|
||||||
|
throw backup_auth_missing();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Void> BlobStoreEndpoint::updateSecret() {
|
Future<Void> BlobStoreEndpoint::updateSecret() {
|
||||||
|
@ -451,12 +471,9 @@ ACTOR Future<Reference<HTTP::Response>> doRequest_impl(Reference<BlobStoreEndpoi
|
||||||
rconn.conn.clear();
|
rconn.conn.clear();
|
||||||
|
|
||||||
} catch(Error &e) {
|
} catch(Error &e) {
|
||||||
// For timeouts, conn failure, or bad reponse reported by HTTP:doRequest, save the error and handle it / possibly retry below.
|
if(e.code() == error_code_actor_cancelled)
|
||||||
// Any other error is rethrown.
|
|
||||||
if(e.code() == error_code_connection_failed || e.code() == error_code_timed_out || e.code() == error_code_http_bad_response)
|
|
||||||
err = e;
|
|
||||||
else
|
|
||||||
throw;
|
throw;
|
||||||
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If err is not present then r is valid.
|
// If err is not present then r is valid.
|
||||||
|
@ -667,15 +684,21 @@ ACTOR Future<BlobStoreEndpoint::ListResult> listBucket_impl(Reference<BlobStoreE
|
||||||
state Future<Void> done = bstore->listBucketStream(bucket, resultStream, prefix, delimiter, maxDepth, recurseFilter);
|
state Future<Void> done = bstore->listBucketStream(bucket, resultStream, prefix, delimiter, maxDepth, recurseFilter);
|
||||||
// Wrap done in an actor which sends end_of_stream because list does not so that many lists can write to the same stream
|
// Wrap done in an actor which sends end_of_stream because list does not so that many lists can write to the same stream
|
||||||
done = map(done, [=](Void) {
|
done = map(done, [=](Void) {
|
||||||
resultStream.sendError(end_of_stream());
|
resultStream.sendError(end_of_stream());
|
||||||
return Void();
|
return Void();
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loop {
|
loop {
|
||||||
BlobStoreEndpoint::ListResult info = waitNext(resultStream.getFuture());
|
choose {
|
||||||
results.commonPrefixes.insert(results.commonPrefixes.end(), info.commonPrefixes.begin(), info.commonPrefixes.end());
|
// Throw if done throws, otherwise don't stop until end_of_stream
|
||||||
results.objects.insert(results.objects.end(), info.objects.begin(), info.objects.end());
|
when(Void _ = wait(done)) {}
|
||||||
|
|
||||||
|
when(BlobStoreEndpoint::ListResult info = waitNext(resultStream.getFuture())) {
|
||||||
|
results.commonPrefixes.insert(results.commonPrefixes.end(), info.commonPrefixes.begin(), info.commonPrefixes.end());
|
||||||
|
results.objects.insert(results.objects.end(), info.objects.begin(), info.objects.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(Error &e) {
|
} catch(Error &e) {
|
||||||
if(e.code() != error_code_end_of_stream)
|
if(e.code() != error_code_end_of_stream)
|
||||||
|
|
|
@ -174,6 +174,8 @@ ERROR( backup_bad_block_size, 2313, "Backup file block size too small")
|
||||||
ERROR( backup_invalid_url, 2314, "Backup Container URL invalid")
|
ERROR( backup_invalid_url, 2314, "Backup Container URL invalid")
|
||||||
ERROR( backup_invalid_info, 2315, "Backup Container URL invalid")
|
ERROR( backup_invalid_info, 2315, "Backup Container URL invalid")
|
||||||
ERROR( backup_cannot_expire, 2316, "Cannot expire requested data from backup without violating minimum restorability")
|
ERROR( backup_cannot_expire, 2316, "Cannot expire requested data from backup without violating minimum restorability")
|
||||||
|
ERROR( backup_auth_missing, 2317, "Cannot find authentication details (such as a password or secret key) for the specified Backup Container URL")
|
||||||
|
ERROR( backup_auth_unreadable, 2318, "Cannot read or parse one or more sources of authentication information for Backup Container URLs")
|
||||||
ERROR( restore_invalid_version, 2361, "Invalid restore version")
|
ERROR( restore_invalid_version, 2361, "Invalid restore version")
|
||||||
ERROR( restore_corrupted_data, 2362, "Corrupted backup data")
|
ERROR( restore_corrupted_data, 2362, "Corrupted backup data")
|
||||||
ERROR( restore_missing_data, 2363, "Missing backup data")
|
ERROR( restore_missing_data, 2363, "Missing backup data")
|
||||||
|
|
Loading…
Reference in New Issue