From c100951154cfe68356d6acabb5c936b02204aecf Mon Sep 17 00:00:00 2001 From: Jeff Fisher Date: Fri, 4 Oct 2019 00:44:26 -0700 Subject: [PATCH] Add tracing to storage-file (#5363) Adds a dependency on @azure/core-tracing from storage-file and makes it possible to pass a parent `Span` to public facing operations. --- sdk/storage/storage-file/package.json | 1 + ...reatefile_and_deletefile_with_tracing.json | 210 +++ .../recording_create_with_tracing.json | 101 ++ ..._createfile_and_deletefile_with_tracing.js | 252 ++++ .../recording_create_with_tracing.js | 114 ++ .../storage-file/rollup.base.config.js | 13 +- .../storage-file/src/DirectoryClient.ts | 373 +++-- sdk/storage/storage-file/src/FileClient.ts | 1223 +++++++++++------ .../storage-file/src/FileServiceClient.ts | 119 +- sdk/storage/storage-file/src/ShareClient.ts | 400 ++++-- sdk/storage/storage-file/src/StorageClient.ts | 9 + sdk/storage/storage-file/src/utils/tracing.ts | 36 + .../storage-file/test/directoryclient.spec.ts | 89 ++ .../storage-file/test/fileclient.spec.ts | 32 + 14 files changed, 2281 insertions(+), 691 deletions(-) create mode 100644 sdk/storage/storage-file/recordings/browsers/directoryclient/recording_createfile_and_deletefile_with_tracing.json create mode 100644 sdk/storage/storage-file/recordings/browsers/fileclient/recording_create_with_tracing.json create mode 100644 sdk/storage/storage-file/recordings/node/directoryclient/recording_createfile_and_deletefile_with_tracing.js create mode 100644 sdk/storage/storage-file/recordings/node/fileclient/recording_create_with_tracing.js create mode 100644 sdk/storage/storage-file/src/utils/tracing.ts diff --git a/sdk/storage/storage-file/package.json b/sdk/storage/storage-file/package.json index ac204ce0613c..3c2215d69073 100644 --- a/sdk/storage/storage-file/package.json +++ b/sdk/storage/storage-file/package.json @@ -81,6 +81,7 @@ "@azure/abort-controller": "1.0.0-preview.2", "@azure/core-http": "1.0.0-preview.3", "@azure/core-paging": "1.0.0-preview.2", + "@azure/core-tracing": "1.0.0-preview.3", "events": "^3.0.0", "tslib": "^1.9.3" }, diff --git a/sdk/storage/storage-file/recordings/browsers/directoryclient/recording_createfile_and_deletefile_with_tracing.json b/sdk/storage/storage-file/recordings/browsers/directoryclient/recording_createfile_and_deletefile_with_tracing.json new file mode 100644 index 000000000000..1d649d88a741 --- /dev/null +++ b/sdk/storage/storage-file/recordings/browsers/directoryclient/recording_createfile_and_deletefile_with_tracing.json @@ -0,0 +1,210 @@ +{ + "recordings": [ + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682", + "query": { + "restype": "share" + }, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:25 GMT", + "last-modified": "Thu, 03 Oct 2019 07:50:25 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D65A44649F\"", + "x-ms-request-id": "127353d9-401a-00dc-76bf-795253000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "23fd9509-a948-480f-9459-1083ef4991ef", + "content-length": "0" + } + }, + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201", + "query": { + "restype": "directory" + }, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:25 GMT", + "x-ms-file-change-time": "2019-10-03T07:50:26.0424091Z", + "x-ms-file-attributes": "Directory", + "x-ms-file-id": "13835128424026341376", + "x-ms-request-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:50:26.0424091Z", + "x-ms-file-parent-id": "0", + "x-ms-file-permission-key": "10601938801812273194*18156966929047809955", + "x-ms-client-request-id": "922c5e1e-9c6b-47b5-94ac-39d48c77b4b7", + "content-length": "0", + "last-modified": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D65A56C59B\"", + "x-ms-request-id": "127353dc-401a-00dc-78bf-795253000000", + "x-ms-file-last-write-time": "2019-10-03T07:50:26.0424091Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219", + "query": { + "restype": "directory" + }, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "x-ms-file-change-time": "2019-10-03T07:50:26.1604916Z", + "x-ms-file-attributes": "Directory", + "x-ms-file-id": "11529285414812647424", + "x-ms-request-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:50:26.1604916Z", + "x-ms-file-parent-id": "13835128424026341376", + "x-ms-file-permission-key": "10601938801812273194*18156966929047809955", + "x-ms-client-request-id": "7af0d550-aed6-4794-80b0-1b65c9dbb589", + "content-length": "0", + "last-modified": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D65A68CA34\"", + "x-ms-request-id": "127353de-401a-00dc-7abf-795253000000", + "x-ms-file-last-write-time": "2019-10-03T07:50:26.1604916Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219/file157008902663800316", + "query": {}, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "x-ms-file-change-time": "2019-10-03T07:50:26.2705685Z", + "x-ms-file-attributes": "Archive", + "x-ms-file-id": "16140971433240035328", + "x-ms-request-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:50:26.2705685Z", + "x-ms-file-parent-id": "11529285414812647424", + "x-ms-file-permission-key": "6006229023213291309*18156966929047809955", + "x-ms-client-request-id": "cacbbb51-e1c6-4d46-bf9f-7757eb338958", + "content-length": "0", + "last-modified": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D65A799615\"", + "x-ms-request-id": "127353e0-401a-00dc-7cbf-795253000000", + "x-ms-file-last-write-time": "2019-10-03T07:50:26.2705685Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "HEAD", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219/file157008902663800316", + "query": {}, + "requestBody": null, + "status": 200, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "last-modified": "Thu, 03 Oct 2019 07:50:26 GMT", + "x-ms-file-attributes": "Archive", + "x-ms-file-id": "16140971433240035328", + "x-ms-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:50:26.2705685Z", + "x-ms-file-parent-id": "11529285414812647424", + "x-ms-file-permission-key": "6006229023213291309*18156966929047809955", + "x-ms-client-request-id": "2206054b-9fc0-4932-9a54-51e9a33ba89c", + "x-ms-type": "File", + "content-length": "256", + "x-ms-meta-key": "value", + "x-ms-file-change-time": "2019-10-03T07:50:26.2705685Z", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D65A799615\"", + "content-type": "application/octet-stream", + "x-ms-request-id": "127353e2-401a-00dc-7ebf-795253000000", + "x-ms-file-last-write-time": "2019-10-03T07:50:26.2705685Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "DELETE", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219/file157008902663800316", + "query": {}, + "requestBody": null, + "status": 202, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "x-ms-request-id": "127353e5-401a-00dc-80bf-795253000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "c1d7b71a-05fc-4377-a9f1-cb4d2c35d22c", + "content-length": "0" + } + }, + { + "method": "HEAD", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219/file157008902663800316", + "query": {}, + "requestBody": null, + "status": 404, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "x-ms-error-code": "ResourceNotFound", + "transfer-encoding": "chunked", + "x-ms-request-id": "127353e7-401a-00dc-02bf-795253000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "a8607656-ca13-4bc2-adc3-81d873a074a7" + } + }, + { + "method": "DELETE", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682/dir157008902641103201/directory157008902652205219", + "query": { + "restype": "directory" + }, + "requestBody": null, + "status": 202, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "x-ms-request-id": "127353ea-401a-00dc-04bf-795253000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "b9b1b51e-8d70-4745-b95c-df4226365074", + "content-length": "0" + } + }, + { + "method": "DELETE", + "url": "https://fakestorageaccount.file.core.windows.net/share157008902598508682", + "query": { + "restype": "share" + }, + "requestBody": null, + "status": 202, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:50:26 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "x-ms-request-id": "127353ec-401a-00dc-06bf-795253000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "c73c326b-ab19-4573-bfe7-324bc405abf3", + "content-length": "0" + } + } + ], + "uniqueTestInfo": { + "share": "share157008902598508682", + "dir": "dir157008902641103201", + "directory": "directory157008902652205219", + "file": "file157008902663800316" + } +} \ No newline at end of file diff --git a/sdk/storage/storage-file/recordings/browsers/fileclient/recording_create_with_tracing.json b/sdk/storage/storage-file/recordings/browsers/fileclient/recording_create_with_tracing.json new file mode 100644 index 000000000000..70969d9f65dd --- /dev/null +++ b/sdk/storage/storage-file/recordings/browsers/fileclient/recording_create_with_tracing.json @@ -0,0 +1,101 @@ +{ + "recordings": [ + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008773128100374", + "query": { + "restype": "share" + }, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:28:50 GMT", + "last-modified": "Thu, 03 Oct 2019 07:28:51 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D356851B72\"", + "x-ms-request-id": "9107613d-c01a-007b-12bc-79ba91000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "a4b94f83-271f-46fb-adaa-904cd58df677", + "content-length": "0" + } + }, + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008773128100374/dir157008773165503232", + "query": { + "restype": "directory" + }, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:28:50 GMT", + "x-ms-file-change-time": "2019-10-03T07:28:51.2995150Z", + "x-ms-file-attributes": "Directory", + "x-ms-file-id": "13835128424026341376", + "x-ms-request-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:28:51.2995150Z", + "x-ms-file-parent-id": "0", + "x-ms-file-permission-key": "10601938801812273194*18156966929047809955", + "x-ms-client-request-id": "63ec792d-796d-4ed1-a0d4-e34d6e7c3735", + "content-length": "0", + "last-modified": "Thu, 03 Oct 2019 07:28:51 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D3569CAF4E\"", + "x-ms-request-id": "91076140-c01a-007b-14bc-79ba91000000", + "x-ms-file-last-write-time": "2019-10-03T07:28:51.2995150Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "PUT", + "url": "https://fakestorageaccount.file.core.windows.net/share157008773128100374/dir157008773165503232/file157008773176801350", + "query": {}, + "requestBody": null, + "status": 201, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:28:51 GMT", + "x-ms-file-change-time": "2019-10-03T07:28:51.4316073Z", + "x-ms-file-attributes": "Archive", + "x-ms-file-id": "11529285414812647424", + "x-ms-request-server-encrypted": "true", + "x-ms-file-creation-time": "2019-10-03T07:28:51.4316073Z", + "x-ms-file-parent-id": "13835128424026341376", + "x-ms-file-permission-key": "6006229023213291309*18156966929047809955", + "x-ms-client-request-id": "3d28f40c-45cc-4378-b524-fe38a4da3f9b", + "content-length": "0", + "last-modified": "Thu, 03 Oct 2019 07:28:51 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "etag": "\"0x8D747D356B0D729\"", + "x-ms-request-id": "91076142-c01a-007b-16bc-79ba91000000", + "x-ms-file-last-write-time": "2019-10-03T07:28:51.4316073Z", + "x-ms-version": "2019-02-02" + } + }, + { + "method": "DELETE", + "url": "https://fakestorageaccount.file.core.windows.net/share157008773128100374", + "query": { + "restype": "share" + }, + "requestBody": null, + "status": 202, + "response": "", + "responseHeaders": { + "date": "Thu, 03 Oct 2019 07:28:51 GMT", + "server": "Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0", + "x-ms-request-id": "91076144-c01a-007b-18bc-79ba91000000", + "x-ms-version": "2019-02-02", + "x-ms-client-request-id": "f8d7dea4-1723-4d5c-80fc-4119c4770d68", + "content-length": "0" + } + } + ], + "uniqueTestInfo": { + "share": "share157008773128100374", + "dir": "dir157008773165503232", + "file": "file157008773176801350" + } +} \ No newline at end of file diff --git a/sdk/storage/storage-file/recordings/node/directoryclient/recording_createfile_and_deletefile_with_tracing.js b/sdk/storage/storage-file/recordings/node/directoryclient/recording_createfile_and_deletefile_with_tracing.js new file mode 100644 index 000000000000..b6eca6ac06cd --- /dev/null +++ b/sdk/storage/storage-file/recordings/node/directoryclient/recording_createfile_and_deletefile_with_tracing.js @@ -0,0 +1,252 @@ +let nock = require('nock'); + +module.exports.testInfo = {"share":"share157008893947504191","dir":"dir157008894411606682","directory":"directory157008894430509739","file":"file157008894445507000"} + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008893947504191') + .query(true) + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:49:03 GMT', + 'ETag', + '"0x8D747D6292EE866"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'd04570cb-201a-0073-71bf-79a09e000000', + 'x-ms-client-request-id', + '21b69fb7-5d9d-4541-a538-d888dd26a605', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:49:03 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008893947504191/dir157008894411606682') + .query(true) + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:49:03 GMT', + 'ETag', + '"0x8D747D629549DE8"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '7da158c1-e01a-007c-57bf-79d6f2000000', + 'x-ms-client-request-id', + '052fcf14-449e-41b4-80ce-f697a881f7c9', + 'x-ms-version', + '2019-02-02', + 'x-ms-file-change-time', + '2019-10-03T07:49:03.8199272Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:49:03.8199272Z', + 'x-ms-file-creation-time', + '2019-10-03T07:49:03.8199272Z', + 'x-ms-file-permission-key', + '10601938801812273194*18156966929047809955', + 'x-ms-file-attributes', + 'Directory', + 'x-ms-file-id', + '13835128424026341376', + 'x-ms-file-parent-id', + '0', + 'x-ms-request-server-encrypted', + 'true', + 'Date', + 'Thu, 03 Oct 2019 07:49:03 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008893947504191/dir157008894411606682/directory157008894430509739') + .query(true) + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:49:03 GMT', + 'ETag', + '"0x8D747D6296EE156"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'f835ce09-301a-0022-48bf-793d12000000', + 'x-ms-client-request-id', + 'd53a7bcf-1f40-4e72-bcae-d5373abe72ab', + 'x-ms-version', + '2019-02-02', + 'x-ms-file-change-time', + '2019-10-03T07:49:03.9920470Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:49:03.9920470Z', + 'x-ms-file-creation-time', + '2019-10-03T07:49:03.9920470Z', + 'x-ms-file-permission-key', + '10601938801812273194*18156966929047809955', + 'x-ms-file-attributes', + 'Directory', + 'x-ms-file-id', + '13835093239654252544', + 'x-ms-file-parent-id', + '13835128424026341376', + 'x-ms-request-server-encrypted', + 'true', + 'Date', + 'Thu, 03 Oct 2019 07:49:03 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008893947504191/dir157008894411606682/directory157008894430509739/file157008894445507000') + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:49:04 GMT', + 'ETag', + '"0x8D747D62986652B"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'df180c40-b01a-002c-15bf-7914a2000000', + 'x-ms-client-request-id', + 'beb21b28-59f0-41ac-b789-29fc4c04a426', + 'x-ms-version', + '2019-02-02', + 'x-ms-file-change-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-creation-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-permission-key', + '6006229023213291309*18156966929047809955', + 'x-ms-file-attributes', + 'Archive', + 'x-ms-file-id', + '13835163608398430208', + 'x-ms-file-parent-id', + '13835093239654252544', + 'x-ms-request-server-encrypted', + 'true', + 'Date', + 'Thu, 03 Oct 2019 07:49:03 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .head('/share157008893947504191/dir157008894411606682/directory157008894430509739/file157008894445507000') + .reply(200, "", [ 'Content-Length', + '256', + 'Content-Type', + 'application/octet-stream', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:49:04 GMT', + 'ETag', + '"0x8D747D62986652B"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '9f37a0db-901a-00ad-1ebf-79b478000000', + 'x-ms-client-request-id', + 'efd6e38c-260f-4053-9d61-60e0ae6e8791', + 'x-ms-version', + '2019-02-02', + 'x-ms-meta-key', + 'value', + 'x-ms-type', + 'File', + 'x-ms-server-encrypted', + 'true', + 'x-ms-file-change-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-creation-time', + '2019-10-03T07:49:04.1461547Z', + 'x-ms-file-permission-key', + '6006229023213291309*18156966929047809955', + 'x-ms-file-attributes', + 'Archive', + 'x-ms-file-id', + '13835163608398430208', + 'x-ms-file-parent-id', + '13835093239654252544', + 'Access-Control-Expose-Headers', + 'x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,x-ms-meta-key,Content-Type,Last-Modified,ETag,x-ms-type,x-ms-server-encrypted,x-ms-file-change-time,x-ms-file-last-write-time,x-ms-file-creation-time,x-ms-file-permission-key,x-ms-file-attributes,x-ms-file-id,x-ms-file-parent-id,Content-Length,Date,Transfer-Encoding', + 'Access-Control-Allow-Origin', + '*', + 'Date', + 'Thu, 03 Oct 2019 07:49:03 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .delete('/share157008893947504191/dir157008894411606682/directory157008894430509739/file157008894445507000') + .reply(202, "", [ 'Content-Length', + '0', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '9107695a-c01a-007b-51bf-79ba91000000', + 'x-ms-client-request-id', + '7e2d3fc5-728f-4061-98ac-1817d4a4cbd4', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:49:04 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .head('/share157008893947504191/dir157008894411606682/directory157008894430509739/file157008894445507000') + .reply(404, "", [ 'Transfer-Encoding', + 'chunked', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '94c5f31d-501a-00c0-12bf-790033000000', + 'x-ms-client-request-id', + '24a2c7d0-7570-4852-b8b7-7f9b88a5b653', + 'x-ms-version', + '2019-02-02', + 'x-ms-error-code', + 'ResourceNotFound', + 'Access-Control-Expose-Headers', + 'x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,x-ms-error-code,Content-Length,Date,Transfer-Encoding', + 'Access-Control-Allow-Origin', + '*', + 'Date', + 'Thu, 03 Oct 2019 07:49:04 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .delete('/share157008893947504191/dir157008894411606682/directory157008894430509739') + .query(true) + .reply(202, "", [ 'Content-Length', + '0', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'bf33ca75-a01a-0052-43bf-7984e5000000', + 'x-ms-client-request-id', + 'd5dc9f3b-1281-4c69-9ae1-74066a917b08', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:49:05 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .delete('/share157008893947504191') + .query(true) + .reply(202, "", [ 'Content-Length', + '0', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'cdc89a58-b01a-00e7-05bf-7917f7000000', + 'x-ms-client-request-id', + 'f0d46034-6432-4710-8428-175c60a44254', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:49:05 GMT' ]); + diff --git a/sdk/storage/storage-file/recordings/node/fileclient/recording_create_with_tracing.js b/sdk/storage/storage-file/recordings/node/fileclient/recording_create_with_tracing.js new file mode 100644 index 000000000000..61533b3bf5df --- /dev/null +++ b/sdk/storage/storage-file/recordings/node/fileclient/recording_create_with_tracing.js @@ -0,0 +1,114 @@ +let nock = require('nock'); + +module.exports.testInfo = {"share":"share157008711292300407","dir":"dir157008711443400651","file":"file157008711460400420"} + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008711292300407') + .query(true) + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:18:33 GMT', + 'ETag', + '"0x8D747D1E67B202A"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + 'baf6ae35-001a-00af-44ba-790ac0000000', + 'x-ms-client-request-id', + '93f3a4b8-4cfa-4dbc-acfd-6ce2a8285055', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:18:33 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008711292300407/dir157008711443400651') + .query(true) + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:18:34 GMT', + 'ETag', + '"0x8D747D1E6C4C85F"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '0cadeebf-b01a-0003-1eba-791969000000', + 'x-ms-client-request-id', + '1d524f55-5945-426b-93bb-358978e4ed18', + 'x-ms-version', + '2019-02-02', + 'x-ms-file-change-time', + '2019-10-03T07:18:34.1607519Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:18:34.1607519Z', + 'x-ms-file-creation-time', + '2019-10-03T07:18:34.1607519Z', + 'x-ms-file-permission-key', + '10601938801812273194*18156966929047809955', + 'x-ms-file-attributes', + 'Directory', + 'x-ms-file-id', + '13835128424026341376', + 'x-ms-file-parent-id', + '0', + 'x-ms-request-server-encrypted', + 'true', + 'Date', + 'Thu, 03 Oct 2019 07:18:33 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .put('/share157008711292300407/dir157008711443400651/file157008711460400420') + .reply(201, "", [ 'Content-Length', + '0', + 'Last-Modified', + 'Thu, 03 Oct 2019 07:18:34 GMT', + 'ETag', + '"0x8D747D1E6E59CAF"', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '70244f5e-201a-005c-04ba-79ad55000000', + 'x-ms-client-request-id', + '47cbf030-bc63-49b9-8f68-b305f1e9be2b', + 'x-ms-version', + '2019-02-02', + 'x-ms-file-change-time', + '2019-10-03T07:18:34.3759023Z', + 'x-ms-file-last-write-time', + '2019-10-03T07:18:34.3759023Z', + 'x-ms-file-creation-time', + '2019-10-03T07:18:34.3759023Z', + 'x-ms-file-permission-key', + '6006229023213291309*18156966929047809955', + 'x-ms-file-attributes', + 'Archive', + 'x-ms-file-id', + '13835093239654252544', + 'x-ms-file-parent-id', + '13835128424026341376', + 'x-ms-request-server-encrypted', + 'true', + 'Date', + 'Thu, 03 Oct 2019 07:18:34 GMT' ]); + + +nock('https://fakestorageaccount.file.core.windows.net:443', {"encodedQueryParams":true}) + .delete('/share157008711292300407') + .query(true) + .reply(202, "", [ 'Content-Length', + '0', + 'Server', + 'Windows-Azure-File/1.0 Microsoft-HTTPAPI/2.0', + 'x-ms-request-id', + '7b727e17-201a-0001-2eba-79a7d1000000', + 'x-ms-client-request-id', + 'd9695e82-fa75-4feb-bfe4-9492b5e130b2', + 'x-ms-version', + '2019-02-02', + 'Date', + 'Thu, 03 Oct 2019 07:18:34 GMT' ]); + diff --git a/sdk/storage/storage-file/rollup.base.config.js b/sdk/storage/storage-file/rollup.base.config.js index 653a1307f663..03383cfa4acf 100644 --- a/sdk/storage/storage-file/rollup.base.config.js +++ b/sdk/storage/storage-file/rollup.base.config.js @@ -23,7 +23,15 @@ const depNames = Object.keys(pkg.dependencies); const production = process.env.NODE_ENV === "production"; export function nodeConfig(test = false) { - const externalNodeBuiltins = ["@azure/core-http", "crypto", "fs", "events", "os", "stream", "util"]; + const externalNodeBuiltins = [ + "@azure/core-http", + "crypto", + "fs", + "events", + "os", + "stream", + "util" + ]; const baseConfig = { input: "dist-esm/src/index.js", external: depNames.concat(externalNodeBuiltins), @@ -126,7 +134,8 @@ export function browserConfig(test = false, production = false) { "deepStrictEqual", "notDeepStrictEqual", "notDeepEqual", - "notEqual" + "notEqual", + "strictEqual" ] } }) diff --git a/sdk/storage/storage-file/src/DirectoryClient.ts b/sdk/storage/storage-file/src/DirectoryClient.ts index d210b38741a8..590b6dcdbc02 100644 --- a/sdk/storage/storage-file/src/DirectoryClient.ts +++ b/sdk/storage/storage-file/src/DirectoryClient.ts @@ -15,7 +15,7 @@ import { validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions } from "./models"; import { newPipeline, NewPipelineOptions, Pipeline } from "./Pipeline"; -import { StorageClient } from "./StorageClient"; +import { StorageClient, CommonOptions } from "./StorageClient"; import { appendToURLPath } from "./utils/utils.common"; import "@azure/core-paging"; import { PageSettings, PagedAsyncIterableIterator } from "@azure/core-paging"; @@ -23,6 +23,8 @@ import { FileClient, FileCreateOptions, FileDeleteOptions } from "./FileClient"; import { Credential } from "./credentials/Credential"; import { AnonymousCredential } from "./credentials/AnonymousCredential"; import { FileSystemAttributes } from "./FileSystemAttributes"; +import { createSpan } from "./utils/tracing"; +import { CanonicalCode } from "@azure/core-tracing"; /** * Options to configure Directory - Create operation. @@ -30,7 +32,7 @@ import { FileSystemAttributes } from "./FileSystemAttributes"; * @export * @interface DirectoryCreateOptions */ -export interface DirectoryCreateOptions extends FileAndDirectoryCreateCommonOptions { +export interface DirectoryCreateOptions extends FileAndDirectoryCreateCommonOptions, CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -48,7 +50,9 @@ export interface DirectoryCreateOptions extends FileAndDirectoryCreateCommonOpti metadata?: Metadata; } -export interface DirectoryProperties extends FileAndDirectorySetPropertiesCommonOptions { +export interface DirectoryProperties + extends FileAndDirectorySetPropertiesCommonOptions, + CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -64,7 +68,7 @@ export interface DirectoryProperties extends FileAndDirectorySetPropertiesCommon * * @interface DirectoryListFilesAndDirectoriesSegmentOptions */ -interface DirectoryListFilesAndDirectoriesSegmentOptions { +interface DirectoryListFilesAndDirectoriesSegmentOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -99,7 +103,7 @@ interface DirectoryListFilesAndDirectoriesSegmentOptions { * @export * @interface DirectoryListFilesAndDirectoriesOptions */ -export interface DirectoryListFilesAndDirectoriesOptions { +export interface DirectoryListFilesAndDirectoriesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -124,7 +128,7 @@ export interface DirectoryListFilesAndDirectoriesOptions { * @export * @interface DirectoryDeleteOptions */ -export interface DirectoryDeleteOptions { +export interface DirectoryDeleteOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -141,7 +145,7 @@ export interface DirectoryDeleteOptions { * @export * @interface DirectoryGetPropertiesOptions */ -export interface DirectoryGetPropertiesOptions { +export interface DirectoryGetPropertiesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -158,7 +162,7 @@ export interface DirectoryGetPropertiesOptions { * @export * @interface DirectorySetMetadataOptions */ -export interface DirectorySetMetadataOptions { +export interface DirectorySetMetadataOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -175,7 +179,7 @@ export interface DirectorySetMetadataOptions { * @export * @interface DirectoryListHandlesSegmentOptions */ -export interface DirectoryListHandlesSegmentOptions { +export interface DirectoryListHandlesSegmentOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -208,7 +212,7 @@ export interface DirectoryListHandlesSegmentOptions { * @export * @interface DirectoryListHandlesOptions */ -export interface DirectoryListHandlesOptions { +export interface DirectoryListHandlesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -233,7 +237,7 @@ export interface DirectoryListHandlesOptions { * @export * @interface DirectoryForceCloseHandlesSegmentOptions */ -export interface DirectoryForceCloseHandlesSegmentOptions { +export interface DirectoryForceCloseHandlesSegmentOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -258,7 +262,7 @@ export interface DirectoryForceCloseHandlesSegmentOptions { * @export * @interface DirectoryForceCloseHandlesOptions */ -export interface DirectoryForceCloseHandlesOptions { +export interface DirectoryForceCloseHandlesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -348,25 +352,37 @@ export class DirectoryClient extends StorageClient { public async create( options: DirectoryCreateOptions = {} ): Promise { - if (!options.fileAttributes) { - options = validateAndSetDefaultsForFileAndDirectoryCreateCommonOptions(options); - // By default set it as a directory. - const attributes: FileSystemAttributes = new FileSystemAttributes(); - attributes.directory = true; - options.fileAttributes = attributes; - } - - return this.context.create( - fileAttributesToString(options.fileAttributes!), - fileCreationTimeToString(options.creationTime!), - fileLastWriteTimeToString(options.lastWriteTime!), - { - abortSignal: options.abortSignal, - metadata: options.metadata, - filePermission: options.filePermission, - filePermissionKey: options.filePermissionKey + const { span, spanOptions } = createSpan("DirectoryClient-create", options.spanOptions); + try { + if (!options.fileAttributes) { + options = validateAndSetDefaultsForFileAndDirectoryCreateCommonOptions(options); + // By default set it as a directory. + const attributes: FileSystemAttributes = new FileSystemAttributes(); + attributes.directory = true; + options.fileAttributes = attributes; } - ); + + return this.context.create( + fileAttributesToString(options.fileAttributes!), + fileCreationTimeToString(options.creationTime!), + fileLastWriteTimeToString(options.lastWriteTime!), + { + abortSignal: options.abortSignal, + metadata: options.metadata, + filePermission: options.filePermission, + filePermissionKey: options.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -381,18 +397,33 @@ export class DirectoryClient extends StorageClient { public async setProperties( properties: DirectoryProperties = {} ): Promise { - properties = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(properties); - - return this.context.setProperties( - fileAttributesToString(properties.fileAttributes!), - fileCreationTimeToString(properties.creationTime!), - fileLastWriteTimeToString(properties.lastWriteTime!), - { - abortSignal: properties.abortSignal, - filePermission: properties.filePermission, - filePermissionKey: properties.filePermissionKey - } + const { span, spanOptions } = createSpan( + "DirectoryClient-setProperties", + properties.spanOptions ); + try { + properties = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(properties); + + return this.context.setProperties( + fileAttributesToString(properties.fileAttributes!), + fileCreationTimeToString(properties.creationTime!), + fileLastWriteTimeToString(properties.lastWriteTime!), + { + abortSignal: properties.abortSignal, + filePermission: properties.filePermission, + filePermissionKey: properties.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -420,17 +451,31 @@ export class DirectoryClient extends StorageClient { */ public async createSubdirectory( directoryName: string, - options?: DirectoryCreateOptions + options: DirectoryCreateOptions = {} ): Promise<{ directoryClient: DirectoryClient; directoryCreateResponse: Models.DirectoryCreateResponse; }> { - const directoryClient = this.getDirectoryClient(directoryName); - const directoryCreateResponse = await directoryClient.create(options); - return { - directoryClient, - directoryCreateResponse - }; + const { span, spanOptions } = createSpan( + "DirectoryClient-createSubdirectory", + options.spanOptions + ); + try { + const directoryClient = this.getDirectoryClient(directoryName); + const directoryCreateResponse = await directoryClient.create({ ...options, spanOptions }); + return { + directoryClient, + directoryCreateResponse + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -445,10 +490,24 @@ export class DirectoryClient extends StorageClient { */ public async deleteSubdirectory( directoryName: string, - options?: DirectoryDeleteOptions + options: DirectoryDeleteOptions = {} ): Promise { - const directoryClient = this.getDirectoryClient(directoryName); - return await directoryClient.delete(options); + const { span, spanOptions } = createSpan( + "DirectoryClient-deleteSubdirectory", + options.spanOptions + ); + try { + const directoryClient = this.getDirectoryClient(directoryName); + return await directoryClient.delete({ ...options, spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -464,14 +523,25 @@ export class DirectoryClient extends StorageClient { public async createFile( fileName: string, size: number, - options?: FileCreateOptions + options: FileCreateOptions = {} ): Promise<{ fileClient: FileClient; fileCreateResponse: Models.FileCreateResponse }> { - const fileClient = this.getFileClient(fileName); - const fileCreateResponse = await fileClient.create(size, options); - return { - fileClient, - fileCreateResponse - }; + const { span, spanOptions } = createSpan("DirectoryClient-createFile", options.spanOptions); + try { + const fileClient = this.getFileClient(fileName); + const fileCreateResponse = await fileClient.create(size, { ...options, spanOptions }); + return { + fileClient, + fileCreateResponse + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -495,10 +565,21 @@ export class DirectoryClient extends StorageClient { */ public async deleteFile( fileName: string, - options?: FileDeleteOptions + options: FileDeleteOptions = {} ): Promise { - const fileClient = this.getFileClient(fileName); - return await fileClient.delete(options); + const { span, spanOptions } = createSpan("DirectoryClient-deleteFile", options.spanOptions); + try { + const fileClient = this.getFileClient(fileName); + return await fileClient.delete({ ...options, spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -525,9 +606,21 @@ export class DirectoryClient extends StorageClient { public async getProperties( options: DirectoryGetPropertiesOptions = {} ): Promise { - return this.context.getProperties({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("DirectoryClient-getProperties", options.spanOptions); + try { + return this.context.getProperties({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -542,9 +635,21 @@ export class DirectoryClient extends StorageClient { public async delete( options: DirectoryDeleteOptions = {} ): Promise { - return this.context.deleteMethod({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("DirectoryClient-delete", options.spanOptions); + try { + return this.context.deleteMethod({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -560,10 +665,22 @@ export class DirectoryClient extends StorageClient { metadata?: Metadata, options: DirectorySetMetadataOptions = {} ): Promise { - return this.context.setMetadata({ - abortSignal: options.abortSignal, - metadata - }); + const { span, spanOptions } = createSpan("DirectoryClient-setMetadata", options.spanOptions); + try { + return this.context.setMetadata({ + abortSignal: options.abortSignal, + metadata, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -752,10 +869,25 @@ export class DirectoryClient extends StorageClient { marker?: string, options: DirectoryListFilesAndDirectoriesSegmentOptions = {} ): Promise { - return this.context.listFilesAndDirectoriesSegment({ - marker, - ...options - }); + const { span, spanOptions } = createSpan( + "DirectoryClient-listFilesAndDirectoriesSegment", + options.spanOptions + ); + try { + return this.context.listFilesAndDirectoriesSegment({ + marker, + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -925,18 +1057,33 @@ export class DirectoryClient extends StorageClient { marker?: string, options: DirectoryListHandlesSegmentOptions = {} ): Promise { - marker = marker === "" ? undefined : marker; - const response = await this.context.listHandles({ - marker, - ...options - }); + const { span, spanOptions } = createSpan( + "DirectoryClient-listHandlesSegment", + options.spanOptions + ); + try { + marker = marker === "" ? undefined : marker; + const response = await this.context.listHandles({ + marker, + ...options, + spanOptions + }); - // TODO: Protocol layer issue that when handle list is in returned XML - // response.handleList is an empty string - if ((response.handleList as any) === "") { - response.handleList = undefined; + // TODO: Protocol layer issue that when handle list is in returned XML + // response.handleList is an empty string + if ((response.handleList as any) === "") { + response.handleList = undefined; + } + return response; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - return response; } /** * Force close all handles for a directory. @@ -957,11 +1104,26 @@ export class DirectoryClient extends StorageClient { marker?: string, options: DirectoryForceCloseHandlesSegmentOptions = {} ): Promise { - marker = marker === "" ? undefined : marker; - return this.context.forceCloseHandles("*", { - marker, - ...options - }); + const { span, spanOptions } = createSpan( + "DirectoryClient-forceCloseHandlesSegment", + options.spanOptions + ); + try { + marker = marker === "" ? undefined : marker; + return this.context.forceCloseHandles("*", { + marker, + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -980,14 +1142,29 @@ export class DirectoryClient extends StorageClient { handleId: string, options: DirectoryForceCloseHandlesOptions = {} ): Promise { - if (handleId === "*") { - throw new RangeError( - `Parameter handleID should be a specified handle ID. Use forceCloseHandlesSegment() to close all handles.` - ); - } + const { span, spanOptions } = createSpan( + "DirectoryClient-forceCloseHandle", + options.spanOptions + ); + try { + if (handleId === "*") { + throw new RangeError( + `Parameter handleID should be a specified handle ID. Use forceCloseHandlesSegment() to close all handles.` + ); + } - return this.context.forceCloseHandles(handleId, { - abortSignal: options.abortSignal - }); + return this.context.forceCloseHandles(handleId, { + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } } diff --git a/sdk/storage/storage-file/src/FileClient.ts b/sdk/storage/storage-file/src/FileClient.ts index 0159b8489dc3..679d906071bc 100644 --- a/sdk/storage/storage-file/src/FileClient.ts +++ b/sdk/storage/storage-file/src/FileClient.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import { HttpRequestBody, HttpResponse, isNode, TransferProgressEvent } from "@azure/core-http"; +import { CanonicalCode } from "@azure/core-tracing"; import { AbortSignalLike } from "@azure/abort-controller"; import { FileDownloadResponse } from "./FileDownloadResponse"; import * as Models from "./generated/src/models"; @@ -20,7 +21,7 @@ import { validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions } from "./models"; import { newPipeline, NewPipelineOptions, Pipeline } from "./Pipeline"; -import { StorageClient } from "./StorageClient"; +import { StorageClient, CommonOptions } from "./StorageClient"; import { DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS, FILE_MAX_SIZE_BYTES, @@ -35,6 +36,7 @@ import { streamToBuffer } from "./utils/utils.node"; import { AnonymousCredential } from "./credentials/AnonymousCredential"; import { readStreamToLocalFile, fsStat } from "./utils/utils.node"; import { FileSystemAttributes } from "./FileSystemAttributes"; +import { createSpan } from "./utils/tracing"; /** * Options to configure File - Create operation. @@ -42,7 +44,7 @@ import { FileSystemAttributes } from "./FileSystemAttributes"; * @export * @interface FileCreateOptions */ -export interface FileCreateOptions extends FileAndDirectoryCreateCommonOptions { +export interface FileCreateOptions extends FileAndDirectoryCreateCommonOptions, CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -68,7 +70,7 @@ export interface FileCreateOptions extends FileAndDirectoryCreateCommonOptions { metadata?: Metadata; } -export interface FileProperties extends FileAndDirectorySetPropertiesCommonOptions { +export interface FileProperties extends FileAndDirectorySetPropertiesCommonOptions, CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -94,7 +96,7 @@ export interface SetPropertiesResponse extends Models.FileSetHTTPHeadersResponse * @export * @interface FileDeleteOptions */ -export interface FileDeleteOptions { +export interface FileDeleteOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -111,7 +113,7 @@ export interface FileDeleteOptions { * @export * @interface FileDownloadOptions */ -export interface FileDownloadOptions { +export interface FileDownloadOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -161,7 +163,7 @@ export interface FileDownloadOptions { * @export * @interface FileUploadRangeOptions */ -export interface FileUploadRangeOptions { +export interface FileUploadRangeOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -197,7 +199,9 @@ export interface FileUploadRangeOptions { * @export * @interface FileUploadRangeFromURLOptions */ -export interface FileUploadRangeFromURLOptions extends Models.FileUploadRangeFromURLOptionalParams { +export interface FileUploadRangeFromURLOptions + extends Models.FileUploadRangeFromURLOptionalParams, + CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -213,7 +217,7 @@ export interface FileUploadRangeFromURLOptions extends Models.FileUploadRangeFro * While it's not ready to be used now, considering Crc64 of source content is * not accessible. */ -// export interface IFileUploadRangeFromURLOptions { +// export interface IFileUploadRangeFromURLOptions extends CommonOptions { // /** // * Crc64 of the source content. // * @@ -237,7 +241,7 @@ export interface FileUploadRangeFromURLOptions extends Models.FileUploadRangeFro * @export * @interface FileGetRangeListOptions */ -export interface FileGetRangeListOptions { +export interface FileGetRangeListOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -261,7 +265,7 @@ export interface FileGetRangeListOptions { * @export * @interface FileGetPropertiesOptions */ -export interface FileGetPropertiesOptions { +export interface FileGetPropertiesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -308,7 +312,7 @@ export type FileGetRangeListResponse = Models.FileGetRangeListHeaders & { * @export * @interface FileStartCopyOptions */ -export interface FileStartCopyOptions { +export interface FileStartCopyOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -332,7 +336,7 @@ export interface FileStartCopyOptions { * @export * @interface FileSetMetadataOptions */ -export interface FileSetMetadataOptions { +export interface FileSetMetadataOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -349,7 +353,9 @@ export interface FileSetMetadataOptions { * @export * @interface FileHTTPHeadersOptions */ -export interface FileSetHTTPHeadersOptions extends FileAndDirectorySetPropertiesCommonOptions { +export interface FileSetHTTPHeadersOptions + extends FileAndDirectorySetPropertiesCommonOptions, + CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -366,7 +372,7 @@ export interface FileSetHTTPHeadersOptions extends FileAndDirectorySetProperties * @export * @interface FileAbortCopyFromURLOptions */ -export interface FileAbortCopyFromURLOptions { +export interface FileAbortCopyFromURLOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -383,7 +389,9 @@ export interface FileAbortCopyFromURLOptions { * @export * @interface FileResizeOptions */ -export interface FileResizeOptions extends FileAndDirectorySetPropertiesCommonOptions { +export interface FileResizeOptions + extends FileAndDirectorySetPropertiesCommonOptions, + CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -400,7 +408,7 @@ export interface FileResizeOptions extends FileAndDirectorySetPropertiesCommonOp * @export * @interface FileClearRangeOptions */ -export interface FileClearRangeOptions { +export interface FileClearRangeOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -417,7 +425,7 @@ export interface FileClearRangeOptions { * @export * @interface FileListHandlesSegmentOptions */ -export interface FileListHandlesSegmentOptions { +export interface FileListHandlesSegmentOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -442,7 +450,7 @@ export interface FileListHandlesSegmentOptions { * @export * @interface FileForceCloseHandlesOptions */ -export interface FileForceCloseHandlesOptions { +export interface FileForceCloseHandlesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -458,7 +466,7 @@ export interface FileForceCloseHandlesOptions { * @export * @interface UploadStreamToAzureFileOptions */ -export interface UploadStreamToAzureFileOptions { +export interface UploadStreamToAzureFileOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -497,7 +505,7 @@ export interface UploadStreamToAzureFileOptions { * @export * @interface UploadToAzureFileOptions */ -export interface UploadToAzureFileOptions { +export interface UploadToAzureFileOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -554,7 +562,7 @@ export interface UploadToAzureFileOptions { * @export * @interface DownloadFromAzureFileOptions */ -export interface DownloadFromAzureFileOptions { +export interface DownloadFromAzureFileOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -690,33 +698,45 @@ export class FileClient extends StorageClient { size: number, options: FileCreateOptions = {} ): Promise { - if (size < 0 || size > FILE_MAX_SIZE_BYTES) { - throw new RangeError(`File size must >= 0 and < ${FILE_MAX_SIZE_BYTES}.`); - } - options = validateAndSetDefaultsForFileAndDirectoryCreateCommonOptions(options); + const { span, spanOptions } = createSpan("FileClient-create", options.spanOptions); + try { + if (size < 0 || size > FILE_MAX_SIZE_BYTES) { + throw new RangeError(`File size must >= 0 and < ${FILE_MAX_SIZE_BYTES}.`); + } + options = validateAndSetDefaultsForFileAndDirectoryCreateCommonOptions(options); - if (!options.fileAttributes) { - // Note: It would be Archive in service side if None is set. - const attributes: FileSystemAttributes = new FileSystemAttributes(); - attributes.none = true; - options.fileAttributes = attributes; - } + if (!options.fileAttributes) { + // Note: It would be Archive in service side if None is set. + const attributes: FileSystemAttributes = new FileSystemAttributes(); + attributes.none = true; + options.fileAttributes = attributes; + } - options.fileHTTPHeaders = options.fileHTTPHeaders || {}; + options.fileHTTPHeaders = options.fileHTTPHeaders || {}; - return this.context.create( - size, - fileAttributesToString(options.fileAttributes!), - fileCreationTimeToString(options.creationTime!), - fileLastWriteTimeToString(options.lastWriteTime!), - { - abortSignal: options.abortSignal, - fileHTTPHeaders: options.fileHTTPHeaders, - metadata: options.metadata, - filePermission: options.filePermission, - filePermissionKey: options.filePermissionKey - } - ); + return this.context.create( + size, + fileAttributesToString(options.fileAttributes!), + fileCreationTimeToString(options.creationTime!), + fileLastWriteTimeToString(options.lastWriteTime!), + { + abortSignal: options.abortSignal, + fileHTTPHeaders: options.fileHTTPHeaders, + metadata: options.metadata, + filePermission: options.filePermission, + filePermissionKey: options.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -738,67 +758,80 @@ export class FileClient extends StorageClient { count?: number, options: FileDownloadOptions = {} ): Promise { - if (options.rangeGetContentMD5 && offset === 0 && count === undefined) { - throw new RangeError(`rangeGetContentMD5 only works with partial data downloading`); - } + const { span, spanOptions } = createSpan("FileClient-download", options.spanOptions); + try { + if (options.rangeGetContentMD5 && offset === 0 && count === undefined) { + throw new RangeError(`rangeGetContentMD5 only works with partial data downloading`); + } - const downloadFullFile = offset === 0 && !count; - const res = await this.context.download({ - abortSignal: options.abortSignal, - onDownloadProgress: !isNode ? options.progress : undefined, - range: downloadFullFile ? undefined : rangeToString({ offset, count }), - rangeGetContentMD5: options.rangeGetContentMD5 - }); - - // Return browser response immediately - if (!isNode) { - return res; - } + const downloadFullFile = offset === 0 && !count; + const res = await this.context.download({ + abortSignal: options.abortSignal, + onDownloadProgress: !isNode ? options.progress : undefined, + range: downloadFullFile ? undefined : rangeToString({ offset, count }), + rangeGetContentMD5: options.rangeGetContentMD5, + spanOptions + }); - // We support retrying when download stream unexpected ends in Node.js runtime - // Following code shouldn't be bundled into browser build, however some - // bundlers may try to bundle following code and "FileReadResponse.ts". - // In this case, "FileDownloadResponse.browser.ts" will be used as a shim of "FileDownloadResponse.ts" - // The config is in package.json "browser" field - if (options.maxRetryRequests === undefined || options.maxRetryRequests < 0) { - // TODO: Default value or make it a required parameter? - options.maxRetryRequests = DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS; - } + // Return browser response immediately + if (!isNode) { + return res; + } - if (res.contentLength === undefined) { - throw new RangeError(`File download response doesn't contain valid content length header`); - } + // We support retrying when download stream unexpected ends in Node.js runtime + // Following code shouldn't be bundled into browser build, however some + // bundlers may try to bundle following code and "FileReadResponse.ts". + // In this case, "FileDownloadResponse.browser.ts" will be used as a shim of "FileDownloadResponse.ts" + // The config is in package.json "browser" field + if (options.maxRetryRequests === undefined || options.maxRetryRequests < 0) { + // TODO: Default value or make it a required parameter? + options.maxRetryRequests = DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS; + } - return new FileDownloadResponse( - res, - async (start: number): Promise => { - const updatedOptions: Models.FileDownloadOptionalParams = { - range: rangeToString({ - count: offset + res.contentLength! - start, - offset: start - }) - }; - - // Debug purpose only - // console.log( - // `Read from internal stream, range: ${ - // updatedOptions.range - // }, options: ${JSON.stringify(updatedOptions)}` - // ); - - return (await this.context.download({ - abortSignal: options.abortSignal, - ...updatedOptions - })).readableStreamBody!; - }, - offset, - res.contentLength!, - { - abortSignal: options.abortSignal, - maxRetryRequests: options.maxRetryRequests, - progress: options.progress + if (res.contentLength === undefined) { + throw new RangeError(`File download response doesn't contain valid content length header`); } - ); + + return new FileDownloadResponse( + res, + async (start: number): Promise => { + const updatedOptions: Models.FileDownloadOptionalParams = { + range: rangeToString({ + count: offset + res.contentLength! - start, + offset: start + }) + }; + + // Debug purpose only + // console.log( + // `Read from internal stream, range: ${ + // updatedOptions.range + // }, options: ${JSON.stringify(updatedOptions)}` + // ); + + return (await this.context.download({ + abortSignal: options.abortSignal, + ...updatedOptions, + spanOptions + })).readableStreamBody!; + }, + offset, + res.contentLength!, + { + abortSignal: options.abortSignal, + maxRetryRequests: options.maxRetryRequests, + progress: options.progress + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -813,9 +846,21 @@ export class FileClient extends StorageClient { public async getProperties( options: FileGetPropertiesOptions = {} ): Promise { - return this.context.getProperties({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("FileClient-getProperties", options.spanOptions); + try { + return this.context.getProperties({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -830,21 +875,33 @@ export class FileClient extends StorageClient { * @memberof FileClient */ public async setProperties(properties: FileProperties = {}): Promise { - properties = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(properties); - - properties.fileHTTPHeaders = properties.fileHTTPHeaders || {}; - - return this.context.setHTTPHeaders( - fileAttributesToString(properties.fileAttributes!), - fileCreationTimeToString(properties.creationTime!), - fileLastWriteTimeToString(properties.lastWriteTime!), - { - abortSignal: properties.abortSignal, - fileHTTPHeaders: properties.fileHTTPHeaders, - filePermission: properties.filePermission, - filePermissionKey: properties.filePermissionKey - } - ); + const { span, spanOptions } = createSpan("FileClient-setProperties", properties.spanOptions); + try { + properties = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(properties); + + properties.fileHTTPHeaders = properties.fileHTTPHeaders || {}; + + return this.context.setHTTPHeaders( + fileAttributesToString(properties.fileAttributes!), + fileCreationTimeToString(properties.creationTime!), + fileLastWriteTimeToString(properties.lastWriteTime!), + { + abortSignal: properties.abortSignal, + fileHTTPHeaders: properties.fileHTTPHeaders, + filePermission: properties.filePermission, + filePermissionKey: properties.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -866,9 +923,21 @@ export class FileClient extends StorageClient { * @memberof FileClient */ public async delete(options: FileDeleteOptions = {}): Promise { - return this.context.deleteMethod({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("FileClient-delete", options.spanOptions); + try { + return this.context.deleteMethod({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -888,19 +957,31 @@ export class FileClient extends StorageClient { fileHTTPHeaders: FileHTTPHeaders = {}, options: FileSetHTTPHeadersOptions = {} ): Promise { - // FileAttributes, filePermission, createTime, lastWriteTime will all be preserved - options = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(options); - return this.context.setHTTPHeaders( - fileAttributesToString(options.fileAttributes!), - fileCreationTimeToString(options.creationTime!), - fileLastWriteTimeToString(options.lastWriteTime!), - { - abortSignal: options.abortSignal, - fileHTTPHeaders: fileHTTPHeaders, - filePermission: options.filePermission, - filePermissionKey: options.filePermissionKey - } - ); + const { span, spanOptions } = createSpan("FileClient-setHTTPHeaders", options.spanOptions); + try { + // FileAttributes, filePermission, createTime, lastWriteTime will all be preserved + options = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(options); + return this.context.setHTTPHeaders( + fileAttributesToString(options.fileAttributes!), + fileCreationTimeToString(options.creationTime!), + fileLastWriteTimeToString(options.lastWriteTime!), + { + abortSignal: options.abortSignal, + fileHTTPHeaders: fileHTTPHeaders, + filePermission: options.filePermission, + filePermissionKey: options.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -919,23 +1000,35 @@ export class FileClient extends StorageClient { length: number, options: FileResizeOptions = {} ): Promise { - if (length < 0) { - throw new RangeError(`Size cannot less than 0 when resizing file.`); - } - // FileAttributes, filePermission, createTime, lastWriteTime will all be preserved. - options = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(options); - - return this.context.setHTTPHeaders( - fileAttributesToString(options.fileAttributes!), - fileCreationTimeToString(options.creationTime!), - fileLastWriteTimeToString(options.lastWriteTime!), - { - abortSignal: options.abortSignal, - fileContentLength: length, - filePermission: options.filePermission, - filePermissionKey: options.filePermissionKey + const { span, spanOptions } = createSpan("FileClient-resize", options.spanOptions); + try { + if (length < 0) { + throw new RangeError(`Size cannot less than 0 when resizing file.`); } - ); + // FileAttributes, filePermission, createTime, lastWriteTime will all be preserved. + options = validateAndSetDefaultsForFileAndDirectorySetPropertiesCommonOptions(options); + + return this.context.setHTTPHeaders( + fileAttributesToString(options.fileAttributes!), + fileCreationTimeToString(options.creationTime!), + fileLastWriteTimeToString(options.lastWriteTime!), + { + abortSignal: options.abortSignal, + fileContentLength: length, + filePermission: options.filePermission, + filePermissionKey: options.filePermissionKey, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -954,10 +1047,22 @@ export class FileClient extends StorageClient { metadata: Metadata = {}, options: FileSetMetadataOptions = {} ): Promise { - return this.context.setMetadata({ - abortSignal: options.abortSignal, - metadata - }); + const { span, spanOptions } = createSpan("FileClient-setMetadata", options.spanOptions); + try { + return this.context.setMetadata({ + abortSignal: options.abortSignal, + metadata, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -979,29 +1084,41 @@ export class FileClient extends StorageClient { contentLength: number, options: FileUploadRangeOptions = {} ): Promise { - if (offset < 0) { - throw new RangeError(`offset must be >= 0`); - } - - if (contentLength <= 0 || contentLength > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`contentLength must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); - } + const { span, spanOptions } = createSpan("FileClient-uploadRange", options.spanOptions); + try { + if (offset < 0) { + throw new RangeError(`offset must be >= 0`); + } - if (contentLength > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`offset must be < ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); - } + if (contentLength <= 0 || contentLength > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`contentLength must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); + } - return this.context.uploadRange( - rangeToString({ count: contentLength, offset }), - "update", - contentLength, - { - abortSignal: options.abortSignal, - contentMD5: options.contentMD5, - onUploadProgress: options.progress, - optionalbody: body + if (contentLength > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`offset must be < ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); } - ); + + return this.context.uploadRange( + rangeToString({ count: contentLength, offset }), + "update", + contentLength, + { + abortSignal: options.abortSignal, + contentMD5: options.contentMD5, + onUploadProgress: options.progress, + optionalbody: body, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1023,24 +1140,36 @@ export class FileClient extends StorageClient { count: number, options: FileUploadRangeFromURLOptions = {} ): Promise { - if (sourceOffset < 0 || destOffset < 0) { - throw new RangeError(`sourceOffset and destOffset must be >= 0`); - } - - if (count <= 0 || count > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`count must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); - } + const { span, spanOptions } = createSpan("FileClient-uploadRangeFromURL", options.spanOptions); + try { + if (sourceOffset < 0 || destOffset < 0) { + throw new RangeError(`sourceOffset and destOffset must be >= 0`); + } - return this.context.uploadRangeFromURL( - rangeToString({ offset: destOffset, count }), - sourceURL, - rangeToString({ offset: sourceOffset, count }), - 0, - { - abortSignal: options.abortSignal, - ...options + if (count <= 0 || count > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`count must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES} bytes`); } - ); + + return this.context.uploadRangeFromURL( + rangeToString({ offset: destOffset, count }), + sourceURL, + rangeToString({ offset: sourceOffset, count }), + 0, + { + abortSignal: options.abortSignal, + ...options, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** * Clears the specified range and @@ -1057,13 +1186,25 @@ export class FileClient extends StorageClient { contentLength: number, options: FileClearRangeOptions = {} ): Promise { - if (offset < 0 || contentLength <= 0) { - throw new RangeError(`offset must >= 0 and contentLength must be > 0`); - } + const { span, spanOptions } = createSpan("FileClient-clearRange", options.spanOptions); + try { + if (offset < 0 || contentLength <= 0) { + throw new RangeError(`offset must >= 0 and contentLength must be > 0`); + } - return this.context.uploadRange(rangeToString({ count: contentLength, offset }), "clear", 0, { - abortSignal: options.abortSignal - }); + return this.context.uploadRange(rangeToString({ count: contentLength, offset }), "clear", 0, { + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1076,23 +1217,35 @@ export class FileClient extends StorageClient { public async getRangeList( options: FileGetRangeListOptions = {} ): Promise { - const originalResponse = await this.context.getRangeList({ - abortSignal: options.abortSignal, - range: options.range ? rangeToString(options.range) : undefined - }); - return { - _response: originalResponse._response, - date: originalResponse.date, - eTag: originalResponse.eTag, - errorCode: originalResponse.errorCode, - fileContentLength: originalResponse.fileContentLength, - lastModified: originalResponse.lastModified, - rangeList: originalResponse.filter(() => { - return true; - }), - requestId: originalResponse.requestId, - version: originalResponse.version - }; + const { span, spanOptions } = createSpan("FileClient-getRangeList", options.spanOptions); + try { + const originalResponse = await this.context.getRangeList({ + abortSignal: options.abortSignal, + range: options.range ? rangeToString(options.range) : undefined, + spanOptions + }); + return { + _response: originalResponse._response, + date: originalResponse.date, + eTag: originalResponse.eTag, + errorCode: originalResponse.errorCode, + fileContentLength: originalResponse.fileContentLength, + lastModified: originalResponse.lastModified, + rangeList: originalResponse.filter(() => { + return true; + }), + requestId: originalResponse.requestId, + version: originalResponse.version + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1113,10 +1266,22 @@ export class FileClient extends StorageClient { copySource: string, options: FileStartCopyOptions = {} ): Promise { - return this.context.startCopy(copySource, { - abortSignal: options.abortSignal, - metadata: options.metadata - }); + const { span, spanOptions } = createSpan("FileClient-startCopyFromURL", options.spanOptions); + try { + return this.context.startCopy(copySource, { + abortSignal: options.abortSignal, + metadata: options.metadata, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1133,9 +1298,21 @@ export class FileClient extends StorageClient { copyId: string, options: FileAbortCopyFromURLOptions = {} ): Promise { - return this.context.abortCopy(copyId, { - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("FileClient-abortCopyFromURL", options.spanOptions); + try { + return this.context.abortCopy(copyId, { + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } // High Level functions @@ -1153,14 +1330,25 @@ export class FileClient extends StorageClient { browserData: Blob | ArrayBuffer | ArrayBufferView, options: UploadToAzureFileOptions = {} ): Promise { - const browserBlob = new Blob([browserData]); - return this.UploadSeekableBlob( - (offset: number, size: number): Blob => { - return browserBlob.slice(offset, offset + size); - }, - browserBlob.size, - options - ); + const { span, spanOptions } = createSpan("FileClient-uploadBrowserData", options.spanOptions); + try { + const browserBlob = new Blob([browserData]); + return this.UploadSeekableBlob( + (offset: number, size: number): Blob => { + return browserBlob.slice(offset, offset + size); + }, + browserBlob.size, + { ...options, spanOptions } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1179,54 +1367,67 @@ export class FileClient extends StorageClient { size: number, options: UploadToAzureFileOptions = {} ): Promise { - if (!options.rangeSize) { - options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; - } - if (options.rangeSize < 0 || options.rangeSize > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`options.rangeSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); - } + const { span, spanOptions } = createSpan("FileClient-UploadSeekableBlob", options.spanOptions); + try { + if (!options.rangeSize) { + options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; + } + if (options.rangeSize < 0 || options.rangeSize > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`options.rangeSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); + } - if (!options.fileHTTPHeaders) { - options.fileHTTPHeaders = {}; - } + if (!options.fileHTTPHeaders) { + options.fileHTTPHeaders = {}; + } - if (!options.parallelism) { - options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; - } - if (options.parallelism < 0) { - throw new RangeError(`options.parallelism cannot less than 0.`); - } + if (!options.parallelism) { + options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; + } + if (options.parallelism < 0) { + throw new RangeError(`options.parallelism cannot less than 0.`); + } - // Create the file - await this.create(size, { - abortSignal: options.abortSignal, - fileHTTPHeaders: options.fileHTTPHeaders, - metadata: options.metadata - }); - - const numBlocks: number = Math.floor((size - 1) / options.rangeSize) + 1; - let transferProgress: number = 0; - - const batch = new Batch(options.parallelism); - for (let i = 0; i < numBlocks; i++) { - batch.addOperation( - async (): Promise => { - const start = options.rangeSize! * i; - const end = i === numBlocks - 1 ? size : start + options.rangeSize!; - const contentLength = end - start; - await this.uploadRange(blobFactory(start, contentLength), start, contentLength, { - abortSignal: options.abortSignal - }); - // Update progress after block is successfully uploaded to server, in case of block trying - // TODO: Hook with convenience layer progress event in finer level - transferProgress += contentLength; - if (options.progress) { - options.progress({ loadedBytes: transferProgress }); + // Create the file + await this.create(size, { + abortSignal: options.abortSignal, + fileHTTPHeaders: options.fileHTTPHeaders, + metadata: options.metadata, + spanOptions + }); + + const numBlocks: number = Math.floor((size - 1) / options.rangeSize) + 1; + let transferProgress: number = 0; + + const batch = new Batch(options.parallelism); + for (let i = 0; i < numBlocks; i++) { + batch.addOperation( + async (): Promise => { + const start = options.rangeSize! * i; + const end = i === numBlocks - 1 ? size : start + options.rangeSize!; + const contentLength = end - start; + await this.uploadRange(blobFactory(start, contentLength), start, contentLength, { + abortSignal: options.abortSignal, + spanOptions + }); + // Update progress after block is successfully uploaded to server, in case of block trying + // TODO: Hook with convenience layer progress event in finer level + transferProgress += contentLength; + if (options.progress) { + options.progress({ loadedBytes: transferProgress }); + } } - } - ); + ); + } + return batch.do(); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - return batch.do(); } /** @@ -1239,18 +1440,29 @@ export class FileClient extends StorageClient { * @param {UploadToAzureFileOptions} [options] * @returns {(Promise)} */ - public async uploadFile(filePath: string, options?: UploadToAzureFileOptions): Promise { - const size = (await fsStat(filePath)).size; - return this.uploadResetableStream( - (offset, count) => - fs.createReadStream(filePath, { - autoClose: true, - end: count ? offset + count - 1 : Infinity, - start: offset - }), - size, - options - ); + public async uploadFile(filePath: string, options: UploadToAzureFileOptions = {}): Promise { + const { span, spanOptions } = createSpan("FileClient-uploadFile", options.spanOptions); + try { + const size = (await fsStat(filePath)).size; + return this.uploadResetableStream( + (offset, count) => + fs.createReadStream(filePath, { + autoClose: true, + end: count ? offset + count - 1 : Infinity, + start: offset + }), + size, + { ...options, spanOptions } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1273,53 +1485,74 @@ export class FileClient extends StorageClient { size: number, options: UploadToAzureFileOptions = {} ): Promise { - if (!options.rangeSize) { - options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; - } - if (options.rangeSize < 0 || options.rangeSize > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`options.rangeSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); - } + const { span, spanOptions } = createSpan( + "FileClient-uploadResetableStream", + options.spanOptions + ); + try { + if (!options.rangeSize) { + options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; + } + if (options.rangeSize < 0 || options.rangeSize > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`options.rangeSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); + } - if (!options.fileHTTPHeaders) { - options.fileHTTPHeaders = {}; - } + if (!options.fileHTTPHeaders) { + options.fileHTTPHeaders = {}; + } - if (!options.parallelism) { - options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; - } - if (options.parallelism < 0) { - throw new RangeError(`options.parallelism cannot less than 0.`); - } + if (!options.parallelism) { + options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; + } + if (options.parallelism < 0) { + throw new RangeError(`options.parallelism cannot less than 0.`); + } - // Create the file - await this.create(size, { - abortSignal: options.abortSignal, - fileHTTPHeaders: options.fileHTTPHeaders, - metadata: options.metadata - }); - - const numBlocks: number = Math.floor((size - 1) / options.rangeSize) + 1; - let transferProgress: number = 0; - const batch = new Batch(options.parallelism); - - for (let i = 0; i < numBlocks; i++) { - batch.addOperation( - async (): Promise => { - const start = options.rangeSize! * i; - const end = i === numBlocks - 1 ? size : start + options.rangeSize!; - const contentLength = end - start; - await this.uploadRange(() => streamFactory(start, contentLength), start, contentLength, { - abortSignal: options.abortSignal - }); - // Update progress after block is successfully uploaded to server, in case of block trying - transferProgress += contentLength; - if (options.progress) { - options.progress({ loadedBytes: transferProgress }); + // Create the file + await this.create(size, { + abortSignal: options.abortSignal, + fileHTTPHeaders: options.fileHTTPHeaders, + metadata: options.metadata, + spanOptions + }); + + const numBlocks: number = Math.floor((size - 1) / options.rangeSize) + 1; + let transferProgress: number = 0; + const batch = new Batch(options.parallelism); + + for (let i = 0; i < numBlocks; i++) { + batch.addOperation( + async (): Promise => { + const start = options.rangeSize! * i; + const end = i === numBlocks - 1 ? size : start + options.rangeSize!; + const contentLength = end - start; + await this.uploadRange( + () => streamFactory(start, contentLength), + start, + contentLength, + { + abortSignal: options.abortSignal, + spanOptions + } + ); + // Update progress after block is successfully uploaded to server, in case of block trying + transferProgress += contentLength; + if (options.progress) { + options.progress({ loadedBytes: transferProgress }); + } } - } - ); + ); + } + return batch.do(); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - return batch.do(); } /** @@ -1340,70 +1573,85 @@ export class FileClient extends StorageClient { count?: number, options: DownloadFromAzureFileOptions = {} ): Promise { - if (!options.rangeSize) { - options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; - } - if (options.rangeSize < 0) { - throw new RangeError("rangeSize option must be > 0"); - } + const { span, spanOptions } = createSpan("FileClient-downloadToBuffer", options.spanOptions); + try { + if (!options.rangeSize) { + options.rangeSize = FILE_RANGE_MAX_SIZE_BYTES; + } + if (options.rangeSize < 0) { + throw new RangeError("rangeSize option must be > 0"); + } - if (offset < 0) { - throw new RangeError("offset option must be >= 0"); - } + if (offset < 0) { + throw new RangeError("offset option must be >= 0"); + } - if (count && count <= 0) { - throw new RangeError("count option must be > 0"); - } + if (count && count <= 0) { + throw new RangeError("count option must be > 0"); + } - if (!options.parallelism) { - options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; - } - if (options.parallelism < 0) { - throw new RangeError(`options.parallelism cannot less than 0.`); - } + if (!options.parallelism) { + options.parallelism = DEFAULT_HIGH_LEVEL_PARALLELISM; + } + if (options.parallelism < 0) { + throw new RangeError(`options.parallelism cannot less than 0.`); + } + + // Customer doesn't specify length, get it + if (!count) { + const response = await this.getProperties({ + abortSignal: options.abortSignal, + spanOptions + }); + count = response.contentLength! - offset; + if (count < 0) { + throw new RangeError( + `offset ${offset} shouldn't be larger than file size ${response.contentLength!}` + ); + } + } - // Customer doesn't specify length, get it - if (!count) { - const response = await this.getProperties({ abortSignal: options.abortSignal }); - count = response.contentLength! - offset; - if (count < 0) { + if (buffer.length < count) { throw new RangeError( - `offset ${offset} shouldn't be larger than file size ${response.contentLength!}` + `The buffer's size should be equal to or larger than the request count of bytes: ${count}` ); } - } - - if (buffer.length < count) { - throw new RangeError( - `The buffer's size should be equal to or larger than the request count of bytes: ${count}` - ); - } - let transferProgress: number = 0; - const batch = new Batch(options.parallelism); - for (let off = offset; off < offset + count; off = off + options.rangeSize) { - batch.addOperation(async () => { - // Exclusive chunk end position - let chunkEnd = offset + count!; - if (off + options.rangeSize! < chunkEnd) { - chunkEnd = off + options.rangeSize!; - } - const response = await this.download(off, chunkEnd - off, { - abortSignal: options.abortSignal, - maxRetryRequests: options.maxRetryRequestsPerRange + let transferProgress: number = 0; + const batch = new Batch(options.parallelism); + for (let off = offset; off < offset + count; off = off + options.rangeSize) { + batch.addOperation(async () => { + // Exclusive chunk end position + let chunkEnd = offset + count!; + if (off + options.rangeSize! < chunkEnd) { + chunkEnd = off + options.rangeSize!; + } + const response = await this.download(off, chunkEnd - off, { + abortSignal: options.abortSignal, + maxRetryRequests: options.maxRetryRequestsPerRange, + spanOptions + }); + const stream = response.readableStreamBody!; + await streamToBuffer(stream, buffer, off - offset, chunkEnd - offset); + // Update progress after block is downloaded, in case of block trying + // Could provide finer grained progress updating inside HTTP requests, + // only if convenience layer download try is enabled + transferProgress += chunkEnd - off; + if (options.progress) { + options.progress({ loadedBytes: transferProgress }); + } }); - const stream = response.readableStreamBody!; - await streamToBuffer(stream, buffer, off - offset, chunkEnd - offset); - // Update progress after block is downloaded, in case of block trying - // Could provide finer grained progress updating inside HTTP requests, - // only if convenience layer download try is enabled - transferProgress += chunkEnd - off; - if (options.progress) { - options.progress({ loadedBytes: transferProgress }); - } + } + await batch.do(); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message }); + throw e; + } finally { + span.end(); } - await batch.do(); } /** @@ -1435,55 +1683,68 @@ export class FileClient extends StorageClient { maxBuffers: number, options: UploadStreamToAzureFileOptions = {} ): Promise { - if (!options.fileHTTPHeaders) { - options.fileHTTPHeaders = {}; - } + const { span, spanOptions } = createSpan("FileClient-uploadStream", options.spanOptions); + try { + if (!options.fileHTTPHeaders) { + options.fileHTTPHeaders = {}; + } - if (bufferSize <= 0 || bufferSize > FILE_RANGE_MAX_SIZE_BYTES) { - throw new RangeError(`bufferSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); - } + if (bufferSize <= 0 || bufferSize > FILE_RANGE_MAX_SIZE_BYTES) { + throw new RangeError(`bufferSize must be > 0 and <= ${FILE_RANGE_MAX_SIZE_BYTES}`); + } - if (maxBuffers < 0) { - throw new RangeError(`maxBuffers must be > 0.`); - } + if (maxBuffers < 0) { + throw new RangeError(`maxBuffers must be > 0.`); + } - // Create the file - await this.create(size, { - abortSignal: options.abortSignal, - fileHTTPHeaders: options.fileHTTPHeaders, - metadata: options.metadata - }); - - let transferProgress: number = 0; - const scheduler = new BufferScheduler( - stream, - bufferSize, - maxBuffers, - async (buffer: Buffer, offset?: number) => { - if (transferProgress + buffer.length > size) { - throw new RangeError( - `Stream size is larger than file size ${size} bytes, uploading failed. ` + - `Please make sure stream length is less or equal than file size.` - ); - } + // Create the file + await this.create(size, { + abortSignal: options.abortSignal, + fileHTTPHeaders: options.fileHTTPHeaders, + metadata: options.metadata, + spanOptions + }); - await this.uploadRange(buffer, offset!, buffer.length, { - abortSignal: options.abortSignal - }); + let transferProgress: number = 0; + const scheduler = new BufferScheduler( + stream, + bufferSize, + maxBuffers, + async (buffer: Buffer, offset?: number) => { + if (transferProgress + buffer.length > size) { + throw new RangeError( + `Stream size is larger than file size ${size} bytes, uploading failed. ` + + `Please make sure stream length is less or equal than file size.` + ); + } - // Update progress after block is successfully uploaded to server, in case of block trying - transferProgress += buffer.length; - if (options.progress) { - options.progress({ loadedBytes: transferProgress }); - } - }, - // Parallelism should set a smaller value than maxBuffers, which is helpful to - // reduce the possibility when a outgoing handler waits for stream data, in - // this situation, outgoing handlers are blocked. - // Outgoing queue shouldn't be empty. - Math.ceil((maxBuffers / 4) * 3) - ); - return scheduler.do(); + await this.uploadRange(buffer, offset!, buffer.length, { + abortSignal: options.abortSignal, + spanOptions + }); + + // Update progress after block is successfully uploaded to server, in case of block trying + transferProgress += buffer.length; + if (options.progress) { + options.progress({ loadedBytes: transferProgress }); + } + }, + // Parallelism should set a smaller value than maxBuffers, which is helpful to + // reduce the possibility when a outgoing handler waits for stream data, in + // this situation, outgoing handlers are blocked. + // Outgoing queue shouldn't be empty. + Math.ceil((maxBuffers / 4) * 3) + ); + return scheduler.do(); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1507,16 +1768,27 @@ export class FileClient extends StorageClient { filePath: string, offset: number = 0, count?: number, - options?: FileDownloadOptions + options: FileDownloadOptions = {} ): Promise { - const response = await this.download(offset, count, options); - if (response.readableStreamBody) { - await readStreamToLocalFile(response.readableStreamBody, filePath); - } + const { span, spanOptions } = createSpan("FileClient-downloadToFile", options.spanOptions); + try { + const response = await this.download(offset, count, { ...options, spanOptions }); + if (response.readableStreamBody) { + await readStreamToLocalFile(response.readableStreamBody, filePath); + } - // The stream is no longer accessible so setting it to undefined. - (response as any).fileDownloadStream = undefined; - return response; + // The stream is no longer accessible so setting it to undefined. + (response as any).fileDownloadStream = undefined; + return response; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1536,19 +1808,31 @@ export class FileClient extends StorageClient { marker?: string, options: FileListHandlesSegmentOptions = {} ): Promise { - marker = marker === "" ? undefined : marker; - const response = await this.context.listHandles({ - abortSignal: options.abortSignal, - marker, - ...options - }); - - // TODO: Protocol layer issue that when handle list is in returned XML - // response.handleList is an empty string - if ((response.handleList as any) === "") { - response.handleList = undefined; + const { span, spanOptions } = createSpan("FileClient-listHandlesSegment", options.spanOptions); + try { + marker = marker === "" ? undefined : marker; + const response = await this.context.listHandles({ + abortSignal: options.abortSignal, + marker, + ...options, + spanOptions + }); + + // TODO: Protocol layer issue that when handle list is in returned XML + // response.handleList is an empty string + if ((response.handleList as any) === "") { + response.handleList = undefined; + } + return response; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - return response; } /** @@ -1567,11 +1851,26 @@ export class FileClient extends StorageClient { marker?: string, options: FileForceCloseHandlesOptions = {} ): Promise { - marker = marker === "" ? undefined : marker; - return this.context.forceCloseHandles("*", { - abortSignal: options.abortSignal, - marker - }); + const { span, spanOptions } = createSpan( + "FileClient-forceCloseHandlesSegment", + options.spanOptions + ); + try { + marker = marker === "" ? undefined : marker; + return this.context.forceCloseHandles("*", { + abortSignal: options.abortSignal, + marker, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -1589,14 +1888,26 @@ export class FileClient extends StorageClient { handleId: string, options: FileForceCloseHandlesOptions = {} ): Promise { - if (handleId === "*") { - throw new RangeError( - `Parameter handleID should be a specified handle ID. Use forceCloseHandlesSegment() to close all handles.` - ); - } + const { span, spanOptions } = createSpan("FileClient-forceCloseHandle", options.spanOptions); + try { + if (handleId === "*") { + throw new RangeError( + `Parameter handleID should be a specified handle ID. Use forceCloseHandlesSegment() to close all handles.` + ); + } - return this.context.forceCloseHandles(handleId, { - abortSignal: options.abortSignal - }); + return this.context.forceCloseHandles(handleId, { + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } } diff --git a/sdk/storage/storage-file/src/FileServiceClient.ts b/sdk/storage/storage-file/src/FileServiceClient.ts index d179b1fb156c..9f55b6b52b53 100644 --- a/sdk/storage/storage-file/src/FileServiceClient.ts +++ b/sdk/storage/storage-file/src/FileServiceClient.ts @@ -5,7 +5,7 @@ import { AbortSignalLike } from "@azure/abort-controller"; import * as Models from "./generated/src/models"; import { Service } from "./generated/src/operations"; import { newPipeline, NewPipelineOptions, Pipeline } from "./Pipeline"; -import { StorageClient } from "./StorageClient"; +import { StorageClient, CommonOptions } from "./StorageClient"; import { ShareClient, ShareCreateOptions, ShareDeleteMethodOptions } from "./ShareClient"; import { appendToURLPath, extractConnectionStringParts } from "./utils/utils.common"; import { Credential } from "./credentials/Credential"; @@ -14,13 +14,15 @@ import { AnonymousCredential } from "./credentials/AnonymousCredential"; import "@azure/core-paging"; import { PagedAsyncIterableIterator, PageSettings } from "@azure/core-paging"; import { isNode } from "@azure/core-http"; +import { CanonicalCode } from "@azure/core-tracing"; +import { createSpan } from "./utils/tracing"; /** * Options to configure List Shares Segment operation. * * @interface ServiceListSharesSegmentOptions */ -interface ServiceListSharesSegmentOptions { +interface ServiceListSharesSegmentOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -64,7 +66,7 @@ interface ServiceListSharesSegmentOptions { * @export * @interface ServiceListSharesOptions */ -export interface ServiceListSharesOptions { +export interface ServiceListSharesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -97,7 +99,7 @@ export interface ServiceListSharesOptions { * @export * @interface ServiceGetPropertiesOptions */ -export interface ServiceGetPropertiesOptions { +export interface ServiceGetPropertiesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -114,7 +116,7 @@ export interface ServiceGetPropertiesOptions { * @export * @interface ServiceSetPropertiesOptions */ -export interface ServiceSetPropertiesOptions { +export interface ServiceSetPropertiesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -245,14 +247,25 @@ export class FileServiceClient extends StorageClient { */ public async createShare( shareName: string, - options?: ShareCreateOptions + options: ShareCreateOptions = {} ): Promise<{ shareCreateResponse: Models.ShareCreateResponse; shareClient: ShareClient }> { - const shareClient = this.getShareClient(shareName); - const shareCreateResponse = await shareClient.create(options); - return { - shareCreateResponse, - shareClient - }; + const { span, spanOptions } = createSpan("FileServiceClient-createShare", options.spanOptions); + try { + const shareClient = this.getShareClient(shareName); + const shareCreateResponse = await shareClient.create({ ...options, spanOptions }); + return { + shareCreateResponse, + shareClient + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -265,10 +278,21 @@ export class FileServiceClient extends StorageClient { */ public async deleteShare( shareName: string, - options?: ShareDeleteMethodOptions + options: ShareDeleteMethodOptions = {} ): Promise { - const shareClient = this.getShareClient(shareName); - return await shareClient.delete(options); + const { span, spanOptions } = createSpan("FileServiceClient-deleteShare", options.spanOptions); + try { + const shareClient = this.getShareClient(shareName); + return await shareClient.delete({ ...options, spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -283,9 +307,24 @@ export class FileServiceClient extends StorageClient { public async getProperties( options: ServiceGetPropertiesOptions = {} ): Promise { - return this.serviceContext.getProperties({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan( + "FileServiceClient-getProperties", + options.spanOptions + ); + try { + return this.serviceContext.getProperties({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -302,9 +341,24 @@ export class FileServiceClient extends StorageClient { properties: Models.StorageServiceProperties, options: ServiceSetPropertiesOptions = {} ): Promise { - return this.serviceContext.setProperties(properties, { - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan( + "FileServiceClient-setProperties", + options.spanOptions + ); + try { + return this.serviceContext.setProperties(properties, { + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -469,9 +523,24 @@ export class FileServiceClient extends StorageClient { marker?: string, options: ServiceListSharesSegmentOptions = {} ): Promise { - return this.serviceContext.listSharesSegment({ - marker, - ...options - }); + const { span, spanOptions } = createSpan( + "FileServiceClient-listSharesSegment", + options.spanOptions + ); + try { + return this.serviceContext.listSharesSegment({ + marker, + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } } diff --git a/sdk/storage/storage-file/src/ShareClient.ts b/sdk/storage/storage-file/src/ShareClient.ts index 1c3d6d8286c6..667fcf8de6fa 100644 --- a/sdk/storage/storage-file/src/ShareClient.ts +++ b/sdk/storage/storage-file/src/ShareClient.ts @@ -2,12 +2,13 @@ // Licensed under the MIT License. import { HttpResponse, isNode } from "@azure/core-http"; +import { CanonicalCode } from "@azure/core-tracing"; import { AbortSignalLike } from "@azure/abort-controller"; import * as Models from "./generated/src/models"; import { Share } from "./generated/src/operations"; import { Metadata } from "./models"; import { newPipeline, NewPipelineOptions, Pipeline } from "./Pipeline"; -import { StorageClient } from "./StorageClient"; +import { StorageClient, CommonOptions } from "./StorageClient"; import { URLConstants } from "./utils/constants"; import { appendToURLPath, @@ -20,6 +21,7 @@ import { FileCreateOptions, FileDeleteOptions, FileClient } from "./FileClient"; import { Credential } from "./credentials/Credential"; import { SharedKeyCredential } from "./credentials/SharedKeyCredential"; import { AnonymousCredential } from "./credentials/AnonymousCredential"; +import { createSpan } from "./utils/tracing"; /** * Options to configure Share - Create operation. @@ -27,7 +29,7 @@ import { AnonymousCredential } from "./credentials/AnonymousCredential"; * @export * @interface ShareCreateOptions */ -export interface ShareCreateOptions { +export interface ShareCreateOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -60,7 +62,7 @@ export interface ShareCreateOptions { * @export * @interface ShareDeleteMethodOptions */ -export interface ShareDeleteMethodOptions { +export interface ShareDeleteMethodOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -86,7 +88,7 @@ export interface ShareDeleteMethodOptions { * @export * @interface ShareSetMetadataOptions */ -export interface ShareSetMetadataOptions { +export interface ShareSetMetadataOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -103,7 +105,7 @@ export interface ShareSetMetadataOptions { * @export * @interface ShareSetAccessPolicyOptions */ -export interface ShareSetAccessPolicyOptions { +export interface ShareSetAccessPolicyOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -120,7 +122,7 @@ export interface ShareSetAccessPolicyOptions { * @export * @interface ShareGetAccessPolicyOptions */ -export interface ShareGetAccessPolicyOptions { +export interface ShareGetAccessPolicyOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -137,7 +139,7 @@ export interface ShareGetAccessPolicyOptions { * @export * @interface ShareGetPropertiesOptions */ -export interface ShareGetPropertiesOptions { +export interface ShareGetPropertiesOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -154,7 +156,7 @@ export interface ShareGetPropertiesOptions { * @export * @interface ShareSetQuotaOptions */ -export interface ShareSetQuotaOptions { +export interface ShareSetQuotaOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -171,7 +173,7 @@ export interface ShareSetQuotaOptions { * @export * @interface ShareGetStatisticsOptions */ -export interface ShareGetStatisticsOptions { +export interface ShareGetStatisticsOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -241,7 +243,7 @@ export declare type ShareGetAccessPolicyResponse = { * @export * @interface ShareCreateSnapshotOptions */ -export interface ShareCreateSnapshotOptions { +export interface ShareCreateSnapshotOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -265,7 +267,7 @@ export interface ShareCreateSnapshotOptions { * @export * @interface ShareCreatePermissionOptions */ -export interface ShareCreatePermissionOptions { +export interface ShareCreatePermissionOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -281,7 +283,7 @@ export interface ShareCreatePermissionOptions { * @export * @interface ShareGetPermissionOptions */ -export interface ShareGetPermissionOptions { +export interface ShareGetPermissionOptions extends CommonOptions { /** * An implementation of the `AbortSignalLike` interface to signal the request to cancel the operation. * For example, use the @azure/abort-controller to create an `AbortSignal`. @@ -442,9 +444,21 @@ export class ShareClient extends StorageClient { * @memberof ShareClient */ public async create(options: ShareCreateOptions = {}): Promise { - return this.context.create({ - ...options - }); + const { span, spanOptions } = createSpan("ShareClient-create", options.spanOptions); + try { + return this.context.create({ + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -484,17 +498,28 @@ export class ShareClient extends StorageClient { */ public async createDirectory( directoryName: string, - options?: DirectoryCreateOptions + options: DirectoryCreateOptions = {} ): Promise<{ directoryClient: DirectoryClient; directoryCreateResponse: Models.DirectoryCreateResponse; }> { - const directoryClient = this.getDirectoryClient(directoryName); - const directoryCreateResponse = await directoryClient.create(options); - return { - directoryClient, - directoryCreateResponse - }; + const { span, spanOptions } = createSpan("ShareClient-createDirectory", options.spanOptions); + try { + const directoryClient = this.getDirectoryClient(directoryName); + const directoryCreateResponse = await directoryClient.create({ ...options, spanOptions }); + return { + directoryClient, + directoryCreateResponse + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -509,10 +534,21 @@ export class ShareClient extends StorageClient { */ public async deleteDirectory( directoryName: string, - options?: DirectoryDeleteOptions + options: DirectoryDeleteOptions = {} ): Promise { - const directoryClient = this.getDirectoryClient(directoryName); - return await directoryClient.delete(options); + const { span, spanOptions } = createSpan("ShareClient-deleteDirectory", options.spanOptions); + try { + const directoryClient = this.getDirectoryClient(directoryName); + return await directoryClient.delete({ ...options, spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -529,15 +565,26 @@ export class ShareClient extends StorageClient { public async createFile( fileName: string, size: number, - options?: FileCreateOptions + options: FileCreateOptions = {} ): Promise<{ fileClient: FileClient; fileCreateResponse: Models.FileCreateResponse }> { - const directoryClient = this.rootDirectoryClient; - const fileClient = directoryClient.getFileClient(fileName); - const fileCreateResponse = await fileClient.create(size, options); - return { - fileClient, - fileCreateResponse - }; + const { span, spanOptions } = createSpan("ShareClient-createFile", options.spanOptions); + try { + const directoryClient = this.rootDirectoryClient; + const fileClient = directoryClient.getFileClient(fileName); + const fileCreateResponse = await fileClient.create(size, { ...options, spanOptions }); + return { + fileClient, + fileCreateResponse + }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -562,11 +609,22 @@ export class ShareClient extends StorageClient { */ public async deleteFile( fileName: string, - options?: FileDeleteOptions + options: FileDeleteOptions = {} ): Promise { - const directoryClient = this.rootDirectoryClient; - const fileClient = directoryClient.getFileClient(fileName); - return await fileClient.delete(options); + const { span, spanOptions } = createSpan("ShareClient-deleteFile", options.spanOptions); + try { + const directoryClient = this.rootDirectoryClient; + const fileClient = directoryClient.getFileClient(fileName); + return await fileClient.delete({ ...options, spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -580,9 +638,21 @@ export class ShareClient extends StorageClient { public async getProperties( options: ShareGetPropertiesOptions = {} ): Promise { - return this.context.getProperties({ - abortSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("ShareClient-getProperties", options.spanOptions); + try { + return this.context.getProperties({ + abortSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -595,9 +665,21 @@ export class ShareClient extends StorageClient { * @memberof ShareClient */ public async delete(options: ShareDeleteMethodOptions = {}): Promise { - return this.context.deleteMethod({ - ...options - }); + const { span, spanOptions } = createSpan("ShareClient-delete", options.spanOptions); + try { + return this.context.deleteMethod({ + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -616,10 +698,22 @@ export class ShareClient extends StorageClient { metadata?: Metadata, options: ShareSetMetadataOptions = {} ): Promise { - return this.context.setMetadata({ - abortSignal: options.abortSignal, - metadata - }); + const { span, spanOptions } = createSpan("ShareClient-setMetadata", options.spanOptions); + try { + return this.context.setMetadata({ + abortSignal: options.abortSignal, + metadata, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -638,32 +732,44 @@ export class ShareClient extends StorageClient { public async getAccessPolicy( options: ShareGetAccessPolicyOptions = {} ): Promise { - const response = await this.context.getAccessPolicy({ - abortSignal: options.abortSignal - }); - - const res: ShareGetAccessPolicyResponse = { - _response: response._response, - date: response.date, - eTag: response.eTag, - lastModified: response.lastModified, - requestId: response.requestId, - signedIdentifiers: [], - version: response.version - }; + const { span, spanOptions } = createSpan("ShareClient-getAccessPolicy", options.spanOptions); + try { + const response = await this.context.getAccessPolicy({ + abortSignal: options.abortSignal, + spanOptions + }); - for (const identifier of response) { - res.signedIdentifiers.push({ - accessPolicy: { - expiry: new Date(identifier.accessPolicy!.expiry!), - permission: identifier.accessPolicy!.permission!, - start: new Date(identifier.accessPolicy!.start!) - }, - id: identifier.id + const res: ShareGetAccessPolicyResponse = { + _response: response._response, + date: response.date, + eTag: response.eTag, + lastModified: response.lastModified, + requestId: response.requestId, + signedIdentifiers: [], + version: response.version + }; + + for (const identifier of response) { + res.signedIdentifiers.push({ + accessPolicy: { + expiry: new Date(identifier.accessPolicy!.expiry!), + permission: identifier.accessPolicy!.permission!, + start: new Date(identifier.accessPolicy!.start!) + }, + id: identifier.id + }); + } + + return res; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message }); + throw e; + } finally { + span.end(); } - - return res; } /** @@ -684,22 +790,34 @@ export class ShareClient extends StorageClient { shareAcl?: SignedIdentifier[], options: ShareSetAccessPolicyOptions = {} ): Promise { - const acl: Models.SignedIdentifier[] = []; - for (const identifier of shareAcl || []) { - acl.push({ - accessPolicy: { - expiry: truncatedISO8061Date(identifier.accessPolicy.expiry), - permission: identifier.accessPolicy.permission, - start: truncatedISO8061Date(identifier.accessPolicy.start) - }, - id: identifier.id + const { span, spanOptions } = createSpan("ShareClient-setAccessPolicy", options.spanOptions); + try { + const acl: Models.SignedIdentifier[] = []; + for (const identifier of shareAcl || []) { + acl.push({ + accessPolicy: { + expiry: truncatedISO8061Date(identifier.accessPolicy.expiry), + permission: identifier.accessPolicy.permission, + start: truncatedISO8061Date(identifier.accessPolicy.start) + }, + id: identifier.id + }); + } + + return this.context.setAccessPolicy({ + abortSignal: options.abortSignal, + shareAcl: acl, + spanOptions }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - - return this.context.setAccessPolicy({ - abortSignal: options.abortSignal, - shareAcl: acl - }); } /** @@ -712,10 +830,22 @@ export class ShareClient extends StorageClient { public async createSnapshot( options: ShareCreateSnapshotOptions = {} ): Promise { - return this.context.createSnapshot({ - abortSignal: options.abortSignal, - ...options - }); + const { span, spanOptions } = createSpan("ShareClient-createSnapshot", options.spanOptions); + try { + return this.context.createSnapshot({ + abortSignal: options.abortSignal, + ...options, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -730,15 +860,27 @@ export class ShareClient extends StorageClient { quotaInGB: number, options: ShareSetQuotaOptions = {} ): Promise { - if (quotaInGB <= 0 || quotaInGB > 5120) { - throw new RangeError( - `Share quota must be greater than 0, and less than or equal to 5Tib (5120GB)` - ); + const { span, spanOptions } = createSpan("ShareClient-setQuota", options.spanOptions); + try { + if (quotaInGB <= 0 || quotaInGB > 5120) { + throw new RangeError( + `Share quota must be greater than 0, and less than or equal to 5Tib (5120GB)` + ); + } + return this.context.setQuota({ + abortSignal: options.abortSignal, + quota: quotaInGB, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); } - return this.context.setQuota({ - abortSignal: options.abortSignal, - quota: quotaInGB - }); } /** @@ -751,10 +893,24 @@ export class ShareClient extends StorageClient { public async getStatistics( options: ShareGetStatisticsOptions = {} ): Promise { - const response = await this.context.getStatistics({ abortSignal: options.abortSignal }); + const { span, spanOptions } = createSpan("ShareClient-getStatistics", options.spanOptions); + try { + const response = await this.context.getStatistics({ + abortSignal: options.abortSignal, + spanOptions + }); - const GBBytes = 1024 * 1024 * 1024; - return { ...response, shareUsage: Math.ceil(response.shareUsageBytes / GBBytes) }; + const GBBytes = 1024 * 1024 * 1024; + return { ...response, shareUsage: Math.ceil(response.shareUsageBytes / GBBytes) }; + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -769,14 +925,26 @@ export class ShareClient extends StorageClient { filePermission: string, options: ShareCreatePermissionOptions = {} ): Promise { - return this.context.createPermission( - { - permission: filePermission - }, - { - abortSignal: options.abortSignal - } - ); + const { span, spanOptions } = createSpan("ShareClient-createPermission", options.spanOptions); + try { + return this.context.createPermission( + { + permission: filePermission + }, + { + abortSignal: options.abortSignal, + spanOptions + } + ); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } /** @@ -791,8 +959,20 @@ export class ShareClient extends StorageClient { filePermissionKey: string, options: ShareGetPermissionOptions = {} ): Promise { - return this.context.getPermission(filePermissionKey, { - aborterSignal: options.abortSignal - }); + const { span, spanOptions } = createSpan("ShareClient-getPermission", options.spanOptions); + try { + return this.context.getPermission(filePermissionKey, { + aborterSignal: options.abortSignal, + spanOptions + }); + } catch (e) { + span.setStatus({ + code: CanonicalCode.UNKNOWN, + message: e.message + }); + throw e; + } finally { + span.end(); + } } } diff --git a/sdk/storage/storage-file/src/StorageClient.ts b/sdk/storage/storage-file/src/StorageClient.ts index d43c1ee25720..cedf46cfce63 100644 --- a/sdk/storage/storage-file/src/StorageClient.ts +++ b/sdk/storage/storage-file/src/StorageClient.ts @@ -5,6 +5,15 @@ import { StorageClientContext } from "./generated/src/storageClientContext"; import { Pipeline } from "./Pipeline"; import { escapeURLPath } from "./utils/utils.common"; import { SERVICE_VERSION } from "./utils/constants"; +import { SpanOptions } from "@azure/core-tracing"; + +/** + * An interface for options common to every remote operation. + */ +export interface CommonOptions { + spanOptions?: SpanOptions; +} + /** * A StorageClient represents a base client class for ServiceClient, ContainerClient and etc. * diff --git a/sdk/storage/storage-file/src/utils/tracing.ts b/sdk/storage/storage-file/src/utils/tracing.ts new file mode 100644 index 000000000000..13c54bf997c1 --- /dev/null +++ b/sdk/storage/storage-file/src/utils/tracing.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { getTracer, Span, SpanOptions, SpanKind } from "@azure/core-tracing"; + +/** + * Creates a span using the global tracer. + * @param name The name of the operation being performed. + * @param options The options for the underlying http request. + */ +export function createSpan( + operationName: string, + options: SpanOptions = {} +): { span: Span; spanOptions: SpanOptions } { + const tracer = getTracer(); + const spanOptions: SpanOptions = { + ...options, + kind: SpanKind.CLIENT + }; + + const span = tracer.startSpan(`Azure.Storage.File.${operationName}`, spanOptions); + span.setAttribute("component", "storage"); + + let newOptions = options; + if (span.isRecordingEvents()) { + newOptions = { + ...options, + parent: span + }; + } + + return { + span, + spanOptions: newOptions + }; +} diff --git a/sdk/storage/storage-file/test/directoryclient.spec.ts b/sdk/storage/storage-file/test/directoryclient.spec.ts index 9753bfa9450a..a62fc145780e 100644 --- a/sdk/storage/storage-file/test/directoryclient.spec.ts +++ b/sdk/storage/storage-file/test/directoryclient.spec.ts @@ -8,6 +8,7 @@ import { DirectoryCreateResponse } from "../src/generated/src/models"; import { truncatedISO8061Date } from "../src/utils/utils.common"; +import { TestTracer, setTracer, SpanGraph } from "@azure/core-tracing"; dotenv.config({ path: "../.env" }); describe("DirectoryClient", () => { @@ -572,6 +573,94 @@ describe("DirectoryClient", () => { await subDirClient.delete(); }); + it("createFile and deleteFile with tracing", async () => { + const tracer = new TestTracer(); + setTracer(tracer); + const rootSpan = tracer.startSpan("root"); + const spanOptions = { parent: rootSpan }; + const directoryName = recorder.getUniqueName("directory"); + const { directoryClient: subDirClient } = await dirClient.createSubdirectory(directoryName, { + spanOptions + }); + const fileName = recorder.getUniqueName("file"); + const metadata = { key: "value" }; + const { fileClient } = await subDirClient.createFile(fileName, 256, { + metadata, + spanOptions + }); + const result = await fileClient.getProperties({ spanOptions }); + assert.deepEqual(result.metadata, metadata); + + await subDirClient.deleteFile(fileName, { spanOptions }); + try { + await fileClient.getProperties({ spanOptions }); + assert.fail( + "Expecting an error in getting properties from a deleted block blob but didn't get one." + ); + } catch (error) { + assert.ok((error.statusCode as number) === 404); + } + await subDirClient.delete({ spanOptions }); + + rootSpan.end(); + + const rootSpans = tracer.getRootSpans(); + assert.strictEqual(rootSpans.length, 1, "Should only have one root span."); + assert.strictEqual(rootSpan, rootSpans[0], "The root span should match what was passed in."); + + const expectedGraph: SpanGraph = { + roots: [ + { + name: rootSpan.name, + children: [ + { + name: "Azure.Storage.File.DirectoryClient-createSubdirectory", + children: [ + { + name: "Azure.Storage.File.DirectoryClient-create", + children: [] + } + ] + }, + { + name: "Azure.Storage.File.DirectoryClient-createFile", + children: [ + { + name: "Azure.Storage.File.FileClient-create", + children: [] + } + ] + }, + { + name: "Azure.Storage.File.FileClient-getProperties", + children: [] + }, + { + name: "Azure.Storage.File.DirectoryClient-deleteFile", + children: [ + { + name: "Azure.Storage.File.FileClient-delete", + children: [] + } + ] + }, + { + name: "Azure.Storage.File.FileClient-getProperties", + children: [] + }, + { + name: "Azure.Storage.File.DirectoryClient-delete", + children: [] + } + ] + } + ] + }; + + assert.deepStrictEqual(tracer.getSpanGraph(rootSpan.context().traceId), expectedGraph); + assert.strictEqual(tracer.getActiveSpans().length, 0, "All spans should have had end called"); + }); + it("listHandles should work", async () => { // TODO: Open or create a handle; Currently can only be done manually; No REST APIs for creating handles diff --git a/sdk/storage/storage-file/test/fileclient.spec.ts b/sdk/storage/storage-file/test/fileclient.spec.ts index a18bf92554bd..fb96f423fafa 100644 --- a/sdk/storage/storage-file/test/fileclient.spec.ts +++ b/sdk/storage/storage-file/test/fileclient.spec.ts @@ -1,5 +1,6 @@ import * as assert from "assert"; import { isNode } from "@azure/core-http"; +import { TestTracer, setTracer, SpanGraph } from "@azure/core-tracing"; import { AbortController } from "@azure/abort-controller"; import { record, delay } from "./utils/recorder"; import * as dotenv from "dotenv"; @@ -498,4 +499,35 @@ describe("FileClient", () => { await dirClient.forceCloseHandle(handle.handleId); } }); + + it("create with tracing", async () => { + const tracer = new TestTracer(); + setTracer(tracer); + const rootSpan = tracer.startSpan("root"); + await fileClient.create(content.length, { + spanOptions: { parent: rootSpan } + }); + rootSpan.end(); + + const rootSpans = tracer.getRootSpans(); + assert.strictEqual(rootSpans.length, 1, "Should only have one root span."); + assert.strictEqual(rootSpan, rootSpans[0], "The root span should match what was passed in."); + + const expectedGraph: SpanGraph = { + roots: [ + { + name: rootSpan.name, + children: [ + { + name: "Azure.Storage.File.FileClient-create", + children: [] + } + ] + } + ] + }; + + assert.deepStrictEqual(tracer.getSpanGraph(rootSpan.context().traceId), expectedGraph); + assert.strictEqual(tracer.getActiveSpans().length, 0, "All spans should have had end called"); + }); });