Mercurial > hg > orthanc-object-storage
view Azure/AzureBlobStoragePlugin.cpp @ 210:408c90c9027f default tip
todo: google soft delete
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Wed, 09 Oct 2024 11:48:14 +0200 |
parents | 6dd8bb916573 |
children |
line wrap: on
line source
/** * Cloud storage plugins for Orthanc * Copyright (C) 2020-2023 Osimis S.A., Belgium * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * 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 "AzureBlobStoragePlugin.h" #include <azure/storage/blobs.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> // #include "cpprest/rawptrstream.h" // #include "cpprest/details/basic_types.h" #include <Logging.h> // Create aliases to make the code easier to read. namespace as = Azure::Storage::Blobs; class AzureBlobStoragePlugin : public BaseStorage { public: as::BlobContainerClient blobClient_; bool storageContainsUnknownFiles_; public: AzureBlobStoragePlugin(const std::string& nameForLogs, const as::BlobContainerClient& blobClient, bool enableLegacyStorageStructure, bool storageContainsUnknownFiles ); virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE; virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE; virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) ORTHANC_OVERRIDE; virtual bool HasFileExists() ORTHANC_OVERRIDE {return false;}; }; class Writer : public IStorage::IWriter { std::string path_; as::BlobContainerClient client_; public: Writer(const std::string& path, const as::BlobContainerClient& client) : path_(path), client_(client) { } virtual ~Writer() { } virtual void Write(const char* data, size_t size) ORTHANC_OVERRIDE { try { as::BlockBlobClient blobClient = client_.GetBlockBlobClient(path_); blobClient.UploadFrom(reinterpret_cast<const uint8_t*>(data), size); } catch (std::exception& ex) { throw StoragePluginException("AzureBlobStorage: error writing file " + std::string(path_) + ": " + ex.what()); } } }; class Reader : public IStorage::IReader { std::string path_; as::BlobContainerClient client_; int64_t size_; public: Reader(const std::list<std::string>& paths, const as::BlobContainerClient& client) : client_(client) { std::string firstExceptionMessage; for (auto& path: paths) { try { as::BlockBlobClient blobClient = client_.GetBlockBlobClient(path); auto properties = blobClient.GetProperties().Value; size_ = properties.BlobSize; path_ = path; return; } catch (std::exception& ex) { if (firstExceptionMessage.empty()) { firstExceptionMessage = "AzureBlobStorage: error opening file for reading " + std::string(path) + ": " + ex.what(); } //ignore to retry } } throw StoragePluginException(firstExceptionMessage); } virtual ~Reader() { } virtual size_t GetSize() ORTHANC_OVERRIDE { try { return static_cast<size_t>(size_); } catch (std::exception& ex) { throw StoragePluginException("AzureBlobStorage: error while reading file " + std::string(path_) + ": " + ex.what()); } } virtual void ReadWhole(char* data, size_t size) ORTHANC_OVERRIDE { try { as::BlockBlobClient blobClient = client_.GetBlockBlobClient(path_); blobClient.DownloadTo(reinterpret_cast<uint8_t*>(data), static_cast<int64_t>(size)); } catch (std::exception& ex) { throw StoragePluginException("AzureBlobStorage: error while reading file " + std::string(path_) + ": " + ex.what()); } } virtual void ReadRange(char* data, size_t size, size_t fromOffset) ORTHANC_OVERRIDE { try { as::BlockBlobClient blobClient = client_.GetBlockBlobClient(path_); as::DownloadBlobToOptions options; options.Range = Azure::Core::Http::HttpRange(); options.Range.Value().Length = static_cast<int64_t>(size); options.Range.Value().Offset = static_cast<int64_t>(fromOffset); blobClient.DownloadTo(reinterpret_cast<uint8_t*>(data), static_cast<int64_t>(size), options); } catch (std::exception& ex) { throw StoragePluginException("AzureBlobStorage: error while reading partial file " + std::string(path_) + ": " + ex.what()); } } }; const char* AzureBlobStoragePluginFactory::GetStoragePluginName() { return "Azure Blob Storage"; } const char* AzureBlobStoragePluginFactory::GetStorageDescription() { return "Stores the Orthanc storage area in Azure Blob"; } // bool IsSasTokenAccountLevel(utility::string_t sasToken) // { // // Use cpprestsdk's utility::string_t here since the expected argument is the return value of // // as::storage_credentials::sas_token(), which is type utility::string_t (which is a std::wstring on Windows and a std::string on Linux) // size_t newIdx = 0; // size_t prevIdx = 0; // while ((newIdx = sasToken.find('&', prevIdx)) != utility::string_t::npos) // { // utility::string_t kvpair = sasToken.substr(prevIdx, newIdx - prevIdx); // prevIdx = newIdx + 1; // start next time from char after '&' // size_t equalsIdx = kvpair.find('='); // utility::string_t key = kvpair.substr(0, equalsIdx); // #ifdef WIN32 // const wchar_t* srt = L"srt"; // #else // const char* srt = "srt"; // #endif // if (key == srt) // only account SAS has this parameter // return true; // } // return false; // } IStorage* AzureBlobStoragePluginFactory::CreateStorage(const std::string& nameForLogs, const OrthancPlugins::OrthancConfiguration& orthancConfig) { std::string connectionString; std::string containerName; bool enableLegacyStorageStructure; bool storageContainsUnknownFiles; bool createContainerIfNotExists; if (orthancConfig.IsSection(GetConfigurationSectionName())) { OrthancPlugins::OrthancConfiguration pluginSection; orthancConfig.GetSection(pluginSection, GetConfigurationSectionName()); if (!BaseStorage::ReadCommonConfiguration(enableLegacyStorageStructure, storageContainsUnknownFiles, pluginSection)) { return nullptr; } if (!pluginSection.LookupStringValue(connectionString, "ConnectionString")) { LOG(ERROR) << "AzureBlobStorage/ConnectionString configuration missing. Unable to initialize plugin"; return nullptr; } if (!pluginSection.LookupStringValue(containerName, "ContainerName")) { LOG(ERROR) << "AzureBlobStorage/ContainerName configuration missing. Unable to initialize plugin"; return nullptr; } boost::trim(connectionString); // without that, if there's an EOL in the string, it fails with "provided uri is invalid" boost::trim(containerName); createContainerIfNotExists = pluginSection.GetBooleanValue("CreateContainerIfNotExists", true); } else if (orthancConfig.IsSection("BlobStorage")) // backward compatibility with the old plugin: { LOG(WARNING) << "AzureBlobStorage: you're using an old configuration format for the plugin."; OrthancPlugins::OrthancConfiguration pluginSection; orthancConfig.GetSection(pluginSection, "BlobStorage"); std::string accountName; std::string accountKey; if (!pluginSection.LookupStringValue(containerName, "ContainerName")) { LOG(ERROR) << "BlobStorage/AccountName configuration missing. Unable to initialize plugin"; return nullptr; } if (!pluginSection.LookupStringValue(accountName, "AccountName")) { LOG(ERROR) << "BlobStorage/AccountName configuration missing. Unable to initialize plugin"; return nullptr; } if (!pluginSection.LookupStringValue(accountKey, "AccountKey")) { LOG(ERROR) << "BlobStorage/ContainerName configuration missing. Unable to initialize plugin"; return nullptr; } std::ostringstream connectionStringBuilder; connectionStringBuilder << "DefaultEndpointsProtocol=https;AccountName=" << accountName << ";AccountKey=" << accountKey; connectionString = connectionStringBuilder.str(); createContainerIfNotExists = pluginSection.GetBooleanValue("CreateContainerIfNotExists", true); } else { LOG(WARNING) << GetStoragePluginName() << " plugin, section missing. Plugin is not enabled."; return nullptr; } try { LOG(INFO) << "Connecting to Azure storage ..."; as::BlobContainerClient client = as::BlobContainerClient::CreateFromConnectionString(connectionString, containerName); LOG(INFO) << "Blob client created"; if (createContainerIfNotExists) { // Note: in version up to 2.1.2, we had this code: // // blobContainer.create_if_not_exists() throws an error if a service SAS (for a blob container) // // was used in the connectionString. // // Only allow the use of this function when using storage account-level credentials, whether // // through accountName/accountKey combo or account SAS. // if ((storageAccount.credentials().is_account_key() || // (storageAccount.credentials().is_sas() && IsSasTokenAccountLevel(storageAccount.credentials().sas_token()))) // && createContainerIfNotExists) auto createResult = client.CreateIfNotExists(); if (createResult.Value.Created) { LOG(WARNING) << "Blob Storage Area container has been created. **** check in the Azure console that your container is private ****"; } } LOG(INFO) << "Blob storage initialized"; return new AzureBlobStoragePlugin(nameForLogs, client, enableLegacyStorageStructure, storageContainsUnknownFiles); } catch (const std::exception& e) { LOG(ERROR) << "AzureBlobStorage plugin: failed to initialize plugin: " << e.what(); return nullptr; } } AzureBlobStoragePlugin::AzureBlobStoragePlugin(const std::string& nameForLogs, const as::BlobContainerClient& blobClient, bool enableLegacyStorageStructure, bool storageContainsUnknownFiles) : BaseStorage(nameForLogs, enableLegacyStorageStructure), blobClient_(blobClient), storageContainsUnknownFiles_(storageContainsUnknownFiles) { } IStorage::IWriter* AzureBlobStoragePlugin::GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) { return new Writer(GetPath(uuid, type, encryptionEnabled), blobClient_); } IStorage::IReader* AzureBlobStoragePlugin::GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) { std::list<std::string> paths; paths.push_back(GetPath(uuid, type, encryptionEnabled, false)); if (storageContainsUnknownFiles_) { paths.push_back(GetPath(uuid, type, encryptionEnabled, true)); } return new Reader(paths, blobClient_); } void AzureBlobStoragePlugin::DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled) { std::string path = GetPath(uuid, type, encryptionEnabled); try { as::BlockBlobClient blobClient = blobClient_.GetBlockBlobClient(path); blobClient.Delete(); } catch (std::exception& ex) { throw StoragePluginException("AzureBlobStorage: error while deleting file " + std::string(path) + ": " + ex.what()); } }