From 5d37c65b38d46b6185073a70096d54be1fba7760 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Fri, 20 Aug 2021 16:19:38 +0200 Subject: [PATCH 1/3] add file impl --- module/apmazure/file.go | 149 +++++++++++++++++++++++++++++++++++ module/apmazure/file_test.go | 66 ++++++++++++++++ module/apmazure/go.mod | 1 + module/apmazure/go.sum | 7 ++ module/apmazure/storage.go | 7 ++ 5 files changed, 230 insertions(+) create mode 100644 module/apmazure/file.go create mode 100644 module/apmazure/file_test.go diff --git a/module/apmazure/file.go b/module/apmazure/file.go new file mode 100644 index 000000000..d290053a6 --- /dev/null +++ b/module/apmazure/file.go @@ -0,0 +1,149 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build go1.14 +// +build go1.14 + +package apmazure // import "go.elastic.co/apm/module/apmazure" + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/Azure/azure-pipeline-go/pipeline" +) + +type fileRPC struct { + accountName string + resourceName string + req pipeline.Request +} + +func (f *fileRPC) name() string { + return fmt.Sprintf("AzureFile %s %s", f.operation(), f.resourceName) +} + +func (f *fileRPC) _type() string { + return "storage" +} + +func (f *fileRPC) subtype() string { + return "azurefile" +} + +func (f *fileRPC) storageAccountName() string { + return f.accountName +} + +func (f *fileRPC) resource() string { + return f.resourceName +} + +func (f *fileRPC) operation() string { + q := f.req.URL.Query() + switch f.req.Method { + case http.MethodOptions: + return "Preflight" + case http.MethodDelete: + return "Delete" + // From net/http documentation: + // For client requests, an empty string means GET. + case http.MethodGet, "": + return f.getOperation(q) + case http.MethodPost: + return f.postOperation() + case http.MethodHead: + return f.headOperation(q) + case http.MethodPut: + return f.putOperation(q, f.req.Header) + default: + return f.req.Method + } +} + +func (f *fileRPC) getOperation(v url.Values) string { + if v.Get("restype") == "share" { + return "GetProperties" + } + + switch comp := v.Get("comp"); comp { + case "": + return "Download" + case "listhandles": + return "ListHandles" + case "rangelist": + return "ListRanges" + case "metadata", "acl": + return "Get" + strings.ToTitle(comp) + case "list", "stats": + return strings.ToTitle(comp) + default: + return "unknown operation" + } +} + +func (f *fileRPC) postOperation() string { + return "unknown operation" + +} + +func (f *fileRPC) headOperation(v url.Values) string { + comp := v.Get("comp") + if v.Get("restype") == "share" || comp == "" { + return "GetProperties" + } + + switch comp { + case "metadata", "acl": + return "Get" + strings.ToTitle(comp) + default: + return "unknown operation" + } +} + +func (f *fileRPC) putOperation(v url.Values, h http.Header) string { + if _, copySource := h["x-ms-copy-source"]; copySource { + return "Copy" + } + + if _, copyAction := h["x-ms-copy-action:abort"]; copyAction { + return "Abort" + } + restype := v.Get("restype") + if restype == "directory" { + return "Create" + } + comp := v.Get("comp") + if restype == "container" && comp == "acl" { + return "SetAcl" + } + + switch comp { + case "range": + return "Upload" + case "forceclosehandles": + return "CloseHandles" + case "lease", "snapshot", "undelete": + return strings.Title(comp) + case "acl", "filepermission", "metadata", "properties": + return "Set" + strings.Title(comp) + default: + return "unknown operation" + } +} diff --git a/module/apmazure/file_test.go b/module/apmazure/file_test.go new file mode 100644 index 000000000..9aa058092 --- /dev/null +++ b/module/apmazure/file_test.go @@ -0,0 +1,66 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//go:build go1.14 +// +build go1.14 + +package apmazure + +import ( + "context" + "net/url" + "testing" + + "github.com/Azure/azure-storage-file-go/azfile" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.elastic.co/apm/apmtest" +) + +func TestFile(t *testing.T) { + retry := azfile.RetryOptions{ + MaxTries: 1, + } + po := azfile.PipelineOptions{ + Retry: retry, + } + p := azfile.NewPipeline(azfile.NewAnonymousCredential(), po) + p = WrapPipeline(p) + u, err := url.Parse("https://fakeaccnt.file.core.windows.net") + require.NoError(t, err) + serviceURL := azfile.NewServiceURL(*u, p) + shareURL := serviceURL.NewShareURL("share") + dirURL := shareURL.NewDirectoryURL("dir") + fileURL := dirURL.NewFileURL("file") + + _, spans, errors := apmtest.WithTransaction(func(ctx context.Context) { + fileURL.Download(ctx, 0, 0, false) + }) + require.Len(t, errors, 1) + require.Len(t, spans, 1) + span := spans[0] + + assert.Equal(t, "storage", span.Type) + assert.Equal(t, "AzureFile Download share/dir/file", span.Name) + assert.Equal(t, "azurefile", span.Subtype) + assert.Equal(t, "Download", span.Action) + destination := span.Context.Destination + assert.Equal(t, "fakeaccnt.file.core.windows.net", destination.Address) + assert.Equal(t, 443, destination.Port) + assert.Equal(t, "azurefile/fakeaccnt", destination.Service.Resource) +} diff --git a/module/apmazure/go.mod b/module/apmazure/go.mod index f2e0f8abe..bb7024e56 100644 --- a/module/apmazure/go.mod +++ b/module/apmazure/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.14.0 + github.com/Azure/azure-storage-file-go v0.8.0 github.com/Azure/azure-storage-queue-go v0.0.0-20191125232315-636801874cdd github.com/stretchr/testify v1.7.0 go.elastic.co/apm v1.13.1 diff --git a/module/apmazure/go.sum b/module/apmazure/go.sum index d4a2ff8f0..6e4a26ae7 100644 --- a/module/apmazure/go.sum +++ b/module/apmazure/go.sum @@ -1,8 +1,11 @@ github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI= +github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c= github.com/Azure/azure-storage-queue-go v0.0.0-20191125232315-636801874cdd h1:b3wyxBl3vvr15tUAziPBPK354y+LSdfPCpex5oBttHo= github.com/Azure/azure-storage-queue-go v0.0.0-20191125232315-636801874cdd/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -42,8 +45,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -79,6 +84,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -87,6 +93,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/module/apmazure/storage.go b/module/apmazure/storage.go index 993dc1244..4cd7b8f63 100644 --- a/module/apmazure/storage.go +++ b/module/apmazure/storage.go @@ -36,6 +36,7 @@ func init() { stacktrace.RegisterLibraryPackage( "github.com/Azure/azure-pipeline-go", "github.com/Azure/azure-storage-blob-go/azblob", + "github.com/Azure/azure-storage-file-go/azfile", "github.com/Azure/azure-storage-queue-go/azqueue", ) } @@ -151,6 +152,12 @@ func newAzureRPC(req pipeline.Request) (azureRPC, error) { accountName: accountName, req: req, } + case "file": + rpc = &fileRPC{ + resourceName: strings.TrimPrefix(req.URL.Path, "/"), + accountName: accountName, + req: req, + } } if rpc == nil { return nil, errors.New("unsupported service") From aaaed96edb04ea63fd85c7d0fa9aff48b380d8ab Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Mon, 23 Aug 2021 11:24:25 +0200 Subject: [PATCH 2/3] add http method-specific tests --- module/apmazure/file.go | 28 ++++--- module/apmazure/file_test.go | 155 +++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 11 deletions(-) diff --git a/module/apmazure/file.go b/module/apmazure/file.go index d290053a6..7bf7cf001 100644 --- a/module/apmazure/file.go +++ b/module/apmazure/file.go @@ -59,9 +59,9 @@ func (f *fileRPC) operation() string { q := f.req.URL.Query() switch f.req.Method { case http.MethodOptions: - return "Preflight" + return f.optionsOperation() case http.MethodDelete: - return "Delete" + return f.deleteOperation() // From net/http documentation: // For client requests, an empty string means GET. case http.MethodGet, "": @@ -77,6 +77,14 @@ func (f *fileRPC) operation() string { } } +func (f *fileRPC) deleteOperation() string { + return "Delete" +} + +func (f *fileRPC) optionsOperation() string { + return "Preflight" +} + func (f *fileRPC) getOperation(v url.Values) string { if v.Get("restype") == "share" { return "GetProperties" @@ -90,9 +98,9 @@ func (f *fileRPC) getOperation(v url.Values) string { case "rangelist": return "ListRanges" case "metadata", "acl": - return "Get" + strings.ToTitle(comp) + return "Get" + strings.Title(comp) case "list", "stats": - return strings.ToTitle(comp) + return strings.Title(comp) default: return "unknown operation" } @@ -111,7 +119,7 @@ func (f *fileRPC) headOperation(v url.Values) string { switch comp { case "metadata", "acl": - return "Get" + strings.ToTitle(comp) + return "Get" + strings.Title(comp) default: return "unknown operation" } @@ -129,20 +137,18 @@ func (f *fileRPC) putOperation(v url.Values, h http.Header) string { if restype == "directory" { return "Create" } - comp := v.Get("comp") - if restype == "container" && comp == "acl" { - return "SetAcl" - } - switch comp { + switch comp := v.Get("comp"); comp { case "range": return "Upload" case "forceclosehandles": return "CloseHandles" case "lease", "snapshot", "undelete": return strings.Title(comp) - case "acl", "filepermission", "metadata", "properties": + case "acl", "metadata", "properties": return "Set" + strings.Title(comp) + case "filepermission": + return "SetPermission" default: return "unknown operation" } diff --git a/module/apmazure/file_test.go b/module/apmazure/file_test.go index 9aa058092..692ecbc7c 100644 --- a/module/apmazure/file_test.go +++ b/module/apmazure/file_test.go @@ -22,6 +22,7 @@ package apmazure import ( "context" + "net/http" "net/url" "testing" @@ -64,3 +65,157 @@ func TestFile(t *testing.T) { assert.Equal(t, 443, destination.Port) assert.Equal(t, "azurefile/fakeaccnt", destination.Service.Resource) } + +func TestFileGetOperation(t *testing.T) { + tcs := []struct { + want string + values url.Values + }{ + // https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-azure.md#determining-operations-3 + { + want: "Download", + values: url.Values{}, + }, + { + want: "GetProperties", + values: url.Values{"restype": []string{"share"}}, + }, + { + want: "ListHandles", + values: url.Values{"comp": []string{"listhandles"}}, + }, + { + want: "ListRanges", + values: url.Values{"comp": []string{"rangelist"}}, + }, + { + want: "Stats", + values: url.Values{"comp": []string{"stats"}}, + }, + { + want: "List", + values: url.Values{"comp": []string{"list"}}, + }, + { + want: "GetMetadata", + values: url.Values{"comp": []string{"metadata"}}, + }, + { + want: "GetAcl", + values: url.Values{"comp": []string{"acl"}}, + }, + } + + q := new(fileRPC) + for _, tc := range tcs { + assert.Equal(t, tc.want, q.getOperation(tc.values)) + } +} + +func TestFilePostOperation(t *testing.T) { + q := new(fileRPC) + assert.Equal(t, "unknown operation", q.postOperation()) +} + +func TestFileHeadOperation(t *testing.T) { + tcs := []struct { + want string + values url.Values + }{ + // https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-azure.md#determining-operations-3 + { + want: "GetProperties", + values: url.Values{}, + }, + { + want: "GetProperties", + values: url.Values{"restype": []string{"share"}}, + }, + { + want: "GetMetadata", + values: url.Values{"comp": []string{"metadata"}}, + }, + { + want: "GetAcl", + values: url.Values{"comp": []string{"acl"}}, + }, + } + + q := new(fileRPC) + for _, tc := range tcs { + assert.Equal(t, tc.want, q.headOperation(tc.values)) + } +} + +func TestFilePutOperation(t *testing.T) { + tcs := []struct { + want string + values url.Values + header http.Header + }{ + // https://github.com/elastic/apm/blob/master/specs/agents/tracing-instrumentation-azure.md#determining-operations + { + want: "Copy", + header: http.Header{"x-ms-copy-source": []string{}}, + }, + { + want: "Abort", + header: http.Header{"x-ms-copy-action:abort": []string{}}, + }, + { + want: "Create", + values: url.Values{"restype": []string{"directory"}}, + }, + { + want: "Upload", + values: url.Values{"comp": []string{"range"}}, + }, + { + want: "CloseHandles", + values: url.Values{"comp": []string{"forceclosehandles"}}, + }, + { + want: "Lease", + values: url.Values{"comp": []string{"lease"}}, + }, + { + want: "Snapshot", + values: url.Values{"comp": []string{"snapshot"}}, + }, + { + want: "Undelete", + values: url.Values{"comp": []string{"undelete"}}, + }, + { + want: "SetAcl", + values: url.Values{"comp": []string{"acl"}}, + }, + { + want: "SetPermission", + values: url.Values{"comp": []string{"filepermission"}}, + }, + { + want: "SetMetadata", + values: url.Values{"comp": []string{"metadata"}}, + }, + { + want: "SetProperties", + values: url.Values{"comp": []string{"properties"}}, + }, + } + + f := new(fileRPC) + for _, tc := range tcs { + assert.Equal(t, tc.want, f.putOperation(tc.values, tc.header)) + } +} + +func TestFileOptionsOperation(t *testing.T) { + f := new(fileRPC) + assert.Equal(t, "Preflight", f.optionsOperation()) +} + +func TestFileDeleteOperation(t *testing.T) { + f := new(fileRPC) + assert.Equal(t, "Delete", f.deleteOperation()) +} From 0b278455c9d99f1ecdde50830128eba1b3505905 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 24 Aug 2021 15:22:58 +0200 Subject: [PATCH 3/3] Update asciidocs --- docs/instrumenting.asciidoc | 1 + docs/supported-tech.asciidoc | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/instrumenting.asciidoc b/docs/instrumenting.asciidoc index 1328e5071..67a826954 100644 --- a/docs/instrumenting.asciidoc +++ b/docs/instrumenting.asciidoc @@ -921,6 +921,7 @@ The following services are supported: - Blob Storage - Queue Storage +- File Storage [source,go] diff --git a/docs/supported-tech.asciidoc b/docs/supported-tech.asciidoc index 0015430b8..06fd3da1d 100644 --- a/docs/supported-tech.asciidoc +++ b/docs/supported-tech.asciidoc @@ -314,6 +314,7 @@ We provide instrumentation for Azure Storage. This is usable with: - github.com/Azure/azure-storage-blob-go/azblob[Azure Blob Storage] - github.com/Azure/azure-storage-queue-go/azqueue[Azure Queue Storage] +- github.com/Azure/azure-storage-file-go/azfile[Azure File Storage] See <> for more information about Azure SDK Go instrumentation.