mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
(cherry picked from commit 989e60f639e7add19c0a72667efa4fa9219f0b6a) # Conflicts: # Interface/BackupFileSystem.h # btrfs/btrfsplugin/BackupFileSystem.h # clouddrive/CloudFile.cpp # clouddrive/KvStoreBackendAzure.cpp # clouddrive/KvStoreBackendAzure.h # clouddrive/KvStoreBackendLocal.cpp # clouddrive/KvStoreBackendLocal.h # clouddrive/KvStoreBackendS3.cpp # clouddrive/KvStoreBackendS3.h # clouddrive/KvStoreFrontend.cpp # clouddrive/OnlineKvStore.cpp # clouddrive/OnlineKvStore.h # clouddrive/PassThroughFileSystem.cpp # clouddrive/dllmain.cpp
1248 lines
35 KiB
C++
1248 lines
35 KiB
C++
/*************************************************************************
|
|
* UrBackup - Client/Server backup system
|
|
* Copyright (C) 2021 Martin Raiber
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
|
|
#include "KvStoreBackendS3.h"
|
|
#include <aws/core/client/ClientConfiguration.h>
|
|
#include <aws/s3/model/GetObjectRequest.h>
|
|
#include <aws/s3/model/ListObjectsRequest.h>
|
|
#include <aws/s3/model/ListObjectVersionsRequest.h>
|
|
#include <aws/s3/model/Object.h>
|
|
#include <aws/s3/model/PutObjectRequest.h>
|
|
#include <aws/s3/model/DeleteObjectsRequest.h>
|
|
#include <aws/s3/model/DeleteObjectRequest.h>
|
|
#include <aws/s3/model/GetBucketLocationRequest.h>
|
|
#include <aws/core/Aws.h>
|
|
#include <aws/core/utils/Array.h>
|
|
#include "../Interface/File.h"
|
|
#include "../Interface/Server.h"
|
|
#include "../Interface/Types.h"
|
|
#include "../Interface/File.h"
|
|
#include "../Interface/BackupFileSystem.h"
|
|
#include "../stringtools.h"
|
|
#include "../urbackupcommon/os_functions.h"
|
|
#include "../urbackupcommon/events.h"
|
|
#include "../md5.h"
|
|
#include <fstream>
|
|
#include <cstdarg>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
|
|
static const char* ALLOCATION_TAG = "DumbOnlineKvStoreBackend";
|
|
static const char* SHARD_TAG = "__SHARD__";
|
|
|
|
class ServerAwsLogger : public Aws::Utils::Logging::LogSystemInterface
|
|
{
|
|
public:
|
|
ServerAwsLogger(Aws::Utils::Logging::LogLevel loglevel)
|
|
: m_loglevel(loglevel) {}
|
|
|
|
virtual Aws::Utils::Logging::LogLevel GetLogLevel(void) const
|
|
{
|
|
return m_loglevel;
|
|
}
|
|
|
|
void Log(Aws::Utils::Logging::LogLevel logLevel, const char* tag, const char* formatStr, ...)
|
|
{
|
|
Aws::StringStream ss;
|
|
|
|
std::va_list args;
|
|
va_start(args, formatStr);
|
|
|
|
va_list tmp_args; //unfortunately you cannot consume a va_list twice
|
|
va_copy(tmp_args, args); //so we have to copy it
|
|
#ifdef WIN32
|
|
const int requiredLength = _vscprintf(formatStr, tmp_args) + 1;
|
|
#else
|
|
const int requiredLength = vsnprintf(nullptr, 0, formatStr, tmp_args) + 1;
|
|
#endif
|
|
va_end(tmp_args);
|
|
|
|
Aws::Utils::Array<char> outputBuff(requiredLength);
|
|
#ifdef WIN32
|
|
vsnprintf_s(outputBuff.GetUnderlyingData(), requiredLength, _TRUNCATE, formatStr, args);
|
|
#else
|
|
vsnprintf(outputBuff.GetUnderlyingData(), requiredLength, formatStr, args);
|
|
#endif // WIN32
|
|
|
|
ss << outputBuff.GetUnderlyingData();
|
|
|
|
Log(logLevel, ss.str());
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
void LogStream(Aws::Utils::Logging::LogLevel logLevel, const char* tag, const Aws::OStringStream &message_stream)
|
|
{
|
|
Log(logLevel, message_stream.rdbuf()->str());
|
|
}
|
|
|
|
void Log(Aws::Utils::Logging::LogLevel loglevel, const Aws::String& msg)
|
|
{
|
|
int ll;
|
|
switch(loglevel)
|
|
{
|
|
case Aws::Utils::Logging::LogLevel::Error: ll=LL_ERROR; break;
|
|
case Aws::Utils::Logging::LogLevel::Fatal: ll=LL_ERROR; break;
|
|
case Aws::Utils::Logging::LogLevel::Warn: ll=LL_WARNING; break;
|
|
case Aws::Utils::Logging::LogLevel::Info: ll=LL_INFO; break;
|
|
default: ll=LL_DEBUG;
|
|
}
|
|
|
|
Server->Log("AWS-Client: "+trim(msg.c_str()), ll);
|
|
}
|
|
|
|
void Flush() {}
|
|
|
|
private:
|
|
Aws::Utils::Logging::LogLevel m_loglevel;
|
|
};
|
|
|
|
Aws::String BucketLocationToRegion(Aws::S3::Model::BucketLocationConstraint b)
|
|
{
|
|
using namespace Aws::S3::Model;
|
|
Aws::String ret;
|
|
switch(b)
|
|
{
|
|
case BucketLocationConstraint::EU: return Aws::Region::EU_WEST_1;
|
|
case BucketLocationConstraint::NOT_SET: return Aws::Region::US_EAST_1;
|
|
default: ret = Aws::S3::Model::BucketLocationConstraintMapper::GetNameForBucketLocationConstraint(b); break;
|
|
}
|
|
|
|
if(ret.empty())
|
|
{
|
|
Server->Log("Unknown bucket location constraint "+convert((int)b)+". Returning US_EAST_1", LL_WARNING);
|
|
return Aws::Region::US_EAST_1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
std::string base64md5(std::string md5)
|
|
{
|
|
std::string b = hexToBytes(md5);
|
|
return base64_encode(reinterpret_cast<const unsigned char*>(b.data()), static_cast<unsigned int>(b.size()));
|
|
}
|
|
|
|
size_t getShardIdx(const std::string& key, size_t n_shards)
|
|
{
|
|
std::string md5sum = Server->GenerateBinaryMD5(key);
|
|
size_t idx_ret;
|
|
memcpy(&idx_ret, md5sum.data(), sizeof(idx_ret));
|
|
return idx_ret%n_shards;
|
|
}
|
|
}
|
|
|
|
class offset_buf : public std::streambuf
|
|
{
|
|
public:
|
|
offset_buf(int64 offset, IFile* f, KvStoreBackendS3* backend, bool own_f)
|
|
: offset(offset), pos(offset), f(f), has_error(false), backend(backend), own_f(own_f),
|
|
size(f->Size())
|
|
{
|
|
buffer.resize(32768);
|
|
char *end = buffer.data() + buffer.size();
|
|
setg(end, end, end);
|
|
}
|
|
|
|
~offset_buf()
|
|
{
|
|
if(own_f)
|
|
{
|
|
std::string tmpfile_path = f->getFilename();
|
|
Server->destroy(f);
|
|
Server->deleteFile(tmpfile_path);
|
|
}
|
|
}
|
|
|
|
bool has_error;
|
|
|
|
protected:
|
|
|
|
std::streambuf::int_type underflow() override
|
|
{
|
|
if (gptr() < egptr())
|
|
{
|
|
return traits_type::to_int_type(*gptr());
|
|
}
|
|
|
|
bool has_read_error=false;
|
|
size_t toread = (std::min)(static_cast<size_t>(size - pos), buffer.size());
|
|
_u32 read = f->Read(pos, buffer.data(), static_cast<_u32>(toread), &has_read_error);
|
|
|
|
if(has_read_error)
|
|
{
|
|
has_error=true;
|
|
std::string msg = "Read error while reading from file "
|
|
+ f->getFilename() + " at position " + convert(pos)
|
|
+ " len " + convert(buffer.size()) + " for S3 submission. " + os_last_error_str();
|
|
Server->Log(msg, LL_ERROR);
|
|
addSystemEvent("cache_err",
|
|
"Error reading from file on cache",
|
|
msg, LL_ERROR);
|
|
}
|
|
|
|
pos+=read;
|
|
if(read==0)
|
|
{
|
|
return traits_type::eof();
|
|
}
|
|
|
|
backend->add_uploaded_bytes(read);
|
|
|
|
setg(buffer.data(), buffer.data(), buffer.data() + read);
|
|
|
|
return traits_type::to_int_type(*gptr());
|
|
}
|
|
|
|
std::streambuf::pos_type seekoff( std::streambuf::off_type off, std::ios_base::seekdir dir,
|
|
std::ios_base::openmode which ) override
|
|
{
|
|
int64 npos=pos;
|
|
if(dir==std::ios::beg)
|
|
{
|
|
npos=off + offset;
|
|
}
|
|
else if(dir==std::ios::cur)
|
|
{
|
|
npos+=off;
|
|
}
|
|
else
|
|
{
|
|
npos = f->Size()-off;
|
|
}
|
|
|
|
if(npos<offset)
|
|
{
|
|
return std::streambuf::pos_type(std::streambuf::off_type(-1));
|
|
}
|
|
|
|
pos=npos;
|
|
|
|
char *end = buffer.data() + buffer.size();
|
|
setg(end, end, end);
|
|
|
|
return std::streambuf::pos_type(pos-offset);
|
|
}
|
|
|
|
virtual std::streambuf::pos_type seekpos( pos_type pos,
|
|
std::ios_base::openmode which) override
|
|
{
|
|
return seekoff(pos, std::ios::beg, which);
|
|
}
|
|
|
|
private:
|
|
std::vector<char> buffer;
|
|
|
|
int64 offset;
|
|
int64 pos;
|
|
int64 size;
|
|
IFile* f;
|
|
bool own_f;
|
|
KvStoreBackendS3* backend;
|
|
};
|
|
|
|
IMutex* KvStoreBackendS3::client_mutex=nullptr;
|
|
int64 KvStoreBackendS3::max_request_timems=0;
|
|
int64 KvStoreBackendS3::n_requests=0;
|
|
|
|
KvStoreBackendS3::KvStoreBackendS3(const std::string& encryption_key, const std::string& access_key, const std::string& secret_access_key,
|
|
const std::string& bucket_name, ICompressEncryptFactory* compress_encrypt_factory, const std::string& s3_endpoint,
|
|
const std::string& s3_region, const std::string& p_storage_class, unsigned int comp_method, unsigned int comp_method_metadata,
|
|
IBackupFileSystem* cachefs)
|
|
: encryption_key(encryption_key),
|
|
compress_encrypt_factory(compress_encrypt_factory), online_kv_store(nullptr),
|
|
s3_endpoint(s3_endpoint), s3_region(s3_region),
|
|
storage_class(Aws::S3::Model::StorageClass::NOT_SET), comp_method(comp_method),
|
|
comp_method_metadata(comp_method_metadata),
|
|
uploaded_bytes(0), downloaded_bytes(0), cachefs(cachefs)
|
|
{
|
|
if(!access_key.empty())
|
|
{
|
|
credentials_provider.reset(new Aws::Auth::SimpleAWSCredentialsProvider(access_key.c_str(), secret_access_key.c_str()));
|
|
}
|
|
else
|
|
{
|
|
credentials_provider.reset(new Aws::Auth::InstanceProfileCredentialsProvider);
|
|
}
|
|
|
|
if (!p_storage_class.empty())
|
|
{
|
|
storage_class = Aws::S3::Model::StorageClassMapper::GetStorageClassForName(p_storage_class.c_str());
|
|
}
|
|
|
|
std::vector<std::string> toks;
|
|
Tokenize(bucket_name, toks, "/");
|
|
|
|
if(toks.empty())
|
|
{
|
|
toks.push_back("");
|
|
}
|
|
|
|
s3_clients.resize(toks.size());
|
|
buckets.resize(toks.size());
|
|
for(size_t i=0;i<toks.size();++i)
|
|
{
|
|
SBucket& bucket = buckets[i];
|
|
bucket.name = toks[i];
|
|
bucket.location = Aws::S3::Model::BucketLocationConstraint();
|
|
if(s3_endpoint.empty())
|
|
{
|
|
Aws::S3::Model::GetBucketLocationRequest getBucketLocationRequest;
|
|
getBucketLocationRequest.SetBucket(bucket.name.c_str());
|
|
auto s3_client = getS3Client(i, false);
|
|
auto getBucketLocationOutcome = s3_client.second->GetBucketLocation(getBucketLocationRequest);
|
|
if(getBucketLocationOutcome.IsSuccess())
|
|
{
|
|
bucket.location = getBucketLocationOutcome.GetResult().GetLocationConstraint();
|
|
}
|
|
}
|
|
}
|
|
|
|
resetClient();
|
|
}
|
|
|
|
void KvStoreBackendS3::init_mutex()
|
|
{
|
|
client_mutex = Server->createMutex();
|
|
|
|
Aws::SDKOptions options;
|
|
Aws::Utils::Logging::LogLevel logLevel = Aws::Utils::Logging::LogLevel::Warn;
|
|
|
|
|
|
if(FileExists("/var/urbackup/trace_aws_s3"))
|
|
{
|
|
logLevel = Aws::Utils::Logging::LogLevel::Trace;
|
|
}
|
|
|
|
options.loggingOptions.logLevel = logLevel;
|
|
options.loggingOptions.logger_create_fn = [logLevel](){
|
|
return Aws::MakeShared<ServerAwsLogger>(ALLOCATION_TAG, logLevel);
|
|
};
|
|
Aws::InitAPI(options);
|
|
}
|
|
|
|
bool KvStoreBackendS3::get( const std::string& key, const std::string& md5sum,
|
|
unsigned int flags, bool allow_error_event, IFsFile* ret_file, std::string& ret_md5sum, unsigned int& get_status)
|
|
{
|
|
assert(ret_file!=nullptr);
|
|
get_status=0;
|
|
|
|
size_t idx0 = 0;
|
|
if(buckets[0].name==SHARD_TAG)
|
|
{
|
|
idx0 = getShardIdx(key, buckets.size()-1)+1;
|
|
}
|
|
|
|
std::string expected_md5sum = get_md5sum(md5sum);
|
|
std::string version = get_locinfo(md5sum);
|
|
|
|
Aws::S3::Model::GetObjectRequest getObjectRequest;
|
|
getObjectRequest.SetBucket(buckets[idx0].name.c_str());
|
|
getObjectRequest.SetKey(key.c_str());
|
|
if(!version.empty())
|
|
getObjectRequest.SetVersionId(version.c_str());
|
|
|
|
IFsFile* tmpfile = Server->openTemporaryFile();
|
|
if(tmpfile==nullptr)
|
|
{
|
|
std::string syserr = os_last_error_str();
|
|
if (allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error opening temporary file",
|
|
"Error opening temporary file. " + syserr, LL_ERROR);
|
|
}
|
|
Server->Log("Error opening temporary file. "+syserr, LL_ERROR);
|
|
return false;
|
|
}
|
|
std::string tmpfile_path = tmpfile->getFilename();
|
|
Server->destroy(tmpfile);
|
|
|
|
if(!(flags & IKvStoreBackend::GetDecrypted))
|
|
{
|
|
Server->Log("Retrieving object "+ key+" and not decrypting", LL_INFO);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Retrieving object "+ key, LL_INFO);
|
|
}
|
|
|
|
getObjectRequest.SetResponseStreamFactory([&tmpfile_path](){ return Aws::New<Aws::FStream>( ALLOCATION_TAG, tmpfile_path,
|
|
std::ios_base::out | std::ios_base::in | std::ios_base::trunc | std::ios_base::binary
|
|
#ifdef _WIN32
|
|
, _SH_DENYNO
|
|
#endif
|
|
); });
|
|
|
|
int64 starttime = Server->getTimeMS();
|
|
auto s3_client = getS3Client(idx0);
|
|
auto getObjectOutcome = s3_client.second->GetObject(getObjectRequest);
|
|
releaseS3Client(idx0, s3_client);
|
|
|
|
for(size_t idx = 1; !getObjectOutcome.IsSuccess() && idx < buckets.size()
|
|
&& (getObjectOutcome.GetError().GetErrorType() == Aws::S3::S3Errors::NO_SUCH_KEY
|
|
|| (getObjectOutcome.GetError().GetErrorType() == Aws::S3::S3Errors::UNKNOWN
|
|
&& getObjectOutcome.GetError().GetMessage().find("Response code: 404")!=std::string::npos ) )
|
|
;++idx)
|
|
{
|
|
Server->Log("Key "+key+" not found in bucket idx "+convert(idx0)+". Trying bucket \""+buckets[idx].name+"\"...", LL_INFO);
|
|
getObjectRequest.SetBucket(buckets[idx].name.c_str());
|
|
auto s3_client = getS3Client(idx);
|
|
getObjectOutcome = s3_client.second->GetObject(getObjectRequest);
|
|
releaseS3Client(idx, s3_client);
|
|
}
|
|
|
|
int64 passedtime = Server->getTimeMS()-starttime;
|
|
|
|
if(getObjectOutcome.IsSuccess())
|
|
{
|
|
{
|
|
IScopedLock lock(client_mutex);
|
|
if(passedtime>max_request_timems)
|
|
{
|
|
max_request_timems=passedtime;
|
|
}
|
|
++n_requests;
|
|
}
|
|
|
|
int mode = MODE_RW;
|
|
#ifdef _WIN32
|
|
mode = MODE_RW_DEVICE;
|
|
#endif // _WIN32
|
|
|
|
tmpfile = Server->openFile(tmpfile_path, mode);
|
|
if(tmpfile==nullptr)
|
|
{
|
|
std::string syserr = os_last_error_str();
|
|
if (allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error opening temporary file ",
|
|
"Error opening temporary file -2. " + syserr, LL_ERROR);
|
|
}
|
|
Server->Log("Error opening temporary file -2. "+syserr, LL_ERROR);
|
|
return false;
|
|
}
|
|
ScopedDeleteFile delete_tmpfile(tmpfile);
|
|
|
|
downloaded_bytes+=tmpfile->Size();
|
|
|
|
std::unique_ptr<IDecryptAndDecompress> decrypt_and_decompress;
|
|
|
|
if(flags & IKvStoreBackend::GetDecrypted)
|
|
{
|
|
decrypt_and_decompress.reset(compress_encrypt_factory->createDecryptAndDecompress(encryption_key, ret_file));
|
|
}
|
|
|
|
std::vector<char> buffer;
|
|
buffer.resize(32768);
|
|
|
|
MD5 md_check;
|
|
tmpfile->Seek(0);
|
|
while(true)
|
|
{
|
|
_u32 read = tmpfile->Read(buffer.data(), static_cast<_u32>(buffer.size()));
|
|
if(read==0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(decrypt_and_decompress.get()!=nullptr
|
|
&& !decrypt_and_decompress->put(buffer.data(), read))
|
|
{
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error decrypting and compressing",
|
|
"Error decrypting and decompressing object "+key+". Last errors:\n"+extractLastLogErrors(), LL_ERROR);
|
|
}
|
|
Server->Log("Error decrypting and decompressing", LL_ERROR);
|
|
return false;
|
|
}
|
|
else if(decrypt_and_decompress.get()==nullptr)
|
|
{
|
|
md_check.update(reinterpret_cast<unsigned char*>(buffer.data()), read);
|
|
if(ret_file->Write(buffer.data(), read)!=read)
|
|
{
|
|
std::string syserr = os_last_error_str();
|
|
Server->Log("Error writing to result file. "+syserr, LL_ERROR);
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error writing to result file",
|
|
"Error writing to result file. "+syserr, LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (decrypt_and_decompress.get() != nullptr)
|
|
{
|
|
if (!decrypt_and_decompress->finalize())
|
|
{
|
|
Server->Log("Error finalizing decryption of object "+key, LL_ERROR);
|
|
if (allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error decrypting object",
|
|
"Error finalizing decryption of object " + key, LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ret_md5sum = hexToBytes(decrypt_and_decompress->md5sum());
|
|
}
|
|
else
|
|
{
|
|
md_check.finalize();
|
|
ret_md5sum.assign(reinterpret_cast<char*>(md_check.raw_digest_int()), 16);
|
|
}
|
|
|
|
if (!expected_md5sum.empty()
|
|
&& expected_md5sum != ret_md5sum)
|
|
{
|
|
Server->Log("Calculated md5sum of downloaded object differs from expected md5sum for object " + key
|
|
+ ". Calculated=" + bytesToHex(ret_md5sum) + " Expected=" + bytesToHex(expected_md5sum), LL_ERROR);
|
|
if (allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Calculated md5sum differs from expected",
|
|
"Calculated md5sum of downloaded object differs from expected md5sum for object " + key
|
|
+ ". Calculated=" + bytesToHex(ret_md5sum) + " Expected=" + bytesToHex(expected_md5sum), LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if(getObjectOutcome.GetError().GetErrorType() == Aws::S3::S3Errors::NO_SUCH_KEY
|
|
|| (getObjectOutcome.GetError().GetErrorType() == Aws::S3::S3Errors::UNKNOWN
|
|
&& getObjectOutcome.GetError().GetMessage().find("Response code: 404")!=std::string::npos) )
|
|
{
|
|
Server->Log("Key "+key+" not found", LL_INFO);
|
|
get_status|=IKvStoreBackend::GetStatusNotFound;
|
|
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error during S3 download (not found)",
|
|
"S3 download of object "+key+" failed. Object not present/not found.\nLast errors:\n"+extractLastLogErrors(5, "AWS-Client: "), LL_ERROR);
|
|
}
|
|
}
|
|
else if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error during S3 download",
|
|
"S3 download of object "+key+" failed.\nLast errors:\n"+extractLastLogErrors(5, "AWS-Client: ", true), LL_ERROR);
|
|
}
|
|
|
|
fixError(getObjectOutcome.GetError().GetErrorType());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool KvStoreBackendS3::list( IListCallback* callback )
|
|
{
|
|
bool etag_error = false;
|
|
Aws::String key_marker;
|
|
Aws::String version_marker;
|
|
size_t idx=0;
|
|
auto s3_client = getS3Client(idx);
|
|
while(idx<buckets.size())
|
|
{
|
|
if(buckets[idx].name == SHARD_TAG)
|
|
{
|
|
if(idx+1<buckets.size())
|
|
{
|
|
++idx;
|
|
s3_client = getS3Client(idx);
|
|
key_marker.clear();
|
|
version_marker.clear();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Aws::S3::Model::ListObjectVersionsRequest listObjectsRequest;
|
|
listObjectsRequest.SetBucket(buckets[idx].name.c_str());
|
|
|
|
if(!key_marker.empty())
|
|
{
|
|
listObjectsRequest.WithKeyMarker(key_marker);
|
|
listObjectsRequest.WithVersionIdMarker(version_marker);
|
|
}
|
|
|
|
Aws::S3::Model::ListObjectVersionsOutcome listObjectsOutcome = s3_client.second->ListObjectVersions(listObjectsRequest);
|
|
|
|
if(!listObjectsOutcome.IsSuccess())
|
|
{
|
|
if (idx == 0
|
|
&& listObjectsOutcome.GetError().GetExceptionName()=="NotImplemented")
|
|
{
|
|
return list_wo_versions(callback);
|
|
}
|
|
Server->Log("Listing objects in bucket \""+buckets[idx].name+"\" with versions failed. "+listObjectsOutcome.GetError().GetMessage().c_str()+" (code: "+convert(static_cast<int64>(listObjectsOutcome.GetError().GetErrorType()))+")", LL_ERROR);
|
|
fixError(listObjectsOutcome.GetError().GetErrorType());
|
|
releaseS3Client(idx, s3_client);
|
|
return false;
|
|
}
|
|
|
|
for (const Aws::S3::Model::ObjectVersion& object: listObjectsOutcome.GetResult().GetVersions())
|
|
{
|
|
key_marker = object.GetKey();
|
|
version_marker = object.GetVersionId();
|
|
|
|
std::string md5sum;
|
|
if (version_marker.size()>=16)
|
|
{
|
|
md5sum.resize(16 + version_marker.size());
|
|
md5sum.replace(md5sum.begin() + 16, md5sum.end(), version_marker.data());
|
|
}
|
|
else
|
|
{
|
|
md5sum.assign(version_marker.begin(), version_marker.end());
|
|
}
|
|
|
|
if(!callback->onlineItem(object.GetKey().c_str(), md5sum, object.GetSize(), object.GetLastModified().Millis()))
|
|
{
|
|
releaseS3Client(idx, s3_client);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!listObjectsOutcome.GetResult().GetIsTruncated())
|
|
{
|
|
releaseS3Client(idx, s3_client);
|
|
|
|
if(idx+1<buckets.size())
|
|
{
|
|
++idx;
|
|
s3_client = getS3Client(idx);
|
|
key_marker.clear();
|
|
version_marker.clear();
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!listObjectsOutcome.GetResult().GetNextKeyMarker().empty())
|
|
{
|
|
key_marker = listObjectsOutcome.GetResult().GetNextKeyMarker();
|
|
version_marker = listObjectsOutcome.GetResult().GetNextVersionIdMarker();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreBackendS3::put( const std::string& key, IFsFile* src,
|
|
unsigned int flags, bool allow_error_event, std::string& md5sum, int64& compressed_size)
|
|
{
|
|
src->Seek(0);
|
|
|
|
unsigned int curr_comp_method = (flags & IKvStoreBackend::GetMetadata) > 0 ? comp_method_metadata
|
|
: comp_method;
|
|
|
|
std::string local_md5;
|
|
std::shared_ptr<Aws::IOStream> upload_file;
|
|
std::shared_ptr<offset_buf> offset_buffer;
|
|
int64 local_size=0;
|
|
if(!(flags & IKvStoreBackend::PutAlreadyCompressedEncrypted))
|
|
{
|
|
IFsFile* tmpfile = Server->openTemporaryFile();
|
|
ScopedDeleteFile tmpfile_delete(tmpfile);
|
|
|
|
if(tmpfile==nullptr)
|
|
{
|
|
std::string syserr = os_last_error_str();
|
|
Server->Log("Error opening temporary file. "+syserr, LL_ERROR);
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error opening temporary file",
|
|
"Error opening temporary file. "+syserr, LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<ICompressAndEncrypt> compress_encrypt(compress_encrypt_factory->createCompressAndEncrypt(encryption_key,
|
|
src, online_kv_store, curr_comp_method));
|
|
|
|
std::vector<char> buffer;
|
|
buffer.resize(32768);
|
|
while(true)
|
|
{
|
|
size_t read = compress_encrypt->read(buffer.data(), buffer.size());
|
|
|
|
if(read==std::string::npos)
|
|
{
|
|
Server->Log("Error compressing and encrypting (S3)", LL_ERROR);
|
|
break;
|
|
}
|
|
|
|
if(read==0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if(tmpfile->Write(buffer.data(), static_cast<_u32>(read))!=read)
|
|
{
|
|
std::string syserr = os_last_error_str();
|
|
Server->Log("Error writing to tmp file \""+tmpfile->getFilename()+"\". " + syserr, LL_ERROR);
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error writing to temporary file",
|
|
"Error writing to tmp file \""+tmpfile->getFilename()+"\". " + syserr, LL_ERROR);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
local_size = tmpfile->Size();
|
|
Server->Log("Uploading object "+ key +"... Uncompressed size="+convert(src->Size())+" Compressed size="+convert(local_size), LL_INFO);
|
|
|
|
local_md5 = compress_encrypt->md5sum();
|
|
|
|
tmpfile_delete.release();
|
|
offset_buffer.reset(new offset_buf(0, tmpfile, this, true));
|
|
}
|
|
else
|
|
{
|
|
local_md5.resize(16);
|
|
if(src->Read(0, &local_md5[0], static_cast<_u32>(local_md5.size()))!=local_md5.size())
|
|
{
|
|
local_md5.clear();
|
|
}
|
|
|
|
local_size = src->Size();
|
|
|
|
if(local_size>=16)
|
|
{
|
|
local_size-=16;
|
|
}
|
|
|
|
Server->Log("Uploading object "+ key +"... Compressed size="+convert(local_size), LL_INFO);
|
|
|
|
offset_buffer.reset(new offset_buf(16, src, this, false));
|
|
}
|
|
|
|
upload_file = Aws::MakeShared<Aws::IOStream>(ALLOCATION_TAG, offset_buffer.get());
|
|
|
|
size_t idx=0;
|
|
if(buckets[idx].name==SHARD_TAG)
|
|
{
|
|
idx = getShardIdx(key, buckets.size()-1)+1;
|
|
}
|
|
|
|
Aws::S3::Model::PutObjectRequest putObjectRequest;
|
|
putObjectRequest.SetBucket(buckets[idx].name.c_str());
|
|
putObjectRequest.SetKey(key.c_str());
|
|
putObjectRequest.SetBody(upload_file);
|
|
putObjectRequest.SetContentMD5(base64_encode(reinterpret_cast<unsigned char*>(local_md5.data()),
|
|
static_cast<unsigned int>(local_md5.size())).c_str());
|
|
if (storage_class != Aws::S3::Model::StorageClass::NOT_SET)
|
|
{
|
|
putObjectRequest.SetStorageClass(storage_class);
|
|
}
|
|
upload_file.reset();
|
|
|
|
int64 starttime = Server->getTimeMS();
|
|
auto s3_client = getS3Client(idx);
|
|
Aws::S3::Model::PutObjectOutcome putObjectOutcome = s3_client.second->PutObject(putObjectRequest);
|
|
releaseS3Client(idx, s3_client);
|
|
|
|
if(offset_buffer.get()!=nullptr
|
|
&& offset_buffer->has_error)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int64 passedtime = Server->getTimeMS()-starttime;
|
|
|
|
if(putObjectOutcome.IsSuccess())
|
|
{
|
|
{
|
|
IScopedLock lock(client_mutex);
|
|
if(passedtime>max_request_timems)
|
|
{
|
|
max_request_timems=passedtime;
|
|
}
|
|
++n_requests;
|
|
}
|
|
|
|
md5sum = local_md5;
|
|
compressed_size = local_size;
|
|
|
|
if (del_with_location_info())
|
|
{
|
|
Aws::String version = putObjectOutcome.GetResult().GetVersionId();
|
|
md5sum.resize(16 + version.size());
|
|
md5sum.replace(md5sum.begin() + 16, md5sum.end(), version.data());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fixError(putObjectOutcome.GetError().GetErrorType());
|
|
|
|
if(allow_error_event)
|
|
{
|
|
addSystemEvent("s3_backend",
|
|
"Error during S3 upload",
|
|
"S3 upload of object "+key+" failed.\nLast errors:\n"+extractLastLogErrors(5, "AWS-Client: ", true), LL_ERROR);
|
|
}
|
|
}
|
|
|
|
return putObjectOutcome.IsSuccess();
|
|
}
|
|
|
|
namespace
|
|
{
|
|
bool hasErrors(Aws::S3::Model::DeleteObjectsOutcome& deleteObjectsOutcome, bool& has_missing)
|
|
{
|
|
bool c_error=false;
|
|
for(Aws::S3::Model::Error error: deleteObjectsOutcome.GetResult().GetErrors())
|
|
{
|
|
if(error.GetCode()!="NoSuchKey")
|
|
{
|
|
Server->Log("Deleting object "+std::string(error.GetKey().c_str())+" failed. "+error.GetMessage().c_str()+" (code: "+error.GetCode().c_str()+")", LL_ERROR);
|
|
c_error=true;
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Deleting object "+ std::string(error.GetKey().c_str())+" failed. "+error.GetMessage().c_str()+" (code: "+error.GetCode().c_str()+")", LL_INFO);
|
|
has_missing=true;
|
|
}
|
|
}
|
|
return c_error;
|
|
}
|
|
}
|
|
|
|
bool KvStoreBackendS3::del(key_next_fun_t key_next_fun,
|
|
locinfo_next_fun_t locinfo_next_fun,
|
|
bool background_queue)
|
|
{
|
|
//shard_optimized=false for now because of https://tracker.ceph.com/issues/41642 (radosgw does not return NoSuckKey if key is not present)
|
|
return del_int(key_next_fun, locinfo_next_fun, false);
|
|
}
|
|
|
|
bool KvStoreBackendS3::del_int( key_next_fun_t key_next_fun,
|
|
locinfo_next_fun_t locinfo_next_fun, bool shard_optimized)
|
|
{
|
|
if(next(s3_endpoint, 0, "https://storage.googleapis.com"))
|
|
{
|
|
//Does not support bulk deletion
|
|
Aws::S3::Model::DeleteObjectRequest deleteObjectRequest;
|
|
|
|
bool sharded=false;
|
|
bool has_missing=false;
|
|
for(size_t idx=0;idx<buckets.size();++idx)
|
|
{
|
|
if(idx==0 && buckets[idx].name==SHARD_TAG)
|
|
{
|
|
sharded=true;
|
|
continue;
|
|
}
|
|
|
|
deleteObjectRequest.SetBucket(buckets[idx].name.c_str());
|
|
|
|
std::string key;
|
|
while(key_next_fun(IKvStoreBackend::key_next_action_t::next, &key))
|
|
{
|
|
std::string locinfo;
|
|
if (locinfo_next_fun != nullptr)
|
|
{
|
|
if (locinfo_next_fun(IKvStoreBackend::key_next_action_t::next, &locinfo))
|
|
break;
|
|
}
|
|
|
|
if(shard_optimized &&
|
|
sharded &&
|
|
getShardIdx(key, buckets.size()-1)+1!=idx)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
deleteObjectRequest.SetKey(key.c_str());
|
|
if (!locinfo.empty())
|
|
{
|
|
deleteObjectRequest.SetVersionId(locinfo.c_str());
|
|
}
|
|
auto s3_client = getS3Client(idx);
|
|
Aws::S3::Model::DeleteObjectOutcome deleteObjectOutcome = s3_client.second->DeleteObject(deleteObjectRequest);
|
|
releaseS3Client(idx, s3_client);
|
|
if (!deleteObjectOutcome.IsSuccess()
|
|
&& deleteObjectOutcome.GetError().GetErrorType()!=Aws::S3::S3Errors::NO_SUCH_KEY )
|
|
{
|
|
Server->Log("Deleting object "+key+" failed. "+deleteObjectOutcome.GetError().GetMessage().c_str()+" (code: "+convert(static_cast<int64>(deleteObjectOutcome.GetError().GetErrorType()))+")", LL_ERROR);
|
|
fixError(deleteObjectOutcome.GetError().GetErrorType());
|
|
return false;
|
|
}
|
|
else if(!deleteObjectOutcome.IsSuccess() &&
|
|
deleteObjectOutcome.GetError().GetErrorType()==Aws::S3::S3Errors::NO_SUCH_KEY)
|
|
{
|
|
has_missing=true;
|
|
}
|
|
}
|
|
if(idx+1<buckets.size())
|
|
{
|
|
key_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
if (locinfo_next_fun != nullptr)
|
|
locinfo_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
}
|
|
}
|
|
|
|
if(shard_optimized && sharded && has_missing)
|
|
{
|
|
key_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
if (locinfo_next_fun != nullptr)
|
|
locinfo_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
return del_int(key_next_fun, locinfo_next_fun, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Aws::S3::Model::DeleteObjectsRequest deleteObjectsRequest;
|
|
|
|
bool sharded=false;
|
|
bool has_missing=false;
|
|
for(size_t idx=0;idx<buckets.size();++idx)
|
|
{
|
|
if(idx==0 && buckets[idx].name==SHARD_TAG)
|
|
{
|
|
sharded=true;
|
|
continue;
|
|
}
|
|
|
|
deleteObjectsRequest.SetBucket(buckets[idx].name.c_str());
|
|
|
|
Aws::S3::Model::Delete deleteList;
|
|
|
|
std::string key;
|
|
while(key_next_fun(IKvStoreBackend::key_next_action_t::next, &key))
|
|
{
|
|
std::string locinfo;
|
|
if (locinfo_next_fun != nullptr)
|
|
{
|
|
if (locinfo_next_fun(IKvStoreBackend::key_next_action_t::next, &locinfo))
|
|
break;
|
|
}
|
|
|
|
if(shard_optimized && sharded &&
|
|
getShardIdx(key, buckets.size()-1)+1!=idx)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Aws::S3::Model::ObjectIdentifier oi;
|
|
oi.SetKey(key.c_str());
|
|
if (!locinfo.empty())
|
|
{
|
|
oi.SetVersionId(locinfo.c_str());
|
|
}
|
|
deleteList.AddObjects(oi);
|
|
|
|
if (deleteList.GetObjects().size() > max_del_size() )
|
|
{
|
|
deleteObjectsRequest.SetDelete(deleteList);
|
|
auto s3_client = getS3Client(idx);
|
|
Aws::S3::Model::DeleteObjectsOutcome deleteObjectsOutcome = s3_client.second->DeleteObjects(deleteObjectsRequest);
|
|
releaseS3Client(idx, s3_client);
|
|
if (!deleteObjectsOutcome.IsSuccess()
|
|
|| hasErrors(deleteObjectsOutcome, has_missing) )
|
|
{
|
|
if(!deleteObjectsOutcome.IsSuccess())
|
|
{
|
|
fixError(deleteObjectsOutcome.GetError().GetErrorType());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
deleteList.SetObjects(Aws::Vector<Aws::S3::Model::ObjectIdentifier>());
|
|
}
|
|
}
|
|
|
|
if (!deleteList.GetObjects().empty())
|
|
{
|
|
deleteObjectsRequest.SetDelete(deleteList);
|
|
|
|
auto s3_client = getS3Client(idx);
|
|
Aws::S3::Model::DeleteObjectsOutcome deleteObjectsOutcome = s3_client.second->DeleteObjects(deleteObjectsRequest);
|
|
releaseS3Client(idx, s3_client);
|
|
if(!deleteObjectsOutcome.IsSuccess()
|
|
|| hasErrors(deleteObjectsOutcome, has_missing) )
|
|
{
|
|
if(!deleteObjectsOutcome.IsSuccess())
|
|
{
|
|
fixError(deleteObjectsOutcome.GetError().GetErrorType());
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(idx+1<buckets.size())
|
|
{
|
|
key_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
if (locinfo_next_fun != nullptr)
|
|
locinfo_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
}
|
|
}
|
|
|
|
if(shard_optimized && sharded && has_missing)
|
|
{
|
|
Server->Log("Sharded and has missing. Re-running delete on all buckets...", LL_INFO);
|
|
key_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
if (locinfo_next_fun != nullptr)
|
|
locinfo_next_fun(IKvStoreBackend::key_next_action_t::reset, nullptr);
|
|
return del_int(key_next_fun, locinfo_next_fun, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool KvStoreBackendS3::list_wo_versions(IListCallback* callback)
|
|
{
|
|
bool etag_error = false;
|
|
Aws::String key_marker;
|
|
size_t idx = 0;
|
|
auto s3_client = getS3Client(idx);
|
|
while (idx < buckets.size())
|
|
{
|
|
if (buckets[idx].name == SHARD_TAG)
|
|
{
|
|
if (idx + 1 < buckets.size())
|
|
{
|
|
++idx;
|
|
s3_client = getS3Client(idx);
|
|
key_marker.clear();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Aws::S3::Model::ListObjectsRequest listObjectsRequest;
|
|
listObjectsRequest.SetBucket(buckets[idx].name.c_str());
|
|
|
|
if (!key_marker.empty())
|
|
{
|
|
listObjectsRequest.WithMarker(key_marker);
|
|
}
|
|
|
|
Aws::S3::Model::ListObjectsOutcome listObjectsOutcome = s3_client.second->ListObjects(listObjectsRequest);
|
|
|
|
if (!listObjectsOutcome.IsSuccess())
|
|
{
|
|
Server->Log("Listing objects in bucket \"" + buckets[idx].name + "\" failed. " + listObjectsOutcome.GetError().GetMessage().c_str() + " (code: " + convert(static_cast<int64>(listObjectsOutcome.GetError().GetErrorType())) + ")", LL_ERROR);
|
|
fixError(listObjectsOutcome.GetError().GetErrorType());
|
|
releaseS3Client(idx, s3_client);
|
|
return false;
|
|
}
|
|
|
|
for (const Aws::S3::Model::Object& object : listObjectsOutcome.GetResult().GetContents())
|
|
{
|
|
key_marker = object.GetKey();
|
|
|
|
std::string md5sum;
|
|
if (!callback->onlineItem(object.GetKey().c_str(), md5sum, object.GetSize(), object.GetLastModified().Millis()))
|
|
{
|
|
releaseS3Client(idx, s3_client);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!listObjectsOutcome.GetResult().GetIsTruncated())
|
|
{
|
|
releaseS3Client(idx, s3_client);
|
|
|
|
if (idx + 1 < buckets.size())
|
|
{
|
|
++idx;
|
|
s3_client = getS3Client(idx);
|
|
key_marker.clear();
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!listObjectsOutcome.GetResult().GetNextMarker().empty())
|
|
{
|
|
key_marker = listObjectsOutcome.GetResult().GetNextMarker();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::pair<int64, std::shared_ptr<Aws::S3::S3Client> > KvStoreBackendS3::getS3Client(size_t idx, bool useVirtualAdressing)
|
|
{
|
|
if(!s3_endpoint.empty())
|
|
{
|
|
useVirtualAdressing=false;
|
|
}
|
|
|
|
IScopedLock lock(client_mutex);
|
|
|
|
int64 curr_requesttimeout;
|
|
if(n_requests>100)
|
|
{
|
|
curr_requesttimeout = max_request_timems + 60000;
|
|
}
|
|
else
|
|
{
|
|
curr_requesttimeout = 10 * 60000;
|
|
}
|
|
|
|
if(s3_clients[idx].empty())
|
|
{
|
|
lock.relock(nullptr);
|
|
return newS3Client(idx, curr_requesttimeout, useVirtualAdressing);
|
|
}
|
|
|
|
std::pair<int64, std::shared_ptr<Aws::S3::S3Client> > ret = s3_clients[idx].top();
|
|
s3_clients[idx].pop();
|
|
|
|
lock.relock(nullptr);
|
|
|
|
int64 tdiff = ret.first - curr_requesttimeout;
|
|
if(tdiff<0) tdiff*=-1;
|
|
|
|
if(tdiff<10000)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
return newS3Client(idx, curr_requesttimeout, useVirtualAdressing);
|
|
}
|
|
|
|
std::pair<int64, std::shared_ptr<Aws::S3::S3Client> > KvStoreBackendS3::newS3Client(size_t idx, int64 curr_requesttimeout, bool useVirtualAdressing)
|
|
{
|
|
Aws::Client::ClientConfiguration clientConfiguration;
|
|
clientConfiguration.region = BucketLocationToRegion(buckets[idx].location);
|
|
clientConfiguration.followRedirects = Aws::Client::FollowRedirectsPolicy::ALWAYS;
|
|
clientConfiguration.requestTimeoutMs = static_cast<long>(curr_requesttimeout);
|
|
clientConfiguration.connectTimeoutMs = 3000;
|
|
if(!s3_endpoint.empty())
|
|
{
|
|
std::string scheme = getuntil("://", s3_endpoint);
|
|
std::string endpoint;
|
|
if(scheme.empty())
|
|
{
|
|
endpoint = s3_endpoint;
|
|
}
|
|
else
|
|
{
|
|
endpoint = getafter("://", s3_endpoint);
|
|
|
|
clientConfiguration.scheme = Aws::Http::SchemeMapper::FromString(scheme.c_str());
|
|
}
|
|
|
|
if(!endpoint.empty() && endpoint[endpoint.size()-1]=='/')
|
|
{
|
|
endpoint.erase(endpoint.size()-1, 1);
|
|
}
|
|
|
|
clientConfiguration.endpointOverride = endpoint;
|
|
|
|
std::string scaleway_tld = ".scw.cloud";
|
|
std::string ovh_tld = ".cloud.ovh.net";
|
|
if(endpoint.find(scaleway_tld)==endpoint.size()-scaleway_tld.size())
|
|
{
|
|
clientConfiguration.region = getbetween("s3.", scaleway_tld, endpoint);
|
|
}
|
|
else if(endpoint.find(ovh_tld)==endpoint.size()-ovh_tld.size())
|
|
{
|
|
clientConfiguration.region = getbetween("storage.", ovh_tld, endpoint);
|
|
}
|
|
}
|
|
if(!s3_region.empty())
|
|
{
|
|
clientConfiguration.region = s3_region;
|
|
}
|
|
auto new_client = Aws::MakeShared<Aws::S3::S3Client>(ALLOCATION_TAG, credentials_provider, clientConfiguration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, useVirtualAdressing);
|
|
return std::make_pair(curr_requesttimeout, new_client);
|
|
}
|
|
|
|
void KvStoreBackendS3::releaseS3Client(size_t idx, std::pair<int64, std::shared_ptr<Aws::S3::S3Client> > client)
|
|
{
|
|
IScopedLock lock(client_mutex);
|
|
s3_clients[idx].push(client);
|
|
}
|
|
|
|
void KvStoreBackendS3::resetClient()
|
|
{
|
|
IScopedLock lock(client_mutex);
|
|
|
|
for(size_t idx=0;idx<s3_clients.size();++idx)
|
|
{
|
|
while(!s3_clients[idx].empty())
|
|
s3_clients[idx].pop();
|
|
}
|
|
}
|
|
|
|
void KvStoreBackendS3::setFrontend( IOnlineKvStore* online_kv_store , bool do_init)
|
|
{
|
|
this->online_kv_store = online_kv_store;
|
|
}
|
|
|
|
void KvStoreBackendS3::fixError(Aws::S3::S3Errors error)
|
|
{
|
|
if(error==Aws::S3::S3Errors::REQUEST_TIME_TOO_SKEWED)
|
|
{
|
|
Server->Log("Fixing system time...", LL_INFO);
|
|
writestring("", "/tmp/time_sync");
|
|
if(system("/root/update_time_often.sh > /dev/null 2>&1")==0)
|
|
{
|
|
Server->Log("...done. Please retry.", LL_INFO);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("...failed.", LL_INFO);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string KvStoreBackendS3::meminfo()
|
|
{
|
|
IScopedLock lock(client_mutex);
|
|
std::string ret = "##KvStoreBackendS3:\n";
|
|
for(size_t idx=0;idx<s3_clients.size();++idx)
|
|
{
|
|
ret+= " s3_clients["+convert(idx)+"]: "+convert(s3_clients[idx].size())+" * "+PrettyPrintBytes(sizeof(int64) + sizeof(std::shared_ptr<Aws::S3::S3Client>))+"\n";
|
|
}
|
|
return ret;
|
|
}
|