Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ public void OutputEventsForTest() =>
"Storage_TestConfigManagedDisk",
() => TestConfigurations.DefaultTargetManagedDiskTenant);

/// <summary>
/// Gets the tenant to use for any tests that require authentication
Comment thread
seanmcc-msft marked this conversation as resolved.
/// with Azure AD.
/// </summary>
public TenantConfiguration TestConfigSoftDelete => GetTestConfig(
"Storage_TestConfigSoftDelete",
() => TestConfigurations.DefaultTargetSoftDeleteTenant);

/// <summary>
/// Gets a cache used for storing serialized tenant configurations. Do
/// not get values from this directly; use GetTestConfig.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public class TestConfigurations
/// </summary>
private string TargetManagedDiskTenantName { get; set; }

/// <summary>
/// Gets the name of the tenant in the Tenants dictionary to use for
/// any tests related to blob and container soft delete.
/// </summary>
private string TargetSoftDeleteTenantName { get; set; }

/// <summary>
/// Gets the tenant to use by default for our tests.
/// </summary>
Expand Down Expand Up @@ -122,6 +128,12 @@ public class TestConfigurations
public static TenantConfiguration DefaultTargetManagedDiskTenant =>
GetTenant("TargetManagedDiskTenant", s_configurations.Value.TargetManagedDiskTenantName);

