diff --git a/sdk/storage/azblob/CHANGELOG.md b/sdk/storage/azblob/CHANGELOG.md index 567682e02c38..497367954ab0 100644 --- a/sdk/storage/azblob/CHANGELOG.md +++ b/sdk/storage/azblob/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.1.0-beta.2 (Unreleased) ### Features Added +* Added support for [Cold tier](https://learn.microsoft.com/azure/storage/blobs/access-tiers-overview?tabs=azure-portal). ### Breaking Changes diff --git a/sdk/storage/azblob/assets.json b/sdk/storage/azblob/assets.json index 4db1d7209d92..b9fb1441baa5 100644 --- a/sdk/storage/azblob/assets.json +++ b/sdk/storage/azblob/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "go", "TagPrefix": "go/storage/azblob", - "Tag": "go/storage/azblob_a772b9c866" + "Tag": "go/storage/azblob_1a8d8f30f5" } diff --git a/sdk/storage/azblob/blob/client_test.go b/sdk/storage/azblob/blob/client_test.go index 1d3040713a51..64bf13390f52 100644 --- a/sdk/storage/azblob/blob/client_test.go +++ b/sdk/storage/azblob/blob/client_test.go @@ -3274,95 +3274,32 @@ func (s *BlobRecordedTestsSuite) TestPermanentDeleteWithoutPermission() { return nil }*/ -// -////func (s *BlobRecordedTestsSuite) TestBlobTierInferred() { -//// svcClient, err := getPremiumserviceClient() -//// if err != nil { -//// c.Skip(err.Error()) -//// } -//// -//// containerClient, _ := testcommon.CreateNewContainer(c, svcClient) -//// defer testcommon.DeleteContainer(context.Background(), _require, containerClient) -//// bbClient, _ := createNewPageBlob(c, containerClient) -//// -//// resp, err := bbClient.GetProperties(context.Background(), nil) -//// _require.Nil(err) -//// _assert(resp.AccessTierInferred(), chk.Equals, "true") -//// -//// resp2, err := containerClient.NewListBlobsFlatPager(ctx, Marker{}, ListBlobsSegmentOptions{}) -//// _require.Nil(err) -//// _assert(resp2.Segment.BlobItems[0].Properties.AccessTierInferred, chk.NotNil) -//// _assert(resp2.Segment.BlobItems[0].Properties.AccessTier, chk.Not(chk.Equals), "") -//// -//// _, err = bbClient.SetTier(ctx, AccessTierP4, LeaseAccessConditions{}) -//// _require.Nil(err) -//// -//// resp, err = bbClient.GetProperties(context.Background(), nil) -//// _require.Nil(err) -//// _assert(resp.AccessTierInferred(), chk.Equals, "") -//// -//// resp2, err = containerClient.NewListBlobsFlatPager(ctx, Marker{}, ListBlobsSegmentOptions{}) -//// _require.Nil(err) -//// _assert(resp2.Segment.BlobItems[0].Properties.AccessTierInferred, chk.IsNil) // AccessTierInferred never returned if false -////} -//// -////func (s *BlobRecordedTestsSuite) TestBlobArchiveStatus() { -//// svcClient, err := getBlobStorageserviceClient() -//// if err != nil { -//// c.Skip(err.Error()) -//// } -//// -//// containerClient, _ := testcommon.CreateNewContainer(c, svcClient) -//// defer testcommon.DeleteContainer(context.Background(), _require, containerClient) -//// bbClient, _ := createNewBlockBlob(c, containerClient) -//// -//// _, err = bbClient.SetTier(ctx, AccessTierArchive, LeaseAccessConditions{}) -//// _require.Nil(err) -//// _, err = bbClient.SetTier(ctx, AccessTierCool, LeaseAccessConditions{}) -//// _require.Nil(err) -//// -//// resp, err := bbClient.GetProperties(context.Background(), nil) -//// _require.Nil(err) -//// _assert(resp.ArchiveStatus(), chk.Equals, string(ArchiveStatusRehydratePendingToCool)) -//// -//// resp2, err := containerClient.NewListBlobsFlatPager(ctx, Marker{}, ListBlobsSegmentOptions{}) -//// _require.Nil(err) -//// _assert(resp2.Segment.BlobItems[0].Properties.ArchiveStatus, chk.Equals, ArchiveStatusRehydratePendingToCool) -//// -//// // delete first blob -//// _, err = bbClient.Delete(context.Background(), DeleteSnapshotsOptionNone, nil) -//// _require.Nil(err) -//// -//// bbClient, _ = createNewBlockBlob(c, containerClient) -//// -//// _, err = bbClient.SetTier(ctx, AccessTierArchive, LeaseAccessConditions{}) -//// _require.Nil(err) -//// _, err = bbClient.SetTier(ctx, AccessTierHot, LeaseAccessConditions{}) -//// _require.Nil(err) -//// -//// resp, err = bbClient.GetProperties(context.Background(), nil) -//// _require.Nil(err) -//// _assert(resp.ArchiveStatus(), chk.Equals, string(ArchiveStatusRehydratePendingToHot)) -//// -//// resp2, err = containerClient.NewListBlobsFlatPager(ctx, Marker{}, ListBlobsSegmentOptions{}) -//// _require.Nil(err) -//// _assert(resp2.Segment.BlobItems[0].Properties.ArchiveStatus, chk.Equals, ArchiveStatusRehydratePendingToHot) -////} -//// -////func (s *BlobRecordedTestsSuite) TestBlobTierInvalidValue() { -//// svcClient, err := getBlobStorageserviceClient() -//// if err != nil { -//// c.Skip(err.Error()) -//// } -//// -//// containerClient, _ := testcommon.CreateNewContainer(c, svcClient) -//// defer testcommon.DeleteContainer(context.Background(), _require, containerClient) -//// bbClient, _ := createNewBlockBlob(c, containerClient) -//// -//// _, err = bbClient.SetTier(ctx, AccessTierType("garbage"), LeaseAccessConditions{}) -//// testcommon.ValidateBlobErrorCode(c, err, bloberror.InvalidHeaderValue) -////} -//// +func (s *BlobRecordedTestsSuite) TestBlobSetTierInvalidAndValid() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + blockBlobName := testcommon.GenerateBlobName(testName) + bbClient := testcommon.CreateNewBlockBlob(context.Background(), _require, blockBlobName, containerClient) + + _, err = bbClient.SetTier(context.Background(), blob.AccessTier("nothing"), nil) + _require.Error(err) + testcommon.ValidateBlobErrorCode(_require, err, bloberror.InvalidHeaderValue) + + for _, tier := range []blob.AccessTier{blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold, blob.AccessTierArchive} { + _, err = bbClient.SetTier(context.Background(), tier, nil) + _require.NoError(err) + + getResp, err := bbClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(*getResp.AccessTier, string(tier)) + } +} func (s *BlobRecordedTestsSuite) TestBlobClientPartsSASQueryTimes() { _require := require.New(s.T()) diff --git a/sdk/storage/azblob/blob/constants.go b/sdk/storage/azblob/blob/constants.go index c15635448ef9..8a9107954381 100644 --- a/sdk/storage/azblob/blob/constants.go +++ b/sdk/storage/azblob/blob/constants.go @@ -53,6 +53,7 @@ type AccessTier = generated.AccessTier const ( AccessTierArchive AccessTier = generated.AccessTierArchive AccessTierCool AccessTier = generated.AccessTierCool + AccessTierCold AccessTier = generated.AccessTierCold AccessTierHot AccessTier = generated.AccessTierHot AccessTierP10 AccessTier = generated.AccessTierP10 AccessTierP15 AccessTier = generated.AccessTierP15 diff --git a/sdk/storage/azblob/blockblob/client_test.go b/sdk/storage/azblob/blockblob/client_test.go index edafdf0622b7..981f0d9ec855 100644 --- a/sdk/storage/azblob/blockblob/client_test.go +++ b/sdk/storage/azblob/blockblob/client_test.go @@ -1247,6 +1247,56 @@ func (s *BlockBlobRecordedTestsSuite) TestPutBlobFromURLCopySourceAuthNegative() testcommon.ValidateBlobErrorCode(_require, err, bloberror.CannotVerifyCopySource) } +func (s *BlockBlobUnrecordedTestsSuite) TestPutBlobFromURLWithTier() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + src := testcommon.GenerateBlobName("src" + testName) + srcBlob := testcommon.CreateNewBlockBlob(context.Background(), _require, src, containerClient) + + // Create SAS for source and get SAS URL + expiryTime := time.Now().UTC().Add(15 * time.Minute) + _require.Nil(err) + + credential, err := testcommon.GetGenericSharedKeyCredential(testcommon.TestAccountDefault) + _require.NoError(err) + + sasQueryParams, err := sas.AccountSignatureValues{ + Protocol: sas.ProtocolHTTPS, + ExpiryTime: expiryTime, + Permissions: to.Ptr(sas.AccountPermissions{Read: true, List: true}).String(), + ResourceTypes: to.Ptr(sas.AccountResourceTypes{Container: true, Object: true}).String(), + }.SignWithSharedKey(credential) + _require.Nil(err) + + srcBlobParts, _ := blob.ParseURL(srcBlob.URL()) + srcBlobParts.SAS = sasQueryParams + srcBlobURLWithSAS := srcBlobParts.String() + + for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold} { + dest := testcommon.GenerateBlobName("dest" + string(tier) + testName) + destBlob := testcommon.CreateNewBlockBlob(context.Background(), _require, dest, containerClient) + + opts := blockblob.UploadBlobFromURLOptions{ + Tier: &tier, + } + // Invoke UploadBlobFromURL + pbResp, err := destBlob.UploadBlobFromURL(context.Background(), srcBlobURLWithSAS, &opts) + _require.NotNil(pbResp) + _require.NoError(err) + + getResp, err := destBlob.GetProperties(context.Background(), nil) + _require.Nil(err) + _require.Equal(*getResp.AccessTier, string(tier)) + } +} + func (s *BlockBlobRecordedTestsSuite) TestPutBlockListWithImmutabilityPolicy() { _require := require.New(s.T()) testName := s.T().Name() @@ -1909,7 +1959,7 @@ func (s *BlockBlobRecordedTestsSuite) TestSetTierOnBlobUpload() { containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) defer testcommon.DeleteContainer(context.Background(), _require, containerClient) - for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot} { + for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold} { blobName := strings.ToLower(string(tier)) + testcommon.GenerateBlobName(testName) bbClient := testcommon.GetBlockBlobClient(blobName, containerClient) @@ -1936,7 +1986,7 @@ func (s *BlockBlobRecordedTestsSuite) TestBlobSetTierOnCommit() { containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) defer testcommon.DeleteContainer(context.Background(), _require, containerClient) - for _, tier := range []blob.AccessTier{blob.AccessTierCool, blob.AccessTierHot} { + for _, tier := range []blob.AccessTier{blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold} { blobName := strings.ToLower(string(tier)) + testcommon.GenerateBlobName(testName) bbClient := testcommon.GetBlockBlobClient(blobName, containerClient) @@ -1955,6 +2005,10 @@ func (s *BlockBlobRecordedTestsSuite) TestBlobSetTierOnCommit() { _require.NotNil(resp.BlockList.CommittedBlocks) _require.Nil(resp.BlockList.UncommittedBlocks) _require.Len(resp.BlockList.CommittedBlocks, 1) + + getResp, err := bbClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(*getResp.AccessTier, string(tier)) } } @@ -1998,7 +2052,7 @@ func (s *BlockBlobUnrecordedTestsSuite) TestSetTierOnCopyBlockBlobFromURL() { srcBlobParts.SAS = sasQueryParams srcBlobURLWithSAS := srcBlobParts.String() - for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot} { + for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold} { destBlobName := strings.ToLower(string(tier)) + testcommon.GenerateBlobName(testName) destBlob := containerClient.NewBlockBlobClient(testcommon.GenerateBlobName(destBlobName)) @@ -2247,6 +2301,34 @@ func (s *BlockBlobRecordedTestsSuite) TestCopyBlobWithRehydratePriority() { _require.Equal(*getResp2.ArchiveStatus, string(blob.ArchiveStatusRehydratePendingToHot)) } +func (s *BlockBlobRecordedTestsSuite) TestCopyWithTier() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + for _, tier := range []blob.AccessTier{blob.AccessTierArchive, blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold} { + src := testcommon.GenerateBlobName("src" + string(tier) + testName) + srcBlob := testcommon.CreateNewBlockBlob(context.Background(), _require, src, containerClient) + + dest := testcommon.GenerateBlobName("dest" + string(tier) + testName) + destBlob := testcommon.CreateNewBlockBlob(context.Background(), _require, dest, containerClient) + + _, err = destBlob.StartCopyFromURL(context.Background(), srcBlob.URL(), &blob.StartCopyFromURLOptions{ + Tier: &tier, + }) + _require.NoError(err) + + getResp, err := destBlob.GetProperties(context.Background(), nil) + _require.Nil(err) + _require.Equal(*getResp.AccessTier, string(tier)) + } +} + func (s *BlockBlobRecordedTestsSuite) TestBlobServiceClientDelete() { _require := require.New(s.T()) svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) diff --git a/sdk/storage/azblob/container/client_test.go b/sdk/storage/azblob/container/client_test.go index 21261c4e4931..83ae74707dd9 100644 --- a/sdk/storage/azblob/container/client_test.go +++ b/sdk/storage/azblob/container/client_test.go @@ -1344,6 +1344,60 @@ func (s *ContainerRecordedTestsSuite) TestBlobListWrapper() { _require.EqualValues(files, found) } +func (s *ContainerRecordedTestsSuite) TestBlobListColdTier() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.GetContainerClient(containerName, svcClient) + + _, err = containerClient.Create(context.Background(), nil) + _require.NoError(err) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + for _, tier := range []blob.AccessTier{blob.AccessTierCool, blob.AccessTierHot, blob.AccessTierCold, blob.AccessTierArchive} { + files := []string{"a123", "b234", "c345"} + testcommon.CreateNewBlobsListTier(context.Background(), _require, files, containerClient, &tier) + + // List blobs flat + found := make([]string, 0) + pager := containerClient.NewListBlobsFlatPager(nil) + for pager.More() { + resp, err := pager.NextPage(context.Background()) + _require.NoError(err) + + for _, blob := range resp.Segment.BlobItems { + _require.Equal(blob.Properties.AccessTier, &tier) + found = append(found, *blob.Name) + } + } + + sort.Strings(files) + sort.Strings(found) + _require.EqualValues(files, found) + + // Try listing blobs with hierarchical listing + found = make([]string, 0) + pg := containerClient.NewListBlobsHierarchyPager("/", nil) + for pg.More() { + resp, err := pg.NextPage(context.Background()) + _require.NoError(err) + + for _, blob := range resp.Segment.BlobItems { + _require.Equal(blob.Properties.AccessTier, &tier) + found = append(found, *blob.Name) + } + } + + sort.Strings(files) + sort.Strings(found) + _require.EqualValues(files, found) + + } +} + func (s *ContainerRecordedTestsSuite) TestBlobListWrapperListingError() { _require := require.New(s.T()) testName := s.T().Name() diff --git a/sdk/storage/azblob/internal/testcommon/clients_auth.go b/sdk/storage/azblob/internal/testcommon/clients_auth.go index 687c7e8a5fee..c4179f73d05d 100644 --- a/sdk/storage/azblob/internal/testcommon/clients_auth.go +++ b/sdk/storage/azblob/internal/testcommon/clients_auth.go @@ -236,6 +236,14 @@ func CreateNewBlobs(ctx context.Context, _require *require.Assertions, blobNames } } +func CreateNewBlobsListTier(ctx context.Context, _require *require.Assertions, blobNames []string, containerClient *container.Client, tier *blob.AccessTier) { + for _, blobName := range blobNames { + bbClient := CreateNewBlockBlob(ctx, _require, blobName, containerClient) + _, err := bbClient.SetTier(ctx, *tier, nil) + _require.NoError(err) + } +} + func GetBlockBlobClient(blockBlobName string, containerClient *container.Client) *blockblob.Client { return containerClient.NewBlockBlobClient(blockBlobName) } diff --git a/sdk/storage/azblob/pageblob/client_test.go b/sdk/storage/azblob/pageblob/client_test.go index 7b659b4bf4ee..a7195b050644 100644 --- a/sdk/storage/azblob/pageblob/client_test.go +++ b/sdk/storage/azblob/pageblob/client_test.go @@ -154,6 +154,33 @@ func (s *PageBlobRecordedTestsSuite) TestPutGetPages() { } } +func (s *PageBlobRecordedTestsSuite) TestBlobTierInferred() { + _require := require.New(s.T()) + testName := s.T().Name() + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountPremium, nil) + _require.NoError(err) + + containerName := testcommon.GenerateContainerName(testName) + containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient) + defer testcommon.DeleteContainer(context.Background(), _require, containerClient) + + blockBlobName := testcommon.GenerateBlobName(testName) + pbClient := createNewPageBlob(context.Background(), _require, blockBlobName, containerClient) + + resp, err := pbClient.GetProperties(context.Background(), nil) + _require.Nil(err) + _require.Equal(*resp.AccessTierInferred, true) + _require.NotEqual(*resp.AccessTier, "") + + _, err = pbClient.SetTier(context.Background(), blob.AccessTierP4, nil) + _require.Nil(err) + + resp, err = pbClient.GetProperties(context.Background(), nil) + _require.Nil(err) + _require.Nil(resp.AccessTierInferred) + _require.NotEqual(*resp.AccessTier, "") +} + // func (s *PageBlobUnrecordedTestsSuite) TestUploadPagesFromURL() { // _require := require.New(s.T()) // testName := s.T().Name()