github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/storage/cloud/azure_storage.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package cloud
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"io"
    17  	"net/url"
    18  	"path"
    19  	"strings"
    20  
    21  	"github.com/Azure/azure-storage-blob-go/azblob"
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    24  	"github.com/cockroachdb/cockroach/pkg/util/contextutil"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  func azureQueryParams(conf *roachpb.ExternalStorage_Azure) string {
    29  	q := make(url.Values)
    30  	if conf.AccountName != "" {
    31  		q.Set(AzureAccountNameParam, conf.AccountName)
    32  	}
    33  	if conf.AccountKey != "" {
    34  		q.Set(AzureAccountKeyParam, conf.AccountKey)
    35  	}
    36  	return q.Encode()
    37  }
    38  
    39  type azureStorage struct {
    40  	conf      *roachpb.ExternalStorage_Azure
    41  	container azblob.ContainerURL
    42  	prefix    string
    43  	settings  *cluster.Settings
    44  }
    45  
    46  var _ ExternalStorage = &azureStorage{}
    47  
    48  func makeAzureStorage(
    49  	conf *roachpb.ExternalStorage_Azure, settings *cluster.Settings,
    50  ) (ExternalStorage, error) {
    51  	if conf == nil {
    52  		return nil, errors.Errorf("azure upload requested but info missing")
    53  	}
    54  	credential, err := azblob.NewSharedKeyCredential(conf.AccountName, conf.AccountKey)
    55  	if err != nil {
    56  		return nil, errors.Wrap(err, "azure credential")
    57  	}
    58  	p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
    59  	u, err := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net", conf.AccountName))
    60  	if err != nil {
    61  		return nil, errors.Wrap(err, "azure: account name is not valid")
    62  	}
    63  	serviceURL := azblob.NewServiceURL(*u, p)
    64  	return &azureStorage{
    65  		conf:      conf,
    66  		container: serviceURL.NewContainerURL(conf.Container),
    67  		prefix:    conf.Prefix,
    68  		settings:  settings,
    69  	}, nil
    70  }
    71  
    72  func (s *azureStorage) getBlob(basename string) azblob.BlockBlobURL {
    73  	name := path.Join(s.prefix, basename)
    74  	return s.container.NewBlockBlobURL(name)
    75  }
    76  
    77  func (s *azureStorage) Conf() roachpb.ExternalStorage {
    78  	return roachpb.ExternalStorage{
    79  		Provider:    roachpb.ExternalStorageProvider_Azure,
    80  		AzureConfig: s.conf,
    81  	}
    82  }
    83  
    84  func (s *azureStorage) WriteFile(
    85  	ctx context.Context, basename string, content io.ReadSeeker,
    86  ) error {
    87  	err := contextutil.RunWithTimeout(ctx, "write azure file", timeoutSetting.Get(&s.settings.SV),
    88  		func(ctx context.Context) error {
    89  			blob := s.getBlob(basename)
    90  			_, err := blob.Upload(
    91  				ctx, content, azblob.BlobHTTPHeaders{}, azblob.Metadata{}, azblob.BlobAccessConditions{},
    92  			)
    93  			return err
    94  		})
    95  	return errors.Wrapf(err, "write file: %s", basename)
    96  }
    97  
    98  func (s *azureStorage) ReadFile(ctx context.Context, basename string) (io.ReadCloser, error) {
    99  	// https://github.com/cockroachdb/cockroach/issues/23859
   100  	blob := s.getBlob(basename)
   101  	get, err := blob.Download(ctx, 0, 0, azblob.BlobAccessConditions{}, false)
   102  	if err != nil {
   103  		return nil, errors.Wrap(err, "failed to create azure reader")
   104  	}
   105  	reader := get.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
   106  	return reader, nil
   107  }
   108  
   109  func (s *azureStorage) ListFiles(ctx context.Context, patternSuffix string) ([]string, error) {
   110  	pattern := s.prefix
   111  	if patternSuffix != "" {
   112  		if containsGlob(s.prefix) {
   113  			return nil, errors.New("prefix cannot contain globs pattern when passing an explicit pattern")
   114  		}
   115  		pattern = path.Join(pattern, patternSuffix)
   116  	}
   117  	var fileList []string
   118  	response, err := s.container.ListBlobsFlatSegment(ctx,
   119  		azblob.Marker{},
   120  		azblob.ListBlobsSegmentOptions{Prefix: getPrefixBeforeWildcard(s.prefix)},
   121  	)
   122  	if err != nil {
   123  		return nil, errors.Wrap(err, "unable to list files for specified blob")
   124  	}
   125  
   126  	for _, blob := range response.Segment.BlobItems {
   127  		matches, err := path.Match(pattern, blob.Name)
   128  		if err != nil {
   129  			continue
   130  		}
   131  		if matches {
   132  			azureURL := url.URL{
   133  				Scheme:   "azure",
   134  				Host:     strings.TrimPrefix(s.container.URL().Path, "/"),
   135  				Path:     blob.Name,
   136  				RawQuery: azureQueryParams(s.conf),
   137  			}
   138  			fileList = append(fileList, azureURL.String())
   139  		}
   140  	}
   141  
   142  	return fileList, nil
   143  }
   144  
   145  func (s *azureStorage) Delete(ctx context.Context, basename string) error {
   146  	err := contextutil.RunWithTimeout(ctx, "delete azure file", timeoutSetting.Get(&s.settings.SV),
   147  		func(ctx context.Context) error {
   148  			blob := s.getBlob(basename)
   149  			_, err := blob.Delete(ctx, azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
   150  			return err
   151  		})
   152  	return errors.Wrap(err, "delete file")
   153  }
   154  
   155  func (s *azureStorage) Size(ctx context.Context, basename string) (int64, error) {
   156  	var props *azblob.BlobGetPropertiesResponse
   157  	err := contextutil.RunWithTimeout(ctx, "size azure file", timeoutSetting.Get(&s.settings.SV),
   158  		func(ctx context.Context) error {
   159  			blob := s.getBlob(basename)
   160  			var err error
   161  			props, err = blob.GetProperties(ctx, azblob.BlobAccessConditions{})
   162  			return err
   163  		})
   164  	if err != nil {
   165  		return 0, errors.Wrap(err, "get file properties")
   166  	}
   167  	return props.ContentLength(), nil
   168  }
   169  
   170  func (s *azureStorage) Close() error {
   171  	return nil
   172  }