azurefile.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "platform.h"
  14. #include "jlib.hpp"
  15. #include "jio.hpp"
  16. #include "jmutex.hpp"
  17. #include "jfile.hpp"
  18. #include "jregexp.hpp"
  19. #include "jstring.hpp"
  20. #include "jsecrets.hpp"
  21. #include "jlog.hpp"
  22. #include "azurefile.hpp"
  23. #include "storage_credential.h"
  24. #include "storage_account.h"
  25. #include "blob/blob_client.h"
  26. #include "blob/create_block_blob_request.h"
  27. using namespace azure::storage_lite;
  28. //#undef some macros that clash with macros or function defined within the azure library
  29. #undef GetCurrentThreadId
  30. #undef __declspec
  31. #define TRACE_AZURE
  32. //#define TEST_AZURE_PAGING
  33. /*
  34. * Azure related comments
  35. *
  36. * Does it make more sense to create input and output streams directly from the IFile rather than going via
  37. * an IFileIO. E.g., append blobs
  38. */
  39. constexpr const char * azureFilePrefix = "azure:";
  40. #ifdef TEST_AZURE_PAGING
  41. constexpr offset_t azureReadRequestSize = 50;
  42. #else
  43. constexpr offset_t azureReadRequestSize = 0x400000; // Default to requesting 4Mb each time
  44. #endif
  45. //---------------------------------------------------------------------------------------------------------------------
  46. typedef std::stringstream BlobPageContents;
  47. class AzureFile;
  48. class AzureFileReadIO : implements CInterfaceOf<IFileIO>
  49. {
  50. public:
  51. AzureFileReadIO(AzureFile * _file, const FileIOStats & _stats);
  52. virtual size32_t read(offset_t pos, size32_t len, void * data) override;
  53. virtual offset_t size() override;
  54. virtual void close() override
  55. {
  56. }
  57. // Write methods not implemented - this is a read-only file
  58. virtual size32_t write(offset_t pos, size32_t len, const void * data) override
  59. {
  60. throwUnexpectedX("Writing to read only file");
  61. }
  62. virtual offset_t appendFile(IFile *file,offset_t pos=0,offset_t len=(offset_t)-1) override
  63. {
  64. throwUnexpectedX("Appending to read only file");
  65. }
  66. virtual void setSize(offset_t size) override
  67. {
  68. throwUnexpectedX("Setting size of read only azure file");
  69. }
  70. virtual void flush() override
  71. {
  72. }
  73. unsigned __int64 getStatistic(StatisticKind kind) override;
  74. protected:
  75. size_t extractDataFromResult(size_t offset, size_t length, void * target);
  76. protected:
  77. Linked<AzureFile> file;
  78. CriticalSection cs;
  79. offset_t startResultOffset = 0;
  80. offset_t endResultOffset = 0;
  81. BlobPageContents contents;
  82. FileIOStats stats;
  83. };
  84. class AzureFileWriteIO : implements CInterfaceOf<IFileIO>
  85. {
  86. public:
  87. AzureFileWriteIO(AzureFile * _file);
  88. virtual void beforeDispose() override;
  89. virtual size32_t read(offset_t pos, size32_t len, void * data) override
  90. {
  91. throwUnexpected();
  92. }
  93. virtual offset_t size() override;
  94. virtual void setSize(offset_t size) override;
  95. virtual void flush() override;
  96. virtual unsigned __int64 getStatistic(StatisticKind kind) override;
  97. protected:
  98. Linked<AzureFile> file;
  99. CriticalSection cs;
  100. FileIOStats stats;
  101. offset_t offset = 0;
  102. };
  103. class AzureFileAppendBlobWriteIO final : implements AzureFileWriteIO
  104. {
  105. public:
  106. AzureFileAppendBlobWriteIO(AzureFile * _file);
  107. virtual void close() override;
  108. virtual offset_t appendFile(IFile *file,offset_t pos=0,offset_t len=(offset_t)-1) override;
  109. virtual offset_t size() override;
  110. virtual size32_t write(offset_t pos, size32_t len, const void * data) override;
  111. };
  112. class AzureFileBlockBlobWriteIO final : implements AzureFileWriteIO
  113. {
  114. public:
  115. AzureFileBlockBlobWriteIO(AzureFile * _file);
  116. virtual void close() override;
  117. virtual offset_t appendFile(IFile *file,offset_t pos=0,offset_t len=(offset_t)-1) override;
  118. virtual size32_t write(offset_t pos, size32_t len, const void * data) override;
  119. };
  120. class AzureFile : implements CInterfaceOf<IFile>
  121. {
  122. friend class AzureFileReadIO;
  123. friend class AzureFileAppendBlobWriteIO;
  124. friend class AzureFileBlockBlobWriteIO;
  125. public:
  126. AzureFile(const char *_azureFileName);
  127. virtual bool exists() override
  128. {
  129. ensureMetaData();
  130. return fileExists;
  131. }
  132. virtual bool getTime(CDateTime * createTime, CDateTime * modifiedTime, CDateTime * accessedTime) override;
  133. virtual fileBool isDirectory() override
  134. {
  135. ensureMetaData();
  136. if (!fileExists)
  137. return fileBool::notFound;
  138. return isDir ? fileBool::foundYes : fileBool::foundNo;
  139. }
  140. virtual fileBool isFile() override
  141. {
  142. ensureMetaData();
  143. if (!fileExists)
  144. return fileBool::notFound;
  145. return !isDir ? fileBool::foundYes : fileBool::foundNo;
  146. }
  147. virtual fileBool isReadOnly() override
  148. {
  149. ensureMetaData();
  150. if (!fileExists)
  151. return fileBool::notFound;
  152. return fileBool::foundYes;
  153. }
  154. virtual IFileIO * open(IFOmode mode, IFEflags extraFlags=IFEnone) override
  155. {
  156. //Should this be derived from a comman base that implements the setShareMode()?
  157. return openShared(mode,IFSHread,extraFlags);
  158. }
  159. virtual IFileAsyncIO * openAsync(IFOmode mode)
  160. {
  161. UNIMPLEMENTED;
  162. }
  163. virtual IFileIO * openShared(IFOmode mode, IFSHmode shmode, IFEflags extraFlags=IFEnone) override
  164. {
  165. if (mode == IFOcreate)
  166. return createFileWriteIO();
  167. assertex(mode==IFOread);
  168. return createFileReadIO();
  169. }
  170. virtual const char * queryFilename() override
  171. {
  172. return fullName.str();
  173. }
  174. virtual offset_t size() override
  175. {
  176. ensureMetaData();
  177. return fileSize;
  178. }
  179. // Directory functions
  180. virtual IDirectoryIterator *directoryFiles(const char *mask, bool sub, bool includeDirs) override
  181. {
  182. UNIMPLEMENTED_X("AzureFile::directoryFiles");
  183. }
  184. virtual bool getInfo(bool &isdir,offset_t &size,CDateTime &modtime) override
  185. {
  186. ensureMetaData();
  187. isdir = isDir;
  188. size = fileSize;
  189. modtime.clear();
  190. return true;
  191. }
  192. // Not going to be implemented - this IFile interface is too big..
  193. virtual bool setTime(const CDateTime * createTime, const CDateTime * modifiedTime, const CDateTime * accessedTime) override
  194. {
  195. DBGLOG("AzureFile::setTime ignored");
  196. return false;
  197. }
  198. virtual bool remove() override;
  199. virtual void rename(const char *newTail) override { UNIMPLEMENTED_X("AzureFile::rename"); }
  200. virtual void move(const char *newName) override { UNIMPLEMENTED_X("AzureFile::move"); }
  201. virtual void setReadOnly(bool ro) override { UNIMPLEMENTED_X("AzureFile::setReadOnly"); }
  202. virtual void setFilePermissions(unsigned fPerms) override
  203. {
  204. DBGLOG("AzureFile::setFilePermissions() ignored");
  205. }
  206. virtual bool setCompression(bool set) override { UNIMPLEMENTED_X("AzureFile::setCompression"); }
  207. virtual offset_t compressedSize() override { UNIMPLEMENTED_X("AzureFile::compressedSize"); }
  208. virtual unsigned getCRC() override { UNIMPLEMENTED_X("AzureFile::getCRC"); }
  209. virtual void setCreateFlags(unsigned short cflags) override { UNIMPLEMENTED_X("AzureFile::setCreateFlags"); }
  210. virtual void setShareMode(IFSHmode shmode) override { UNIMPLEMENTED_X("AzureFile::setSharedMode"); }
  211. virtual bool createDirectory() override;
  212. virtual IDirectoryDifferenceIterator *monitorDirectory(
  213. IDirectoryIterator *prev=NULL, // in (NULL means use current as baseline)
  214. const char *mask=NULL,
  215. bool sub=false,
  216. bool includedirs=false,
  217. unsigned checkinterval=60*1000,
  218. unsigned timeout=(unsigned)-1,
  219. Semaphore *abortsem=NULL) override { UNIMPLEMENTED_X("AzureFile::monitorDirectory"); }
  220. virtual void copySection(const RemoteFilename &dest, offset_t toOfs=(offset_t)-1, offset_t fromOfs=0, offset_t size=(offset_t)-1, ICopyFileProgress *progress=NULL, CFflags copyFlags=CFnone) override { UNIMPLEMENTED_X("AzureFile::copySection"); }
  221. virtual void copyTo(IFile *dest, size32_t buffersize=DEFAULT_COPY_BLKSIZE, ICopyFileProgress *progress=NULL, bool usetmp=false, CFflags copyFlags=CFnone) override { UNIMPLEMENTED_X("AzureFile::copyTo"); }
  222. virtual IMemoryMappedFile *openMemoryMapped(offset_t ofs=0, memsize_t len=(memsize_t)-1, bool write=false) override { UNIMPLEMENTED_X("AzureFile::openMemoryMapped"); }
  223. protected:
  224. void createAppendBlob();
  225. void appendToAppendBlob(size32_t len, const void * data);
  226. void createBlockBlob();
  227. void appendToBlockBlob(size32_t len, const void * data);
  228. void commitBlockBlob();
  229. offset_t readBlock(BlobPageContents & contents, FileIOStats & stats, offset_t from = 0, offset_t length = unknownFileSize);
  230. void ensureMetaData();
  231. void gatherMetaData();
  232. IFileIO * createFileReadIO();
  233. IFileIO * createFileWriteIO();
  234. std::shared_ptr<azure::storage_lite::blob_client> getClient();
  235. protected:
  236. StringBuffer fullName;
  237. StringAttr accountName;
  238. StringAttr accountKey;
  239. std::string containerName;
  240. std::string blobName;
  241. offset_t fileSize = unknownFileSize;
  242. bool haveMeta = false;
  243. bool isDir = false;
  244. bool fileExists = false;
  245. time_t blobModifiedTime = 0;
  246. CriticalSection cs;
  247. };
  248. //---------------------------------------------------------------------------------------------------------------------
  249. AzureFileReadIO::AzureFileReadIO(AzureFile * _file, const FileIOStats & _firstStats)
  250. : file(_file), stats(_firstStats)
  251. {
  252. startResultOffset = 0;
  253. endResultOffset = 0;
  254. }
  255. size32_t AzureFileReadIO::read(offset_t pos, size32_t len, void * data)
  256. {
  257. if (pos > file->fileSize)
  258. return 0;
  259. if (pos + len > file->fileSize)
  260. len = file->fileSize - pos;
  261. if (len == 0)
  262. return 0;
  263. size32_t sizeRead = 0;
  264. offset_t lastOffset = pos + len;
  265. // MORE: Do we ever read file IO from more than one thread? I'm not convinced we do, and the critical blocks waste space and slow it down.
  266. //It might be worth revisiting (although I'm not sure what effect stranding has)
  267. CriticalBlock block(cs);
  268. for(;;)
  269. {
  270. //Check if part of the request can be fulfilled from the current read block
  271. if (pos >= startResultOffset && pos < endResultOffset)
  272. {
  273. size_t copySize = ((lastOffset > endResultOffset) ? endResultOffset : lastOffset) - pos;
  274. size_t extractedSize = extractDataFromResult((pos - startResultOffset), copySize, data);
  275. assertex(copySize == extractedSize);
  276. pos += copySize;
  277. len -= copySize;
  278. data = (byte *)data + copySize;
  279. sizeRead += copySize;
  280. if (len == 0)
  281. return sizeRead;
  282. }
  283. #ifdef TEST_AZURE_PAGING
  284. offset_t readSize = azureReadRequestSize;
  285. #else
  286. offset_t readSize = (len > azureReadRequestSize) ? len : azureReadRequestSize;
  287. #endif
  288. offset_t contentSize = file->readBlock(contents, stats, pos, readSize);
  289. //If the results are inconsistent then do not loop forever
  290. if (contentSize == 0)
  291. return sizeRead;
  292. startResultOffset = pos;
  293. endResultOffset = pos + contentSize;
  294. }
  295. }
  296. offset_t AzureFileReadIO::size()
  297. {
  298. return file->size();
  299. }
  300. size_t AzureFileReadIO::extractDataFromResult(size_t offset, size_t length, void * target)
  301. {
  302. //MORE: There are probably more efficient ways of extracting data - but this avoids the clone calling str()
  303. contents.seekg(offset);
  304. contents.read((char *)target, length);
  305. return contents.gcount();
  306. }
  307. unsigned __int64 AzureFileReadIO::getStatistic(StatisticKind kind)
  308. {
  309. return stats.getStatistic(kind);
  310. }
  311. unsigned __int64 FileIOStats::getStatistic(StatisticKind kind)
  312. {
  313. switch (kind)
  314. {
  315. case StCycleDiskReadIOCycles:
  316. return ioReadCycles.load();
  317. case StCycleDiskWriteIOCycles:
  318. return ioWriteCycles.load();
  319. case StTimeDiskReadIO:
  320. return cycle_to_nanosec(ioReadCycles.load());
  321. case StTimeDiskWriteIO:
  322. return cycle_to_nanosec(ioWriteCycles.load());
  323. case StSizeDiskRead:
  324. return ioReadBytes.load();
  325. case StSizeDiskWrite:
  326. return ioWriteBytes.load();
  327. case StNumDiskReads:
  328. return ioReads.load();
  329. case StNumDiskWrites:
  330. return ioWrites.load();
  331. }
  332. return 0;
  333. }
  334. //---------------------------------------------------------------------------------------------------------------------
  335. AzureFileWriteIO::AzureFileWriteIO(AzureFile * _file)
  336. : file(_file)
  337. {
  338. }
  339. void AzureFileWriteIO::beforeDispose()
  340. {
  341. try
  342. {
  343. close();
  344. }
  345. catch (...)
  346. {
  347. }
  348. }
  349. offset_t AzureFileWriteIO::size()
  350. {
  351. throwUnexpected();
  352. }
  353. void AzureFileWriteIO::setSize(offset_t size)
  354. {
  355. UNIMPLEMENTED;
  356. }
  357. void AzureFileWriteIO::flush()
  358. {
  359. }
  360. unsigned __int64 AzureFileWriteIO::getStatistic(StatisticKind kind)
  361. {
  362. return stats.getStatistic(kind);
  363. }
  364. //---------------------------------------------------------------------------------------------------------------------
  365. AzureFileAppendBlobWriteIO::AzureFileAppendBlobWriteIO(AzureFile * _file) : AzureFileWriteIO(_file)
  366. {
  367. file->createAppendBlob();
  368. }
  369. void AzureFileAppendBlobWriteIO::close()
  370. {
  371. }
  372. offset_t AzureFileAppendBlobWriteIO::appendFile(IFile *file, offset_t pos, offset_t len)
  373. {
  374. UNIMPLEMENTED_X("AzureFileAppendBlobWriteIO::appendFile");
  375. }
  376. offset_t AzureFileAppendBlobWriteIO::size()
  377. {
  378. #ifdef TRACE_AZURE
  379. //The following is fairly unusual, and suggests an unnecessary operation.
  380. DBGLOG("Warning: Size (%" I64F "u) requested on output IO", offset);
  381. #endif
  382. return offset;
  383. }
  384. size32_t AzureFileAppendBlobWriteIO::write(offset_t pos, size32_t len, const void * data)
  385. {
  386. if (len)
  387. {
  388. if (offset != pos)
  389. throw makeStringExceptionV(100, "Azure file output only supports append. File %s %" I64F "u v %" I64F "u", file->queryFilename(), pos, offset);
  390. stats.ioWrites++;
  391. stats.ioWriteBytes += len;
  392. CCycleTimer timer;
  393. {
  394. file->appendToAppendBlob(len, data);
  395. offset += len;
  396. }
  397. stats.ioWriteCycles += timer.elapsedCycles();
  398. }
  399. return len;
  400. }
  401. //---------------------------------------------------------------------------------------------------------------------
  402. AzureFileBlockBlobWriteIO::AzureFileBlockBlobWriteIO(AzureFile * _file) : AzureFileWriteIO(_file)
  403. {
  404. file->createAppendBlob();
  405. }
  406. void AzureFileBlockBlobWriteIO::close()
  407. {
  408. file->commitBlockBlob();
  409. }
  410. offset_t AzureFileBlockBlobWriteIO::appendFile(IFile *file, offset_t pos, offset_t len)
  411. {
  412. UNIMPLEMENTED_X("AzureFileBlockBlobWriteIO::appendFile");
  413. return 0;
  414. }
  415. size32_t AzureFileBlockBlobWriteIO::write(offset_t pos, size32_t len, const void * data)
  416. {
  417. if (len)
  418. {
  419. assertex(offset == pos);
  420. file->appendToBlockBlob(len, data);
  421. offset += len;
  422. }
  423. return len;
  424. }
  425. //---------------------------------------------------------------------------------------------------------------------
  426. void throwStorageError(const azure::storage_lite::storage_error & error)
  427. {
  428. unsigned errCode = atoi(error.code.c_str());
  429. throw makeStringExceptionV(errCode, "Azure Error: %s, %s", error.code.c_str(), error.code_name.c_str());
  430. }
  431. template <typename RESULT>
  432. void checkError(const RESULT & result)
  433. {
  434. if (!result.success())
  435. throwStorageError(result.error());
  436. }
  437. static bool isBase64Char(char c)
  438. {
  439. return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=');
  440. }
  441. static std::shared_ptr<azure::storage_lite::blob_client> getClient(const char * accountName, const char * key)
  442. {
  443. //MORE: The client should be cached and shared between different file access - implement when secret storage is added.
  444. StringBuffer keyTemp;
  445. if (!accountName)
  446. accountName = getenv("AZURE_ACCOUNT_NAME");
  447. if (!key)
  448. {
  449. key = getenv("AZURE_ACCOUNT_KEY");
  450. if (!key)
  451. {
  452. StringBuffer secretName;
  453. secretName.append("azure-").append(accountName);
  454. getSecretValue(keyTemp, "storage", secretName, "key", true);
  455. //Trim trailing whitespace/newlines in case the secret has been entered by hand e.g. on bare metal
  456. size32_t len = keyTemp.length();
  457. for (;;)
  458. {
  459. if (!len)
  460. break;
  461. if (isBase64Char(keyTemp.charAt(len-1)))
  462. break;
  463. len--;
  464. }
  465. keyTemp.setLength(len);
  466. key = keyTemp.str();
  467. }
  468. }
  469. std::shared_ptr<azure::storage_lite::storage_credential> cred = nullptr;
  470. try
  471. {
  472. cred = std::make_shared<azure::storage_lite::shared_key_credential>(accountName, key);
  473. }
  474. catch (const std::exception & e)
  475. {
  476. IException * error = makeStringExceptionV(1234, "Azure access: %s", e.what());
  477. throw error;
  478. }
  479. std::shared_ptr<azure::storage_lite::storage_account> account = std::make_shared<azure::storage_lite::storage_account>(accountName, cred, /* use_https */ true);
  480. const int max_concurrency = 10;
  481. return std::make_shared<azure::storage_lite::blob_client>(account, max_concurrency);
  482. }
  483. AzureFile::AzureFile(const char *_azureFileName) : fullName(_azureFileName)
  484. {
  485. const char * filename = fullName + strlen(azureFilePrefix);
  486. if (filename[0] != '/' || filename[1] != '/')
  487. throw makeStringException(99, "// missing from azure: file reference");
  488. //Allow the access key to be provided after the // before a @ i.e. azure://<account>:<access-key>@...
  489. filename += 2;
  490. //Allow the account and key to be quoted so that it can support slashes within the access key (since they are part of base64 encoding)
  491. //e.g. i.e. azure://'<account>:<access-key>'@...
  492. StringBuffer accessExtra;
  493. if (filename[0] == '"' || filename[0] == '\'')
  494. {
  495. const char * endQuote = strchr(filename + 1, filename[0]);
  496. if (!endQuote)
  497. throw makeStringException(99, "access key is missing terminating quote");
  498. accessExtra.append(endQuote - (filename + 1), filename + 1);
  499. filename = endQuote+1;
  500. if (*filename != '@')
  501. throw makeStringException(99, "missing @ following quoted access key");
  502. filename++;
  503. }
  504. const char * at = strchr(filename, '@');
  505. const char * slash = strchr(filename, '/');
  506. assertex(slash); // could probably relax this....
  507. //Possibly pedantic - only spot @s before the first leading /
  508. if (at && (!slash || at < slash))
  509. {
  510. accessExtra.append(at - filename, filename);
  511. filename = at+1;
  512. }
  513. if (accessExtra)
  514. {
  515. const char * colon = strchr(accessExtra, ':');
  516. if (colon)
  517. {
  518. accountName.set(accessExtra, colon-accessExtra);
  519. accountKey.set(colon+1);
  520. }
  521. else
  522. accountName.set(accessExtra); // Key is retrieved from the secrets
  523. }
  524. containerName.assign(filename, slash-filename);
  525. blobName.assign(slash+1);
  526. }
  527. bool AzureFile::createDirectory()
  528. {
  529. for (;;)
  530. {
  531. //Ensure that the container exists...
  532. auto client = getClient();
  533. auto ret = client->create_container(containerName).get();
  534. if (ret.success())
  535. return true; // No need to create any directory structure - all blobs are flat within the container
  536. DBGLOG("Failed to create container, Error: %s, %s", ret.error().code.c_str(), ret.error().code_name.c_str());
  537. //MORE: Need to update the code / code_names so the following works:
  538. if (streq(ret.error().code.c_str(), "ContainerAlreadyExists"))
  539. return true;
  540. if (!streq(ret.error().code.c_str(), "ContainerBeingDeleted"))
  541. return true;
  542. MilliSleep(100);
  543. }
  544. }
  545. void AzureFile::createAppendBlob()
  546. {
  547. auto client = getClient();
  548. auto ret = client->create_append_blob(containerName, blobName).get();
  549. checkError(ret);
  550. }
  551. void AzureFile::appendToAppendBlob(size32_t len, const void * data)
  552. {
  553. auto client = getClient();
  554. std::istringstream input(std::string((const char *)data, len));
  555. //implement append_block_from_buffer based on upload_block_from_buffer
  556. auto ret = client->append_block_from_stream(containerName, blobName, input).get();
  557. checkError(ret);
  558. }
  559. void AzureFile::createBlockBlob()
  560. {
  561. auto client = getClient();
  562. auto http = client->client()->get_handle();
  563. auto request = std::make_shared<azure::storage_lite::create_block_blob_request>(containerName, blobName);
  564. auto ret = async_executor<void>::submit(client->account(), request, http, client->context()).get();
  565. checkError(ret);
  566. }
  567. void AzureFile::appendToBlockBlob(size32_t len, const void * data)
  568. {
  569. auto client = getClient();
  570. std::string blockid;
  571. auto ret = client->upload_block_from_buffer(containerName, blobName, blockid, (const char *)data, len).get();
  572. checkError(ret);
  573. }
  574. void AzureFile::commitBlockBlob()
  575. {
  576. auto client = getClient();
  577. std::vector<azure::storage_lite::put_block_list_request_base::block_item> blocks;
  578. auto ret = client->put_block_list(containerName, blobName, blocks, {}).get();
  579. checkError(ret);
  580. }
  581. std::shared_ptr<azure::storage_lite::blob_client> AzureFile::getClient()
  582. {
  583. return ::getClient(accountName, accountKey);
  584. }
  585. bool AzureFile::getTime(CDateTime * createTime, CDateTime * modifiedTime, CDateTime * accessedTime)
  586. {
  587. ensureMetaData();
  588. if (createTime)
  589. createTime->clear();
  590. if (modifiedTime)
  591. {
  592. modifiedTime->clear();
  593. modifiedTime->set(blobModifiedTime);
  594. }
  595. if (accessedTime)
  596. accessedTime->clear();
  597. return false;
  598. }
  599. offset_t AzureFile::readBlock(BlobPageContents & contents, FileIOStats & stats, offset_t from, offset_t length)
  600. {
  601. CCycleTimer timer;
  602. {
  603. auto client = getClient();
  604. azure::storage_lite::blob_client_wrapper wrapper(client);
  605. //NOTE: Currently each call starts a new thread and then waits for the result. It will be better in
  606. //the long term to avoid the wrapper calls and use the asynchronous calls to read ahead.
  607. contents.seekp(0);
  608. wrapper.download_blob_to_stream(containerName, blobName, from, length, contents);
  609. }
  610. offset_t sizeRead = contents.tellp();
  611. stats.ioReads++;
  612. stats.ioReadCycles += timer.elapsedCycles();
  613. stats.ioReadBytes += sizeRead;
  614. return sizeRead;
  615. }
  616. IFileIO * AzureFile::createFileReadIO()
  617. {
  618. //Read the first chunk of the file. If it is the full file then fill in the meta information, otherwise
  619. //ensure the meta information is calculated before creating the file IO object
  620. FileIOStats readStats;
  621. CriticalBlock block(cs);
  622. if (!exists())
  623. return nullptr;
  624. return new AzureFileReadIO(this, readStats);
  625. }
  626. IFileIO * AzureFile::createFileWriteIO()
  627. {
  628. return new AzureFileAppendBlobWriteIO(this);
  629. }
  630. void AzureFile::ensureMetaData()
  631. {
  632. CriticalBlock block(cs);
  633. if (haveMeta)
  634. return;
  635. gatherMetaData();
  636. haveMeta = true;
  637. }
  638. void AzureFile::gatherMetaData()
  639. {
  640. CCycleTimer timer;
  641. auto client = getClient();
  642. azure::storage_lite::blob_client_wrapper wrapper(client);
  643. auto properties = wrapper.get_blob_property(containerName, blobName);
  644. if (errno == 0)
  645. {
  646. fileExists = true;
  647. fileSize = properties.size;
  648. blobModifiedTime = properties.last_modified; // Could be more accurate
  649. //MORE: extract information from properties.metadata
  650. }
  651. else
  652. {
  653. fileExists = false;
  654. }
  655. }
  656. bool AzureFile::remove()
  657. {
  658. auto client = getClient();
  659. azure::storage_lite::blob_client_wrapper wrapper(client);
  660. wrapper.delete_blob(containerName, blobName);
  661. return (errno == 0);
  662. }
  663. //---------------------------------------------------------------------------------------------------------------------
  664. static IFile *createAzureFile(const char *azureFileName)
  665. {
  666. return new AzureFile(azureFileName);
  667. }
  668. //---------------------------------------------------------------------------------------------------------------------
  669. class AzureFileHook : public CInterfaceOf<IContainedFileHook>
  670. {
  671. public:
  672. virtual IFile * createIFile(const char *fileName) override
  673. {
  674. if (isAzureFileName(fileName))
  675. return createAzureFile(fileName);
  676. else
  677. return nullptr;
  678. }
  679. protected:
  680. static bool isAzureFileName(const char *fileName)
  681. {
  682. if (!startsWith(fileName, azureFilePrefix))
  683. return false;
  684. const char * filename = fileName + strlen(azureFilePrefix);
  685. const char * slash = strchr(filename, '/');
  686. if (!slash)
  687. return false;
  688. return true;
  689. }
  690. } *azureFileHook;
  691. extern AZURE_FILE_API void installFileHook()
  692. {
  693. if (!azureFileHook)
  694. {
  695. azureFileHook = new AzureFileHook;
  696. addContainedFileHook(azureFileHook);
  697. }
  698. }
  699. extern AZURE_FILE_API void removeFileHook()
  700. {
  701. if (azureFileHook)
  702. {
  703. removeContainedFileHook(azureFileHook);
  704. delete azureFileHook;
  705. azureFileHook = NULL;
  706. }
  707. }
  708. MODULE_INIT(INIT_PRIORITY_STANDARD)
  709. {
  710. azureFileHook = NULL; // Not really needed, but you have to have a modinit to match a modexit
  711. return true;
  712. }
  713. MODULE_EXIT()
  714. {
  715. if (azureFileHook)
  716. {
  717. removeContainedFileHook(azureFileHook);
  718. ::Release(azureFileHook);
  719. azureFileHook = NULL;
  720. }
  721. }