Skip to content

Commit

Permalink
[apmazure] file storage (#1109)
Browse files Browse the repository at this point in the history
* add file impl

* add http method-specific tests

* Update asciidocs
  • Loading branch information
stuartnelson3 authored Aug 25, 2021
1 parent 4173391 commit 763e745
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/instrumenting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@ The following services are supported:

- Blob Storage
- Queue Storage
- File Storage


[source,go]
Expand Down
1 change: 1 addition & 0 deletions docs/supported-tech.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<builtin-modules-apmazure, module/apmazure>> for more information
about Azure SDK Go instrumentation.
Expand Down
155 changes: 155 additions & 0 deletions module/apmazure/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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 f.optionsOperation()
case http.MethodDelete:
return f.deleteOperation()
// 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) 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"
}

switch comp := v.Get("comp"); comp {
case "":
return "Download"
case "listhandles":
return "ListHandles"
case "rangelist":
return "ListRanges"
case "metadata", "acl":
return "Get" + strings.Title(comp)
case "list", "stats":
return strings.Title(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.Title(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"
}

switch comp := v.Get("comp"); comp {
case "range":
return "Upload"
case "forceclosehandles":
return "CloseHandles"
case "lease", "snapshot", "undelete":
return strings.Title(comp)
case "acl", "metadata", "properties":
return "Set" + strings.Title(comp)
case "filepermission":
return "SetPermission"
default:
return "unknown operation"
}
}
221 changes: 221 additions & 0 deletions module/apmazure/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// 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/http"
"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)
}

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())
}
Loading

0 comments on commit 763e745

Please sign in to comment.