/// <summary>
/// Gets a tenant to use for any tests related to blob or container soft delete.
/// </summary>
public static TenantConfiguration DefaultTargetSoftDeleteTenant =>
GetTenant("TargetBlobAndContainerSoftDeleteTenant", s_configurations.Value.TargetSoftDeleteTenantName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Soft Delete an account level setting that applies regardless of whether you're using Blobs/Files?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is Container Soft Delete and Blob Soft Delete for the Blobs service, and Share Soft Delete for Files. I'd like to just make 1 new test account with all soft delete features enabled.

The reason I want a separate account is that 1. it takes time to enable/disable these features on an account, and 2. if you list with the "Deleted" option, and soft delete has been enabled for weeks, the resulting list could be huge.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it - I'm on board with the separate account. Just wonder if we should call the tenant name "TargetBlobsAndFilesSoftDeleteTenant" or just "TargetSoftDeleteTenant" since you need to enable it for both services? I don't feel very strongly here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add additional documentation around what settings this tenant requres.


/// <summary>
/// When loading our test configuration, we'll check the
/// AZ_STORAGE_CONFIG_PATH first.
Expand Down Expand Up @@ -219,6 +231,7 @@ private static TestConfigurations ReadFromXml(XDocument doc)
TargetOAuthTenantName = Get("TargetOAuthTenant"),
TargetHierarchicalNamespaceTenantName = Get("TargetHierarchicalNamespaceTenant"),
TargetManagedDiskTenantName = Get("TargetManagedDiskTenant"),
TargetSoftDeleteTenantName = Get("TargetBlobAndContainerSoftDeleteTenant"),
Tenants =
config.Element("TenantConfigurations").Elements("TenantConfiguration")
.Select(TenantConfiguration.Parse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,23 @@
<FileServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.file.core.windows.net</FileServiceSecondaryEndpoint>
<TableServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.table.core.windows.net</TableServiceSecondaryEndpoint>
</TenantConfiguration>

<!-- An account with Blob Soft Delete, Container Soft Delete, and Share Soft Delete Enabled -->
<!-- Replace [ACCOUNT] and [ACCOUNT-KEY] -->
<TenantConfiguration>
<TenantName>SoftDeleteTenant</TenantName>
<TenantType>Cloud</TenantType>
<AccountName>[ACCOUNT]</AccountName>
<AccountKey>[ACCOUNT-KEY]</AccountKey>

<BlobServiceEndpoint>http://[ACCOUNT].blob.core.windows.net</BlobServiceEndpoint>
<QueueServiceEndpoint>http://[ACCOUNT].queue.core.windows.net</QueueServiceEndpoint>
<TableServiceEndpoint>http://[ACCOUNT].table.core.windows.net</TableServiceEndpoint>
<FileServiceEndpoint>http://[ACCOUNT].file.core.windows.net</FileServiceEndpoint>
<BlobServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.blob.core.windows.net</BlobServiceSecondaryEndpoint>
<QueueServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.queue.core.windows.net</QueueServiceSecondaryEndpoint>
<FileServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.file.core.windows.net</FileServiceSecondaryEndpoint>
<TableServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.table.core.windows.net</TableServiceSecondaryEndpoint>
</TenantConfiguration>
</TenantConfigurations>
</TestConfigurations>
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public ShareClient(System.Uri shareUri, Azure.Storage.StorageSharedKeyCredential
public virtual Azure.Storage.Files.Shares.ShareDirectoryClient GetRootDirectoryClient() { throw null; }
public virtual Azure.Response<Azure.Storage.Files.Shares.Models.ShareStatistics> GetStatistics(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Files.Shares.Models.ShareStatistics>> GetStatisticsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Storage.Files.Shares.Models.ShareInfo> Restore(string deletedShareName, string deletedShareVersion, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Files.Shares.Models.ShareInfo>> RestoreAsync(string deletedShareName, string deletedShareVersion, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Storage.Files.Shares.Models.ShareInfo> SetAccessPolicy(System.Collections.Generic.IEnumerable<Azure.Storage.Files.Shares.Models.ShareSignedIdentifier> permissions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Files.Shares.Models.ShareInfo>> SetAccessPolicyAsync(System.Collections.Generic.IEnumerable<Azure.Storage.Files.Shares.Models.ShareSignedIdentifier> permissions, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Storage.Files.Shares.Models.ShareInfo> SetMetadata(System.Collections.Generic.IDictionary<string, string> metadata, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -695,6 +697,7 @@ public enum ShareStates
All = -1,
None = 0,
Snapshots = 1,
Deleted = 2,
}
public partial class ShareStatistics
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ internal static IEnumerable<ListSharesIncludeType> AsIncludeItems(ShareTraits tr
// NOTE: Multiple strings MUST be appended in alphabetic order or signing the string for authentication fails!
// TODO: Remove this requirement by pushing it closer to header generation.
var items = new List<ListSharesIncludeType>();
if ((states & ShareStates.Deleted) == ShareStates.Deleted)
{
items.Add(ListSharesIncludeType.Deleted);
}
if ((traits & ShareTraits.Metadata) == ShareTraits.Metadata)
{
items.Add(ListSharesIncludeType.Metadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public enum ShareStates
/// </summary>
Snapshots = 1,

/// <summary>
/// Flag specifying that deleted shares should be included.
/// </summary>
Deleted = 2,

/// <summary>
/// Flag specifying that shares of all states should be included.
/// </summary>
Expand Down
141 changes: 141 additions & 0 deletions sdk/storage/Azure.Storage.Files.Shares/src/ShareClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,147 @@ private async Task<Response<PermissionInfo>> CreatePermissionInternal(
}
#endregion CreatePermission

#region RestoreShare
/// <summary>
/// Restores a previously created share. The restored share
/// will be renamed to the name of this <see cref="ShareServiceClient"/>.
/// If the container associated with this <see cref="ShareServiceClient"/>
/// already exists, this call will result in a 409 (conflict).
/// This API is only functional is Share Soft Delete is enabled
/// for the storage account associated with the share.
/// </summary>
/// <param name="deletedShareName">
/// The name of the share to restore.
/// </param>
/// <param name="deletedShareVersion">
/// The version of the share to restore.
/// </param>
/// <param name="cancellationToken">
/// Optional <see cref="CancellationToken"/> to propagate
/// notifications that the operation should be cancelled.
/// </param>
/// <returns>
/// A <see cref="Response"/>.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
public virtual Response<ShareInfo> Restore(
string deletedShareName,
string deletedShareVersion,
CancellationToken cancellationToken = default)
=> RestoreInternal(
deletedShareName,
deletedShareVersion,
async: false,
cancellationToken).EnsureCompleted();

/// <summary>
/// Restores a previously created share. The restored share
/// will be renamed to the name of this <see cref="ShareServiceClient"/>.
/// If the container associated with this <see cref="ShareServiceClient"/>
/// already exists, this call will result in a 409 (conflict).
/// This API is only functional is Share Soft Delete is enabled
/// for the storage account associated with the share.
/// </summary>
/// <param name="deletedShareName">
/// The name of the share to restore.
/// </param>
/// <param name="deletedShareVersion">
/// The version of the share to restore.
/// </param>
/// <param name="cancellationToken">
/// Optional <see cref="CancellationToken"/> to propagate
/// notifications that the operation should be cancelled.
/// </param>
/// <returns>
/// A <see cref="Response"/>.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs
/// </remarks>
public virtual async Task<Response<ShareInfo>> RestoreAsync(
string deletedShareName,
string deletedShareVersion,
CancellationToken cancellationToken = default)
=> await RestoreInternal(
deletedShareName,
deletedShareVersion,
async: true,
cancellationToken).ConfigureAwait(false);

/// <summary>
/// Restores a previously created share. The restored share
/// will be renamed to the name of this <see cref="ShareServiceClient"/>.
/// If the container associated with this <see cref="ShareServiceClient"/>
/// already exists, this call will result in a 409 (conflict).
/// This API is only functional is Share Soft Delete is enabled
/// for the storage account associated with the share.
/// </summary>
/// <param name="deletedShareName">
/// The name of the share to restore.
/// </param>
/// <param name="deletedShareVersion">
/// The version of the share to restore.
/// </param>
/// <param name="async">
/// Whether to invoke the operation asynchronously.
/// </param>
/// <param name="cancellationToken">
/// Optional <see cref="CancellationToken"/> to propagate
/// notifications that the operation should be cancelled.
/// </param>
/// <returns>
/// A <see cref="Response"/>.
/// </returns>
/// <remarks>
/// A <see cref="RequestFailedException"/> will be thrown if
/// a failure occurs.
/// </remarks>
private async Task<Response<ShareInfo>> RestoreInternal(
string deletedShareName,
string deletedShareVersion,
bool async,
CancellationToken cancellationToken)
{
using (Pipeline.BeginLoggingScope(nameof(ShareServiceClient)))
{
Pipeline.LogMethodEnter(
nameof(ShareServiceClient),
message:
$"{nameof(Uri)}: {Uri}\n" +
$"{nameof(deletedShareName)}: {deletedShareName}\n" +
$"{nameof(deletedShareVersion)}: {deletedShareVersion}");

try
{
return await FileRestClient.Share.RestoreAsync(
ClientDiagnostics,
Pipeline,
Uri,
Version.ToVersionString(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the operation just fail if you call this with an older service version? I can't imagine this causing any problems, but want to double check we don't need a version guard here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it will fail, with a message from the service saying the version is incorrect. Peter decided we weren't doing version guarding in the SDK.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was specifically no guards unless it could lead to data corruption or loss - which is why I like double checking for some of these new operations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing will happen to your share if you call Restore with a service version that doesn't support it - you will just get a 400 class error.

deletedShareName: deletedShareName,
deletedShareVersion: deletedShareVersion,
async: async,
operationName: $"{nameof(ShareClient)}.{nameof(Restore)}",
cancellationToken: cancellationToken)
.ConfigureAwait(false);
}
catch (Exception ex)
{
Pipeline.LogException(ex);
throw;
}
finally
{
Pipeline.LogMethodExit(nameof(ShareServiceClient));
}
}
}
#endregion RestoreShare

#region CreateDirectory
/// <summary>
/// The <see cref="CreateDirectory"/> operation creates a new
Expand Down
9 changes: 9 additions & 0 deletions sdk/storage/Azure.Storage.Files.Shares/tests/FileTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ public ShareServiceClient GetServiceClient_Premium()
TestConfigPremiumBlob.AccountKey),
GetOptions()));

public ShareServiceClient GetServiceClient_SoftDelete()
=> InstrumentClient(
new ShareServiceClient(
new Uri(TestConfigSoftDelete.FileServiceEndpoint),
new StorageSharedKeyCredential(
TestConfigSoftDelete.AccountName,
TestConfigSoftDelete.AccountKey),
GetOptions()));

public ShareServiceClient GetServiceClient_AccountSas(StorageSharedKeyCredential sharedKeyCredentials = default, SasQueryParameters sasCredentials = default)
=> InstrumentClient(
new ShareServiceClient(
Expand Down
19 changes: 19 additions & 0 deletions sdk/storage/Azure.Storage.Files.Shares/tests/ServiceClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,25 @@ public async Task ListSharesSegmentAsync_Metadata()
shares.Where(s => s.Name == test.Share.Name).FirstOrDefault().Properties.Metadata);
}

[Test]
[ServiceVersion(Min = ShareClientOptions.ServiceVersion.V2019_12_12)]
public async Task ListSharesSegmentAsync_Deleted()
{
// Arrange
ShareServiceClient service = GetServiceClient_SoftDelete();
ShareClient share = InstrumentClient(service.GetShareClient(GetNewShareName()));
await share.CreateAsync();
await share.DeleteAsync();

// Act
IList<ShareItem> shares = await service.GetSharesAsync(states: ShareStates.Deleted).ToListAsync();

// Assert
ShareItem shareItem = shares.Where(s => s.Name == share.Name).FirstOrDefault();
Assert.IsTrue(shareItem.Deleted);
Assert.IsNotNull(shareItem.Version);
}

[Test]
public async Task ListShareSegmentAsync_Error()
{
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading