/* * BackupContainerAzureBlobStore.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/BackupContainerAzureBlobStore.h" #if (!defined(TLS_DISABLED) && !defined(_WIN32)) #include "fdbrpc/AsyncFileEncrypted.h" #emdif #include "flow/actorcompiler.h" // This must be the last #include. class BackupContainerAzureBlobStoreImpl { public: using AzureClient = azure::storage_lite::blob_client; class ReadFile final : public IAsyncFile, ReferenceCounted { AsyncTaskThread& asyncTaskThread; std::string containerName; std::string blobName; AzureClient* client; public: ReadFile(AsyncTaskThread& asyncTaskThread, const std::string& containerName, const std::string& blobName, AzureClient* client) : asyncTaskThread(asyncTaskThread), containerName(containerName), blobName(blobName), client(client) {} void addref() override { ReferenceCounted::addref(); } void delref() override { ReferenceCounted::delref(); } Future read(void* data, int length, int64_t offset) { return asyncTaskThread.execAsync([client = this->client, containerName = this->containerName, blobName = this->blobName, data, length, offset] { std::ostringstream oss(std::ios::out | std::ios::binary); client->download_blob_to_stream(containerName, blobName, offset, length, oss); auto str = std::move(oss).str(); memcpy(data, str.c_str(), str.size()); return static_cast(str.size()); }); } Future zeroRange(int64_t offset, int64_t length) override { throw file_not_writable(); } Future write(void const* data, int length, int64_t offset) override { throw file_not_writable(); } Future truncate(int64_t size) override { throw file_not_writable(); } Future sync() override { throw file_not_writable(); } Future size() const override { return asyncTaskThread.execAsync([client = this->client, containerName = this->containerName, blobName = this->blobName] { return static_cast(client->get_blob_properties(containerName, blobName).get().response().size); }); } std::string getFilename() const override { return blobName; } int64_t debugFD() const override { return 0; } }; class WriteFile final : public IAsyncFile, ReferenceCounted { AsyncTaskThread& asyncTaskThread; AzureClient* client; std::string containerName; std::string blobName; int64_t m_cursor{ 0 }; // Ideally this buffer should not be a string, but // the Azure SDK only supports/tests uploading to append // blobs from a stringstream. std::string buffer; static constexpr size_t bufferLimit = 1 << 20; public: WriteFile(AsyncTaskThread& asyncTaskThread, const std::string& containerName, const std::string& blobName, AzureClient* client) : asyncTaskThread(asyncTaskThread), containerName(containerName), blobName(blobName), client(client) {} void addref() override { ReferenceCounted::addref(); } void delref() override { ReferenceCounted::delref(); } Future read(void* data, int length, int64_t offset) override { throw file_not_readable(); } Future write(void const* data, int length, int64_t offset) override { if (offset != m_cursor) { throw non_sequential_op(); } m_cursor += length; auto p = static_cast(data); buffer.append(p, length); if (buffer.size() > bufferLimit) { return sync(); } else { return Void(); } } Future truncate(int64_t size) override { if (size != m_cursor) { throw non_sequential_op(); } return Void(); } Future sync() override { auto movedBuffer = std::move(buffer); buffer.clear(); return asyncTaskThread.execAsync([client = this->client, containerName = this->containerName, blobName = this->blobName, buffer = std::move(movedBuffer)] { std::istringstream iss(std::move(buffer)); auto resp = client->append_block_from_stream(containerName, blobName, iss).get(); return Void(); }); } Future size() const override { return asyncTaskThread.execAsync( [client = this->client, containerName = this->containerName, blobName = this->blobName] { auto resp = client->get_blob_properties(containerName, blobName).get().response(); ASSERT(resp.valid()); // TODO: Should instead throw here return static_cast(resp.size); }); } std::string getFilename() const override { return blobName; } int64_t debugFD() const override { return -1; } }; class BackupFile final : public IBackupFile, ReferenceCounted { Reference m_file; int64_t m_offset; public: BackupFile(const 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() override { ReferenceCounted::addref(); } void delref() override { ReferenceCounted::delref(); } }; static bool isDirectory(const std::string& blobName) { return blobName.size() && blobName.back() == '/'; } ACTOR static Future> readFile(BackupContainerAzureBlobStore* self, std::string fileName) { bool exists = wait(self->blobExists(fileName)); if (!exists) { throw file_not_found(); } Reference f = makeReference(self->asyncTaskThread, self->containerName, fileName, self->client.get()); if (self->usesEncryption()) { f = makeReference(f, false); } return f; } ACTOR static Future> writeFile(BackupContainerAzureBlobStore* self, std::string fileName) { wait(self->asyncTaskThread.execAsync( [client = self->client.get(), containerName = self->containerName, fileName = fileName] { auto outcome = client->create_append_blob(containerName, fileName).get(); return Void(); })); auto f = makeReference(self->asyncTaskThread, self->containerName, fileName, self->client.get()); if (self->usesEncryption()) { f = makeReference(f, true); } return makeReference(fileName, f); } static void listFiles(AzureClient* client, const std::string& containerName, const std::string& path, std::function folderPathFilter, BackupContainerFileSystem::FilesAndSizesT& result) { auto resp = client->list_blobs_segmented(containerName, "/", "", path).get().response(); for (const auto& blob : resp.blobs) { if (isDirectory(blob.name) && folderPathFilter(blob.name)) { listFiles(client, containerName, blob.name, folderPathFilter, result); } else { result.emplace_back(blob.name, blob.content_length); } } } ACTOR static Future deleteContainer(BackupContainerAzureBlobStore* self, int* pNumDeleted) { state int filesToDelete = 0; if (pNumDeleted) { BackupContainerFileSystem::FilesAndSizesT files = wait(self->listFiles()); filesToDelete = files.size(); } wait(self->asyncTaskThread.execAsync([containerName = self->containerName, client = self->client.get()] { client->delete_container(containerName).wait(); return Void(); })); if (pNumDeleted) { *pNumDeleted += filesToDelete; } return Void(); } ACTOR static Future create(BackupContainerAzureBlobStore* self) { state Future f1 = self->asyncTaskThread.execAsync([containerName = self->containerName, client = self->client.get()] { client->create_container(containerName).wait(); return Void(); }); state Future f2 = self->usesEncryption() ? self->encryptionSetupComplete() : Void(); return f1 && f2; } }; Future BackupContainerAzureBlobStore::blobExists(const std::string& fileName) { return asyncTaskThread.execAsync( [client = this->client.get(), containerName = this->containerName, fileName = fileName] { auto resp = client->get_blob_properties(containerName, fileName).get().response(); return resp.valid(); }); } BackupContainerAzureBlobStore::BackupContainerAzureBlobStore(const NetworkAddress& address, const std::string& accountName, const std::string& containerName, const Optional& encryptionKeyFileName) : containerName(containerName) { #if (!defined(TLS_DISABLED) && !defined(_WIN32)) setEncryptionKey(encryptionKeyFileName); #endif std::string accountKey = std::getenv("AZURE_KEY"); auto credential = std::make_shared(accountName, accountKey); auto storageAccount = std::make_shared( accountName, credential, false, format("http://%s/%s", address.toString().c_str(), accountName.c_str())); client = std::make_unique(storageAccount, 1); } void BackupContainerAzureBlobStore::addref() { return ReferenceCounted::addref(); } void BackupContainerAzureBlobStore::delref() { return ReferenceCounted::delref(); } Future BackupContainerAzureBlobStore::create() { return BackupContainerAzureBlobStoreImpl::create(this); } Future BackupContainerAzureBlobStore::exists() { return asyncTaskThread.execAsync([containerName = this->containerName, client = this->client.get()] { auto resp = client->get_container_properties(containerName).get().response(); return resp.valid(); }); } Future> BackupContainerAzureBlobStore::readFile(const std::string& fileName) { return BackupContainerAzureBlobStoreImpl::readFile(this, fileName); } Future> BackupContainerAzureBlobStore::writeFile(const std::string& fileName) { return BackupContainerAzureBlobStoreImpl::writeFile(this, fileName); } Future BackupContainerAzureBlobStore::listFiles( const std::string& path, std::function folderPathFilter) { return asyncTaskThread.execAsync([client = this->client.get(), containerName = this->containerName, path = path, folderPathFilter = folderPathFilter] { FilesAndSizesT result; BackupContainerAzureBlobStoreImpl::listFiles(client, containerName, path, folderPathFilter, result); return result; }); } Future BackupContainerAzureBlobStore::deleteFile(const std::string& fileName) { return asyncTaskThread.execAsync( [containerName = this->containerName, fileName = fileName, client = client.get()]() { client->delete_blob(containerName, fileName).wait(); return Void(); }); } Future BackupContainerAzureBlobStore::deleteContainer(int* pNumDeleted) { return BackupContainerAzureBlobStoreImpl::deleteContainer(this, pNumDeleted); } Future> BackupContainerAzureBlobStore::listURLs(const std::string& baseURL) { // TODO: Implement this return std::vector{}; } std::string BackupContainerAzureBlobStore::getURLFormat() { return "azure://:///"; }