1
|
1 /**
|
|
2 * Cloud storage plugins for Orthanc
|
|
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
|
|
4 *
|
|
5 * This program is free software: you can redistribute it and/or
|
|
6 * modify it under the terms of the GNU Affero General Public License
|
|
7 * as published by the Free Software Foundation, either version 3 of
|
|
8 * the License, or (at your option) any later version.
|
|
9 *
|
|
10 * This program is distributed in the hope that it will be useful, but
|
|
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
13 * Affero General Public License for more details.
|
|
14 *
|
|
15 * You should have received a copy of the GNU Affero General Public License
|
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17 **/
|
|
18
|
|
19 #include "AzureBlobStoragePlugin.h"
|
|
20
|
|
21 #include <was/storage_account.h>
|
|
22 #include <was/blob.h>
|
|
23 #include <boost/lexical_cast.hpp>
|
|
24 #include "cpprest/rawptrstream.h"
|
|
25
|
|
26
|
|
27 // Create aliases to make the code easier to read.
|
|
28 namespace as = azure::storage;
|
|
29
|
|
30
|
|
31 class AzureBlobStoragePlugin : public IStoragePlugin
|
|
32 {
|
|
33 public:
|
|
34
|
|
35 as::cloud_blob_client blobClient_;
|
|
36 as::cloud_blob_container blobContainer_;
|
|
37 public:
|
|
38
|
|
39 // AzureBlobStoragePlugin(const std::string& connectionString,
|
|
40 // const std::string& containerName
|
|
41 // );
|
|
42 AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer);
|
|
43
|
|
44 virtual IWriter* GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
45 virtual IReader* GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
46 virtual void DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
47 private:
|
|
48 virtual std::string GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled);
|
|
49 };
|
|
50
|
|
51
|
|
52 class Writer : public IStoragePlugin::IWriter
|
|
53 {
|
|
54 std::string path_;
|
|
55 as::cloud_blob_client client_;
|
|
56 as::cloud_blob_container container_;
|
|
57
|
|
58 public:
|
|
59 Writer(const as::cloud_blob_container& container, const std::string& path, const as::cloud_blob_client& client)
|
|
60 : path_(path),
|
|
61 client_(client),
|
|
62 container_(container)
|
|
63 {
|
|
64 }
|
|
65
|
|
66 virtual ~Writer()
|
|
67 {
|
|
68 }
|
|
69
|
|
70 virtual void Write(const char* data, size_t size)
|
|
71 {
|
|
72 try
|
|
73 {
|
|
74 concurrency::streams::istream inputStream = concurrency::streams::rawptr_stream<uint8_t>::open_istream(reinterpret_cast<const uint8_t*>(data), size);
|
|
75 azure::storage::cloud_block_blob blob = container_.get_block_blob_reference(path_);
|
|
76 blob.upload_from_stream(inputStream);
|
|
77 inputStream.close().wait();
|
|
78 }
|
|
79 catch (std::exception& ex)
|
|
80 {
|
|
81 throw StoragePluginException("AzureBlobStorage: error writing file " + std::string(path_) + ": " + ex.what());
|
|
82 }
|
|
83 }
|
|
84 };
|
|
85
|
|
86
|
|
87 class Reader : public IStoragePlugin::IReader
|
|
88 {
|
|
89 std::string path_;
|
|
90 as::cloud_blob_client client_;
|
|
91 as::cloud_blob_container container_;
|
|
92 as::cloud_block_blob block_;
|
|
93
|
|
94 public:
|
|
95 Reader(const as::cloud_blob_container& container, const std::string& path, const as::cloud_blob_client& client)
|
|
96 : path_(path),
|
|
97 client_(client),
|
|
98 container_(container)
|
|
99 {
|
|
100 try
|
|
101 {
|
|
102 block_ = container_.get_block_blob_reference(path_);
|
|
103 block_.download_attributes(); // to retrieve the properties
|
|
104 }
|
|
105 catch (std::exception& ex)
|
|
106 {
|
|
107 throw StoragePluginException("AzureBlobStorage: error opening file for reading " + std::string(path_) + ": " + ex.what());
|
|
108 }
|
|
109 }
|
|
110
|
|
111 virtual ~Reader()
|
|
112 {
|
|
113
|
|
114 }
|
|
115 virtual size_t GetSize()
|
|
116 {
|
|
117 try
|
|
118 {
|
|
119 return block_.properties().size();
|
|
120 }
|
|
121 catch (std::exception& ex)
|
|
122 {
|
|
123 throw StoragePluginException("AzureBlobStorage: error while reading file " + std::string(path_) + ": " + ex.what());
|
|
124 }
|
|
125 }
|
|
126
|
|
127 virtual void Read(char* data, size_t size)
|
|
128 {
|
|
129 try
|
|
130 {
|
|
131 concurrency::streams::ostream outputStream = concurrency::streams::rawptr_stream<uint8_t>::open_ostream(reinterpret_cast<uint8_t*>(data), size);
|
|
132
|
|
133 block_.download_to_stream(outputStream);
|
|
134 }
|
|
135 catch (std::exception& ex)
|
|
136 {
|
|
137 throw StoragePluginException("AzureBlobStorage: error while reading file " + std::string(path_) + ": " + ex.what());
|
|
138 }
|
|
139 }
|
|
140
|
|
141 };
|
|
142
|
|
143
|
|
144
|
|
145 const char* AzureBlobStoragePluginFactory::GetStoragePluginName()
|
|
146 {
|
|
147 return "Azure Blob Storage";
|
|
148 }
|
|
149
|
|
150 IStoragePlugin* AzureBlobStoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig)
|
|
151 {
|
|
152 std::string connectionString;
|
|
153 std::string containerName;
|
|
154
|
|
155 static const char* const PLUGIN_SECTION = "AzureBlobStorage";
|
|
156 if (orthancConfig.IsSection(PLUGIN_SECTION))
|
|
157 {
|
|
158 OrthancPlugins::OrthancConfiguration pluginSection;
|
|
159 orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
|
|
160
|
|
161 if (!pluginSection.LookupStringValue(connectionString, "ConnectionString"))
|
|
162 {
|
|
163 OrthancPlugins::LogError("AzureBlobStorage/ConnectionString configuration missing. Unable to initialize plugin");
|
|
164 return nullptr;
|
|
165 }
|
|
166
|
|
167 if (!pluginSection.LookupStringValue(containerName, "ContainerName"))
|
|
168 {
|
|
169 OrthancPlugins::LogError("AzureBlobStorage/ContainerName configuration missing. Unable to initialize plugin");
|
|
170 return nullptr;
|
|
171 }
|
|
172
|
|
173 }
|
|
174 else if (orthancConfig.IsSection("BlobStorage")) // backward compatibility with the old plugin:
|
|
175 {
|
|
176 OrthancPlugins::LogWarning("AzureBlobStorage: you're using an old configuration format for the plugin.");
|
|
177
|
|
178 OrthancPlugins::OrthancConfiguration pluginSection;
|
|
179 orthancConfig.GetSection(pluginSection, "BlobStorage");
|
|
180
|
|
181 std::string accountName;
|
|
182 std::string accountKey;
|
|
183
|
|
184 if (!pluginSection.LookupStringValue(containerName, "ContainerName"))
|
|
185 {
|
|
186 OrthancPlugins::LogError("BlobStorage/AccountName configuration missing. Unable to initialize plugin");
|
|
187 return nullptr;
|
|
188 }
|
|
189
|
|
190 if (!pluginSection.LookupStringValue(accountName, "AccountName"))
|
|
191 {
|
|
192 OrthancPlugins::LogError("BlobStorage/AccountName configuration missing. Unable to initialize plugin");
|
|
193 return nullptr;
|
|
194 }
|
|
195
|
|
196 if (!pluginSection.LookupStringValue(accountKey, "AccountKey"))
|
|
197 {
|
|
198 OrthancPlugins::LogError("BlobStorage/ContainerName configuration missing. Unable to initialize plugin");
|
|
199 return nullptr;
|
|
200 }
|
|
201
|
|
202 std::ostringstream connectionStringBuilder;
|
|
203 connectionStringBuilder << "DefaultEndpointsProtocol=https;AccountName=" << accountName << ";AccountKey=" << accountKey;
|
|
204 connectionString = connectionStringBuilder.str();
|
|
205 }
|
|
206 else
|
|
207 {
|
|
208 OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing. Plugin is not enabled.");
|
|
209 return nullptr;
|
|
210 }
|
|
211
|
|
212 try
|
|
213 {
|
|
214 as::cloud_storage_account storageAccount = as::cloud_storage_account::parse(connectionString);
|
|
215
|
|
216 as::cloud_blob_client blobClient = storageAccount.create_cloud_blob_client();
|
|
217 as::cloud_blob_container blobContainer = blobClient.get_container_reference(containerName);
|
|
218
|
|
219 // Return value is true if the container did not exist and was successfully created.
|
|
220 bool containerCreated = blobContainer.create_if_not_exists();
|
|
221
|
|
222 if (containerCreated)
|
|
223 {
|
|
224 OrthancPlugins::LogWarning("Blob Storage Area container has been created. **** check in the Azure console that your container is private ****");
|
|
225 }
|
|
226
|
|
227 OrthancPlugins::LogInfo("Blob storage initialized");
|
|
228
|
|
229 return new AzureBlobStoragePlugin(blobClient, blobContainer);
|
|
230 }
|
|
231 catch (const std::exception& e)
|
|
232 {
|
|
233 OrthancPlugins::LogError(std::string("AzureBlobStorage plugin: failed to initialize plugin: ") + e.what());
|
|
234 return nullptr;
|
|
235 }
|
|
236
|
|
237 }
|
|
238
|
|
239 AzureBlobStoragePlugin::AzureBlobStoragePlugin(const as::cloud_blob_client& blobClient, const as::cloud_blob_container& blobContainer) //const std::string &containerName) //, google::cloud::storage::Client& mainClient)
|
|
240 : blobClient_(blobClient),
|
|
241 blobContainer_(blobContainer)
|
|
242 {
|
|
243
|
|
244 }
|
|
245
|
|
246 IStoragePlugin::IWriter* AzureBlobStoragePlugin::GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
247 {
|
|
248 return new Writer(blobContainer_, GetPath(uuid, type, encryptionEnabled), blobClient_);
|
|
249 }
|
|
250
|
|
251 IStoragePlugin::IReader* AzureBlobStoragePlugin::GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
252 {
|
|
253 return new Reader(blobContainer_, GetPath(uuid, type, encryptionEnabled), blobClient_);
|
|
254 }
|
|
255
|
|
256 void AzureBlobStoragePlugin::DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
257 {
|
|
258 std::string path = GetPath(uuid, type, encryptionEnabled);
|
|
259
|
|
260 try
|
|
261 {
|
|
262 as::cloud_block_blob blockBlob = blobContainer_.get_block_blob_reference(path);
|
|
263
|
|
264 blockBlob.delete_blob();
|
|
265 }
|
|
266 catch (std::exception& ex)
|
|
267 {
|
|
268 throw StoragePluginException("AzureBlobStorage: error while deleting file " + std::string(path) + ": " + ex.what());
|
|
269 }
|
|
270 }
|
|
271
|
|
272 std::string AzureBlobStoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
273 {
|
|
274 std::string path = std::string(uuid);
|
|
275
|
|
276 if (type == OrthancPluginContentType_Dicom)
|
|
277 {
|
|
278 path += ".dcm";
|
|
279 }
|
|
280 else if (type == OrthancPluginContentType_DicomAsJson)
|
|
281 {
|
|
282 path += ".json";
|
|
283 }
|
|
284 else
|
|
285 {
|
|
286 path += ".unk";
|
|
287 }
|
|
288
|
|
289 if (encryptionEnabled)
|
|
290 {
|
|
291 path += ".enc";
|
|
292 }
|
|
293 return path;
|
|
294 }
|