/* * BackupContainerS3BlobStore.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * 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. */ #include "fdbclient/AsyncFileS3BlobStore.actor.h" #include "fdbclient/BackupContainerS3BlobStore.h" #include "fdbrpc/AsyncFileEncrypted.h" #include "fdbrpc/AsyncFileReadAhead.actor.h" #include "flow/actorcompiler.h" // This must be the last #include. class BackupContainerS3BlobStoreImpl { public: // Backup files to under a single folder prefix with subfolders for each named backup static const std::string DATAFOLDER; // Indexfolder contains keys for which user-named backups exist. Backup names can contain an arbitrary // number of slashes so the backup names are kept in a separate folder tree from their actual data. static const std::string INDEXFOLDER; ACTOR static Future> listURLs(Reference bstore, std::string bucket) { state std::string basePath = INDEXFOLDER + '/'; S3BlobStoreEndpoint::ListResult contents = wait(bstore->listObjects(bucket, basePath)); std::vector results; for (const auto& f : contents.objects) { results.push_back( bstore->getResourceURL(f.name.substr(basePath.size()), format("bucket=%s", bucket.c_str()))); } return results; } class BackupFile : public IBackupFile, ReferenceCounted { public: BackupFile(std::string fileName, Reference file) : IBackupFile(fileName), m_file(file), m_offset(0) {} Future append(const void* data, int len) override { Future r = m_file->write(data, len, m_offset); m_offset += len; return r; } Future finish() override { Reference self = Reference::addRef(this); return map(m_file->sync(), [=](Void _) { self->m_file.clear(); return Void(); }); } int64_t size() const override { return m_offset; } void addref() final { return ReferenceCounted::addref(); } void delref() final { return ReferenceCounted::delref(); } private: Reference m_file; int64_t m_offset; }; ACTOR static Future listFiles( Reference bc, std::string path, std::function pathFilter) { // pathFilter expects container based paths, so create a wrapper which converts a raw path // to a container path by removing the known backup name prefix. state int prefixTrim = bc->dataPath("").size(); std::function rawPathFilter = [=](const std::string& folderPath) { ASSERT(folderPath.size() >= prefixTrim); return pathFilter(folderPath.substr(prefixTrim)); }; state S3BlobStoreEndpoint::ListResult result = wait(bc->m_bstore->listObjects( bc->m_bucket, bc->dataPath(path), '/', std::numeric_limits::max(), rawPathFilter)); BackupContainerFileSystem::FilesAndSizesT files; for (const auto& o : result.objects) { ASSERT(o.name.size() >= prefixTrim); files.push_back({ o.name.substr(prefixTrim), o.size }); } return files; } ACTOR static Future create(Reference bc) { wait(bc->m_bstore->createBucket(bc->m_bucket)); // Check/create the index entry bool exists = wait(bc->m_bstore->objectExists(bc->m_bucket, bc->indexEntry())); if (!exists) { wait(bc->m_bstore->writeEntireFile(bc->m_bucket, bc->indexEntry(), "")); } if (bc->usesEncryption()) { wait(bc->encryptionSetupComplete()); } return Void(); } ACTOR static Future deleteContainer(Reference bc, int* pNumDeleted) { bool e = wait(bc->exists()); if (!e) { TraceEvent(SevWarnAlways, "BackupContainerDoesNotExist").detail("URL", bc->getURL()); throw backup_does_not_exist(); } // First delete everything under the data prefix in the bucket wait(bc->m_bstore->deleteRecursively(bc->m_bucket, bc->dataPath(""), pNumDeleted)); // Now that all files are deleted, delete the index entry wait(bc->m_bstore->deleteObject(bc->m_bucket, bc->indexEntry())); return Void(); } }; const std::string BackupContainerS3BlobStoreImpl::DATAFOLDER = "data"; const std::string BackupContainerS3BlobStoreImpl::INDEXFOLDER = "backups"; std::string BackupContainerS3BlobStore::dataPath(const std::string& path) { return BackupContainerS3BlobStoreImpl::DATAFOLDER + "/" + m_name + "/" + path; } // Get the path of the backups's index entry std::string BackupContainerS3BlobStore::indexEntry() { return BackupContainerS3BlobStoreImpl::INDEXFOLDER + "/" + m_name; } BackupContainerS3BlobStore::BackupContainerS3BlobStore(Reference bstore, const std::string& name, const S3BlobStoreEndpoint::ParametersT& params, const Optional& encryptionKeyFileName) : m_bstore(bstore), m_name(name), m_bucket("FDB_BACKUPS_V2") { setEncryptionKey(encryptionKeyFileName); // Currently only one parameter is supported, "bucket" for (const auto& [name, value] : params) { if (name == "bucket") { m_bucket = value; continue; } TraceEvent(SevWarn, "BackupContainerS3BlobStoreInvalidParameter").detail("Name", name).detail("Value", value); IBackupContainer::lastOpenError = format("Unknown URL parameter: '%s'", name.c_str()); throw backup_invalid_url(); } } void BackupContainerS3BlobStore::addref() { return ReferenceCounted::addref(); } void BackupContainerS3BlobStore::delref() { return ReferenceCounted::delref(); } std::string BackupContainerS3BlobStore::getURLFormat() { return S3BlobStoreEndpoint::getURLFormat(true) + " (Note: The 'bucket' parameter is required.)"; } Future> BackupContainerS3BlobStore::readFile(const std::string& path) { Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); if (usesEncryption()) { f = makeReference(f, false); } f = makeReference(f, m_bstore->knobs.read_block_size, m_bstore->knobs.read_ahead_blocks, m_bstore->knobs.concurrent_reads_per_file, m_bstore->knobs.read_cache_blocks_per_file); return f; } Future> BackupContainerS3BlobStore::listURLs(Reference bstore, const std::string& bucket) { return BackupContainerS3BlobStoreImpl::listURLs(bstore, bucket); } Future> BackupContainerS3BlobStore::writeFile(const std::string& path) { Reference f = makeReference(m_bstore, m_bucket, dataPath(path)); if (usesEncryption()) { f = makeReference(f, true); } return Future>(makeReference(path, f)); } Future BackupContainerS3BlobStore::deleteFile(const std::string& path) { return m_bstore->deleteObject(m_bucket, dataPath(path)); } Future BackupContainerS3BlobStore::listFiles( const std::string& path, std::function pathFilter) { return BackupContainerS3BlobStoreImpl::listFiles( Reference::addRef(this), path, pathFilter); } Future BackupContainerS3BlobStore::create() { return BackupContainerS3BlobStoreImpl::create(Reference::addRef(this)); } Future BackupContainerS3BlobStore::exists() { return m_bstore->objectExists(m_bucket, indexEntry()); } Future BackupContainerS3BlobStore::deleteContainer(int* pNumDeleted) { return BackupContainerS3BlobStoreImpl::deleteContainer(Reference::addRef(this), pNumDeleted); } std::string BackupContainerS3BlobStore::getBucket() const { return m_bucket; }