From ee03ba4e0908feb1c5ebe0f534524083de9f6e5a Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 17 Oct 2016 15:24:53 -0700 Subject: [PATCH 01/37] Premium tables (#48) Premium tables V1 --- Lib/ClassLibraryCommon/Table/CloudTable.cs | 232 ++++++++++++++++-- .../Table/CloudTableClient.cs | 45 +++- .../Table/TableOperation.cs | 9 + Lib/Common/Shared/Protocol/Constants.cs | 3 +- Lib/Common/Table/CloudTable.Common.cs | 7 + Lib/Common/Table/Protocol/TableConstants.cs | 15 ++ Lib/Common/Table/TableOperation.Common.cs | 2 +- Lib/Common/Table/TableProperties.cs | 54 ++++ Lib/Common/Table/TableStatus.cs | 40 +++ Lib/Common/Table/TableStorageModel.cs | 7 + Lib/WindowsRuntime/Table/CloudTable.cs | 6 + Lib/WindowsRuntime/Table/CloudTableClient.cs | 16 +- .../Table/CloudTableClientTests.cs | 79 ++++++ Test/Common/TestCategoryConstants.cs | 2 + .../Table/CloudTableClientTest.cs | 43 ++++ 15 files changed, 533 insertions(+), 27 deletions(-) create mode 100644 Lib/Common/Table/TableProperties.cs create mode 100644 Lib/Common/Table/TableStatus.cs diff --git a/Lib/ClassLibraryCommon/Table/CloudTable.cs b/Lib/ClassLibraryCommon/Table/CloudTable.cs index 30bea59ff..7ef53234d 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTable.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTable.cs @@ -262,6 +262,190 @@ public virtual Task> ExecuteBatchAsync(TableBatchOperation ba #endregion + #region Fetch Table Properties +#if SYNC + /// + /// Populates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// For premium tables only. + [DoesServiceRequest] + public virtual void FetchAttributes(TableRequestOptions requestOptions = null, OperationContext operationContext = null) + { + IEnumerator listResult = this.ServiceClient.ListTables(this.Name, requestOptions, operationContext).GetEnumerator(); + listResult.MoveNext(); + this.Properties.ProvisionedIops = listResult.Current.Properties.ProvisionedIops; + this.Properties.RequestedIops = listResult.Current.Properties.RequestedIops; + this.Properties.TableStatus = listResult.Current.Properties.TableStatus; + } +#endif + + /// + /// Populates the table's properties. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// For premium tables only. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginFetchAttributes(AsyncCallback callback, object state) + { + return this.BeginFetchAttributes(null /* RequestOptions */, null /* OperationContext */, callback, state); + } + + /// + /// Populates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// For premium tables only. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginFetchAttributes(TableRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.ServiceClient.BeginListTablesSegmented(this.Name, null, null, requestOptions, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to fetch attributes. + /// + /// An that references the pending asynchronous operation. + public virtual void EndFetchAttributes(IAsyncResult asyncResult) + { + IEnumerator listResult = this.ServiceClient.EndListTablesSegmented(asyncResult).GetEnumerator(); + listResult.MoveNext(); + this.Properties.ProvisionedIops = listResult.Current.Properties.ProvisionedIops; + this.Properties.RequestedIops = listResult.Current.Properties.RequestedIops; + this.Properties.TableStatus = listResult.Current.Properties.TableStatus; + } + +#if TASK + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// For premium tables only + [DoesServiceRequest] + public virtual Task FetchAttributesAsync(TableRequestOptions requestOptions = null, OperationContext operationContext = null) + { + return this.FetchAttributesAsync(requestOptions, operationContext, CancellationToken.None); + } + + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// For premium tables only + [DoesServiceRequest] + public virtual Task FetchAttributesAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginFetchAttributes, this.EndFetchAttributes, requestOptions, operationContext, CancellationToken.None); + } +#endif + #endregion + + #region Set Table Properties +#if SYNC + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// For premium tables only. + [DoesServiceRequest] + public virtual void SetProperties(TableRequestOptions requestOptions = null, OperationContext operationContext = null) + { + DynamicTableEntity iopsEntity = new DynamicTableEntity(); + iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + + CommonUtility.AssertNotNull("RequestedIops", this.Properties.RequestedIops.Value); + CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); + iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); + + TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Merge, false); + operation.IsTableEntity = true; + CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); + + operation.Execute(this.ServiceClient, serviceTable, requestOptions, operationContext); + } +#endif + /// + /// Updates the table's properties. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// For premium tables only. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginSetProperties(AsyncCallback callback, object state) + { + return this.BeginSetProperties(null /* RequestOptions */, null /* OperationContext */, callback, state); + } + + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// For premium tables only + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginSetProperties(TableRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) + { + DynamicTableEntity iopsEntity = new DynamicTableEntity(); + iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + + CommonUtility.AssertNotNull("RequestedIops", this.Properties.RequestedIops.Value); + CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); + iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); + + TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Merge, false); + operation.IsTableEntity = true; + CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); + + return operation.BeginExecute(this.ServiceClient, serviceTable, requestOptions, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to set table properties on a premium table. + /// + /// An that references the pending asynchronous operation. + public virtual void EndSetProperties(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// For premium tables only. + [DoesServiceRequest] + public virtual Task SetPropertiesAsync(TableRequestOptions requestOptions = null, OperationContext operationContext = null) + { + return this.SetPropertiesAsync(requestOptions, operationContext, CancellationToken.None); + } + + /// + /// Updates the table's properties. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// For premium tables only. + [DoesServiceRequest] + public virtual Task SetPropertiesAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetProperties, this.EndSetProperties, requestOptions, operationContext, CancellationToken.None); + } +#endif + #endregion + #region TableQuery Execute Methods #region NonGeneric #if SYNC @@ -1078,9 +1262,15 @@ public virtual void Create(TableRequestOptions requestOptions = null, OperationC requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); + DynamicTableEntity iopsEntity = new DynamicTableEntity(); + iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + if (this.Properties.RequestedIops.HasValue) + { + CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); + iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); + } + + TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1113,9 +1303,15 @@ public virtual ICancellableAsyncResult BeginCreate(TableRequestOptions requestOp requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); + DynamicTableEntity iopsEntity = new DynamicTableEntity(); + iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + if (this.Properties.RequestedIops.HasValue) + { + CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); + iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); + } + + TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1359,9 +1555,9 @@ public virtual void Delete(TableRequestOptions requestOptions = null, OperationC requestOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Delete); + DynamicTableEntity tableEntity = new DynamicTableEntity(); + tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tableEntity, TableOperationType.Delete); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1394,9 +1590,9 @@ public virtual ICancellableAsyncResult BeginDelete(TableRequestOptions requestOp requestOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Delete); + DynamicTableEntity tableEntity = new DynamicTableEntity(); + tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tableEntity, TableOperationType.Delete); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1729,9 +1925,9 @@ private bool Exists(bool primaryOnly, TableRequestOptions requestOptions, Operat requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Retrieve); + DynamicTableEntity tableEntity = new DynamicTableEntity(); + tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tableEntity, TableOperationType.Retrieve); operation.IsTableEntity = true; operation.IsPrimaryOnlyRetrieve = primaryOnly; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1783,9 +1979,9 @@ private ICancellableAsyncResult BeginExists(bool primaryOnly, TableRequestOption requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tblEntity = new DynamicTableEntity(); - tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Retrieve); + DynamicTableEntity tableEntity = new DynamicTableEntity(); + tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tableEntity, TableOperationType.Retrieve); operation.IsTableEntity = true; operation.IsPrimaryOnlyRetrieve = primaryOnly; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); diff --git a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs index 588103c47..25e5a0006 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs @@ -115,9 +115,26 @@ public virtual IEnumerable ListTables(string prefix = null, TableReq requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this); operationContext = operationContext ?? new OperationContext(); CloudTable serviceTable = this.GetTableReference(TableConstants.TableServiceTablesName); + requestOptions.PropertyResolver = (pk, rk, propName, propValue) => + { + if (propName == TableConstants.RequestedIops || propName == TableConstants.ProvisionedIops) + { + return EdmType.Int32; + } + + return EdmType.String; + }; return CloudTableClient.GenerateListTablesQuery(prefix, null).ExecuteInternal(this, serviceTable, requestOptions, operationContext).Select( - tbl => new CloudTable(tbl[TableConstants.TableName].StringValue, this)); + table => + { + CloudTable tableResult = new CloudTable(table[TableConstants.TableName].StringValue, this); + tableResult.Properties = new TableProperties(); + tableResult.Properties.RequestedIops = table[TableConstants.RequestedIops].Int32Value; + tableResult.Properties.ProvisionedIops = table[TableConstants.ProvisionedIops].Int32Value; + tableResult.Properties.TableStatus = (TableStatus)Enum.Parse(typeof(TableStatus), table[TableConstants.TableStatus].StringValue); + return tableResult; + }); } /// @@ -162,8 +179,8 @@ public virtual TableResultSegment ListTablesSegmented(string prefix, int? maxRes TableQuerySegment res = CloudTableClient.GenerateListTablesQuery(prefix, maxResults).ExecuteQuerySegmentedInternal(currentToken, this, serviceTable, requestOptions, operationContext); - List tables = res.Results.Select(tbl => new CloudTable( - tbl.Properties[TableConstants.TableName].StringValue, + List tables = res.Results.Select(table => new CloudTable( + table.Properties[TableConstants.TableName].StringValue, this)).ToList(); TableResultSegment retSeg = new TableResultSegment(tables) { ContinuationToken = res.ContinuationToken as TableContinuationToken }; @@ -216,6 +233,15 @@ public virtual ICancellableAsyncResult BeginListTablesSegmented(string prefix, i requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this); operationContext = operationContext ?? new OperationContext(); CloudTable serviceTable = this.GetTableReference(TableConstants.TableServiceTablesName); + requestOptions.PropertyResolver = (pk, rk, propName, propValue) => + { + if (propName == TableConstants.RequestedIops || propName == TableConstants.ProvisionedIops) + { + return EdmType.Int32; + } + + return EdmType.String; + }; return CloudTableClient.GenerateListTablesQuery(prefix, maxResults).BeginExecuteQuerySegmentedInternal( currentToken, @@ -236,9 +262,16 @@ public virtual TableResultSegment EndListTablesSegmented(IAsyncResult asyncResul { TableQuerySegment res = Executor.EndExecuteAsync>(asyncResult); - List tables = res.Results.Select(tbl => new CloudTable( - tbl.Properties[TableConstants.TableName].StringValue, - this)).ToList(); + List tables = res.Results.Select(table => + { + CloudTable tableResult = new CloudTable(table.Properties[TableConstants.TableName].StringValue, this); + tableResult.Properties = new TableProperties(); + tableResult.Properties.RequestedIops = table[TableConstants.RequestedIops].Int32Value; + tableResult.Properties.ProvisionedIops = table[TableConstants.ProvisionedIops].Int32Value; + tableResult.Properties.TableStatus = + (TableStatus)Enum.Parse(typeof(TableStatus), table[TableConstants.TableStatus].StringValue); + return tableResult; + }).ToList(); TableResultSegment retSeg = new TableResultSegment(tables) { ContinuationToken = res.ContinuationToken }; return retSeg; diff --git a/Lib/ClassLibraryCommon/Table/TableOperation.cs b/Lib/ClassLibraryCommon/Table/TableOperation.cs index b6e5c70dd..6192803cc 100644 --- a/Lib/ClassLibraryCommon/Table/TableOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableOperation.cs @@ -93,6 +93,7 @@ internal RESTCommand GenerateCMDForOperation(CloudTableClient clien } else if (this.OperationType == TableOperationType.Merge) { + CommonUtility.AssertNotNullOrEmpty("Merge requires a valid ETag", this.ETag); CommonUtility.AssertNotNull("Merge requires a valid PartitionKey", this.PartitionKey); CommonUtility.AssertNotNull("Merge requires a valid RowKey", this.RowKey); @@ -105,6 +106,14 @@ internal RESTCommand GenerateCMDForOperation(CloudTableClient clien CommonUtility.AssertNotNull("RotateEncryptionKey requires a valid PartitionKey", this.PartitionKey); CommonUtility.AssertNotNull("RotateEncryptionKey requires a valid RowKey", this.RowKey); + if (!this.isTableEntity) + { + CommonUtility.AssertNotNullOrEmpty("Merge requires a valid ETag", this.Entity.ETag); + CommonUtility.AssertNotNull("Merge requires a valid PartitionKey", this.Entity.PartitionKey); + CommonUtility.AssertNotNull("Merge requires a valid RowKey", this.Entity.RowKey); + } + + return MergeImpl(this, client, table, modifiedOptions); } else if (this.OperationType == TableOperationType.Replace) diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 76af558e1..27fab0fcb 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1095,7 +1095,8 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion = "2016-05-31"; + + public const string TargetStorageVersion ="2016-07-01"; /// /// Specifies the file type. diff --git a/Lib/Common/Table/CloudTable.Common.cs b/Lib/Common/Table/CloudTable.Common.cs index 3a17d9025..b9236b78f 100644 --- a/Lib/Common/Table/CloudTable.Common.cs +++ b/Lib/Common/Table/CloudTable.Common.cs @@ -73,6 +73,7 @@ internal CloudTable(string tableName, CloudTableClient client) this.Name = tableName; this.StorageUri = NavigationHelper.AppendPathToUri(client.StorageUri, tableName); this.ServiceClient = client; + this.Properties = new TableProperties(); } /// @@ -87,6 +88,12 @@ internal CloudTable(string tableName, CloudTableClient client) /// A string containing the name of the table. public string Name { get; private set; } + /// + /// Gets the table's properties. + /// + /// A object. + public TableProperties Properties { get; internal set; } + /// /// Gets the table URI for the primary location. /// diff --git a/Lib/Common/Table/Protocol/TableConstants.cs b/Lib/Common/Table/Protocol/TableConstants.cs index 7ad43fa3c..556398c74 100644 --- a/Lib/Common/Table/Protocol/TableConstants.cs +++ b/Lib/Common/Table/Protocol/TableConstants.cs @@ -106,6 +106,21 @@ static class TableConstants /// public const string TableName = "TableName"; + /// + /// The requested IOPS for the table. + /// + public const string RequestedIops = "RequestedIOPS"; + + /// + /// The provisioned IOPS for the table. + /// + public const string ProvisionedIops = "ProvisionedIOPS"; + + /// + /// The table status. + /// + public const string TableStatus = "TableStatus"; + /// /// The query filter clause name. /// diff --git a/Lib/Common/Table/TableOperation.Common.cs b/Lib/Common/Table/TableOperation.Common.cs index 08f28e161..a907eb471 100644 --- a/Lib/Common/Table/TableOperation.Common.cs +++ b/Lib/Common/Table/TableOperation.Common.cs @@ -427,7 +427,7 @@ internal Uri GenerateRequestURI(Uri uri, string tableName) if (this.isTableEntity) { // Note tableEntity is only used internally, so we can assume operationContext is not needed - identity = string.Format(CultureInfo.InvariantCulture, "'{0}'", this.Entity.WriteEntity(null /* OperationContext */)[TableConstants.TableName].StringValue); + identity = string.Format(CultureInfo.InvariantCulture, "'{0}'", this.Entity.WriteEntity(null /* OperationContext */)[TableConstants.TableName].StringValue); } else { diff --git a/Lib/Common/Table/TableProperties.cs b/Lib/Common/Table/TableProperties.cs new file mode 100644 index 000000000..033d5a4f0 --- /dev/null +++ b/Lib/Common/Table/TableProperties.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Table +{ + /// + /// The table properties. + /// + public sealed class TableProperties + { + /// + /// Initializes a new instance of the class. + /// + public TableProperties() + { + this.TableStatus = TableStatus.Unspecified; + } + + /// + /// Gets the . + /// + /// This is only supported for premium tables + /// + public TableStatus TableStatus { get; internal set; } + + /// + /// Gets the provisioned IOPS for the table. Value indicates the IOPS for which the table was provisioned last. + /// + /// This is only supported for premium tables. + /// The requested IOPS for the table. Value indicates the IOPS for which the table was provisioned last. + public int? ProvisionedIops { get; internal set; } + + /// + /// Gets or sets the requested IOPS for the table. + /// + /// This is only supported for premium tables. + /// The requested IOPS for the table. + public int? RequestedIops { get; set; } + } +} \ No newline at end of file diff --git a/Lib/Common/Table/TableStatus.cs b/Lib/Common/Table/TableStatus.cs new file mode 100644 index 000000000..16b8af66b --- /dev/null +++ b/Lib/Common/Table/TableStatus.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Table +{ + /// + /// The table status. + /// + public enum TableStatus + { + /// + /// The table staus is not specified. + /// + Unspecified, + + /// + /// Indicates that the table is under going provisioning and the IOPS are not guaranteed at this time. + /// + Provisioning, + + /// + /// The table is ready to handle the provisioned IOPS. + /// + Ready, + } +} \ No newline at end of file diff --git a/Lib/Common/Table/TableStorageModel.cs b/Lib/Common/Table/TableStorageModel.cs index 339281045..4c54a7216 100644 --- a/Lib/Common/Table/TableStorageModel.cs +++ b/Lib/Common/Table/TableStorageModel.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Table using Microsoft.Data.OData; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Microsoft.WindowsAzure.Storage.Table.Protocol; using System; /// @@ -49,6 +50,9 @@ public TableStorageModel(string accountName) this.accountName = accountName; this.tableType = new EdmEntityType(this.accountName, Constants.EntitySetName); this.tableType.AddKeys(this.tableType.AddStructuralProperty(Constants.DefaultTableName, EdmPrimitiveTypeKind.String)); + this.TableType.AddStructuralProperty(TableConstants.ProvisionedIops, EdmPrimitiveTypeKind.Int32, true); + this.TableType.AddStructuralProperty(TableConstants.RequestedIops, EdmPrimitiveTypeKind.Int32, true); + this.TableType.AddStructuralProperty(TableConstants.TableStatus, EdmPrimitiveTypeKind.String, true); this.AddElement(this.tableType); // Add the default entity container - the name and namespace should not matter since we look for default entity container. @@ -100,6 +104,9 @@ internal static EdmEntityType CreateEntityType(string namespaceName, string name entityType.AddStructuralProperty("RowKey", EdmPrimitiveTypeKind.String), entityType.AddStructuralProperty("PartitionKey", EdmPrimitiveTypeKind.String)); entityType.AddStructuralProperty("Timestamp", EdmCoreModel.Instance.GetDateTime(false), null, EdmConcurrencyMode.Fixed); // We need this because we want to ensure that this property is not used for optimistic concurrency checks. + entityType.AddStructuralProperty(TableConstants.ProvisionedIops, EdmPrimitiveTypeKind.Int32, true); + entityType.AddStructuralProperty(TableConstants.RequestedIops, EdmPrimitiveTypeKind.Int32, true); + entityType.AddStructuralProperty(TableConstants.TableStatus, EdmPrimitiveTypeKind.String, true); return entityType; } diff --git a/Lib/WindowsRuntime/Table/CloudTable.cs b/Lib/WindowsRuntime/Table/CloudTable.cs index 745a692ec..35f6543ac 100644 --- a/Lib/WindowsRuntime/Table/CloudTable.cs +++ b/Lib/WindowsRuntime/Table/CloudTable.cs @@ -217,6 +217,12 @@ public virtual Task CreateAsync(TableRequestOptions requestOptions, OperationCon DynamicTableEntity tblEntity = new DynamicTableEntity(); tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + if (this.Properties != null && this.Properties.RequestedIops.HasValue) + { + CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); + tblEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); + } + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; diff --git a/Lib/WindowsRuntime/Table/CloudTableClient.cs b/Lib/WindowsRuntime/Table/CloudTableClient.cs index 45c667ada..96db81e71 100644 --- a/Lib/WindowsRuntime/Table/CloudTableClient.cs +++ b/Lib/WindowsRuntime/Table/CloudTableClient.cs @@ -186,7 +186,21 @@ public virtual Task ListTablesSegmentedAsync(string prefix, return Task.Run(async () => { TableQuerySegment seg = await this.ExecuteQuerySegmentedAsync(TableConstants.TableServiceTablesName, query, currentToken, requestOptions, operationContext, cancellationToken); - TableResultSegment retSegment = new TableResultSegment(seg.Results.Select(tbl => new CloudTable(tbl.Properties[TableConstants.TableName].StringValue, this)).ToList()); + TableResultSegment retSegment = new TableResultSegment(seg.Results.Select(table => + { + CloudTable tableResult = new CloudTable(table.Properties[TableConstants.TableName].StringValue, this); + + tableResult.Properties = new TableProperties(); + if (table.Properties.ContainsKey(TableConstants.RequestedIops)) + { + tableResult.Properties.RequestedIops = table.Properties[TableConstants.RequestedIops].Int32Value; + tableResult.Properties.ProvisionedIops = table.Properties[TableConstants.ProvisionedIops].Int32Value; + tableResult.Properties.TableStatus = (TableStatus)Enum.Parse(typeof(TableStatus), table.Properties[TableConstants.TableStatus].StringValue); + } + + return tableResult; + }).ToList()); + retSegment.ContinuationToken = seg.ContinuationToken; return retSegment; }, cancellationToken); diff --git a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs index 5dc35ad31..490d2ce7a 100644 --- a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs +++ b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs @@ -215,6 +215,85 @@ private void DoListTablesWithPrefixBasic(TablePayloadFormat format) Assert.AreEqual(createdTables.Where((tbl) => tbl.Name.StartsWith(prefixTablesPrefix)).Count(), retrievedTables.Count()); } + //[TestMethod] + //[Description("Test List Tables For Premium Tables")] + //[TestCategory(ComponentCategory.Table)] + //public void ListPremiumTablesPropertiesCheck() + //{ + // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + // { + // ListPremiumTablesPropertiesCheck(payloadFormat); + // } + //} + + //private void ListPremiumTablesPropertiesCheck(TablePayloadFormat format) + //{ + // CloudTableClient tableClient = GenerateCloudTableClient(); + // tableClient.DefaultRequestOptions.PayloadFormat = format; + // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); + // try + // { + // testTable.Properties = new TableProperties(); + // testTable.Properties.RequestedIops = 1000; + // testTable.Create(); + + // CloudTable returnedTable = tableClient.ListTables().ToList().Where((t) => t.Name == testTable.Name).FirstOrDefault(); + // Assert.AreEqual(1000, returnedTable.Properties.RequestedIops.Value); + // Assert.AreEqual(0, returnedTable.Properties.ProvisionedIops.Value); + // Assert.AreEqual(TableStatus.Provisioning, returnedTable.Properties.TableStatus.Value); + + // Assert.IsTrue(testTable.Properties.RequestedIops.HasValue); + // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); + // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); + // } + // finally + // { + // testTable.Delete(); + // } + //} + + //[TestMethod] + //[Description("Test Set and Fetch Table Properties For Premium Tables")] + //[TestCategory(ComponentCategory.PremiumTables)] + //public void PremiumTableGetSetProperties() + //{ + // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + // { + // PremiumTableGetSetProperties(payloadFormat); + // } + //} + + //private void PremiumTableGetSetProperties(TablePayloadFormat format) + //{ + // CloudTableClient tableClient = GenerateCloudTableClient(); + // tableClient.DefaultRequestOptions.PayloadFormat = format; + // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); + // try + // { + // testTable.Properties = new TableProperties(); + // testTable.Properties.RequestedIops = 1000; + // testTable.Create(); + + // Thread.Sleep(250000); + // testTable.Properties.RequestedIops = 3000; + // testTable.SetProperties(); + + // Assert.AreEqual(3000, testTable.Properties.RequestedIops); + // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); + // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); + + // testTable.FetchAttributes(); + + // Assert.AreEqual(3000, testTable.Properties.RequestedIops.Value); + // Assert.AreEqual(1000, testTable.Properties.ProvisionedIops.Value); + // Assert.AreEqual(TableStatus.Provisioning, testTable.Properties.TableStatus.Value); + // } + // finally + // { + // testTable.Delete(); + // } + //} + [TestMethod] [Description("Test List Tables Iterator With Prefix Extended, will check a variety of ")] [TestCategory(ComponentCategory.Table)] diff --git a/Test/Common/TestCategoryConstants.cs b/Test/Common/TestCategoryConstants.cs index df122690d..1cdbf6e06 100644 --- a/Test/Common/TestCategoryConstants.cs +++ b/Test/Common/TestCategoryConstants.cs @@ -32,6 +32,8 @@ public static class ComponentCategory public const string Queue = "Queue"; public const string Table = "Table"; + + public const string PremiumTables = "PremiumTables"; } public static class TestTypeCategory diff --git a/Test/WindowsRuntime/Table/CloudTableClientTest.cs b/Test/WindowsRuntime/Table/CloudTableClientTest.cs index 9ef141824..f3458c960 100644 --- a/Test/WindowsRuntime/Table/CloudTableClientTest.cs +++ b/Test/WindowsRuntime/Table/CloudTableClientTest.cs @@ -262,6 +262,49 @@ private async Task DoListTablesSegmentedWithPrefixAsync(TablePayloadFormat paylo } } + //[TestMethod] + //[Description("Test List Tables For Premium Tables")] + //[TestCategory(ComponentCategory.PremiumTables)] + //public async Task ListPremiumTablesPropertiesCheckAsync() + //{ + // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + // { + // await ListPremiumTablesPropertiesCheckAsync(payloadFormat); + // } + //} + + //private async Task ListPremiumTablesPropertiesCheckAsync(TablePayloadFormat format) + //{ + // CloudTableClient tableClient = GenerateCloudTableClient(); + // tableClient.DefaultRequestOptions.PayloadFormat = format; + // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); + + // testTable.Properties = new TableProperties(); + // testTable.Properties.RequestedIops = 1000; + // await testTable.CreateAsync(); + + // TableResultSegment segment = null; + // List totalResults = new List(); + // do + // { + // segment = await tableClient.ListTablesSegmentedAsync(/*testTable.Name,*/ segment != null ? segment.ContinuationToken : null); + // totalResults.AddRange(segment); + // } + // while (segment.ContinuationToken != null); + + + // CloudTable returnedTable = totalResults.Find((t) => t.Name == testTable.Name); + // Assert.AreEqual(1000, returnedTable.Properties.RequestedIops.Value); + // Assert.AreEqual(0, returnedTable.Properties.ProvisionedIops.Value); + // Assert.AreEqual(TableStatus.Provisioning, returnedTable.Properties.TableStatus.Value); + + // Assert.IsTrue(testTable.Properties.RequestedIops.HasValue); + // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); + // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); + + // await testTable.DeleteAsync(); + //} + [TestMethod] [Description("Test List Tables Segmented with Shared Key Lite")] [TestCategory(ComponentCategory.Table)] From ff6166196fbe0088a175e0d5c53e949054085fa4 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Mon, 21 Nov 2016 21:08:59 -0800 Subject: [PATCH 02/37] Update to the service version --- Lib/Common/Shared/Protocol/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 27fab0fcb..cf30c660c 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1096,7 +1096,7 @@ static HeaderConstants() /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion ="2016-07-01"; + public const string TargetStorageVersion ="2016-10-16"; /// /// Specifies the file type. From 8e5e3f5179e223d194ce0c988a064ad6ef542487 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Tue, 22 Nov 2016 21:48:42 -0800 Subject: [PATCH 03/37] Fix ShareSnapshot QueryParameter for Del/GProp/GMeta APIs --- .../File/Protocol/ShareHttpWebRequestFactory.cs | 2 +- .../File/Protocol/ShareHttpRequestMessageFactory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 589e5226f..4c09e1184 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -388,7 +388,7 @@ private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapsho { if (snapshot.HasValue) { - builder.Add(Constants.QueryConstants.Snapshot, Request.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add(Constants.QueryConstants.ShareSnapshot, Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } } diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index 5a4edeab6..ca0301975 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -309,7 +309,7 @@ private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapsho { if (snapshot.HasValue) { - builder.Add(Constants.QueryConstants.Snapshot, Request.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add(Constants.QueryConstants.ShareSnapshot, Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } } From ab12be74df70dd66122dc5d5f9662c8c8d9da428 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 11 Nov 2016 11:27:58 -0800 Subject: [PATCH 04/37] [.Net] Share Snapshot --- Lib/ClassLibraryCommon/File/CloudFile.cs | 2 + Lib/ClassLibraryCommon/File/CloudFileShare.cs | 28 + .../Protocol/ShareHttpWebRequestFactory.cs | 10 +- .../Core/Auth/SharedAccessSignatureHelper.cs | 1 + Lib/Common/File/FileShareProperties.cs | 20 +- Lib/WindowsRuntime/File/CloudFile.cs | 1 + Lib/WindowsRuntime/File/CloudFileDirectory.cs | 3 + Lib/WindowsRuntime/File/CloudFileShare.cs | 23 + .../ShareHttpRequestMessageFactory.cs | 10 +- .../File/CloudFileClientTest.cs | 179 +++++ .../File/CloudFileDirectoryTest.cs | 279 ++++++++ .../File/CloudFileShareTest.cs | 627 +++++++++++++++++- .../File/CloudFileClientTest.cs | 60 ++ .../File/CloudFileDirectoryTest.cs | 77 +++ .../WindowsRuntime/File/CloudFileShareTest.cs | 120 ++++ 15 files changed, 1410 insertions(+), 30 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFile.cs b/Lib/ClassLibraryCommon/File/CloudFile.cs index 1b7d53f60..ce26f438b 100644 --- a/Lib/ClassLibraryCommon/File/CloudFile.cs +++ b/Lib/ClassLibraryCommon/File/CloudFile.cs @@ -2506,6 +2506,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti [DoesServiceRequest] public virtual bool DeleteIfExists(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -3908,6 +3909,7 @@ public virtual Task StartCopyAsync(CloudBlob source, AccessCondition sou [DoesServiceRequest] public virtual void AbortCopy(string copyId, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); Executor.ExecuteSync( this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index e8f919218..fcff64864 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -46,6 +46,11 @@ public partial class CloudFileShare public virtual void Create(FileRequestOptions requestOptions = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -80,6 +85,11 @@ public virtual ICancellableAsyncResult BeginCreate(AsyncCallback callback, objec public virtual ICancellableAsyncResult BeginCreate(FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Executor.BeginExecuteAsync( this.CreateShareImpl(modifiedOptions), @@ -203,6 +213,12 @@ public virtual ICancellableAsyncResult BeginCreateIfNotExists(AsyncCallback call [DoesServiceRequest] public virtual ICancellableAsyncResult BeginCreateIfNotExists(FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { + this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -1116,6 +1132,7 @@ public virtual Task GetPermissionsAsync(AccessCondition ac [DoesServiceRequest] public virtual ShareStats GetStats(FileRequestOptions options = null, OperationContext operationContext = null) { + this.AssertNoSnapshot(); options = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); return Executor.ExecuteSync( @@ -1148,6 +1165,7 @@ public virtual ICancellableAsyncResult BeginGetStats(AsyncCallback callback, obj [DoesServiceRequest] public virtual ICancellableAsyncResult BeginGetStats(FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { + this.AssertNoSnapshot(); options = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); return Executor.BeginExecuteAsync( @@ -1458,6 +1476,11 @@ public virtual Task SetPermissionsAsync(FileSharePermissions permissions, Access public virtual void SetProperties(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); Executor.ExecuteSync( this.SetPropertiesImpl(accessCondition, modifiedOptions), @@ -1491,6 +1514,11 @@ public virtual ICancellableAsyncResult BeginSetProperties(AsyncCallback callback public virtual ICancellableAsyncResult BeginSetProperties(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Executor.BeginExecuteAsync( this.SetPropertiesImpl(accessCondition, modifiedOptions), diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 4c09e1184..5dbec5935 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -93,7 +93,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpWebRequestFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); HttpWebRequest request = HttpWebRequestFactory.Delete(uri, shareBuilder, timeout, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); @@ -127,7 +127,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpWebRequestFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); HttpWebRequest request = HttpWebRequestFactory.GetMetadata(uri, timeout, shareBuilder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); @@ -161,7 +161,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpWebRequestFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, shareBuilder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); @@ -380,11 +380,11 @@ public static HttpWebRequest SetAcl(Uri uri, int? timeout, FileSharePublicAccess } /// - /// Adds the snapshot. + /// Adds the share snapshot. /// /// An object of type that contains additional parameters to add to the URI query string. /// The snapshot version, if the share is a snapshot. - private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapshot) + private static void AddShareSnapshot(UriQueryBuilder builder, DateTimeOffset? snapshot) { if (snapshot.HasValue) { diff --git a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs index deea5729d..586f8fe20 100644 --- a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs +++ b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs @@ -380,6 +380,7 @@ internal static StorageCredentials ParseQuery(IDictionary queryP case Constants.QueryConstants.Component: case Constants.QueryConstants.Snapshot: case Constants.QueryConstants.ApiVersion: + case Constants.QueryConstants.ShareSnapshot: removeList.Add(parameter.Key); break; diff --git a/Lib/Common/File/FileShareProperties.cs b/Lib/Common/File/FileShareProperties.cs index 8870a6d5c..681389dee 100644 --- a/Lib/Common/File/FileShareProperties.cs +++ b/Lib/Common/File/FileShareProperties.cs @@ -26,8 +26,6 @@ namespace Microsoft.WindowsAzure.Storage.File /// public sealed class FileShareProperties { - private int? quota; - /// /// Gets the ETag value for the share. /// @@ -43,22 +41,6 @@ public sealed class FileShareProperties /// /// Gets or sets the maximum size for the share, in gigabytes. /// - public int? Quota - { - get - { - return this.quota; - } - - set - { - if (value.HasValue) - { - CommonUtility.AssertInBounds("Quota", value.Value, 1); - } - - this.quota = value; - } - } + public int? Quota { get; set; } } } diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index 8bb4e75b8..dc5ed55fd 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -1539,6 +1539,7 @@ public virtual Task AbortCopyAsync(string copyId, AccessCondition accessConditio [DoesServiceRequest] public virtual Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), diff --git a/Lib/WindowsRuntime/File/CloudFileDirectory.cs b/Lib/WindowsRuntime/File/CloudFileDirectory.cs index dfe093e6d..343af80a8 100644 --- a/Lib/WindowsRuntime/File/CloudFileDirectory.cs +++ b/Lib/WindowsRuntime/File/CloudFileDirectory.cs @@ -65,6 +65,7 @@ public virtual Task CreateAsync(FileRequestOptions options, OperationContext ope [DoesServiceRequest] public virtual Task CreateAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async() => await Executor.ExecuteAsyncNullReturn( this.CreateDirectoryImpl(modifiedOptions), @@ -167,6 +168,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.DeleteDirectoryImpl(accessCondition, modifiedOptions), @@ -457,6 +459,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques [DoesServiceRequest] public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetMetadataImpl(accessCondition, modifiedOptions), diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index 56de6e614..2a0c30ca4 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -65,6 +65,12 @@ public virtual Task CreateAsync(FileRequestOptions options, OperationContext ope [DoesServiceRequest] public virtual Task CreateAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.CreateShareImpl(modifiedOptions), @@ -108,6 +114,12 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope [DoesServiceRequest] public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -188,6 +200,7 @@ public virtual Task SnapshotAsync(IDictionary me [DoesServiceRequest] public virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.SnapshotImpl(metadata, accessCondition, modifiedOptions), @@ -437,6 +450,7 @@ public virtual Task SetPermissionsAsync(FileSharePermissions permissions, Access [DoesServiceRequest] public virtual Task SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetPermissionsImpl(permissions, accessCondition, modifiedOptions), @@ -478,6 +492,12 @@ public virtual Task SetPropertiesAsync(AccessCondition accessCondition, FileRequ [DoesServiceRequest] public virtual Task SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) + { + CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); + } + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetPropertiesImpl(accessCondition, modifiedOptions), @@ -519,6 +539,7 @@ public virtual Task GetPermissionsAsync(AccessCondition ac [DoesServiceRequest] public virtual Task GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.GetPermissionsImpl(accessCondition, modifiedOptions), @@ -558,6 +579,7 @@ public virtual Task GetStatsAsync(FileRequestOptions options, Operat [DoesServiceRequest] public virtual Task GetStatsAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.GetStatsImpl(modifiedOptions), @@ -597,6 +619,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques [DoesServiceRequest] public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetMetadataImpl(accessCondition, modifiedOptions), diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index ca0301975..d4acf9225 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -61,7 +61,7 @@ public static StorageRequestMessage Create(Uri uri, FileShareProperties properti public static StorageRequestMessage Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpRequestMessageFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpRequestMessageFactory.AddShareSnapshot(shareBuilder, snapshot); StorageRequestMessage request = HttpRequestMessageFactory.Delete(uri, timeout, shareBuilder, content, operationContext, canonicalizer, credentials); request.ApplyAccessCondition(accessCondition); @@ -79,7 +79,7 @@ public static StorageRequestMessage Delete(Uri uri, int? timeout, DateTimeOffset public static StorageRequestMessage GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpRequestMessageFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpRequestMessageFactory.AddShareSnapshot(shareBuilder, snapshot); StorageRequestMessage request = HttpRequestMessageFactory.GetMetadata(uri, timeout, shareBuilder, content, operationContext, canonicalizer, credentials); request.ApplyAccessCondition(accessCondition); @@ -97,7 +97,7 @@ public static StorageRequestMessage GetMetadata(Uri uri, int? timeout, DateTimeO public static StorageRequestMessage GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - ShareHttpRequestMessageFactory.AddSnapshot(shareBuilder, snapshot); + ShareHttpRequestMessageFactory.AddShareSnapshot(shareBuilder, snapshot); StorageRequestMessage request = HttpRequestMessageFactory.GetProperties(uri, timeout, shareBuilder, content, operationContext, canonicalizer, credentials); request.ApplyAccessCondition(accessCondition); @@ -301,11 +301,11 @@ public static StorageRequestMessage Snapshot(Uri uri, int? timeout, AccessCondit } /// - /// Adds the snapshot. + /// Adds the share snapshot. /// /// An object of type that contains additional parameters to add to the URI query string. /// The snapshot version, if the share is a snapshot. - private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapshot) + private static void AddShareSnapshot(UriQueryBuilder builder, DateTimeOffset? snapshot) { if (snapshot.HasValue) { diff --git a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs index 41f8dbf3a..3d9b4aa3b 100644 --- a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs @@ -1145,5 +1145,184 @@ public void CloudFileClientMaximumExecutionTimeCheck() Assert.IsInstanceOfType(ex, typeof(ArgumentOutOfRangeException)); } } + + [TestMethod] + [Description("Test list shares with a snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListSharesWithSnapshot() + { + CloudFileShare share = GetRandomShareReference(); + share.Create(); + share.Metadata["key1"] = "value1"; + share.SetMetadata(); + + CloudFileShare snapshot = share.Snapshot(); + share.Metadata["key2"] = "value2"; + share.SetMetadata(); + + CloudFileClient client = GenerateCloudFileClient(); + IEnumerable listResult = client.ListShares(share.Name, ShareListingDetails.All, null, null); + + int count = 0; + bool originalFound = false; + bool snapshotFound = false; + foreach (CloudFileShare listShareItem in listResult) + { + if (listShareItem.Name.Equals(share.Name) && !listShareItem.IsSnapshot && !originalFound) + { + count++; + originalFound = true; + Assert.AreEqual(2, listShareItem.Metadata.Count); + Assert.AreEqual("value2", listShareItem.Metadata["key2"]); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(share.StorageUri, listShareItem.StorageUri); + } + else if (listShareItem.Name.Equals(share.Name) && + listShareItem.IsSnapshot && !snapshotFound) + { + count++; + snapshotFound = true; + Assert.AreEqual(1, listShareItem.Metadata.Count); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(snapshot.StorageUri, listShareItem.StorageUri); + } + } + + Assert.AreEqual(2, count); + } + + [TestMethod] + [Description("Test list shares with a snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListSharesWithSnapshotAPM() + { + CloudFileShare share = GetRandomShareReference(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + share.Metadata["key1"] = "value1"; + result = share.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetMetadata(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + + share.Metadata["key2"] = "value2"; + result = share.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetMetadata(result); + + CloudFileClient client = GenerateCloudFileClient(); + result = client.BeginListSharesSegmented(share.Name, ShareListingDetails.All, null, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + IEnumerable listResult = client.EndListSharesSegmented(result).Results; + + int count = 0; + bool originalFound = false; + bool snapshotFound = false; + foreach (CloudFileShare listShareItem in listResult) + { + if (listShareItem.Name.Equals(share.Name) && !listShareItem.IsSnapshot && !originalFound) + { + count++; + originalFound = true; + Assert.AreEqual(2, listShareItem.Metadata.Count); + Assert.AreEqual("value2", listShareItem.Metadata["key2"]); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(share.StorageUri, listShareItem.StorageUri); + } + else if (listShareItem.Name.Equals(share.Name) && + listShareItem.IsSnapshot && !snapshotFound) + { + count++; + snapshotFound = true; + Assert.AreEqual(1, listShareItem.Metadata.Count); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(snapshot.StorageUri, listShareItem.StorageUri); + } + } + + Assert.AreEqual(2, count); + + //snapshot.Delete(); + //share.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Test list shares with a snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListSharesWithSnapshotTask() + { + CloudFileShare share = GetRandomShareReference(); + share.CreateAsync().Wait(); + share.Metadata["key1"] = "value1"; + share.SetMetadataAsync().Wait(); + + CloudFileShare snapshot = share.Snapshot(); + share.Metadata["key2"] = "value2"; + share.SetMetadataAsync().Wait(); + + CloudFileClient client = GenerateCloudFileClient(); + List listedShares = new List(); + FileContinuationToken token = null; + do + { + ShareResultSegment resultSegment = client.ListSharesSegmentedAsync(share.Name, ShareListingDetails.All, null, token, null, null).Result; + token = resultSegment.ContinuationToken; + + foreach (CloudFileShare listResultShare in resultSegment.Results) + { + listedShares.Add(listResultShare); + } + } + while (token != null); + + int count = 0; + bool originalFound = false; + bool snapshotFound = false; + foreach (CloudFileShare listShareItem in listedShares) + { + if (listShareItem.Name.Equals(share.Name) && !listShareItem.IsSnapshot && !originalFound) + { + count++; + originalFound = true; + Assert.AreEqual(2, listShareItem.Metadata.Count); + Assert.AreEqual("value2", listShareItem.Metadata["key2"]); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(share.StorageUri, listShareItem.StorageUri); + } + else if (listShareItem.Name.Equals(share.Name) && + listShareItem.IsSnapshot && !snapshotFound) + { + count++; + snapshotFound = true; + Assert.AreEqual(1, listShareItem.Metadata.Count); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(snapshot.StorageUri, listShareItem.StorageUri); + } + } + + Assert.AreEqual(2, count); + } +#endif } } diff --git a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs index 12b93d0c8..731210d3a 100644 --- a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs @@ -25,6 +25,7 @@ namespace Microsoft.WindowsAzure.Storage.File using System.Linq; using System.Net; using System.Threading; + using Microsoft.WindowsAzure.Storage.Core; [TestClass] public class CloudFileDirectoryTest : FileTestBase @@ -1423,6 +1424,284 @@ public void CloudFileDirectoryCreateAndDeleteWithWriteOnlyPermissionsAPM() } + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInShareSnapshot() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.Create(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.Create(); + dir.Metadata["key1"] = "value1"; + dir.SetMetadata(); + CloudFileShare snapshot = share.Snapshot(); + CloudFileDirectory snapshotDir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.Metadata["key2"] = "value2"; + dir.SetMetadata(); + snapshotDir.FetchAttributes(); + + Assert.IsTrue(snapshotDir.Metadata.Count == 1 && snapshotDir.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotDir.Properties.ETag); + + dir.FetchAttributes(); + Assert.IsTrue(dir.Metadata.Count == 2 && dir.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(dir.Properties.ETag); + Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); + + //snapshot.Delete(); + //share.Delete(); + } + + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInShareSnapshotAPM() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + result = dir.BeginCreate(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndCreate(result); + + dir.Metadata["key1"] = "value1"; + result = dir.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndSetMetadata(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + CloudFileDirectory snapshotDir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + + dir.Metadata["key2"] = "value2"; + result = dir.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndSetMetadata(result); + + result = snapshotDir.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshotDir.EndFetchAttributes(result); + + Assert.IsTrue(snapshotDir.Metadata.Count == 1 && snapshotDir.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotDir.Properties.ETag); + + result = dir.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndFetchAttributes(result); + + Assert.IsTrue(dir.Metadata.Count == 2 && dir.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(dir.Properties.ETag); + Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); + + //snapshot.Delete(); + //share.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInShareSnapshotTask() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.CreateAsync().Wait(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.CreateAsync().Wait(); + dir.Metadata["key1"] = "value1"; + dir.SetMetadataAsync().Wait(); + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFileDirectory snapshotDir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.Metadata["key2"] = "value2"; + dir.SetMetadataAsync().Wait(); + snapshotDir.FetchAttributes(); + + Assert.IsTrue(snapshotDir.Metadata.Count == 1 && snapshotDir.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotDir.Properties.ETag); + + dir.FetchAttributes(); + Assert.IsTrue(dir.Metadata.Count == 2 && dir.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(dir.Properties.ETag); + Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); + + //snapshot.Delete(); + //share.Delete(); + } +#endif + + [TestMethod] + [Description("Test invalid CloudFileDirectory APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInvalidApisInShareSnapshot() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.Create(); + + CloudFileShare snapshot = share.Snapshot(); + CloudFileDirectory dir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + try + { + dir.Create(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.Delete(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.SetMetadata(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + //snapshot.Delete(); + //share.Delete(); + } + + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInvalidApisInShareSnapshotAPM() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + CloudFileDirectory dir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + try + { + result = dir.BeginCreate(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndCreate(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = dir.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndDelete(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = dir.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndSetMetadata(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + //snapshot.Delete(); + //share.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryApisInvalidApisInShareSnapshotTask() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.CreateAsync().Wait(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFileDirectory dir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + try + { + dir.CreateAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.DeleteAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.SetMetadataAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + //snapshot.Delete(); + //share.Delete(); + } +#endif + #if TASK [TestMethod] [Description("Test to ensure CreateIfNotExists/DeleteIfNotExists succeeds with write-only Account SAS permissions - TASK")] diff --git a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs index ab3f31046..2b36b6cb6 100644 --- a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.File.Protocol; using System; using System.Collections.Generic; @@ -2285,7 +2286,8 @@ public void CloudFileShareCreateAndDeleteWithWriteOnlyPermissionsTask() } #endif - private CloudFileShare GenerateRandomWriteOnlyFileShare() { + private CloudFileShare GenerateRandomWriteOnlyFileShare() + { string fileName = "n" + Guid.NewGuid().ToString("N"); SharedAccessAccountPolicy sasAccountPolicy = new SharedAccessAccountPolicy() @@ -2308,6 +2310,629 @@ private CloudFileShare GenerateRandomWriteOnlyFileShare() { return fileShareWithSAS; } + [TestMethod] + [Description("Test share snapshot create")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCreateShareSnapshot() + { + CloudFileShare share = GetRandomShareReference(); + share.Create(); + share.Metadata["key1"] = "value1"; + share.SetMetadata(); + + CloudFileDirectory dir1 = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir1.Create(); + CloudFile file1 = dir1.GetFileReference("file1"); + file1.Create(1024); + byte[] buffer = GetRandomBuffer(1024); + file1.UploadFromByteArray(buffer, 0, 1024); + dir1.Metadata["key2"] = "value2"; + dir1.SetMetadata(); + + CloudFileShare snapshot = share.Snapshot(); + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + Assert.IsTrue(snapshotRef.Exists()); + Assert.IsTrue(snapshotRef.Metadata.Count == 1 && snapshotRef.Metadata["key1"].Equals("value1")); + + CloudFileShare snapshotRef2 = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + snapshotRef2.FetchAttributes(); + Assert.IsTrue(snapshotRef2.Metadata.Count == 1 && snapshotRef2.Metadata["key1"].Equals("value1")); + + Assert.IsTrue(snapshot.Metadata.Count == 1 && snapshot.Metadata["key1"].Equals("value1")); + + CloudFileDirectory snapshotDir1 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + snapshotDir1.Exists(); + Assert.IsTrue(snapshotDir1.Metadata.Count == 1 && snapshotDir1.Metadata["key2"].Equals("value2")); + + CloudFileDirectory snapshotDir2 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + snapshotDir2.FetchAttributes(); + Assert.IsTrue(snapshotDir2.Metadata.Count == 1 && snapshotDir2.Metadata["key2"].Equals("value2")); + + // create snapshot with metadata + IDictionary shareMeta2 = new Dictionary(); + shareMeta2.Add("abc", "def"); + CloudFileShare snapshotRef3 = share.Snapshot(shareMeta2, null, null, null); + CloudFileShare snapshotRef4 = client.GetShareReference(snapshotRef3.Name, snapshotRef3.SnapshotTime); + Assert.IsTrue(snapshotRef4.Exists()); + Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); + } + + [TestMethod] + [Description("Test share snapshot create - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCreateShareSnapshotAPM() + { + CloudFileShare share = GetRandomShareReference(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + share.Metadata["key1"] = "value1"; + result = share.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetMetadata(result); + + CloudFileDirectory dir1 = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + result = dir1.BeginCreate(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir1.EndCreate(result); + + CloudFile file1 = dir1.GetFileReference("file1"); + result = file1.BeginCreate(1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file1.EndCreate(result); + + byte[] buffer = GetRandomBuffer(1024); + result = file1.BeginUploadFromByteArray(buffer, 0, 1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file1.EndUploadFromByteArray(result); + + dir1.Metadata["key2"] = "value2"; + result = dir1.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir1.EndSetMetadata(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + result = snapshotRef.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsTrue(snapshotRef.EndExists(result)); + + result = snapshotRef.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshotRef.EndFetchAttributes(result); + + Assert.IsTrue(snapshotRef.Metadata.Count == 1 && snapshotRef.Metadata["key1"].Equals("value1")); + Assert.IsTrue(snapshot.Metadata.Count == 1 && snapshot.Metadata["key1"].Equals("value1")); + CloudFileDirectory snapshotDir1 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + result = snapshotDir1.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshotDir1.EndFetchAttributes(result); + Assert.IsTrue(snapshotDir1.Metadata.Count == 1 && snapshotDir1.Metadata["key2"].Equals("value2")); + + // create snapshot with metadata + IDictionary shareMeta2 = new Dictionary(); + shareMeta2.Add("abc", "def"); + result = share.BeginSnapshot(shareMeta2, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshotRef3 = share.EndSnapshot(result); + + CloudFileShare snapshotRef4 = client.GetShareReference(snapshotRef3.Name, snapshotRef3.SnapshotTime); + result = snapshotRef4.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsTrue(snapshotRef4.EndExists(result)); + Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); + } + } + +#if TASK + [TestMethod] + [Description("Test share snapshot create - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCreateShareSnapshotTask() + { + CloudFileShare share = GetRandomShareReference(); + share.CreateAsync().Wait(); + share.Metadata["key1"] = "value1"; + share.SetMetadataAsync().Wait(); + + CloudFileDirectory dir1 = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir1.CreateAsync().Wait(); + CloudFile file1 = dir1.GetFileReference("file1"); + file1.CreateAsync(1024).Wait(); + byte[] buffer = GetRandomBuffer(1024); + file1.UploadFromByteArrayAsync(buffer, 0, 1024).Wait(); + dir1.Metadata["key2"] = "value2"; + dir1.SetMetadataAsync().Wait(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + Assert.IsTrue(snapshotRef.ExistsAsync().Result); + Assert.IsTrue(snapshotRef.Metadata.Count == 1 && snapshotRef.Metadata["key1"].Equals("value1")); + + CloudFileShare snapshotRef2 = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + snapshotRef2.FetchAttributesAsync().Wait(); + Assert.IsTrue(snapshotRef2.Metadata.Count == 1 && snapshotRef2.Metadata["key1"].Equals("value1")); + + Assert.IsTrue(snapshot.Metadata.Count == 1 && snapshot.Metadata["key1"].Equals("value1")); + + CloudFileDirectory snapshotDir1 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + snapshotDir1.FetchAttributesAsync().Wait(); + Assert.IsTrue(snapshotDir1.Metadata.Count == 1 && snapshotDir1.Metadata["key2"].Equals("value2")); + + // create snapshot with metadata + IDictionary shareMeta2 = new Dictionary(); + shareMeta2.Add("abc", "def"); + CloudFileShare snapshotRef3 = share.SnapshotAsync(shareMeta2, null, null, null).Result; + CloudFileShare snapshotRef4 = client.GetShareReference(snapshotRef3.Name, snapshotRef3.SnapshotTime); + Assert.IsTrue(snapshotRef4.ExistsAsync().Result); + Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); + } +#endif + [TestMethod] + [Description("Test invalid APIs on a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisForShareSnapshot() + { + CloudFileShare share = GetRandomShareReference(); + share.Create(); + CloudFileShare snapshot = share.Snapshot(); + try + { + snapshot.Create(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.GetPermissions(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.GetStats(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetMetadata(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetPermissions(null); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetProperties(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.Snapshot(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + } + + [TestMethod] + [Description("Test invalid APIs on a share snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisForShareSnapshotAPM() + { + CloudFileShare share = GetRandomShareReference(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + try + { + result = snapshot.BeginCreate(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSnapshot(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginGetPermissions(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndGetPermissions(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginGetStats(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndGetStats(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetMetadata(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginSetPermissions(null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetPermissions(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginSetProperties(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetProperties(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = snapshot.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSnapshot(result); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + } + } + +#if TASK + [TestMethod] + [Description("Test invalid APIs on a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisForShareSnapshotTask() + { + CloudFileShare share = GetRandomShareReference(); + share.CreateAsync().Wait(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + try + { + snapshot.CreateAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.GetPermissionsAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.GetStatsAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetMetadataAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetPermissionsAsync(null).Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SetPropertiesAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + snapshot.SnapshotAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + } +#endif + + [TestMethod] + [Description("Test list files and directories within a snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListFilesAndDirectoriesWithinSnapshot() + { + CloudFileShare share = GetRandomShareReference(); + share.Create(); + CloudFileDirectory myDir = share.GetRootDirectoryReference().GetDirectoryReference("mydir"); + + myDir.Create(); + myDir.GetFileReference("myfile").Create(1024);; + myDir.GetDirectoryReference("yourDir").Create(); + Assert.IsTrue(share.Exists()); + CloudFileShare snapshot = share.Snapshot(); + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + IEnumerable listResult = snapshotRef.GetRootDirectoryReference().ListFilesAndDirectories(); + int count = 0; + foreach (IListFileItem listFileItem in listResult) { + count++; + Assert.AreEqual("mydir", ((CloudFileDirectory) listFileItem).Name); + } + + Assert.AreEqual(1, count); + + count = 0; + listResult = snapshotRef.GetRootDirectoryReference().GetDirectoryReference("mydir").ListFilesAndDirectories(); + foreach (IListFileItem listFileItem in listResult) { + if (listFileItem is CloudFileDirectory) { + count++; + Assert.AreEqual("yourDir", ((CloudFileDirectory) listFileItem).Name); + } + else { + count++; + Assert.AreEqual("myfile", ((CloudFile) listFileItem).Name); + } + } + + Assert.AreEqual(2, count); + + snapshot.Delete(); + share.Delete(); + } + + [TestMethod] + [Description("Test list files and directories within a snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListFilesAndDirectoriesWithinSnapshotAPM() + { + CloudFileShare share = GetRandomShareReference(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + CloudFileDirectory myDir = share.GetRootDirectoryReference().GetDirectoryReference("mydir"); + result = myDir.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + myDir.EndCreate(result); + + myDir.GetFileReference("myfile").Create(1024); + CloudFileDirectory yourDir = myDir.GetDirectoryReference("yourDir"); + result = yourDir.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + yourDir.EndCreate(result); + + result = share.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsTrue(share.EndExists(result)); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + result = snapshotRef.GetRootDirectoryReference().BeginListFilesAndDirectoriesSegmented(null, ArgIterator => waitHandle.Set(), null); + waitHandle.WaitOne(); + IEnumerable listResult = snapshotRef.GetRootDirectoryReference().EndListFilesAndDirectoriesSegmented(result).Results; + int count = 0; + foreach (IListFileItem listFileItem in listResult) + { + count++; + Assert.AreEqual("mydir", ((CloudFileDirectory)listFileItem).Name); + } + + Assert.AreEqual(1, count); + + count = 0; + result = snapshotRef.GetRootDirectoryReference().GetDirectoryReference("mydir").BeginListFilesAndDirectoriesSegmented(null, ArgIterator => waitHandle.Set(), null); + waitHandle.WaitOne(); + listResult = snapshotRef.GetRootDirectoryReference().EndListFilesAndDirectoriesSegmented(result).Results; + foreach (IListFileItem listFileItem in listResult) + { + if (listFileItem is CloudFileDirectory) + { + count++; + Assert.AreEqual("yourDir", ((CloudFileDirectory)listFileItem).Name); + } + else + { + count++; + Assert.AreEqual("myfile", ((CloudFile)listFileItem).Name); + } + } + + Assert.AreEqual(2, count); + + //result = snapshot.BeginDelete(ar => waitHandle.Set(), null); + //waitHandle.WaitOne(); + //snapshot.EndDelete(result); + + //result = share.BeginDelete(ar => waitHandle.Set(), null); + //waitHandle.WaitOne(); + //share.EndDelete(result); + } + } + +#if TASK + [TestMethod] + [Description("Test list files and directories within a snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileListFilesAndDirectoriesWithinSnapshotTask() + { + CloudFileShare share = GetRandomShareReference(); + share.CreateAsync().Wait(); + CloudFileDirectory myDir = share.GetRootDirectoryReference().GetDirectoryReference("mydir"); + + myDir.CreateAsync().Wait(); + myDir.GetFileReference("myfile").Create(1024); ; + myDir.GetDirectoryReference("yourDir").Create(); + Assert.IsTrue(share.ExistsAsync().Result); + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + List listedFileItems = new List(); + FileContinuationToken token = null; + do + { + FileResultSegment resultSegment = snapshotRef.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(token).Result; + token = resultSegment.ContinuationToken; + + foreach (IListFileItem listResultItem in resultSegment.Results) + { + listedFileItems.Add(listResultItem); + } + } + while (token != null); + + int count = 0; + foreach (IListFileItem listFileItem in listedFileItems) + { + count++; + Assert.AreEqual("mydir", ((CloudFileDirectory)listFileItem).Name); + } + + Assert.AreEqual(1, count); + + token = null; + listedFileItems.Clear(); + do + { + FileResultSegment resultSegment = snapshotRef.GetRootDirectoryReference().GetDirectoryReference("mydir").ListFilesAndDirectoriesSegmentedAsync(token).Result; + token = resultSegment.ContinuationToken; + + foreach (IListFileItem listResultItem in resultSegment.Results) + { + listedFileItems.Add(listResultItem); + } + } + while (token != null); + + count = 0; + foreach (IListFileItem listFileItem in listedFileItems) + { + if (listFileItem is CloudFileDirectory) + { + count++; + Assert.AreEqual("yourDir", ((CloudFileDirectory)listFileItem).Name); + } + else + { + count++; + Assert.AreEqual("myfile", ((CloudFile)listFileItem).Name); + } + } + + Assert.AreEqual(2, count); + + //snapshot.DeleteAsync().Wait(); + //share.DeleteAsync().Wait(); + } +#endif + /* [TestMethod] [Description("Test conditional access on a share")] diff --git a/Test/WindowsRuntime/File/CloudFileClientTest.cs b/Test/WindowsRuntime/File/CloudFileClientTest.cs index 8aa4ff02b..5b7975219 100644 --- a/Test/WindowsRuntime/File/CloudFileClientTest.cs +++ b/Test/WindowsRuntime/File/CloudFileClientTest.cs @@ -399,5 +399,65 @@ public async Task CloudFileClientServerTimeoutAsync() await share.ExistsAsync(options, context); Assert.IsNull(timeout); } + + [TestMethod] + [Description("Test list shares with a snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileListSharesWithSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + share.Metadata["key1"] = "value1"; + await share.SetMetadataAsync(); + + CloudFileShare snapshot = await share.SnapshotAsync(); + share.Metadata["key2"] = "value2"; + await share.SetMetadataAsync(); + + CloudFileClient client = GenerateCloudFileClient(); + List listedShares = new List(); + FileContinuationToken token = null; + do + { + ShareResultSegment resultSegment = await client.ListSharesSegmentedAsync(share.Name, ShareListingDetails.All, null, token, null, null); + token = resultSegment.ContinuationToken; + + foreach (CloudFileShare listResultShare in resultSegment.Results) + { + listedShares.Add(listResultShare); + } + } + while (token != null); + + int count = 0; + bool originalFound = false; + bool snapshotFound = false; + foreach (CloudFileShare listShareItem in listedShares) + { + if (listShareItem.Name.Equals(share.Name) && !listShareItem.IsSnapshot && !originalFound) + { + count++; + originalFound = true; + Assert.AreEqual(2, listShareItem.Metadata.Count); + Assert.AreEqual("value2", listShareItem.Metadata["key2"]); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(share.StorageUri, listShareItem.StorageUri); + } + else if (listShareItem.Name.Equals(share.Name) && + listShareItem.IsSnapshot && !snapshotFound) + { + count++; + snapshotFound = true; + Assert.AreEqual(1, listShareItem.Metadata.Count); + Assert.AreEqual("value1", listShareItem.Metadata["key1"]); + Assert.AreEqual(snapshot.StorageUri, listShareItem.StorageUri); + } + } + + Assert.AreEqual(2, count); + } } } diff --git a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs index d86ab35fa..cb37c8590 100644 --- a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs +++ b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs @@ -18,7 +18,9 @@ namespace Microsoft.WindowsAzure.Storage.File { using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.Linq; @@ -716,5 +718,80 @@ public void CloudFileDirectoryAbsoluteUriAppended() dir = share.GetRootDirectoryReference().GetDirectoryReference(share.Uri.AbsoluteUri + "/TopDir1"); Assert.AreEqual(NavigationHelper.AppendPathToSingleUri(share.Uri, share.Uri.AbsoluteUri + "/TopDir1"), dir.Uri); } + + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileDirectoryApisInShareSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + await dir.CreateAsync(); + dir.Metadata["key1"] = "value1"; + await dir.SetMetadataAsync(null, null, null); + CloudFileShare snapshot = await share.SnapshotAsync(); + + CloudFileDirectory snapshotDir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.Metadata["key2"] = "value2"; + await dir.SetMetadataAsync(null, null, null); + await snapshotDir.FetchAttributesAsync(); + + Assert.IsTrue(snapshotDir.Metadata.Count == 1 && snapshotDir.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotDir.Properties.ETag); + + await dir.FetchAttributesAsync(); + Assert.IsTrue(dir.Metadata.Count == 2 && dir.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(dir.Properties.ETag); + Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); + + //snapshot.Delete(); + //share.Delete(); + } + + [TestMethod] + [Description("Test CloudFileDirectory APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileDirectoryApisInvalidApisInShareSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + CloudFileShare snapshot = await share.SnapshotAsync(); + CloudFileDirectory dir = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + + try + { + dir.CreateAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.DeleteAsync().Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + dir.SetMetadataAsync(null, null, null).Wait(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + //snapshot.Delete(); + //share.Delete(); + } } } diff --git a/Test/WindowsRuntime/File/CloudFileShareTest.cs b/Test/WindowsRuntime/File/CloudFileShareTest.cs index 99f23a5be..666caac43 100644 --- a/Test/WindowsRuntime/File/CloudFileShareTest.cs +++ b/Test/WindowsRuntime/File/CloudFileShareTest.cs @@ -26,6 +26,7 @@ using System.Globalization; #else using Windows.Globalization; +using Microsoft.WindowsAzure.Storage.Core; #endif namespace Microsoft.WindowsAzure.Storage.File @@ -818,6 +819,125 @@ public void CloudFileSharePermissionsFromStringAsync() Assert.AreEqual(SharedAccessFilePermissions.Write, policy.Permissions); } + [TestMethod] + [Description("Test creating a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareCreateSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + share.Metadata["key1"] = "value1"; + await share.SetMetadataAsync(); + + CloudFileDirectory dir1 = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + await dir1.CreateAsync(); + CloudFile file1 = dir1.GetFileReference("file1"); + await file1.CreateAsync(1024); + byte[] buffer = GetRandomBuffer(1024); + await file1.UploadFromByteArrayAsync(buffer, 0, 1024); + dir1.Metadata["key2"] = "value2"; + await dir1.SetMetadataAsync(null, null, null); + + CloudFileShare snapshot = await share.SnapshotAsync(); + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + Assert.IsTrue(await snapshotRef.ExistsAsync()); + Assert.IsTrue(snapshotRef.Metadata.Count == 1 && snapshotRef.Metadata["key1"].Equals("value1")); + + CloudFileShare snapshotRef2 = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + await snapshotRef2.FetchAttributesAsync(); + Assert.IsTrue(snapshotRef2.Metadata.Count == 1 && snapshotRef2.Metadata["key1"].Equals("value1")); + + Assert.IsTrue(snapshot.Metadata.Count == 1 && snapshot.Metadata["key1"].Equals("value1")); + CloudFileDirectory snapshotDir1 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + await snapshotDir1.ExistsAsync(); + Assert.IsTrue(snapshotDir1.Metadata.Count == 1 && snapshotDir1.Metadata["key2"].Equals("value2")); + + CloudFileDirectory snapshotDir2 = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1"); + await snapshotDir2.FetchAttributesAsync(); + Assert.IsTrue(snapshotDir2.Metadata.Count == 1 && snapshotDir2.Metadata["key2"].Equals("value2")); + + // create snapshot with metadata + IDictionary shareMeta2 = new Dictionary(); + shareMeta2.Add("abc", "def"); + CloudFileShare snapshotRef3 = await share.SnapshotAsync(shareMeta2, null, null, null); + CloudFileShare snapshotRef4 = client.GetShareReference(snapshotRef3.Name, snapshotRef3.SnapshotTime); + Assert.IsTrue(await snapshotRef4.ExistsAsync()); + Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); + } + + [TestMethod] + [Description("Test invalid APIs on a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileInvalidApisForShareSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + CloudFileShare snapshot = await share.SnapshotAsync(); + try + { + await snapshot.CreateAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.GetPermissionsAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.GetStatsAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.SetMetadataAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.SetPermissionsAsync(null); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.SetPropertiesAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await snapshot.SnapshotAsync(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + } + /* [TestMethod] [Description("Test conditional access on a share")] From de7a08be6972d1cecf0f284b3215601eae4861da Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 23 Jan 2017 16:22:42 -0800 Subject: [PATCH 05/37] Encryption at REST for files --- .../Blob/CloudAppendBlob.cs | 4 +- Lib/ClassLibraryCommon/Blob/CloudBlob.cs | 13 +- Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs | 6 +- Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs | 4 +- Lib/ClassLibraryCommon/File/CloudFile.cs | 4 + .../File/CloudFileDirectory.cs | 2 + .../Protocol/DirectoryHttpResponseParsers.cs | 3 + .../File/Protocol/FileHttpResponseParsers.cs | 3 + .../Shared/Protocol/HttpResponseParsers.cs | 11 ++ Lib/Common/Blob/BlobProperties.cs | 4 +- Lib/Common/File/FileDirectoryProperties.cs | 6 + Lib/Common/File/FileProperties.cs | 7 + Lib/WindowsRuntime/Blob/CloudAppendBlob.cs | 4 +- Lib/WindowsRuntime/Blob/CloudBlob.cs | 13 +- Lib/WindowsRuntime/Blob/CloudBlockBlob.cs | 6 +- Lib/WindowsRuntime/Blob/CloudPageBlob.cs | 4 +- Lib/WindowsRuntime/File/CloudFile.cs | 4 + Lib/WindowsRuntime/File/CloudFileDirectory.cs | 2 + .../Protocol/DirectoryHttpResponseParsers.cs | 4 + .../File/Protocol/FileHttpResponseParsers.cs | 3 + .../Shared/Protocol/HttpResponseParsers.cs | 12 ++ .../File/FileServerEncryptionTests.cs | 144 ++++++++++++++ .../File/FileServerEncryptionTests.cs | 184 ++++++++++++++++++ ...rosoft.WindowsAzure.Storage.RT.Test.csproj | 1 + 24 files changed, 409 insertions(+), 39 deletions(-) create mode 100644 Test/ClassLibraryCommon/File/FileServerEncryptionTests.cs create mode 100644 Test/WindowsRuntime/File/FileServerEncryptionTests.cs diff --git a/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs index 9d5c92a0c..aa112fa10 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs @@ -2810,7 +2810,7 @@ private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRe { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = 0; return NullType.Value; }; @@ -2856,7 +2856,7 @@ internal RESTCommand AppendBlockImpl(Stream source, string contentMD5, Acc HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, appendOffset, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return appendOffset; }; diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs index ac15b1aa5..d2efee64b 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs @@ -3257,7 +3257,7 @@ private RESTCommand SetMetadataImpl(BlobAttributes blobAttributes, Acc { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(blobAttributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -3753,16 +3753,5 @@ internal static Uri SourceBlobToUri(CloudBlob source) CommonUtility.AssertNotNull("source", source); return source.ServiceClient.Credentials.TransformUri(source.SnapshotQualifiedUri); } - - /// - /// Parses the server request encrypted response header. - /// - /// Response to be parsed. - /// true if write content was encrypted by service or false if not. - internal static bool ParseServerRequestEncrypted(HttpWebResponse response) - { - string requestEncrypted = response.Headers[Constants.HeaderConstants.ServerRequestEncrypted]; - return string.Equals(requestEncrypted, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); - } } } \ No newline at end of file diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index 319853aa1..b80f34eeb 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -2416,7 +2416,7 @@ private RESTCommand PutBlobImpl(Stream stream, long? length, string co { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = putCmd.SendStreamLength.Value; return NullType.Value; }; @@ -2456,7 +2456,7 @@ internal RESTCommand PutBlockImpl(Stream source, string blockId, strin putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -2507,7 +2507,7 @@ internal RESTCommand PutBlockListImpl(IEnumerable bl { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = -1; return NullType.Value; }; diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index 1bea9a15d..722be6491 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -2477,7 +2477,7 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = sizeInBytes; return NullType.Value; }; @@ -2642,7 +2642,7 @@ private RESTCommand PutPageImpl(Stream pageData, long startOffset, str { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/ClassLibraryCommon/File/CloudFile.cs b/Lib/ClassLibraryCommon/File/CloudFile.cs index eb136fb2f..bc2ef96ac 100644 --- a/Lib/ClassLibraryCommon/File/CloudFile.cs +++ b/Lib/ClassLibraryCommon/File/CloudFile.cs @@ -4150,6 +4150,7 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); this.Properties.Length = sizeInBytes; + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -4274,6 +4275,7 @@ private RESTCommand SetPropertiesImpl(AccessCondition accessCondition, { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -4323,6 +4325,7 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -4369,6 +4372,7 @@ private RESTCommand PutRangeImpl(Stream rangeData, long startOffset, s { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs index d7bc0b5a0..0ac6205c9 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs @@ -1305,6 +1305,7 @@ private RESTCommand CreateDirectoryImpl(FileRequestOptions options) { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagAndLastModified(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1453,6 +1454,7 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagAndLastModified(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs index 11fcd85dc..ec105346b 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs @@ -19,6 +19,7 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; using System.Net; @@ -48,6 +49,8 @@ public static FileDirectoryProperties GetProperties(HttpWebResponse response) FileDirectoryProperties directoryProperties = new FileDirectoryProperties(); directoryProperties.ETag = HttpResponseParsers.GetETag(response); + string directoryEncryption = response.Headers[Constants.HeaderConstants.ServerEncrypted]; + directoryProperties.IsServerEncrypted = string.Equals(directoryEncryption, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); #if WINDOWS_PHONE directoryProperties.LastModified = HttpResponseParsers.GetLastModified(response); diff --git a/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs b/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs index 4e3089b2f..f706a2942 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs @@ -80,6 +80,9 @@ public static FileProperties GetProperties(HttpWebResponse response) properties.ContentType = response.Headers[HttpResponseHeader.ContentType]; properties.CacheControl = response.Headers[HttpResponseHeader.CacheControl]; + string fileEncryption = response.Headers[Constants.HeaderConstants.ServerEncrypted]; + properties.IsServerEncrypted = string.Equals(fileEncryption, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); + // Get the content length. Prioritize range and x-ms over content length for the special cases. string rangeHeader = response.Headers[HttpResponseHeader.ContentRange]; string contentLengthHeader = response.Headers[Constants.HeaderConstants.ContentLengthHeader]; diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs b/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs index d7822b421..a02bd2c7c 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/HttpResponseParsers.cs @@ -68,6 +68,17 @@ internal static IDictionary GetMetadata(HttpWebResponse response return GetMetadataOrProperties(response, Constants.HeaderConstants.PrefixForStorageMetadata); } + /// + /// Parses the server request encrypted response header. + /// + /// Response to be parsed. + /// true if write content was encrypted by service or false if not. + internal static bool ParseServerRequestEncrypted(HttpWebResponse response) + { + string requestEncrypted = response.Headers[Constants.HeaderConstants.ServerRequestEncrypted]; + return string.Equals(requestEncrypted, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); + } + /// /// Gets the metadata or properties. /// diff --git a/Lib/Common/Blob/BlobProperties.cs b/Lib/Common/Blob/BlobProperties.cs index 6b9a6b29a..4d26b8e8c 100644 --- a/Lib/Common/Blob/BlobProperties.cs +++ b/Lib/Common/Blob/BlobProperties.cs @@ -163,11 +163,13 @@ public BlobProperties(BlobProperties other) /// /// Gets the blob's server-side encryption state. /// + /// A bool representing the blob's server-side encryption state. public bool IsServerEncrypted { get; internal set; } /// - /// Gets a value indicating whether or not this blob is an incremental copy + /// Gets a value indicating whether or not this blob is an incremental copy. /// + /// A bool representing if the blob is an incremental copy. public bool IsIncrementalCopy { get; internal set; } } } diff --git a/Lib/Common/File/FileDirectoryProperties.cs b/Lib/Common/File/FileDirectoryProperties.cs index 1854b5836..3bc83a24c 100644 --- a/Lib/Common/File/FileDirectoryProperties.cs +++ b/Lib/Common/File/FileDirectoryProperties.cs @@ -41,5 +41,11 @@ public sealed class FileDirectoryProperties /// /// The directory's last-modified time. public DateTimeOffset? LastModified { get; internal set; } + + /// + /// Gets the directory's server-side encryption state. + /// + /// A bool representing the directory's server-side encryption state. + public bool IsServerEncrypted { get; internal set; } } } diff --git a/Lib/Common/File/FileProperties.cs b/Lib/Common/File/FileProperties.cs index e1f4756cb..7691dfd1d 100644 --- a/Lib/Common/File/FileProperties.cs +++ b/Lib/Common/File/FileProperties.cs @@ -50,6 +50,7 @@ public FileProperties(FileProperties other) this.Length = other.Length; this.ETag = other.ETag; this.LastModified = other.LastModified; + this.IsServerEncrypted = other.IsServerEncrypted; } /// @@ -117,5 +118,11 @@ public FileProperties(FileProperties other) /// /// The file's last-modified time, in UTC format. public DateTimeOffset? LastModified { get; internal set; } + + /// + /// Gets the file's server-side encryption state. + /// + /// A bool representing the file's server-side encryption state. + public bool IsServerEncrypted { get; internal set; } } } diff --git a/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs index 8a4da9da3..11992062c 100644 --- a/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs @@ -1170,7 +1170,7 @@ private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRe { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = 0; return NullType.Value; }; @@ -1206,7 +1206,7 @@ internal RESTCommand AppendBlockImpl(Stream source, string contentMD5, Acc HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, appendOffset, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return appendOffset; }; diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index fae7fbbd9..98b726a0b 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -1259,7 +1259,7 @@ private RESTCommand SetMetadataImpl(BlobAttributes attributes, AccessC { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1625,16 +1625,5 @@ internal static Uri SourceBlobToUri(CloudBlob source) CommonUtility.AssertNotNull("source", source); return source.ServiceClient.Credentials.TransformUri(source.SnapshotQualifiedUri); } - - /// - /// Parses the server request encrypted response header. - /// - /// Response to be parsed. - /// true if write content was encrypted by service or false if not. - internal static bool ParseServerRequestEncrypted(HttpResponseMessage response) - { - string requestEncrypted = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.ServerRequestEncrypted); - return string.Equals(requestEncrypted, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); - } } } diff --git a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs index e30060cff..f9fc82d10 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs @@ -1011,7 +1011,7 @@ private RESTCommand PutBlobImpl(Stream stream, long? length, string co { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = length.Value; return NullType.Value; }; @@ -1041,7 +1041,7 @@ internal RESTCommand PutBlockImpl(Stream source, string blockId, strin putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1082,7 +1082,7 @@ internal RESTCommand PutBlockListImpl(IEnumerable bl { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = -1; return NullType.Value; }; diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index 6fcf2d421..b93a456d6 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -991,7 +991,7 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = sizeInBytes; return NullType.Value; }; @@ -1196,7 +1196,7 @@ private RESTCommand PutPageImpl(Stream pageData, long startOffset, str { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - cmd.CurrentResult.IsRequestServerEncrypted = CloudBlob.ParseServerRequestEncrypted(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index dc5ed55fd..08640c219 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -1678,6 +1678,7 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); this.Properties.Length = sizeInBytes; + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1807,6 +1808,7 @@ private RESTCommand SetPropertiesImpl(AccessCondition accessCondition, { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1858,6 +1860,7 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -1896,6 +1899,7 @@ private RESTCommand PutRangeImpl(Stream rangeData, long startOffset, s { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/WindowsRuntime/File/CloudFileDirectory.cs b/Lib/WindowsRuntime/File/CloudFileDirectory.cs index 343af80a8..2a4761013 100644 --- a/Lib/WindowsRuntime/File/CloudFileDirectory.cs +++ b/Lib/WindowsRuntime/File/CloudFileDirectory.cs @@ -488,6 +488,7 @@ private RESTCommand CreateDirectoryImpl(FileRequestOptions options) { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); this.UpdateETagAndLastModified(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; @@ -633,6 +634,7 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagAndLastModified(resp); + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; diff --git a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs index 6a22bc008..4d559bf23 100644 --- a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs @@ -17,7 +17,9 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; using System.Net.Http; @@ -34,6 +36,8 @@ public static FileDirectoryProperties GetProperties(HttpResponseMessage response FileDirectoryProperties directoryProperties = new FileDirectoryProperties(); directoryProperties.ETag = (response.Headers.ETag == null) ? null : response.Headers.ETag.ToString(); + string directoryEncryption = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.ServerEncrypted); + directoryProperties.IsServerEncrypted = string.Equals(directoryEncryption, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); if (response.Content != null) { diff --git a/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs b/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs index 8c75c11cc..700161c1c 100644 --- a/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs @@ -67,6 +67,9 @@ public static FileProperties GetProperties(HttpResponseMessage response) properties.ContentType = response.Content.Headers.ContentType.ToString(); } + string fileEncryption = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.ServerEncrypted); + properties.IsServerEncrypted = string.Equals(fileEncryption, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); + // Get the content length. Prioritize range and x-ms over content length for the special cases. string contentLengthHeader = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.FileContentLengthHeader); if ((response.Content.Headers.ContentRange != null) && diff --git a/Lib/WindowsRuntime/Shared/Protocol/HttpResponseParsers.cs b/Lib/WindowsRuntime/Shared/Protocol/HttpResponseParsers.cs index 6f545cb5c..fafb02cbb 100644 --- a/Lib/WindowsRuntime/Shared/Protocol/HttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Shared/Protocol/HttpResponseParsers.cs @@ -18,6 +18,7 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol { using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Core.Util; using System; using System.Collections.Generic; using System.Net; @@ -46,6 +47,17 @@ internal static IDictionary GetMetadata(HttpResponseMessage resp return GetMetadataOrProperties(response, Constants.HeaderConstants.PrefixForStorageMetadata); } + /// + /// Parses the server request encrypted response header. + /// + /// Response to be parsed. + /// true if write content was encrypted by service or false if not. + internal static bool ParseServerRequestEncrypted(HttpResponseMessage response) + { + string requestEncrypted = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.ServerRequestEncrypted); + return string.Equals(requestEncrypted, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); + } + /// /// Gets the metadata or properties. /// diff --git a/Test/ClassLibraryCommon/File/FileServerEncryptionTests.cs b/Test/ClassLibraryCommon/File/FileServerEncryptionTests.cs new file mode 100644 index 000000000..e196001d5 --- /dev/null +++ b/Test/ClassLibraryCommon/File/FileServerEncryptionTests.cs @@ -0,0 +1,144 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2016 Microsoft Corporation +// +// Licensed 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. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.Azure.KeyVault; + using Microsoft.Azure.KeyVault.Core; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using System.IO; + using System.Security.Cryptography; + using System.Threading; + using System.Threading.Tasks; + + [TestClass] + public class FileServerEncryptionTests : FileTestBase + { + private CloudFileShare share; + private CloudFileDirectory directory; + private CloudFile file; + + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + + this.share = GetRandomShareReference(); + this.share.CreateIfNotExists(); + CloudFileDirectory directory = share.GetRootDirectoryReference(); + this.directory = directory.GetDirectoryReference("directory"); + this.directory.Create(); + this.file = this.directory.GetFileReference("file"); + this.file.UploadText("test"); + } + + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + + this.share.DeleteIfExists(); + } + + [TestMethod] + [Description("Download encrypted file attributes.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void TestFileAttributesEncryption() + { + this.file.FetchAttributes(); + Assert.IsTrue(this.file.Properties.IsServerEncrypted); + + CloudFile testFile = this.directory.GetFileReference(this.file.Name); + testFile.DownloadText(); + Assert.IsTrue(testFile.Properties.IsServerEncrypted); + } + + [TestMethod] + [Description("Upload encrypted file.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void TestFileEncryption() + { + bool requestFound = false; + + OperationContext ctxt = new OperationContext(); + ctxt.RequestCompleted += (sender, args) => + { + Assert.IsTrue(args.RequestInformation.IsRequestServerEncrypted); + requestFound = true; + }; + + this.file.UploadText("test", null, null, null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + this.file.SetProperties(null, null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + this.file.SetMetadata(null, null, ctxt); + Assert.IsTrue(requestFound); + } + + [TestMethod] + [Description("Upload encrypted file directory.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void TestFileDirectoryEncryption() + { + bool requestFound = false; + + OperationContext ctxt = new OperationContext(); + ctxt.RequestCompleted += (sender, args) => + { + Assert.IsTrue(args.RequestInformation.IsRequestServerEncrypted); + requestFound = true; + }; + + CloudFileDirectory dir2 = this.share.GetRootDirectoryReference().GetDirectoryReference("dir2"); + dir2.Create(null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + dir2.SetMetadata(null, null, ctxt); + Assert.IsTrue(requestFound); + } + } +} \ No newline at end of file diff --git a/Test/WindowsRuntime/File/FileServerEncryptionTests.cs b/Test/WindowsRuntime/File/FileServerEncryptionTests.cs new file mode 100644 index 000000000..aba0c88fa --- /dev/null +++ b/Test/WindowsRuntime/File/FileServerEncryptionTests.cs @@ -0,0 +1,184 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2016 Microsoft Corporation +// +// Licensed 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. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Threading.Tasks; + + + [TestClass] + public class FileServerEncryptionTests : FileTestBase +#if XUNIT +, IDisposable +#endif + { + +#if XUNIT + // Todo: The simple/nonefficient workaround is to minimize change and support Xunit, + public FileServerEncryptionTests() + { + MyTestInitialize(); + } + public void Dispose() + { + MyTestCleanup(); + } +#endif + + // + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + + // + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("Download encrypted file attributes.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TestFileAttributesEncryptionAsync() + { + CloudFileShare share = GetRandomShareReference(); + + try + { + await share.CreateIfNotExistsAsync(); + + CloudFileDirectory directory = share.GetRootDirectoryReference(); + CloudFile file = directory.GetFileReference("file"); + await file.UploadTextAsync("test"); + + await file.FetchAttributesAsync(); + Assert.IsTrue(file.Properties.IsServerEncrypted); + + CloudFile testFile = directory.GetFileReference(file.Name); + await testFile.DownloadTextAsync(); + Assert.IsTrue(testFile.Properties.IsServerEncrypted); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Upload encrypted file.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TestFileEncryptionAsync() + { + bool requestFound = false; + + OperationContext ctxt = new OperationContext(); + CloudFileShare share = GetRandomShareReference(); + + try + { + await share.CreateIfNotExistsAsync(); + + CloudFileDirectory directory = share.GetRootDirectoryReference(); + CloudFile file = directory.GetFileReference("file"); + + await file.UploadTextAsync("test"); + + ctxt.RequestCompleted += (sender, args) => + { + Assert.IsTrue(args.RequestInformation.IsRequestServerEncrypted); + requestFound = true; + }; + + await file.UploadTextAsync("test", null, null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + await file.SetPropertiesAsync(null, null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + await file.SetMetadataAsync(null, null, ctxt); + Assert.IsTrue(requestFound); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Upload encrypted directory.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TestFileDirectoryEncryptionAsync() + { + bool requestFound = false; + + OperationContext ctxt = new OperationContext(); + CloudFileShare share = GetRandomShareReference(); + + try + { + await share.CreateIfNotExistsAsync(); + + ctxt.RequestCompleted += (sender, args) => + { + Assert.IsTrue(args.RequestInformation.IsRequestServerEncrypted); + requestFound = true; + }; + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("dir"); + + await directory.CreateAsync(null, ctxt); + Assert.IsTrue(requestFound); + + requestFound = false; + await directory.SetMetadataAsync(null, null, ctxt); + Assert.IsTrue(requestFound); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } + } +} \ No newline at end of file diff --git a/Test/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.Test.csproj b/Test/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.Test.csproj index efe3e7c29..f9e769e18 100644 --- a/Test/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.Test.csproj +++ b/Test/WindowsRuntime/Microsoft.WindowsAzure.Storage.RT.Test.csproj @@ -100,6 +100,7 @@ + From f5d0344704bb94f06dbbcabf6db9e700ed834d63 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 23 Jan 2017 16:39:19 -0800 Subject: [PATCH 06/37] Delete Share Snapshot changes --- Lib/ClassLibraryCommon/Blob/CloudBlob.cs | 18 +-- Lib/ClassLibraryCommon/Blob/ICloudBlob.cs | 16 +-- .../Protocol/BlobHttpWebRequestFactory.cs | 4 +- Lib/ClassLibraryCommon/File/CloudFileShare.cs | 120 ++++++++++++++++-- .../Protocol/ShareHttpWebRequestFactory.cs | 24 +++- Lib/Common/File/DeleteShareSnapshotsOption.cs | 35 +++++ Lib/WindowsRuntime/Blob/CloudBlob.cs | 10 +- Lib/WindowsRuntime/Blob/ICloudBlob.cs | 4 +- .../Protocol/BlobHttpRequestMessageFactory.cs | 2 +- Lib/WindowsRuntime/File/CloudFileShare.cs | 45 +++++-- .../ShareHttpRequestMessageFactory.cs | 16 ++- .../File/CloudFileShareTest.cs | 109 ++++++++++++++++ .../File/Protocol/FileTests.cs | 2 +- .../WindowsRuntime/File/CloudFileShareTest.cs | 29 +++++ 14 files changed, 382 insertions(+), 52 deletions(-) create mode 100644 Lib/Common/File/DeleteShareSnapshotsOption.cs diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs index d2efee64b..5a06f1227 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs @@ -1671,7 +1671,7 @@ public virtual Task SetPropertiesAsync(AccessCondition accessCondition, BlobRequ /// /// Deletes the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. If null, default options are applied to the request. /// An object that represents the context for the current operation. @@ -1701,7 +1701,7 @@ public virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, objec /// /// Begins an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1754,7 +1754,7 @@ public virtual Task DeleteAsync(CancellationToken cancellationToken) /// /// Initiates an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1768,7 +1768,7 @@ public virtual Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, Acc /// /// Initiates an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1785,7 +1785,7 @@ public virtual Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, Acc /// /// Deletes the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. If null, default options are applied to the request. /// An object that represents the context for the current operation. @@ -1839,7 +1839,7 @@ public virtual ICancellableAsyncResult BeginDeleteIfExists(AsyncCallback callbac /// /// Begins an asynchronous request to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1946,7 +1946,7 @@ public virtual Task DeleteIfExistsAsync(CancellationToken cancellationToke /// /// Initiates an asynchronous operation to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1960,7 +1960,7 @@ public virtual Task DeleteIfExistsAsync(DeleteSnapshotsOption deleteSnapsh /// /// Initiates an asynchronous operation to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -3295,7 +3295,7 @@ private RESTCommand SetPropertiesImpl(BlobAttributes blobAttributes, A /// Implements the DeleteBlob method. /// /// The attributes. - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// diff --git a/Lib/ClassLibraryCommon/Blob/ICloudBlob.cs b/Lib/ClassLibraryCommon/Blob/ICloudBlob.cs index a1c68939c..ccc8b6f1b 100644 --- a/Lib/ClassLibraryCommon/Blob/ICloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/ICloudBlob.cs @@ -1183,7 +1183,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Deletes the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. If null, default options are applied to the request. /// An object that represents the context for the current operation. @@ -1201,7 +1201,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Begins an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1233,7 +1233,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Initiates an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1243,7 +1243,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Initiates an asynchronous operation to delete the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1256,7 +1256,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Deletes the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. If null, default options are applied to the request. /// An object that represents the context for the current operation. @@ -1275,7 +1275,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Begins an asynchronous request to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1308,7 +1308,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Initiates an asynchronous operation to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1318,7 +1318,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Initiates an asynchronous operation to delete the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the condition that must be met in order for the request to proceed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs index d7ed6262c..2d988e467 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs @@ -634,7 +634,7 @@ public static void AddMetadata(HttpWebRequest request, string name, string value /// A specifying the absolute URI to the blob. /// An integer specifying the server timeout interval. /// A specifying the snapshot timestamp, if the blob is a snapshot. - /// A set of options indicating whether to delete only blobs, only snapshots, or both. + /// A object indicating whether to delete only blobs, only snapshots, or both. /// An object that represents the condition that must be met in order for the request to proceed. /// An object that represents the context for the current operation. /// A object. @@ -649,7 +649,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snaps /// A specifying the absolute URI to the blob. /// An integer specifying the server timeout interval. /// A specifying the snapshot timestamp, if the blob is a snapshot. - /// A set of options indicating whether to delete only blobs, only snapshots, or both. + /// A object indicating whether to delete only blobs, only snapshots, or both. /// An object that represents the condition that must be met in order for the request to proceed. /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index 799283b71..eb392176f 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -449,10 +449,23 @@ internal virtual Task SnapshotAsync(IDictionary /// An object that represents the context for the current operation. [DoesServiceRequest] public virtual void Delete(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + this.Delete(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext); + } + + /// + /// Deletes the share. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void Delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); Executor.ExecuteSync( - this.DeleteShareImpl(accessCondition, modifiedOptions), + this.DeleteShareImpl(deleteSnapshotsOption, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } @@ -467,7 +480,7 @@ public virtual void Delete(AccessCondition accessCondition = null, FileRequestOp [DoesServiceRequest] public virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, object state) { - return this.BeginDelete(null /* accessCondition */, null /* options */, null /*operationContext */, callback, state); + return this.BeginDelete(DeleteShareSnapshotsOption.None, null /* accessCondition */, null /* options */, null /*operationContext */, callback, state); } /// @@ -481,10 +494,27 @@ public virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, objec /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginDelete(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginDelete(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to delete a share. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The callback delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginDelete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Executor.BeginExecuteAsync( - this.DeleteShareImpl(accessCondition, modifiedOptions), + this.DeleteShareImpl(deleteSnapshotsOption, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -532,7 +562,7 @@ public virtual Task DeleteAsync(CancellationToken cancellationToken) [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { - return this.DeleteAsync(accessCondition, options, operationContext, CancellationToken.None); + return this.DeleteAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -546,7 +576,22 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginDelete, this.EndDelete, accessCondition, options, operationContext, cancellationToken); + return this.DeleteAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Returns a task that performs an asynchronous operation to delete a share. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the current operation. + [DoesServiceRequest] + public virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginDelete, this.EndDelete, deleteSnapshotsOption, accessCondition, options, operationContext, cancellationToken); } #endif @@ -560,6 +605,20 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti /// true if the share did not already exist and was created; otherwise false. [DoesServiceRequest] public virtual bool DeleteIfExists(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + return this.DeleteIfExists(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext); + } + + /// + /// Deletes the share if it already exists. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// true if the share did not already exist and was created; otherwise false. + [DoesServiceRequest] + public virtual bool DeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -582,7 +641,7 @@ public virtual bool DeleteIfExists(AccessCondition accessCondition = null, FileR try { - this.Delete(accessCondition, modifiedOptions, operationContext); + this.Delete(deleteSnapshotsOption, accessCondition, modifiedOptions, operationContext); return true; } catch (StorageException e) @@ -616,7 +675,7 @@ public virtual bool DeleteIfExists(AccessCondition accessCondition = null, FileR [DoesServiceRequest] public virtual ICancellableAsyncResult BeginDeleteIfExists(AsyncCallback callback, object state) { - return this.BeginDeleteIfExists(null, null, null, callback, state); + return this.BeginDeleteIfExists(DeleteShareSnapshotsOption.None, null, null, null, callback, state); } /// @@ -630,6 +689,23 @@ public virtual ICancellableAsyncResult BeginDeleteIfExists(AsyncCallback callbac /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginDeleteIfExists(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginDeleteIfExists(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous request to delete the share if it already exists. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The callback delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginDeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -640,11 +716,11 @@ public virtual ICancellableAsyncResult BeginDeleteIfExists(AccessCondition acces OperationContext = operationContext, }; - this.DeleteIfExistsHandler(accessCondition, modifiedOptions, operationContext, storageAsyncResult); + this.DeleteIfExistsHandler(deleteSnapshotsOption, accessCondition, modifiedOptions, operationContext, storageAsyncResult); return storageAsyncResult; } - private void DeleteIfExistsHandler(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, StorageAsyncResult storageAsyncResult) + private void DeleteIfExistsHandler(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, StorageAsyncResult storageAsyncResult) { ICancellableAsyncResult savedExistsResult = this.BeginExists( options, @@ -681,6 +757,7 @@ private void DeleteIfExistsHandler(AccessCondition accessCondition, FileRequestO } ICancellableAsyncResult savedDeleteResult = this.BeginDelete( + deleteSnapshotsOption, accessCondition, options, operationContext, @@ -772,7 +849,7 @@ public virtual Task DeleteIfExistsAsync(CancellationToken cancellationToke [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { - return this.DeleteIfExistsAsync(accessCondition, options, operationContext, CancellationToken.None); + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -786,7 +863,23 @@ public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, F [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromApm(this.BeginDeleteIfExists, this.EndDeleteIfExists, accessCondition, options, operationContext, cancellationToken); + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Returns a task that performs an asynchronous request to delete the share if it already exists. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the current operation. + [DoesServiceRequest] + public virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginDeleteIfExists, this.EndDeleteIfExists, deleteSnapshotsOption, accessCondition, options, operationContext, cancellationToken); } #endif @@ -1647,15 +1740,16 @@ internal RESTCommand SnapshotImpl(IDictionary me /// /// Implementation for the Delete method. /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. /// An object that represents the access conditions for the share. If null, no condition is used. /// An object that specifies additional options for the request. /// A that deletes the share. - private RESTCommand DeleteShareImpl(AccessCondition accessCondition, FileRequestOptions options) + private RESTCommand DeleteShareImpl(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options) { RESTCommand deleteCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); options.ApplyToStorageCommand(deleteCmd); - deleteCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.Delete(uri, serverTimeout, this.SnapshotTime, accessCondition, useVersionHeader, ctx); + deleteCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.Delete(uri, serverTimeout, this.SnapshotTime, deleteSnapshotsOption, accessCondition, useVersionHeader, ctx); deleteCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, NullType.Value, cmd, ex); diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 38b50fc98..49879bc18 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -23,6 +23,7 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; + using System.Globalization; using System.Net; using System.Text; @@ -77,7 +78,7 @@ public static HttpWebRequest Create(Uri uri, FileShareProperties properties, int /// A web request to use to perform the operation. public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { - return ShareHttpWebRequestFactory.Delete(uri, timeout, null /* snapshot */, accessCondition, useVersionHeader, operationContext); + return ShareHttpWebRequestFactory.Delete(uri, timeout, null /* snapshot */, DeleteShareSnapshotsOption.None, accessCondition, useVersionHeader, operationContext); } /// @@ -86,16 +87,35 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces /// The absolute URI to the share. /// The server timeout interval. /// A specifying the snapshot timestamp, if the share is a snapshot. + /// A object indicating whether to only delete the share or delete the share and all snapshots. /// The access condition to apply to the request. /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { + if ((snapshot != null) && (deleteSnapshotsOption != DeleteShareSnapshotsOption.None)) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, SR.DeleteSnapshotsNotValidError, "deleteSnapshotsOption", "snapshot")); + } + UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); HttpWebRequest request = HttpWebRequestFactory.Delete(uri, shareBuilder, timeout, useVersionHeader, operationContext); + + switch (deleteSnapshotsOption) + { + case DeleteShareSnapshotsOption.None: + break; // nop + + case DeleteShareSnapshotsOption.IncludeSnapshots: + request.Headers.Add( + Constants.HeaderConstants.DeleteSnapshotHeader, + Constants.HeaderConstants.IncludeSnapshotsValue); + break; + } + request.ApplyAccessCondition(accessCondition); return request; } diff --git a/Lib/Common/File/DeleteShareSnapshotsOption.cs b/Lib/Common/File/DeleteShareSnapshotsOption.cs new file mode 100644 index 000000000..6639d00c4 --- /dev/null +++ b/Lib/Common/File/DeleteShareSnapshotsOption.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + /// + /// The set of options describing delete operation. + /// + public enum DeleteShareSnapshotsOption + { + /// + /// Delete the share only. If the share has snapshots, this option will result in an error from the service. + /// + None, + + /// + /// Delete the share and its snapshots. + /// + IncludeSnapshots + } +} \ No newline at end of file diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index 98b726a0b..ba601133c 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -597,7 +597,7 @@ public virtual Task DeleteAsync() /// /// Deletes the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -611,7 +611,7 @@ public virtual Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, Acc /// /// Deletes the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -641,7 +641,7 @@ public virtual Task DeleteIfExistsAsync() /// /// Deletes the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -655,7 +655,7 @@ public virtual Task DeleteIfExistsAsync(DeleteSnapshotsOption deleteSnapsh /// /// Deletes the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1298,7 +1298,7 @@ private RESTCommand SetPropertiesImpl(BlobAttributes attributes, Acces /// Implements the DeleteBlob method. /// /// The blob's attributes. - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. If null, no condition is used. /// An object that specifies additional options for the request. /// A that deletes the blob. diff --git a/Lib/WindowsRuntime/Blob/ICloudBlob.cs b/Lib/WindowsRuntime/Blob/ICloudBlob.cs index 2adc8fe1e..0cfdf1a9c 100644 --- a/Lib/WindowsRuntime/Blob/ICloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/ICloudBlob.cs @@ -310,7 +310,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Deletes the blob. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the blob. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -326,7 +326,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// Deletes the blob if it already exists. /// - /// Whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. + /// A object indicating whether to only delete the blob, to delete the blob and all snapshots, or to only delete the snapshots. /// An object that represents the access conditions for the container. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs index d60f8a51b..0738485c3 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs @@ -423,7 +423,7 @@ public static void AddMetadata(StorageRequestMessage request, string name, strin /// The absolute URI to the blob. /// The server timeout interval. /// The snapshot timestamp, if the blob is a snapshot. - /// A set of options indicating whether to delete only blobs, only snapshots, or both. + /// A object indicating whether to delete only blobs, only snapshots, or both. /// The access condition to apply to the request. /// The HTTP entity body and content headers. /// An object that represents the context for the current operation. diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index c0236f7df..b6b69aab4 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -215,7 +215,7 @@ internal virtual Task SnapshotAsync(IDictionary [DoesServiceRequest] public virtual Task DeleteAsync() { - return this.DeleteAsync(null, null, null); + return this.DeleteAsync(DeleteShareSnapshotsOption.None, null, null, null, CancellationToken.None); } /// @@ -227,7 +227,7 @@ public virtual Task DeleteAsync() [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { - return this.DeleteAsync(accessCondition, options, operationContext, CancellationToken.None); + return this.DeleteAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -239,10 +239,24 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti /// A to observe while waiting for a task to complete. [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Deletes the share. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + [DoesServiceRequest] + public virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( - this.DeleteShareImpl(accessCondition, modifiedOptions), + this.DeleteShareImpl(deleteSnapshotsOption, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -255,7 +269,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti [DoesServiceRequest] public virtual Task DeleteIfExistsAsync() { - return this.DeleteIfExistsAsync(null, null, null); + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, null, null, null, CancellationToken.None); } /// @@ -268,7 +282,7 @@ public virtual Task DeleteIfExistsAsync() [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { - return this.DeleteIfExistsAsync(accessCondition, options, operationContext, CancellationToken.None); + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -280,6 +294,20 @@ public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, F /// true if the share already existed and was deleted; otherwise, false. [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.DeleteIfExistsAsync(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Deletes the share if it already exists. + /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// true if the share already existed and was deleted; otherwise, false. + [DoesServiceRequest] + public virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -304,7 +332,7 @@ public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, F try { - await this.DeleteAsync(accessCondition, modifiedOptions, operationContext, cancellationToken); + await this.DeleteAsync(deleteSnapshotsOption, accessCondition, modifiedOptions, operationContext, cancellationToken); return true; } catch (Exception) @@ -694,15 +722,16 @@ internal RESTCommand SnapshotImpl(IDictionary me /// /// Implementation for the Delete method. /// + /// A object indicating whether to only delete the share or delete the share and all snapshots. /// An object that represents the access conditions for the share. If null, no condition is used. /// An object that specifies additional options for the request. /// A that deletes the share. - private RESTCommand DeleteShareImpl(AccessCondition accessCondition, FileRequestOptions options) + private RESTCommand DeleteShareImpl(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options) { RESTCommand deleteCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); options.ApplyToStorageCommand(deleteCmd); - deleteCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ShareHttpRequestMessageFactory.Delete(uri, serverTimeout, this.SnapshotTime, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + deleteCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ShareHttpRequestMessageFactory.Delete(uri, serverTimeout, this.SnapshotTime, deleteSnapshotsOption, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, NullType.Value, cmd, ex); return deleteCmd; diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index 59d3f5879..4d7bfbe7b 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -56,14 +56,28 @@ public static StorageRequestMessage Create(Uri uri, FileShareProperties properti /// The absolute URI to the share. /// The server timeout interval. /// A specifying the snapshot timestamp, if the share is a snapshot. + /// A object indicating whether to only delete the share or delete the share and all snapshots. /// The access condition to apply to the request. /// A web request to use to perform the operation. - public static StorageRequestMessage Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + public static StorageRequestMessage Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpRequestMessageFactory.AddShareSnapshot(shareBuilder, snapshot); StorageRequestMessage request = HttpRequestMessageFactory.Delete(uri, timeout, shareBuilder, content, operationContext, canonicalizer, credentials); + + switch (deleteSnapshotsOption) + { + case DeleteShareSnapshotsOption.None: + break; // nop + + case DeleteShareSnapshotsOption.IncludeSnapshots: + request.Headers.Add( + Constants.HeaderConstants.DeleteSnapshotHeader, + Constants.HeaderConstants.IncludeSnapshotsValue); + break; + } + request.ApplyAccessCondition(accessCondition); return request; } diff --git a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs index 2b36b6cb6..6ef78a90d 100644 --- a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs @@ -2488,6 +2488,115 @@ public void CloudFileCreateShareSnapshotTask() Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); } #endif + + [TestMethod] + [Description("Test deleting a share that contains snapshots")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareDeleteSnapshotOptions() + { + CloudFileShare share = GetRandomShareReference(); + share.Create(); + CloudFileShare snapshot = share.Snapshot(); + + try + { + share.Delete(DeleteShareSnapshotsOption.None, null, null, null); + Assert.Fail("Should not be able to delete a share that has snapshots"); + } + catch (StorageException e) + { + Assert.AreEqual("ShareHasSnapshots", e.RequestInformation.ExtendedErrorInformation.ErrorCode); + } + + share.Delete(DeleteShareSnapshotsOption.IncludeSnapshots, null, null, null); + + Assert.IsFalse(share.Exists()); + Assert.IsFalse(snapshot.Exists()); + } + + [TestMethod] + [Description("Test deleting a share that contains snapshots - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareDeleteSnapshotOptionsAPM() + { + CloudFileShare share = GetRandomShareReference(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + try + { + result = share.BeginDelete( DeleteShareSnapshotsOption.None, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); + + Assert.Fail("Should not be able to delete a share that has snapshots"); + } + catch (StorageException e) + { + Assert.AreEqual("ShareHasSnapshots", e.RequestInformation.ExtendedErrorInformation.ErrorCode); + } + + result = share.BeginDelete(DeleteShareSnapshotsOption.IncludeSnapshots, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); + + result = share.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsFalse(share.EndExists(result)); + + result = snapshot.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsFalse(snapshot.EndExists(result)); + } + } + +#if TASK + [TestMethod] + [Description("Test deleting a share that contains snapshots - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareDeleteSnapshotOptionsTask() + { + CloudFileShare share = GetRandomShareReference(); + share.CreateAsync().Wait(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + + try + { + share.DeleteAsync(DeleteShareSnapshotsOption.None, null, null, null, CancellationToken.None).Wait(); + Assert.Fail("Should not be able to delete a share that has snapshots"); + } + catch (AggregateException ae) + { + StorageException se = ae.InnerException as StorageException; + Assert.AreEqual("ShareHasSnapshots", se.RequestInformation.ExtendedErrorInformation.ErrorCode); + } + + share.DeleteAsync(DeleteShareSnapshotsOption.IncludeSnapshots, null, null, null, CancellationToken.None).Wait(); + + Assert.IsFalse(share.ExistsAsync().Result); + Assert.IsFalse(snapshot.ExistsAsync().Result); + } +#endif + [TestMethod] [Description("Test invalid APIs on a share snapshot")] [TestCategory(ComponentCategory.File)] diff --git a/Test/ClassLibraryCommon/File/Protocol/FileTests.cs b/Test/ClassLibraryCommon/File/Protocol/FileTests.cs index 0ca429b6d..c4544b6ae 100644 --- a/Test/ClassLibraryCommon/File/Protocol/FileTests.cs +++ b/Test/ClassLibraryCommon/File/Protocol/FileTests.cs @@ -271,7 +271,7 @@ public static HttpWebRequest DeleteShareRequest(FileContext context, string shar { Uri uri = FileClientTests.ConstructUri(context.Address, shareName); OperationContext opContext = new OperationContext(); - HttpWebRequest request = ShareHttpWebRequestFactory.Delete(uri, context.Timeout, null, accessCondition, true, opContext); + HttpWebRequest request = ShareHttpWebRequestFactory.Delete(uri, context.Timeout, null, DeleteShareSnapshotsOption.None, accessCondition, true, opContext); Assert.IsNotNull(request); Assert.IsNotNull(request.Method); Assert.AreEqual("DELETE", request.Method); diff --git a/Test/WindowsRuntime/File/CloudFileShareTest.cs b/Test/WindowsRuntime/File/CloudFileShareTest.cs index 666caac43..0ac30e97f 100644 --- a/Test/WindowsRuntime/File/CloudFileShareTest.cs +++ b/Test/WindowsRuntime/File/CloudFileShareTest.cs @@ -27,6 +27,7 @@ #else using Windows.Globalization; using Microsoft.WindowsAzure.Storage.Core; +using System.Threading; #endif namespace Microsoft.WindowsAzure.Storage.File @@ -869,6 +870,34 @@ public async Task CloudFileShareCreateSnapshotAsync() Assert.IsTrue(snapshotRef4.Metadata.Count == 1 && snapshotRef4.Metadata["abc"].Equals("def")); } + [TestMethod] + [Description("Test deleting a share that contains snapshots")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareDeleteSnapshotOptionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + CloudFileShare snapshot = await share.SnapshotAsync(); + + try + { + await share.DeleteAsync(DeleteShareSnapshotsOption.None, null, null, null, CancellationToken.None); + Assert.Fail("Should not be able to delete a share that has snapshots"); + } + catch (StorageException e) + { + Assert.AreEqual("The share has snapshots and the operation requires no snapshots.", e.Message); + } + + await share.DeleteAsync(DeleteShareSnapshotsOption.IncludeSnapshots, null, null, null, CancellationToken.None); + + Assert.IsFalse(await share.ExistsAsync()); + Assert.IsFalse(await snapshot.ExistsAsync()); + } + [TestMethod] [Description("Test invalid APIs on a share snapshot")] [TestCategory(ComponentCategory.File)] From 30f535237f2d45c77cabb76a63ee74010637b80c Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 23 Jan 2017 16:40:55 -0800 Subject: [PATCH 07/37] [ShareSnapshot] Fix listing files within a share snapshot --- Lib/Common/File/CloudFile.Common.cs | 11 ++- Lib/Common/File/CloudFileDirectory.Common.cs | 8 +- .../File/CloudFileClientTest.cs | 10 ++- .../File/CloudFileDirectoryTest.cs | 34 ++++--- .../File/CloudFileShareTest.cs | 63 ++++++++----- .../File/CloudFileClientTest.cs | 3 + .../File/CloudFileDirectoryTest.cs | 8 +- .../WindowsRuntime/File/CloudFileShareTest.cs | 90 ++++++++++++++++++- 8 files changed, 179 insertions(+), 48 deletions(-) diff --git a/Lib/Common/File/CloudFile.Common.cs b/Lib/Common/File/CloudFile.Common.cs index 455ae5aa1..d95af53c9 100644 --- a/Lib/Common/File/CloudFile.Common.cs +++ b/Lib/Common/File/CloudFile.Common.cs @@ -119,7 +119,7 @@ internal CloudFile(CloudFileAttributes attributes, CloudFileClient serviceClient /// /// Stores the file's attributes. /// - private readonly CloudFileAttributes attributes; + internal CloudFileAttributes attributes; /// /// Gets the object that represents the File service. @@ -173,6 +173,11 @@ public FileProperties Properties { return this.attributes.Properties; } + + internal set + { + this.attributes.Properties = value; + } } /// @@ -436,13 +441,13 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti { this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials ?? parsedCredentials); } - + // Create ServiceClient before creating share. if (parsedShareSnapshot.HasValue) { this.Share.SnapshotTime = parsedShareSnapshot; } - + this.Name = NavigationHelper.GetFileName(this.Uri, this.ServiceClient.UsePathStyleUris); } } diff --git a/Lib/Common/File/CloudFileDirectory.Common.cs b/Lib/Common/File/CloudFileDirectory.Common.cs index a22531d51..237fb9749 100644 --- a/Lib/Common/File/CloudFileDirectory.Common.cs +++ b/Lib/Common/File/CloudFileDirectory.Common.cs @@ -240,9 +240,11 @@ private IListFileItem SelectListFileItem(IListFileEntry protocolItem) ListFileEntry file = protocolItem as ListFileEntry; if (file != null) { - CloudFileAttributes attributes = file.Attributes; - attributes.StorageUri = NavigationHelper.AppendPathToUri(this.StorageUri, file.Name); - return new CloudFile(attributes, this.ServiceClient); + CloudFile cloudFile = this.GetFileReference(file.Name); + cloudFile.Properties = file.Properties; + cloudFile.attributes = file.Attributes; + + return cloudFile; } ListFileDirectoryEntry fileDirectory = protocolItem as ListFileDirectoryEntry; diff --git a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs index 3d9b4aa3b..8b2601c97 100644 --- a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs @@ -1192,6 +1192,9 @@ public void CloudFileListSharesWithSnapshot() } Assert.AreEqual(2, count); + + snapshot.Delete(); + share.Delete(); } [TestMethod] @@ -1258,8 +1261,8 @@ public void CloudFileListSharesWithSnapshotAPM() Assert.AreEqual(2, count); - //snapshot.Delete(); - //share.Delete(); + snapshot.Delete(); + share.Delete(); } } @@ -1322,6 +1325,9 @@ public void CloudFileListSharesWithSnapshotTask() } Assert.AreEqual(2, count); + + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); } #endif } diff --git a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs index 731210d3a..79e971990 100644 --- a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs @@ -1454,8 +1454,8 @@ public void CloudFileDirectoryApisInShareSnapshot() Assert.IsNotNull(dir.Properties.ETag); Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); - //snapshot.Delete(); - //share.Delete(); + snapshot.Delete(); + share.Delete(); } [TestMethod] @@ -1512,8 +1512,13 @@ public void CloudFileDirectoryApisInShareSnapshotAPM() Assert.IsNotNull(dir.Properties.ETag); Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); - //snapshot.Delete(); - //share.Delete(); + result = snapshot.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshot.EndDelete(result); + + result = share.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); } } @@ -1548,8 +1553,8 @@ public void CloudFileDirectoryApisInShareSnapshotTask() Assert.IsNotNull(dir.Properties.ETag); Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); - //snapshot.Delete(); - //share.Delete(); + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); } #endif @@ -1593,8 +1598,8 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshot() Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); } - //snapshot.Delete(); - //share.Delete(); + snapshot.Delete(); + share.Delete(); } [TestMethod] @@ -1651,8 +1656,13 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotAPM() Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); } - //snapshot.Delete(); - //share.Delete(); + result = snapshot.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshot.EndDelete(result); + + result = share.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); } } @@ -1697,8 +1707,8 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotTask() Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); } - //snapshot.Delete(); - //share.Delete(); + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); } #endif diff --git a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs index 6ef78a90d..e32201a47 100644 --- a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs @@ -2853,9 +2853,10 @@ public void CloudFileListFilesAndDirectoriesWithinSnapshot() CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); IEnumerable listResult = snapshotRef.GetRootDirectoryReference().ListFilesAndDirectories(); int count = 0; - foreach (IListFileItem listFileItem in listResult) { + foreach (IListFileItem listFileItem in listResult) + { count++; - Assert.AreEqual("mydir", ((CloudFileDirectory) listFileItem).Name); + Assert.AreEqual("mydir", ((CloudFileDirectory)listFileItem).Name); } Assert.AreEqual(1, count); @@ -2863,16 +2864,24 @@ public void CloudFileListFilesAndDirectoriesWithinSnapshot() count = 0; listResult = snapshotRef.GetRootDirectoryReference().GetDirectoryReference("mydir").ListFilesAndDirectories(); foreach (IListFileItem listFileItem in listResult) { - if (listFileItem is CloudFileDirectory) { + if (listFileItem is CloudFileDirectory) + { count++; - Assert.AreEqual("yourDir", ((CloudFileDirectory) listFileItem).Name); + CloudFileDirectory listedDir = (CloudFileDirectory)listFileItem; + Assert.IsTrue(listedDir.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("yourDir", listedDir.Name); } - else { + else + { count++; - Assert.AreEqual("myfile", ((CloudFile) listFileItem).Name); + CloudFile listedFile = (CloudFile)listFileItem; + Assert.IsTrue(listedFile.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("myfile", listedFile.Name); } } - + Assert.AreEqual(2, count); snapshot.Delete(); @@ -2942,24 +2951,30 @@ public void CloudFileListFilesAndDirectoriesWithinSnapshotAPM() if (listFileItem is CloudFileDirectory) { count++; - Assert.AreEqual("yourDir", ((CloudFileDirectory)listFileItem).Name); + CloudFileDirectory listedDir = (CloudFileDirectory)listFileItem; + Assert.IsTrue(listedDir.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("yourDir", listedDir.Name); } else { count++; - Assert.AreEqual("myfile", ((CloudFile)listFileItem).Name); + CloudFile listedFile = (CloudFile)listFileItem; + Assert.IsTrue(listedFile.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("myfile", listedFile.Name); } } Assert.AreEqual(2, count); - //result = snapshot.BeginDelete(ar => waitHandle.Set(), null); - //waitHandle.WaitOne(); - //snapshot.EndDelete(result); + result = snapshot.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshot.EndDelete(result); - //result = share.BeginDelete(ar => waitHandle.Set(), null); - //waitHandle.WaitOne(); - //share.EndDelete(result); + result = share.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); } } @@ -2977,8 +2992,8 @@ public void CloudFileListFilesAndDirectoriesWithinSnapshotTask() CloudFileDirectory myDir = share.GetRootDirectoryReference().GetDirectoryReference("mydir"); myDir.CreateAsync().Wait(); - myDir.GetFileReference("myfile").Create(1024); ; - myDir.GetDirectoryReference("yourDir").Create(); + myDir.GetFileReference("myfile").CreateAsync(1024); + myDir.GetDirectoryReference("yourDir").CreateAsync(); Assert.IsTrue(share.ExistsAsync().Result); CloudFileShare snapshot = share.SnapshotAsync().Result; CloudFileClient client = GenerateCloudFileClient(); @@ -3026,19 +3041,25 @@ public void CloudFileListFilesAndDirectoriesWithinSnapshotTask() if (listFileItem is CloudFileDirectory) { count++; - Assert.AreEqual("yourDir", ((CloudFileDirectory)listFileItem).Name); + CloudFileDirectory listedDir = (CloudFileDirectory)listFileItem; + Assert.IsTrue(listedDir.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("yourDir", listedDir.Name); } else { count++; - Assert.AreEqual("myfile", ((CloudFile)listFileItem).Name); + CloudFile listedFile = (CloudFile)listFileItem; + Assert.IsTrue(listedFile.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("myfile", listedFile.Name); } } Assert.AreEqual(2, count); - //snapshot.DeleteAsync().Wait(); - //share.DeleteAsync().Wait(); + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); } #endif diff --git a/Test/WindowsRuntime/File/CloudFileClientTest.cs b/Test/WindowsRuntime/File/CloudFileClientTest.cs index a56e8bfb9..37b5d3713 100644 --- a/Test/WindowsRuntime/File/CloudFileClientTest.cs +++ b/Test/WindowsRuntime/File/CloudFileClientTest.cs @@ -462,6 +462,9 @@ public async Task CloudFileListSharesWithSnapshotAsync() } Assert.AreEqual(2, count); + + await snapshot.DeleteAsync(); + await share.DeleteAsync(); } #endif } diff --git a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs index cb37c8590..f76847438 100644 --- a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs +++ b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs @@ -748,8 +748,8 @@ public async Task CloudFileDirectoryApisInShareSnapshotAsync() Assert.IsNotNull(dir.Properties.ETag); Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); - //snapshot.Delete(); - //share.Delete(); + await snapshot.DeleteAsync(); + await share.DeleteAsync(); } [TestMethod] @@ -790,8 +790,8 @@ public async Task CloudFileDirectoryApisInvalidApisInShareSnapshotAsync() Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); } - //snapshot.Delete(); - //share.Delete(); + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); } } } diff --git a/Test/WindowsRuntime/File/CloudFileShareTest.cs b/Test/WindowsRuntime/File/CloudFileShareTest.cs index 0ac30e97f..d10a271c5 100644 --- a/Test/WindowsRuntime/File/CloudFileShareTest.cs +++ b/Test/WindowsRuntime/File/CloudFileShareTest.cs @@ -18,13 +18,12 @@ using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net; using System.Threading.Tasks; -#if NETCORE -using System.Globalization; -#else +#if !NETCORE using Windows.Globalization; using Microsoft.WindowsAzure.Storage.Core; using System.Threading; @@ -965,6 +964,91 @@ public async Task CloudFileInvalidApisForShareSnapshotAsync() { Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); } + + await snapshot.DeleteAsync(); + } + + [TestMethod] + [Description("Test list files and directories within a snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileListFilesAndDirectoriesWithinSnapshotAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + CloudFileDirectory myDir = share.GetRootDirectoryReference().GetDirectoryReference("mydir"); + + await myDir.CreateAsync(); + await myDir.GetFileReference("myfile").CreateAsync(1024); + await myDir.GetDirectoryReference("yourDir").CreateAsync(); + Assert.IsTrue(await share.ExistsAsync()); + CloudFileShare snapshot = await share.SnapshotAsync(); + CloudFileClient client = GenerateCloudFileClient(); + CloudFileShare snapshotRef = client.GetShareReference(snapshot.Name, snapshot.SnapshotTime); + List listedFileItems = new List(); + FileContinuationToken token = null; + do + { + FileResultSegment resultSegment = await snapshotRef.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(token); + token = resultSegment.ContinuationToken; + + foreach (IListFileItem listResultItem in resultSegment.Results) + { + listedFileItems.Add(listResultItem); + } + } + while (token != null); + + int count = 0; + foreach (IListFileItem listFileItem in listedFileItems) + { + count++; + Assert.AreEqual("mydir", ((CloudFileDirectory)listFileItem).Name); + } + + Assert.AreEqual(1, count); + + token = null; + listedFileItems.Clear(); + do + { + FileResultSegment resultSegment = await snapshotRef.GetRootDirectoryReference().GetDirectoryReference("mydir").ListFilesAndDirectoriesSegmentedAsync(token); + token = resultSegment.ContinuationToken; + + foreach (IListFileItem listResultItem in resultSegment.Results) + { + listedFileItems.Add(listResultItem); + } + } + while (token != null); + + count = 0; + foreach (IListFileItem listFileItem in listedFileItems) + { + if (listFileItem is CloudFileDirectory) + { + count++; + CloudFileDirectory listedDir = (CloudFileDirectory)listFileItem; + Assert.IsTrue(listedDir.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("yourDir", listedDir.Name); + } + else + { + count++; + CloudFile listedFile = (CloudFile)listFileItem; + Assert.IsTrue(listedFile.SnapshotQualifiedUri.ToString().Contains( + "sharesnapshot=" + snapshot.SnapshotTime.Value.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture))); + Assert.AreEqual("myfile", listedFile.Name); + } + } + + Assert.AreEqual(2, count); + + await snapshot.DeleteAsync(); + await share.DeleteAsync(); } /* From b2e8dcac720a2163ca34a13bb565e47ee2a62a9a Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 24 Jan 2017 11:21:23 -0800 Subject: [PATCH 08/37] Added share snapshot and file encyption to changelog.txt --- changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 7b89aeb3d..150115aed 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +Changes in X.X.X : +- All: Support for 2016-10-16 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. +- Files: Added support for creating the snapshot of a share. See the service documentation for more information on how to use this API. +- Files: Added support for server side encryption. + Changes in 8.0.1 : - (NetStandard/Xamarin) : Fix for a break in Xamarin Apps with Streaming APIs caused by NotImplemented IncrementalHash APIs in Xamarin Runtime. From a20ca0def90118d4d59a01d142b04f3c04349bfd Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 22 Feb 2017 22:16:14 -0800 Subject: [PATCH 09/37] Add Snapshots option to listing shares --- Lib/Common/File/ShareListingDetails.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/Common/File/ShareListingDetails.cs b/Lib/Common/File/ShareListingDetails.cs index 3aaed78de..48c1d217e 100644 --- a/Lib/Common/File/ShareListingDetails.cs +++ b/Lib/Common/File/ShareListingDetails.cs @@ -35,9 +35,14 @@ public enum ShareListingDetails /// Metadata = 0x1, + /// + /// Retrieve share snapshots. + /// + Snapshots = 0x2, + /// /// Retrieve all available details. /// - All = Metadata + All = Metadata | Snapshots } } From 3b347440a75a1fd8c4566f485636b9f143108f2f Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Thu, 23 Feb 2017 09:03:09 -0800 Subject: [PATCH 10/37] Revert "Premium tables (#48)" This reverts commit ee03ba4e0908feb1c5ebe0f534524083de9f6e5a. --- Lib/ClassLibraryCommon/Table/CloudTable.cs | 232 ++---------------- .../Table/CloudTableClient.cs | 45 +--- .../Table/TableOperation.cs | 9 - Lib/Common/Shared/Protocol/Constants.cs | 1 - Lib/Common/Table/CloudTable.Common.cs | 7 - Lib/Common/Table/Protocol/TableConstants.cs | 15 -- Lib/Common/Table/TableOperation.Common.cs | 2 +- Lib/Common/Table/TableProperties.cs | 54 ---- Lib/Common/Table/TableStatus.cs | 40 --- Lib/Common/Table/TableStorageModel.cs | 7 - Lib/WindowsRuntime/Table/CloudTable.cs | 6 - Lib/WindowsRuntime/Table/CloudTableClient.cs | 16 +- .../Table/CloudTableClientTests.cs | 79 ------ Test/Common/TestCategoryConstants.cs | 2 - .../Table/CloudTableClientTest.cs | 43 ---- 15 files changed, 26 insertions(+), 532 deletions(-) delete mode 100644 Lib/Common/Table/TableProperties.cs delete mode 100644 Lib/Common/Table/TableStatus.cs diff --git a/Lib/ClassLibraryCommon/Table/CloudTable.cs b/Lib/ClassLibraryCommon/Table/CloudTable.cs index 62f982e92..ff68c6097 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTable.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTable.cs @@ -262,190 +262,6 @@ public virtual Task> ExecuteBatchAsync(TableBatchOperation ba #endregion - #region Fetch Table Properties -#if SYNC - /// - /// Populates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// For premium tables only. - [DoesServiceRequest] - public virtual void FetchAttributes(TableRequestOptions requestOptions = null, OperationContext operationContext = null) - { - IEnumerator listResult = this.ServiceClient.ListTables(this.Name, requestOptions, operationContext).GetEnumerator(); - listResult.MoveNext(); - this.Properties.ProvisionedIops = listResult.Current.Properties.ProvisionedIops; - this.Properties.RequestedIops = listResult.Current.Properties.RequestedIops; - this.Properties.TableStatus = listResult.Current.Properties.TableStatus; - } -#endif - - /// - /// Populates the table's properties. - /// - /// An delegate that will receive notification when the asynchronous operation completes. - /// A user-defined object that will be passed to the callback delegate. - /// For premium tables only. - [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginFetchAttributes(AsyncCallback callback, object state) - { - return this.BeginFetchAttributes(null /* RequestOptions */, null /* OperationContext */, callback, state); - } - - /// - /// Populates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// For premium tables only. - [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginFetchAttributes(TableRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) - { - return this.ServiceClient.BeginListTablesSegmented(this.Name, null, null, requestOptions, operationContext, callback, state); - } - - /// - /// Ends an asynchronous operation to fetch attributes. - /// - /// An that references the pending asynchronous operation. - public virtual void EndFetchAttributes(IAsyncResult asyncResult) - { - IEnumerator listResult = this.ServiceClient.EndListTablesSegmented(asyncResult).GetEnumerator(); - listResult.MoveNext(); - this.Properties.ProvisionedIops = listResult.Current.Properties.ProvisionedIops; - this.Properties.RequestedIops = listResult.Current.Properties.RequestedIops; - this.Properties.TableStatus = listResult.Current.Properties.TableStatus; - } - -#if TASK - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// For premium tables only - [DoesServiceRequest] - public virtual Task FetchAttributesAsync(TableRequestOptions requestOptions = null, OperationContext operationContext = null) - { - return this.FetchAttributesAsync(requestOptions, operationContext, CancellationToken.None); - } - - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// A to observe while waiting for a task to complete. - /// For premium tables only - [DoesServiceRequest] - public virtual Task FetchAttributesAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) - { - return AsyncExtensions.TaskFromVoidApm(this.BeginFetchAttributes, this.EndFetchAttributes, requestOptions, operationContext, CancellationToken.None); - } -#endif - #endregion - - #region Set Table Properties -#if SYNC - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// For premium tables only. - [DoesServiceRequest] - public virtual void SetProperties(TableRequestOptions requestOptions = null, OperationContext operationContext = null) - { - DynamicTableEntity iopsEntity = new DynamicTableEntity(); - iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - - CommonUtility.AssertNotNull("RequestedIops", this.Properties.RequestedIops.Value); - CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); - iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); - - TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Merge, false); - operation.IsTableEntity = true; - CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); - - operation.Execute(this.ServiceClient, serviceTable, requestOptions, operationContext); - } -#endif - /// - /// Updates the table's properties. - /// - /// An delegate that will receive notification when the asynchronous operation completes. - /// A user-defined object that will be passed to the callback delegate. - /// For premium tables only. - /// An that references the asynchronous operation. - [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginSetProperties(AsyncCallback callback, object state) - { - return this.BeginSetProperties(null /* RequestOptions */, null /* OperationContext */, callback, state); - } - - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// An delegate that will receive notification when the asynchronous operation completes. - /// A user-defined object that will be passed to the callback delegate. - /// For premium tables only - [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginSetProperties(TableRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) - { - DynamicTableEntity iopsEntity = new DynamicTableEntity(); - iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - - CommonUtility.AssertNotNull("RequestedIops", this.Properties.RequestedIops.Value); - CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); - iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); - - TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Merge, false); - operation.IsTableEntity = true; - CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); - - return operation.BeginExecute(this.ServiceClient, serviceTable, requestOptions, operationContext, callback, state); - } - - /// - /// Ends an asynchronous operation to set table properties on a premium table. - /// - /// An that references the pending asynchronous operation. - public virtual void EndSetProperties(IAsyncResult asyncResult) - { - Executor.EndExecuteAsync(asyncResult); - } - -#if TASK - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// For premium tables only. - [DoesServiceRequest] - public virtual Task SetPropertiesAsync(TableRequestOptions requestOptions = null, OperationContext operationContext = null) - { - return this.SetPropertiesAsync(requestOptions, operationContext, CancellationToken.None); - } - - /// - /// Updates the table's properties. - /// - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// A to observe while waiting for a task to complete. - /// For premium tables only. - [DoesServiceRequest] - public virtual Task SetPropertiesAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) - { - return AsyncExtensions.TaskFromVoidApm(this.BeginSetProperties, this.EndSetProperties, requestOptions, operationContext, CancellationToken.None); - } -#endif - #endregion - #region TableQuery Execute Methods #region NonGeneric #if SYNC @@ -1262,15 +1078,9 @@ public virtual void Create(TableRequestOptions requestOptions = null, OperationC requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity iopsEntity = new DynamicTableEntity(); - iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - if (this.Properties.RequestedIops.HasValue) - { - CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); - iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); - } - - TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Insert, false); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1303,15 +1113,9 @@ public virtual ICancellableAsyncResult BeginCreate(TableRequestOptions requestOp requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity iopsEntity = new DynamicTableEntity(); - iopsEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - if (this.Properties.RequestedIops.HasValue) - { - CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); - iopsEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); - } - - TableOperation operation = new TableOperation(iopsEntity, TableOperationType.Insert, false); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1555,9 +1359,9 @@ public virtual void Delete(TableRequestOptions requestOptions = null, OperationC requestOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tableEntity = new DynamicTableEntity(); - tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tableEntity, TableOperationType.Delete); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Delete); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1590,9 +1394,9 @@ public virtual ICancellableAsyncResult BeginDelete(TableRequestOptions requestOp requestOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tableEntity = new DynamicTableEntity(); - tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tableEntity, TableOperationType.Delete); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Delete); operation.IsTableEntity = true; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1925,9 +1729,9 @@ private bool Exists(bool primaryOnly, TableRequestOptions requestOptions, Operat requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tableEntity = new DynamicTableEntity(); - tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tableEntity, TableOperationType.Retrieve); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Retrieve); operation.IsTableEntity = true; operation.IsPrimaryOnlyRetrieve = primaryOnly; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); @@ -1979,9 +1783,9 @@ private ICancellableAsyncResult BeginExists(bool primaryOnly, TableRequestOption requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - DynamicTableEntity tableEntity = new DynamicTableEntity(); - tableEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - TableOperation operation = new TableOperation(tableEntity, TableOperationType.Retrieve); + DynamicTableEntity tblEntity = new DynamicTableEntity(); + tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); + TableOperation operation = new TableOperation(tblEntity, TableOperationType.Retrieve); operation.IsTableEntity = true; operation.IsPrimaryOnlyRetrieve = primaryOnly; CloudTable serviceTable = this.ServiceClient.GetTableReference(TableConstants.TableServiceTablesName); diff --git a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs index 4b9e10dfb..3dd243fb7 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs @@ -115,26 +115,9 @@ public virtual IEnumerable ListTables(string prefix = null, TableReq requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this); operationContext = operationContext ?? new OperationContext(); CloudTable serviceTable = this.GetTableReference(TableConstants.TableServiceTablesName); - requestOptions.PropertyResolver = (pk, rk, propName, propValue) => - { - if (propName == TableConstants.RequestedIops || propName == TableConstants.ProvisionedIops) - { - return EdmType.Int32; - } - - return EdmType.String; - }; return CloudTableClient.GenerateListTablesQuery(prefix, null).ExecuteInternal(this, serviceTable, requestOptions, operationContext).Select( - table => - { - CloudTable tableResult = new CloudTable(table[TableConstants.TableName].StringValue, this); - tableResult.Properties = new TableProperties(); - tableResult.Properties.RequestedIops = table[TableConstants.RequestedIops].Int32Value; - tableResult.Properties.ProvisionedIops = table[TableConstants.ProvisionedIops].Int32Value; - tableResult.Properties.TableStatus = (TableStatus)Enum.Parse(typeof(TableStatus), table[TableConstants.TableStatus].StringValue); - return tableResult; - }); + tbl => new CloudTable(tbl[TableConstants.TableName].StringValue, this)); } /// @@ -179,8 +162,8 @@ public virtual TableResultSegment ListTablesSegmented(string prefix, int? maxRes TableQuerySegment res = CloudTableClient.GenerateListTablesQuery(prefix, maxResults).ExecuteQuerySegmentedInternal(currentToken, this, serviceTable, requestOptions, operationContext); - List tables = res.Results.Select(table => new CloudTable( - table.Properties[TableConstants.TableName].StringValue, + List tables = res.Results.Select(tbl => new CloudTable( + tbl.Properties[TableConstants.TableName].StringValue, this)).ToList(); TableResultSegment retSeg = new TableResultSegment(tables) { ContinuationToken = res.ContinuationToken as TableContinuationToken }; @@ -233,15 +216,6 @@ public virtual ICancellableAsyncResult BeginListTablesSegmented(string prefix, i requestOptions = TableRequestOptions.ApplyDefaultsAndClearEncryption(requestOptions, this); operationContext = operationContext ?? new OperationContext(); CloudTable serviceTable = this.GetTableReference(TableConstants.TableServiceTablesName); - requestOptions.PropertyResolver = (pk, rk, propName, propValue) => - { - if (propName == TableConstants.RequestedIops || propName == TableConstants.ProvisionedIops) - { - return EdmType.Int32; - } - - return EdmType.String; - }; return CloudTableClient.GenerateListTablesQuery(prefix, maxResults).BeginExecuteQuerySegmentedInternal( currentToken, @@ -262,16 +236,9 @@ public virtual TableResultSegment EndListTablesSegmented(IAsyncResult asyncResul { TableQuerySegment res = Executor.EndExecuteAsync>(asyncResult); - List tables = res.Results.Select(table => - { - CloudTable tableResult = new CloudTable(table.Properties[TableConstants.TableName].StringValue, this); - tableResult.Properties = new TableProperties(); - tableResult.Properties.RequestedIops = table[TableConstants.RequestedIops].Int32Value; - tableResult.Properties.ProvisionedIops = table[TableConstants.ProvisionedIops].Int32Value; - tableResult.Properties.TableStatus = - (TableStatus)Enum.Parse(typeof(TableStatus), table[TableConstants.TableStatus].StringValue); - return tableResult; - }).ToList(); + List tables = res.Results.Select(tbl => new CloudTable( + tbl.Properties[TableConstants.TableName].StringValue, + this)).ToList(); TableResultSegment retSeg = new TableResultSegment(tables) { ContinuationToken = res.ContinuationToken }; return retSeg; diff --git a/Lib/ClassLibraryCommon/Table/TableOperation.cs b/Lib/ClassLibraryCommon/Table/TableOperation.cs index 6192803cc..b6e5c70dd 100644 --- a/Lib/ClassLibraryCommon/Table/TableOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableOperation.cs @@ -93,7 +93,6 @@ internal RESTCommand GenerateCMDForOperation(CloudTableClient clien } else if (this.OperationType == TableOperationType.Merge) { - CommonUtility.AssertNotNullOrEmpty("Merge requires a valid ETag", this.ETag); CommonUtility.AssertNotNull("Merge requires a valid PartitionKey", this.PartitionKey); CommonUtility.AssertNotNull("Merge requires a valid RowKey", this.RowKey); @@ -106,14 +105,6 @@ internal RESTCommand GenerateCMDForOperation(CloudTableClient clien CommonUtility.AssertNotNull("RotateEncryptionKey requires a valid PartitionKey", this.PartitionKey); CommonUtility.AssertNotNull("RotateEncryptionKey requires a valid RowKey", this.RowKey); - if (!this.isTableEntity) - { - CommonUtility.AssertNotNullOrEmpty("Merge requires a valid ETag", this.Entity.ETag); - CommonUtility.AssertNotNull("Merge requires a valid PartitionKey", this.Entity.PartitionKey); - CommonUtility.AssertNotNull("Merge requires a valid RowKey", this.Entity.RowKey); - } - - return MergeImpl(this, client, table, modifiedOptions); } else if (this.OperationType == TableOperationType.Replace) diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 6e5ecfab2..a78a094e7 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1115,7 +1115,6 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion ="2016-10-16"; /// diff --git a/Lib/Common/Table/CloudTable.Common.cs b/Lib/Common/Table/CloudTable.Common.cs index be2222e3c..cce2f124e 100644 --- a/Lib/Common/Table/CloudTable.Common.cs +++ b/Lib/Common/Table/CloudTable.Common.cs @@ -73,7 +73,6 @@ internal CloudTable(string tableName, CloudTableClient client) this.Name = tableName; this.StorageUri = NavigationHelper.AppendPathToUri(client.StorageUri, tableName); this.ServiceClient = client; - this.Properties = new TableProperties(); } /// @@ -88,12 +87,6 @@ internal CloudTable(string tableName, CloudTableClient client) /// A string containing the name of the table. public string Name { get; private set; } - /// - /// Gets the table's properties. - /// - /// A object. - public TableProperties Properties { get; internal set; } - /// /// Gets the table URI for the primary location. /// diff --git a/Lib/Common/Table/Protocol/TableConstants.cs b/Lib/Common/Table/Protocol/TableConstants.cs index 556398c74..7ad43fa3c 100644 --- a/Lib/Common/Table/Protocol/TableConstants.cs +++ b/Lib/Common/Table/Protocol/TableConstants.cs @@ -106,21 +106,6 @@ static class TableConstants /// public const string TableName = "TableName"; - /// - /// The requested IOPS for the table. - /// - public const string RequestedIops = "RequestedIOPS"; - - /// - /// The provisioned IOPS for the table. - /// - public const string ProvisionedIops = "ProvisionedIOPS"; - - /// - /// The table status. - /// - public const string TableStatus = "TableStatus"; - /// /// The query filter clause name. /// diff --git a/Lib/Common/Table/TableOperation.Common.cs b/Lib/Common/Table/TableOperation.Common.cs index 857884e75..01eb4748e 100644 --- a/Lib/Common/Table/TableOperation.Common.cs +++ b/Lib/Common/Table/TableOperation.Common.cs @@ -427,7 +427,7 @@ internal Uri GenerateRequestURI(Uri uri, string tableName) if (this.isTableEntity) { // Note tableEntity is only used internally, so we can assume operationContext is not needed - identity = string.Format(CultureInfo.InvariantCulture, "'{0}'", this.Entity.WriteEntity(null /* OperationContext */)[TableConstants.TableName].StringValue); + identity = string.Format(CultureInfo.InvariantCulture, "'{0}'", this.Entity.WriteEntity(null /* OperationContext */)[TableConstants.TableName].StringValue); } else { diff --git a/Lib/Common/Table/TableProperties.cs b/Lib/Common/Table/TableProperties.cs deleted file mode 100644 index 033d5a4f0..000000000 --- a/Lib/Common/Table/TableProperties.cs +++ /dev/null @@ -1,54 +0,0 @@ -// ----------------------------------------------------------------------------------------- -// -// Copyright 2013 Microsoft Corporation -// -// Licensed 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. -// -// ----------------------------------------------------------------------------------------- - -namespace Microsoft.WindowsAzure.Storage.Table -{ - /// - /// The table properties. - /// - public sealed class TableProperties - { - /// - /// Initializes a new instance of the class. - /// - public TableProperties() - { - this.TableStatus = TableStatus.Unspecified; - } - - /// - /// Gets the . - /// - /// This is only supported for premium tables - /// - public TableStatus TableStatus { get; internal set; } - - /// - /// Gets the provisioned IOPS for the table. Value indicates the IOPS for which the table was provisioned last. - /// - /// This is only supported for premium tables. - /// The requested IOPS for the table. Value indicates the IOPS for which the table was provisioned last. - public int? ProvisionedIops { get; internal set; } - - /// - /// Gets or sets the requested IOPS for the table. - /// - /// This is only supported for premium tables. - /// The requested IOPS for the table. - public int? RequestedIops { get; set; } - } -} \ No newline at end of file diff --git a/Lib/Common/Table/TableStatus.cs b/Lib/Common/Table/TableStatus.cs deleted file mode 100644 index 16b8af66b..000000000 --- a/Lib/Common/Table/TableStatus.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ----------------------------------------------------------------------------------------- -// -// Copyright 2013 Microsoft Corporation -// -// Licensed 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. -// -// ----------------------------------------------------------------------------------------- - -namespace Microsoft.WindowsAzure.Storage.Table -{ - /// - /// The table status. - /// - public enum TableStatus - { - /// - /// The table staus is not specified. - /// - Unspecified, - - /// - /// Indicates that the table is under going provisioning and the IOPS are not guaranteed at this time. - /// - Provisioning, - - /// - /// The table is ready to handle the provisioned IOPS. - /// - Ready, - } -} \ No newline at end of file diff --git a/Lib/Common/Table/TableStorageModel.cs b/Lib/Common/Table/TableStorageModel.cs index 4c54a7216..339281045 100644 --- a/Lib/Common/Table/TableStorageModel.cs +++ b/Lib/Common/Table/TableStorageModel.cs @@ -22,7 +22,6 @@ namespace Microsoft.WindowsAzure.Storage.Table using Microsoft.Data.OData; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; - using Microsoft.WindowsAzure.Storage.Table.Protocol; using System; /// @@ -50,9 +49,6 @@ public TableStorageModel(string accountName) this.accountName = accountName; this.tableType = new EdmEntityType(this.accountName, Constants.EntitySetName); this.tableType.AddKeys(this.tableType.AddStructuralProperty(Constants.DefaultTableName, EdmPrimitiveTypeKind.String)); - this.TableType.AddStructuralProperty(TableConstants.ProvisionedIops, EdmPrimitiveTypeKind.Int32, true); - this.TableType.AddStructuralProperty(TableConstants.RequestedIops, EdmPrimitiveTypeKind.Int32, true); - this.TableType.AddStructuralProperty(TableConstants.TableStatus, EdmPrimitiveTypeKind.String, true); this.AddElement(this.tableType); // Add the default entity container - the name and namespace should not matter since we look for default entity container. @@ -104,9 +100,6 @@ internal static EdmEntityType CreateEntityType(string namespaceName, string name entityType.AddStructuralProperty("RowKey", EdmPrimitiveTypeKind.String), entityType.AddStructuralProperty("PartitionKey", EdmPrimitiveTypeKind.String)); entityType.AddStructuralProperty("Timestamp", EdmCoreModel.Instance.GetDateTime(false), null, EdmConcurrencyMode.Fixed); // We need this because we want to ensure that this property is not used for optimistic concurrency checks. - entityType.AddStructuralProperty(TableConstants.ProvisionedIops, EdmPrimitiveTypeKind.Int32, true); - entityType.AddStructuralProperty(TableConstants.RequestedIops, EdmPrimitiveTypeKind.Int32, true); - entityType.AddStructuralProperty(TableConstants.TableStatus, EdmPrimitiveTypeKind.String, true); return entityType; } diff --git a/Lib/WindowsRuntime/Table/CloudTable.cs b/Lib/WindowsRuntime/Table/CloudTable.cs index 25cf8b588..14b96b459 100644 --- a/Lib/WindowsRuntime/Table/CloudTable.cs +++ b/Lib/WindowsRuntime/Table/CloudTable.cs @@ -214,12 +214,6 @@ public virtual Task CreateAsync(TableRequestOptions requestOptions, OperationCon DynamicTableEntity tblEntity = new DynamicTableEntity(); tblEntity.Properties.Add(TableConstants.TableName, new EntityProperty(this.Name)); - if (this.Properties != null && this.Properties.RequestedIops.HasValue) - { - CommonUtility.AssertInBounds("RequestedIops", this.Properties.RequestedIops.Value, 1); - tblEntity.Properties.Add(TableConstants.RequestedIops, new EntityProperty(this.Properties.RequestedIops)); - } - TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; diff --git a/Lib/WindowsRuntime/Table/CloudTableClient.cs b/Lib/WindowsRuntime/Table/CloudTableClient.cs index bcaf894f4..105535a1d 100644 --- a/Lib/WindowsRuntime/Table/CloudTableClient.cs +++ b/Lib/WindowsRuntime/Table/CloudTableClient.cs @@ -182,21 +182,7 @@ public virtual Task ListTablesSegmentedAsync(string prefix, return Task.Run(async () => { TableQuerySegment seg = await this.ExecuteQuerySegmentedAsync(TableConstants.TableServiceTablesName, query, currentToken, requestOptions, operationContext, cancellationToken); - TableResultSegment retSegment = new TableResultSegment(seg.Results.Select(table => - { - CloudTable tableResult = new CloudTable(table.Properties[TableConstants.TableName].StringValue, this); - - tableResult.Properties = new TableProperties(); - if (table.Properties.ContainsKey(TableConstants.RequestedIops)) - { - tableResult.Properties.RequestedIops = table.Properties[TableConstants.RequestedIops].Int32Value; - tableResult.Properties.ProvisionedIops = table.Properties[TableConstants.ProvisionedIops].Int32Value; - tableResult.Properties.TableStatus = (TableStatus)Enum.Parse(typeof(TableStatus), table.Properties[TableConstants.TableStatus].StringValue); - } - - return tableResult; - }).ToList()); - + TableResultSegment retSegment = new TableResultSegment(seg.Results.Select(tbl => new CloudTable(tbl.Properties[TableConstants.TableName].StringValue, this)).ToList()); retSegment.ContinuationToken = seg.ContinuationToken; return retSegment; }, cancellationToken); diff --git a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs index 490d2ce7a..5dc35ad31 100644 --- a/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs +++ b/Test/ClassLibraryCommon/Table/CloudTableClientTests.cs @@ -215,85 +215,6 @@ private void DoListTablesWithPrefixBasic(TablePayloadFormat format) Assert.AreEqual(createdTables.Where((tbl) => tbl.Name.StartsWith(prefixTablesPrefix)).Count(), retrievedTables.Count()); } - //[TestMethod] - //[Description("Test List Tables For Premium Tables")] - //[TestCategory(ComponentCategory.Table)] - //public void ListPremiumTablesPropertiesCheck() - //{ - // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) - // { - // ListPremiumTablesPropertiesCheck(payloadFormat); - // } - //} - - //private void ListPremiumTablesPropertiesCheck(TablePayloadFormat format) - //{ - // CloudTableClient tableClient = GenerateCloudTableClient(); - // tableClient.DefaultRequestOptions.PayloadFormat = format; - // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); - // try - // { - // testTable.Properties = new TableProperties(); - // testTable.Properties.RequestedIops = 1000; - // testTable.Create(); - - // CloudTable returnedTable = tableClient.ListTables().ToList().Where((t) => t.Name == testTable.Name).FirstOrDefault(); - // Assert.AreEqual(1000, returnedTable.Properties.RequestedIops.Value); - // Assert.AreEqual(0, returnedTable.Properties.ProvisionedIops.Value); - // Assert.AreEqual(TableStatus.Provisioning, returnedTable.Properties.TableStatus.Value); - - // Assert.IsTrue(testTable.Properties.RequestedIops.HasValue); - // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); - // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); - // } - // finally - // { - // testTable.Delete(); - // } - //} - - //[TestMethod] - //[Description("Test Set and Fetch Table Properties For Premium Tables")] - //[TestCategory(ComponentCategory.PremiumTables)] - //public void PremiumTableGetSetProperties() - //{ - // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) - // { - // PremiumTableGetSetProperties(payloadFormat); - // } - //} - - //private void PremiumTableGetSetProperties(TablePayloadFormat format) - //{ - // CloudTableClient tableClient = GenerateCloudTableClient(); - // tableClient.DefaultRequestOptions.PayloadFormat = format; - // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); - // try - // { - // testTable.Properties = new TableProperties(); - // testTable.Properties.RequestedIops = 1000; - // testTable.Create(); - - // Thread.Sleep(250000); - // testTable.Properties.RequestedIops = 3000; - // testTable.SetProperties(); - - // Assert.AreEqual(3000, testTable.Properties.RequestedIops); - // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); - // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); - - // testTable.FetchAttributes(); - - // Assert.AreEqual(3000, testTable.Properties.RequestedIops.Value); - // Assert.AreEqual(1000, testTable.Properties.ProvisionedIops.Value); - // Assert.AreEqual(TableStatus.Provisioning, testTable.Properties.TableStatus.Value); - // } - // finally - // { - // testTable.Delete(); - // } - //} - [TestMethod] [Description("Test List Tables Iterator With Prefix Extended, will check a variety of ")] [TestCategory(ComponentCategory.Table)] diff --git a/Test/Common/TestCategoryConstants.cs b/Test/Common/TestCategoryConstants.cs index 1cdbf6e06..df122690d 100644 --- a/Test/Common/TestCategoryConstants.cs +++ b/Test/Common/TestCategoryConstants.cs @@ -32,8 +32,6 @@ public static class ComponentCategory public const string Queue = "Queue"; public const string Table = "Table"; - - public const string PremiumTables = "PremiumTables"; } public static class TestTypeCategory diff --git a/Test/WindowsRuntime/Table/CloudTableClientTest.cs b/Test/WindowsRuntime/Table/CloudTableClientTest.cs index 08a2245ce..49c17f2de 100644 --- a/Test/WindowsRuntime/Table/CloudTableClientTest.cs +++ b/Test/WindowsRuntime/Table/CloudTableClientTest.cs @@ -262,49 +262,6 @@ private async Task DoListTablesSegmentedWithPrefixAsync(TablePayloadFormat paylo } } - //[TestMethod] - //[Description("Test List Tables For Premium Tables")] - //[TestCategory(ComponentCategory.PremiumTables)] - //public async Task ListPremiumTablesPropertiesCheckAsync() - //{ - // foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) - // { - // await ListPremiumTablesPropertiesCheckAsync(payloadFormat); - // } - //} - - //private async Task ListPremiumTablesPropertiesCheckAsync(TablePayloadFormat format) - //{ - // CloudTableClient tableClient = GenerateCloudTableClient(); - // tableClient.DefaultRequestOptions.PayloadFormat = format; - // CloudTable testTable = tableClient.GetTableReference(TableTestBase.GenerateRandomTableName()); - - // testTable.Properties = new TableProperties(); - // testTable.Properties.RequestedIops = 1000; - // await testTable.CreateAsync(); - - // TableResultSegment segment = null; - // List totalResults = new List(); - // do - // { - // segment = await tableClient.ListTablesSegmentedAsync(/*testTable.Name,*/ segment != null ? segment.ContinuationToken : null); - // totalResults.AddRange(segment); - // } - // while (segment.ContinuationToken != null); - - - // CloudTable returnedTable = totalResults.Find((t) => t.Name == testTable.Name); - // Assert.AreEqual(1000, returnedTable.Properties.RequestedIops.Value); - // Assert.AreEqual(0, returnedTable.Properties.ProvisionedIops.Value); - // Assert.AreEqual(TableStatus.Provisioning, returnedTable.Properties.TableStatus.Value); - - // Assert.IsTrue(testTable.Properties.RequestedIops.HasValue); - // Assert.IsFalse(testTable.Properties.ProvisionedIops.HasValue); - // Assert.IsFalse(testTable.Properties.TableStatus.HasValue); - - // await testTable.DeleteAsync(); - //} - [TestMethod] [Description("Test List Tables Segmented with Shared Key Lite")] [TestCategory(ComponentCategory.Table)] From dd6d6f9efa8684cc299c3a182afe10ec02e27e46 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Thu, 23 Feb 2017 09:44:18 -0800 Subject: [PATCH 11/37] Add snapshots option to WebRequestFactory for listing shares --- .../File/Protocol/ShareHttpWebRequestFactory.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 49879bc18..3e681dc10 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -344,6 +344,20 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC sb.Append("metadata"); } + if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) + { + if (!started) + { + started = true; + } + else + { + sb.Append(","); + } + + sb.Append("snapshots"); + } + builder.Add("include", sb.ToString()); } From 50a3b708457658dbd66438d39a8f8ab565bb1d1b Mon Sep 17 00:00:00 2001 From: erezvani Date: Thu, 23 Feb 2017 22:29:04 -0800 Subject: [PATCH 12/37] re-enable ShareSnapshot --- Lib/ClassLibraryCommon/File/CloudFileShare.cs | 21 ++++++++++++------- .../DirectoryHttpWebRequestFactory.cs | 6 +++--- .../Protocol/FileHttpWebRequestFactory.cs | 10 ++++----- .../Protocol/ShareHttpWebRequestFactory.cs | 21 +++++++++++++++---- Lib/Common/Core/Util/NavigationHelper.cs | 9 ++++++++ Lib/Common/File/CloudFile.Common.cs | 4 ++-- Lib/Common/File/CloudFileClient.Common.cs | 2 +- Lib/Common/File/CloudFileDirectory.Common.cs | 4 ++-- Lib/Common/File/CloudFileShare.Common.cs | 12 +++++------ Lib/Common/File/Protocol/FileShareEntry.cs | 2 +- Lib/Common/File/ShareListingDetails.cs | 7 ++++++- Lib/WindowsRuntime/File/CloudFileShare.cs | 9 ++++---- .../ShareHttpRequestMessageFactory.cs | 17 ++++++++++++++- 13 files changed, 85 insertions(+), 39 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index eb392176f..7e96bda7c 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -46,6 +46,7 @@ public partial class CloudFileShare public virtual void Create(FileRequestOptions requestOptions = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) { CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); @@ -85,6 +86,7 @@ public virtual ICancellableAsyncResult BeginCreate(AsyncCallback callback, objec public virtual ICancellableAsyncResult BeginCreate(FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) { CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); @@ -332,7 +334,7 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope /// An object that represents the context for the current operation. /// A object that is a share snapshot. [DoesServiceRequest] - internal virtual CloudFileShare Snapshot(IDictionary metadata = null, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + public CloudFileShare Snapshot(IDictionary metadata = null, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -350,7 +352,7 @@ internal virtual CloudFileShare Snapshot(IDictionary metadata = /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - internal virtual ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, object state) + public ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, object state) { return this.BeginSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } @@ -366,7 +368,7 @@ internal virtual ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, o /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - internal virtual ICancellableAsyncResult BeginSnapshot(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + public ICancellableAsyncResult BeginSnapshot(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -383,7 +385,7 @@ internal virtual ICancellableAsyncResult BeginSnapshot(IDictionary /// An that references the pending asynchronous operation. /// A object that is a share snapshot. - internal virtual CloudFileShare EndSnapshot(IAsyncResult asyncResult) + public CloudFileShare EndSnapshot(IAsyncResult asyncResult) { return Executor.EndExecuteAsync(asyncResult); } @@ -394,7 +396,7 @@ internal virtual CloudFileShare EndSnapshot(IAsyncResult asyncResult) /// /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - internal virtual Task SnapshotAsync() + public Task SnapshotAsync() { return this.SnapshotAsync(CancellationToken.None); } @@ -405,7 +407,7 @@ internal virtual Task SnapshotAsync() /// A to observe while waiting for a task to complete. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - internal virtual Task SnapshotAsync(CancellationToken cancellationToken) + public Task SnapshotAsync(CancellationToken cancellationToken) { return AsyncExtensions.TaskFromApm(this.BeginSnapshot, this.EndSnapshot, cancellationToken); } @@ -419,7 +421,7 @@ internal virtual Task SnapshotAsync(CancellationToken cancellati /// An object that represents the context for the current operation. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + public Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { return this.SnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); } @@ -434,7 +436,7 @@ internal virtual Task SnapshotAsync(IDictionary /// A to observe while waiting for a task to complete. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { return AsyncExtensions.TaskFromApm(this.BeginSnapshot, this.EndSnapshot, metadata, accessCondition, options, operationContext, cancellationToken); } @@ -1569,6 +1571,7 @@ public virtual Task SetPermissionsAsync(FileSharePermissions permissions, Access public virtual void SetProperties(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) { CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); @@ -1607,6 +1610,7 @@ public virtual ICancellableAsyncResult BeginSetProperties(AsyncCallback callback public virtual ICancellableAsyncResult BeginSetProperties(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); + if (this.Properties.Quota.HasValue) { CommonUtility.AssertInBounds("Quota", this.Properties.Quota.Value, 1); @@ -1749,6 +1753,7 @@ private RESTCommand DeleteShareImpl(DeleteShareSnapshotsOption deleteS RESTCommand deleteCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); options.ApplyToStorageCommand(deleteCmd); + deleteCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.Delete(uri, serverTimeout, this.SnapshotTime, deleteSnapshotsOption, accessCondition, useVersionHeader, ctx); deleteCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; deleteCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, NullType.Value, cmd, ex); diff --git a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs index f5e6a621c..abf5ce5ae 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs @@ -105,7 +105,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); @@ -139,7 +139,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); @@ -173,7 +173,7 @@ public static HttpWebRequest List(Uri uri, int? timeout, FileListingContext list /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest List(Uri uri, int? timeout, FileListingContext listingContext, DateTimeOffset? shareSnapshot, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest List(Uri uri, int? timeout, FileListingContext listingContext, DateTimeOffset? shareSnapshot, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); diff --git a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs index 158cd6035..eaf2519a1 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs @@ -152,7 +152,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -186,7 +186,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -289,7 +289,7 @@ public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, lon /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, long? count, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, long? count, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if (offset.HasValue) { @@ -386,7 +386,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, AccessCondition accessCo /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - internal static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -442,7 +442,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? coun /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? count, bool rangeContentMD5, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? count, bool rangeContentMD5, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if (offset.HasValue && offset.Value < 0) { diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 49879bc18..b1ca4c9d8 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -101,7 +101,6 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snaps UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); - HttpWebRequest request = HttpWebRequestFactory.Delete(uri, shareBuilder, timeout, useVersionHeader, operationContext); switch (deleteSnapshotsOption) @@ -144,7 +143,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); @@ -178,7 +177,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); @@ -260,7 +259,7 @@ public static HttpWebRequest GetStats(Uri uri, int? timeout, bool useVersionHead /// A flag indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. - internal static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.ResourceType, "share"); @@ -330,6 +329,20 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC StringBuilder sb = new StringBuilder(); bool started = false; + if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) + { + if (!started) + { + started = true; + } + else + { + sb.Append(","); + } + + sb.Append("snapshots"); + } + if ((detailsIncluded & ShareListingDetails.Metadata) == ShareListingDetails.Metadata) { if (!started) diff --git a/Lib/Common/Core/Util/NavigationHelper.cs b/Lib/Common/Core/Util/NavigationHelper.cs index e7bc47b49..eab5946c7 100644 --- a/Lib/Common/Core/Util/NavigationHelper.cs +++ b/Lib/Common/Core/Util/NavigationHelper.cs @@ -794,6 +794,15 @@ private static Uri ParseFileQueryAndVerify(Uri address, out StorageCredentials p IDictionary queryParameters = HttpWebUtility.ParseQueryString(address.Query); + string snapshot; + if (queryParameters.TryGetValue(Constants.QueryConstants.ShareSnapshot, out snapshot)) + { + if (!string.IsNullOrEmpty(snapshot)) + { + parsedShareSnapshot = ParseSnapshotTime(snapshot); + } + } + parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters); return new Uri(address.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped)); diff --git a/Lib/Common/File/CloudFile.Common.cs b/Lib/Common/File/CloudFile.Common.cs index d95af53c9..b8233dde5 100644 --- a/Lib/Common/File/CloudFile.Common.cs +++ b/Lib/Common/File/CloudFile.Common.cs @@ -220,7 +220,7 @@ public StorageUri StorageUri /// Gets the absolute URI to the file, including query string information if the file's share is a snapshot. /// /// A specifying the absolute URI to the file, including snapshot query information if the file's share is a snapshot. - internal Uri SnapshotQualifiedUri + public Uri SnapshotQualifiedUri { get { @@ -242,7 +242,7 @@ internal Uri SnapshotQualifiedUri /// /// An object of type containing the file's URIs for both the primary and secondary locations, /// including snapshot query information if the file's share is a snapshot. - internal StorageUri SnapshotQualifiedStorageUri + public StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/CloudFileClient.Common.cs b/Lib/Common/File/CloudFileClient.Common.cs index bcedf5525..377006fcd 100644 --- a/Lib/Common/File/CloudFileClient.Common.cs +++ b/Lib/Common/File/CloudFileClient.Common.cs @@ -122,7 +122,7 @@ public virtual CloudFileShare GetShareReference(string shareName) /// A string containing the name of the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A reference to a share. - internal CloudFileShare GetShareReference(string shareName, DateTimeOffset? snapshotTime) + public CloudFileShare GetShareReference(string shareName, DateTimeOffset? snapshotTime) { CommonUtility.AssertNotNullOrEmpty("shareName", shareName); return new CloudFileShare(shareName, snapshotTime, this); diff --git a/Lib/Common/File/CloudFileDirectory.Common.cs b/Lib/Common/File/CloudFileDirectory.Common.cs index 237fb9749..0755fda45 100644 --- a/Lib/Common/File/CloudFileDirectory.Common.cs +++ b/Lib/Common/File/CloudFileDirectory.Common.cs @@ -121,7 +121,7 @@ public Uri Uri /// Gets the absolute URI to the directory, including query string information if the directory's share is a snapshot. /// /// A specifying the absolute URI to the directory, including snapshot query information if the directory's share is a snapshot. - internal Uri SnapshotQualifiedUri + public Uri SnapshotQualifiedUri { get { @@ -143,7 +143,7 @@ internal Uri SnapshotQualifiedUri /// /// An object of type containing the directory's URIs for both the primary and secondary locations, /// including snapshot query information if the directory's share is a snapshot. - internal StorageUri SnapshotQualifiedStorageUri + public StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/CloudFileShare.Common.cs b/Lib/Common/File/CloudFileShare.Common.cs index e2536ffe8..fa8bdd7d8 100644 --- a/Lib/Common/File/CloudFileShare.Common.cs +++ b/Lib/Common/File/CloudFileShare.Common.cs @@ -57,7 +57,7 @@ public CloudFileShare(Uri shareAddress, StorageCredentials credentials) /// The absolute URI to the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A object. - internal CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + public CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) : this(new StorageUri(shareAddress), snapshotTime, credentials) { } @@ -79,7 +79,7 @@ public CloudFileShare(StorageUri shareAddress, StorageCredentials credentials) /// The absolute URI to the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A object. - internal CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + public CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) { CommonUtility.AssertNotNull("shareAddress", shareAddress); CommonUtility.AssertNotNull("shareAddress", shareAddress.PrimaryUri); @@ -151,13 +151,13 @@ public Uri Uri /// /// If the share is not a snapshot, the value of this property is null. /// - internal DateTimeOffset? SnapshotTime { get; set; } + public DateTimeOffset? SnapshotTime { get; internal set; } /// /// Gets a value indicating whether this share is a snapshot. /// /// true if this share is a snapshot; otherwise, false. - internal bool IsSnapshot + public bool IsSnapshot { get { @@ -169,7 +169,7 @@ internal bool IsSnapshot /// Gets the absolute URI to the share, including query string information if the share is a snapshot. /// /// A specifying the absolute URI to the share, including snapshot query information if the share is a snapshot. - internal Uri SnapshotQualifiedUri + public Uri SnapshotQualifiedUri { get { @@ -191,7 +191,7 @@ internal Uri SnapshotQualifiedUri /// /// An object of type containing the share's URIs for both the primary and secondary locations, /// including snapshot query information if the share is a snapshot. - internal StorageUri SnapshotQualifiedStorageUri + public StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/Protocol/FileShareEntry.cs b/Lib/Common/File/Protocol/FileShareEntry.cs index f9377265b..4cd09ff4d 100644 --- a/Lib/Common/File/Protocol/FileShareEntry.cs +++ b/Lib/Common/File/Protocol/FileShareEntry.cs @@ -69,6 +69,6 @@ internal FileShareEntry() /// Gets the share's snapshot time, if any. /// /// A specifying the snapshot timestamp, if the share is a snapshot. - internal DateTimeOffset? SnapshotTime { get; /*internal*/ set; } + public DateTimeOffset? SnapshotTime { get; internal set; } } } diff --git a/Lib/Common/File/ShareListingDetails.cs b/Lib/Common/File/ShareListingDetails.cs index 3aaed78de..48c1d217e 100644 --- a/Lib/Common/File/ShareListingDetails.cs +++ b/Lib/Common/File/ShareListingDetails.cs @@ -35,9 +35,14 @@ public enum ShareListingDetails /// Metadata = 0x1, + /// + /// Retrieve share snapshots. + /// + Snapshots = 0x2, + /// /// Retrieve all available details. /// - All = Metadata + All = Metadata | Snapshots } } diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index b6b69aab4..6e1d6c19e 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -158,7 +158,7 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope /// /// A share snapshot. [DoesServiceRequest] - internal virtual Task SnapshotAsync() + public virtual Task SnapshotAsync() { return this.SnapshotAsync(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */); } @@ -169,7 +169,7 @@ internal virtual Task SnapshotAsync() /// A to observe while waiting for a task to complete. /// A share snapshot. [DoesServiceRequest] - internal virtual Task SnapshotAsync(CancellationToken cancellationToken) + public virtual Task SnapshotAsync(CancellationToken cancellationToken) { return this.SnapshotAsync(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */, cancellationToken); } @@ -183,7 +183,7 @@ internal virtual Task SnapshotAsync(CancellationToken cancellati /// An object that represents the context for the current operation. /// A share snapshot. [DoesServiceRequest] - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + public virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { return this.SnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); } @@ -198,9 +198,8 @@ internal virtual Task SnapshotAsync(IDictionary /// A to observe while waiting for a task to complete. /// A share snapshot. [DoesServiceRequest] - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.SnapshotImpl(metadata, accessCondition, modifiedOptions), diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index 4d7bfbe7b..92c32ad74 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -56,6 +56,7 @@ public static StorageRequestMessage Create(Uri uri, FileShareProperties properti /// The absolute URI to the share. /// The server timeout interval. /// A specifying the snapshot timestamp, if the share is a snapshot. + /// A object indicating whether to only delete the share or delete the share and all snapshots. /// The access condition to apply to the request. /// A web request to use to perform the operation. @@ -63,7 +64,6 @@ public static StorageRequestMessage Delete(Uri uri, int? timeout, DateTimeOffset { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpRequestMessageFactory.AddShareSnapshot(shareBuilder, snapshot); - StorageRequestMessage request = HttpRequestMessageFactory.Delete(uri, timeout, shareBuilder, content, operationContext, canonicalizer, credentials); switch (deleteSnapshotsOption) @@ -215,6 +215,21 @@ public static StorageRequestMessage List(Uri uri, int? timeout, ListingContext l StringBuilder sb = new StringBuilder(); bool started = false; + + if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) + { + if (!started) + { + started = true; + } + else + { + sb.Append(","); + } + + sb.Append("snapshots"); + } + if ((detailsIncluded & ShareListingDetails.Metadata) == ShareListingDetails.Metadata) { if (!started) From 2dc6aa1a7b1e8877e0d74954a89e44b60346fa21 Mon Sep 17 00:00:00 2001 From: erezvani Date: Mon, 27 Feb 2017 14:33:19 -0800 Subject: [PATCH 13/37] Fix duplicate code in ShareHttpWebresponseParser List API caused by AutoMerge --Fix build for NetCore caused by addingtion of ShareSnapshot Tests --- .../File/Protocol/ShareHttpWebRequestFactory.cs | 14 -------------- Test/WindowsRuntime/File/CloudFileShareTest.cs | 5 +++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 627a5ab9a..70728c1c1 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -329,20 +329,6 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC StringBuilder sb = new StringBuilder(); bool started = false; - if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) - { - if (!started) - { - started = true; - } - else - { - sb.Append(","); - } - - sb.Append("snapshots"); - } - if ((detailsIncluded & ShareListingDetails.Metadata) == ShareListingDetails.Metadata) { if (!started) diff --git a/Test/WindowsRuntime/File/CloudFileShareTest.cs b/Test/WindowsRuntime/File/CloudFileShareTest.cs index d10a271c5..a81254dc7 100644 --- a/Test/WindowsRuntime/File/CloudFileShareTest.cs +++ b/Test/WindowsRuntime/File/CloudFileShareTest.cs @@ -22,13 +22,14 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using System.Threading; #if !NETCORE using Windows.Globalization; -using Microsoft.WindowsAzure.Storage.Core; -using System.Threading; #endif +using Microsoft.WindowsAzure.Storage.Core; + namespace Microsoft.WindowsAzure.Storage.File { [TestClass] From f8c8cd0b4d3ac5f19e62abfc8be70a7a6295473a Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 3 Mar 2017 20:30:00 -0800 Subject: [PATCH 14/37] Fixing changelog --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 5a305334b..b904529fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,7 +9,6 @@ Changes in 8.1.1 : Changes in 8.1.0 : - All: Updated OData references to the latest Netstandard friendly package version 5.8.2. - Queues (WinRT/NetCore): Fixed a bug where connections were not closed after an add message operation. ->>>>>>> f4bc857d98f8d26c96f49f90206aea63f36af437 Changes in 8.0.1 : - (NetStandard/Xamarin) : Fix for a break in Xamarin Apps with Streaming APIs caused by NotImplemented IncrementalHash APIs in Xamarin Runtime. From cf89c9355d3c3d2649ca5f5a5c11abab986f23af Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 27 Mar 2017 20:08:57 -0700 Subject: [PATCH 15/37] [ShareSnapshot]Bug fixes and more tests --- Lib/ClassLibraryCommon/File/CloudFile.cs | 1 + Lib/Common/File/CloudFileDirectory.Common.cs | 10 +- Lib/WindowsRuntime/File/CloudFile.cs | 4 + .../File/CloudFileDirectoryTest.cs | 13 + Test/ClassLibraryCommon/File/CloudFileTest.cs | 425 ++++++++++++++++++ .../File/CloudFileDirectoryTest.cs | 3 + Test/WindowsRuntime/File/CloudFileTest.cs | 125 ++++++ 7 files changed, 576 insertions(+), 5 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFile.cs b/Lib/ClassLibraryCommon/File/CloudFile.cs index bc2ef96ac..22627cb63 100644 --- a/Lib/ClassLibraryCommon/File/CloudFile.cs +++ b/Lib/ClassLibraryCommon/File/CloudFile.cs @@ -3944,6 +3944,7 @@ public virtual ICancellableAsyncResult BeginAbortCopy(string copyId, AsyncCallba [DoesServiceRequest] public virtual ICancellableAsyncResult BeginAbortCopy(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Executor.BeginExecuteAsync( this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), diff --git a/Lib/Common/File/CloudFileDirectory.Common.cs b/Lib/Common/File/CloudFileDirectory.Common.cs index 0755fda45..3582e00c9 100644 --- a/Lib/Common/File/CloudFileDirectory.Common.cs +++ b/Lib/Common/File/CloudFileDirectory.Common.cs @@ -295,11 +295,6 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti DateTimeOffset? parsedShareSnapshot; this.StorageUri = NavigationHelper.ParseFileQueryAndVerify(address, out parsedCredentials, out parsedShareSnapshot); - if (parsedShareSnapshot.HasValue) - { - this.Share.SnapshotTime = parsedShareSnapshot; - } - if (parsedCredentials != null && credentials != null) { string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); @@ -311,6 +306,11 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials ?? parsedCredentials); } + if (parsedShareSnapshot.HasValue) + { + this.Share.SnapshotTime = parsedShareSnapshot; + } + this.Name = NavigationHelper.GetFileName(this.Uri, this.ServiceClient.UsePathStyleUris); } } diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index 08640c219..8ee836803 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -884,6 +884,7 @@ public virtual Task CreateAsync(long size, AccessCondition accessCondition, File [DoesServiceRequest] public virtual Task CreateAsync(long size, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.CreateImpl(size, accessCondition, modifiedOptions), @@ -1236,6 +1237,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques [DoesServiceRequest] public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetMetadataImpl(accessCondition, modifiedOptions), @@ -1390,6 +1392,7 @@ public virtual Task ClearRangeAsync(long startOffset, long length, AccessConditi [DoesServiceRequest] public virtual Task ClearRangeAsync(long startOffset, long length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.ClearRangeImpl(startOffset, length, accessCondition, modifiedOptions), @@ -1478,6 +1481,7 @@ public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAcc [DoesServiceRequest] public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { + this.share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), diff --git a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs index 79e971990..85eceaf68 100644 --- a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs @@ -1454,6 +1454,10 @@ public void CloudFileDirectoryApisInShareSnapshot() Assert.IsNotNull(dir.Properties.ETag); Assert.AreNotEqual(dir.Properties.ETag, snapshotDir.Properties.ETag); + CloudFileDirectory snapshotDir2 = new CloudFileDirectory(snapshotDir.SnapshotQualifiedStorageUri, client.Credentials); + Assert.IsTrue(snapshotDir2.Exists()); + Assert.IsTrue(snapshotDir2.Share.SnapshotTime.HasValue); + snapshot.Delete(); share.Delete(); } @@ -1576,6 +1580,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshot() try { dir.Create(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1584,6 +1589,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshot() try { dir.Delete(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1592,6 +1598,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshot() try { dir.SetMetadata(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1630,6 +1637,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotAPM() result = dir.BeginCreate(ar => waitHandle.Set(), null); waitHandle.WaitOne(); dir.EndCreate(result); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1640,6 +1648,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotAPM() result = dir.BeginDelete(ar => waitHandle.Set(), null); waitHandle.WaitOne(); dir.EndDelete(result); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1650,6 +1659,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotAPM() result = dir.BeginSetMetadata(ar => waitHandle.Set(), null); waitHandle.WaitOne(); dir.EndSetMetadata(result); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1685,6 +1695,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotTask() try { dir.CreateAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1693,6 +1704,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotTask() try { dir.DeleteAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -1701,6 +1713,7 @@ public void CloudFileDirectoryApisInvalidApisInShareSnapshotTask() try { dir.SetMetadataAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { diff --git a/Test/ClassLibraryCommon/File/CloudFileTest.cs b/Test/ClassLibraryCommon/File/CloudFileTest.cs index d3807c438..65f10a5fc 100644 --- a/Test/ClassLibraryCommon/File/CloudFileTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileTest.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Core; using System; using System.Collections.Generic; using System.IO; @@ -2645,6 +2646,430 @@ public void CloudFileInvalidSas() } } + [TestMethod] + [Description("Test CloudFile APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileApisInShareSnapshot() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.Create(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.Create(); + + CloudFile file = dir.GetFileReference("file"); + file.Create(1024); + file.Metadata["key1"] = "value1"; + file.SetMetadata(); + CloudFileShare snapshot = share.Snapshot(); + CloudFile snapshotFile = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + file.Metadata["key2"] = "value2"; + file.SetMetadata(); + snapshotFile.FetchAttributes(); + + Assert.IsTrue(snapshotFile.Metadata.Count == 1 && snapshotFile.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotFile.Properties.ETag); + + file.FetchAttributes(); + Assert.IsTrue(file.Metadata.Count == 2 && file.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(file.Properties.ETag); + Assert.AreNotEqual(file.Properties.ETag, snapshotFile.Properties.ETag); + + CloudFile snapshotFile2 = new CloudFile(snapshotFile.SnapshotQualifiedStorageUri, client.Credentials); + Assert.IsTrue(snapshotFile2.Exists()); + Assert.IsTrue(snapshotFile2.Share.SnapshotTime.HasValue); + + snapshot.Delete(); + share.Delete(); + } + + [TestMethod] + [Description("Test CloudFile APIs within a share snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileApisInShareSnapshotAPM() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + result = dir.BeginCreate(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + dir.EndCreate(result); + + CloudFile file = dir.GetFileReference("file"); + result = file.BeginCreate(1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndCreate(result); + + file.Metadata["key1"] = "value1"; + result = file.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndSetMetadata(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + + CloudFile snapshotFile = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + file.Metadata["key2"] = "value2"; + result = file.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndSetMetadata(result); + result = snapshotFile.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshotFile.EndFetchAttributes(result); + + Assert.IsTrue(snapshotFile.Metadata.Count == 1 && snapshotFile.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotFile.Properties.ETag); + + result = file.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndFetchAttributes(result); + Assert.IsTrue(file.Metadata.Count == 2 && file.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(file.Properties.ETag); + Assert.AreNotEqual(file.Properties.ETag, snapshotFile.Properties.ETag); + + result = snapshot.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshot.EndDelete(result); + + result = share.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndDelete(result); + } + } + +#if TASK + [TestMethod] + [Description("Test CloudFile APIs within a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileApisInShareSnapshotTask() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.CreateAsync().Wait(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + dir.CreateAsync().Wait(); + + CloudFile file = dir.GetFileReference("file"); + file.CreateAsync(1024).Wait(); + file.Metadata["key1"] = "value1"; + file.SetMetadataAsync().Wait(); + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFile snapshotFile = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + file.Metadata["key2"] = "value2"; + file.SetMetadataAsync().Wait(); + snapshotFile.FetchAttributesAsync().Wait(); + + Assert.IsTrue(snapshotFile.Metadata.Count == 1 && snapshotFile.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotFile.Properties.ETag); + + file.FetchAttributesAsync().Wait(); + Assert.IsTrue(file.Metadata.Count == 2 && file.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(file.Properties.ETag); + Assert.AreNotEqual(file.Properties.ETag, snapshotFile.Properties.ETag); + + CloudFile snapshotFile2 = new CloudFile(snapshotFile.SnapshotQualifiedStorageUri, client.Credentials); + Assert.IsTrue(snapshotFile2.ExistsAsync().Result); + Assert.IsTrue(snapshotFile2.Share.SnapshotTime.HasValue); + + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); + } +#endif + + [TestMethod] + [Description("Test invalid CloudFile APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisInShareSnapshot() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.Create(); + + CloudFileShare snapshot = share.Snapshot(); + CloudFile file = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + try + { + file.Create(1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.Delete(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.SetMetadata(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.AbortCopy(null); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.ClearRange(0, 1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.StartCopy(file); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.UploadFromByteArray(new byte[1024], 0, 1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + snapshot.Delete(); + share.Delete(); + } + + [TestMethod] + [Description("Test invalid CloudFile APIs within a share snapshot - APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisInShareSnapshotAPM() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + result = share.BeginSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudFileShare snapshot = share.EndSnapshot(result); + CloudFile file = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + try + { + result = file.BeginCreate(1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndCreate(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndDelete(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginSetMetadata(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndSetMetadata(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginAbortCopy(null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndAbortCopy(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginClearRange(0, 1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndClearRange(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginStartCopy(file, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndStartCopy(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + result = file.BeginUploadFromByteArray(new byte[1024], 0, 1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + file.EndUploadFromByteArray(result); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + snapshot.Delete(); + share.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Test invalid CloudFile APIs within a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidApisInShareSnapshotTask() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + share.CreateAsync().Wait(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFile file = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + try + { + file.CreateAsync(1024).Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.DeleteAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.SetMetadataAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.AbortCopyAsync(null).Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.ClearRangeAsync(0, 1024).Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.StartCopyAsync(file).Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + file.UploadFromByteArrayAsync(new byte[1024], 0, 1024).Wait(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + snapshot.DeleteAsync().Wait(); + share.DeleteAsync().Wait(); + } +#endif + private CloudFileShare GenerateRandomWriteOnlyFileShare() { string fileName = "n" + Guid.NewGuid().ToString("N"); diff --git a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs index f76847438..acca29106 100644 --- a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs +++ b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs @@ -768,6 +768,7 @@ public async Task CloudFileDirectoryApisInvalidApisInShareSnapshotAsync() try { dir.CreateAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -776,6 +777,7 @@ public async Task CloudFileDirectoryApisInvalidApisInShareSnapshotAsync() try { dir.DeleteAsync().Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { @@ -784,6 +786,7 @@ public async Task CloudFileDirectoryApisInvalidApisInShareSnapshotAsync() try { dir.SetMetadataAsync(null, null, null).Wait(); + Assert.Fail("API should fail in a snapshot"); } catch (InvalidOperationException e) { diff --git a/Test/WindowsRuntime/File/CloudFileTest.cs b/Test/WindowsRuntime/File/CloudFileTest.cs index 83d36f0bf..597c3ccd2 100644 --- a/Test/WindowsRuntime/File/CloudFileTest.cs +++ b/Test/WindowsRuntime/File/CloudFileTest.cs @@ -29,6 +29,7 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; +using Microsoft.WindowsAzure.Storage.Core; #endif namespace Microsoft.WindowsAzure.Storage.File @@ -925,5 +926,129 @@ await TestHelper.ExpectedExceptionAsync( share.DeleteIfExistsAsync().Wait(); } } + + [TestMethod] + [Description("Test CloudFile APIs within a share snapshot")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileApisInShareSnapshotAsync() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + await share.CreateAsync(); + CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("dir1"); + await dir.CreateAsync(); + + CloudFile file = dir.GetFileReference("file"); + await file.CreateAsync(1024); + file.Metadata["key1"] = "value1"; + await file.SetMetadataAsync(); + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFile snapshotFile = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + file.Metadata["key2"] = "value2"; + await file.SetMetadataAsync(); + await snapshotFile.FetchAttributesAsync(); + + Assert.IsTrue(snapshotFile.Metadata.Count == 1 && snapshotFile.Metadata["key1"].Equals("value1")); + Assert.IsNotNull(snapshotFile.Properties.ETag); + + await file.FetchAttributesAsync(); + Assert.IsTrue(file.Metadata.Count == 2 && file.Metadata["key2"].Equals("value2")); + Assert.IsNotNull(file.Properties.ETag); + Assert.AreNotEqual(file.Properties.ETag, snapshotFile.Properties.ETag); + + CloudFile snapshotFile2 = new CloudFile(snapshotFile.SnapshotQualifiedStorageUri, client.Credentials); + Assert.IsTrue(snapshotFile2.ExistsAsync().Result); + Assert.IsTrue(snapshotFile2.Share.SnapshotTime.HasValue); + + await snapshot.DeleteAsync(); + await share.DeleteAsync(); + } + + [TestMethod] + [Description("Test invalid CloudFile APIs within a share snapshot - TASK")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileInvalidApisInShareSnapshotAsync() + { + CloudFileClient client = GenerateCloudFileClient(); + string name = GetRandomShareName(); + CloudFileShare share = client.GetShareReference(name); + await share.CreateAsync(); + + CloudFileShare snapshot = share.SnapshotAsync().Result; + CloudFile file = snapshot.GetRootDirectoryReference().GetDirectoryReference("dir1").GetFileReference("file"); + try + { + await file.CreateAsync(1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.DeleteAsync(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.SetMetadataAsync(); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.AbortCopyAsync(null); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.ClearRangeAsync(0, 1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.StartCopyAsync(file); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + try + { + await file.UploadFromByteArrayAsync(new byte[1024], 0, 1024); + Assert.Fail("API should fail in a snapshot"); + } + catch (InvalidOperationException e) + { + Assert.AreEqual(SR.CannotModifyShareSnapshot, e.Message); + } + + await snapshot.DeleteAsync(); + await share.DeleteAsync(); + } } } From da037c12a0f2ee53fb88d3876bfef9dd63a011fc Mon Sep 17 00:00:00 2001 From: zezha-msft Date: Tue, 28 Mar 2017 13:26:06 -0700 Subject: [PATCH 16/37] BUG FIX 872165: [XClient][NetCore]It throws 405 exception when invoking CreateIfNotExistsAsync against azure file root directory. (#92) Invoking CreateIfNotExistsAsync on a root directory should not throw the error 405. If a share exists, the root directory always exists, so calling CreateIfNotExistsAsync on an existing root directory should return false, meaning nothing was created. If a share is nonexistent, then CreateIfNotExistsAsync should return an error 404. This behavior is consistent with the Desktop version of .NET library. --- Lib/WindowsRuntime/File/CloudFileDirectory.cs | 14 +++++++ .../File/CloudFileDirectoryTest.cs | 42 +++++++++++++++++++ changelog.txt | 3 ++ 3 files changed, 59 insertions(+) diff --git a/Lib/WindowsRuntime/File/CloudFileDirectory.cs b/Lib/WindowsRuntime/File/CloudFileDirectory.cs index dfe093e6d..a3f164d14 100644 --- a/Lib/WindowsRuntime/File/CloudFileDirectory.cs +++ b/Lib/WindowsRuntime/File/CloudFileDirectory.cs @@ -113,6 +113,20 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope { try { + // Root directory always exists if the share exists. + // We cannot call this.CreateDirectoryImpl if this is the root directory, because the service will always + // return a 405 error in that case, regardless of whether or not the share exists. + if (string.IsNullOrEmpty(this.Name)) + { + // If the share does not exist, this fetch call will throw a 404, which is what we want. + await Executor.ExecuteAsync( + this.FetchAttributesImpl(null, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken); + return false; + } + await Executor.ExecuteAsync( this.CreateDirectoryImpl(modifiedOptions), modifiedOptions.RetryPolicy, diff --git a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs index d86ab35fa..444a50fa2 100644 --- a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs +++ b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs @@ -153,6 +153,48 @@ public async Task CloudFileDirectoryCreateIfNotExistsAsync() } } + [TestMethod] + [Description("Calling CreateIfNotExistsAsync on an existing root directory should return false")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileExistingRootDirectoryCreateIfNotExistsAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.CreateAsync(); + + try + { + CloudFileDirectory rootDirectory = share.GetRootDirectoryReference(); + Assert.IsFalse(await rootDirectory.CreateIfNotExistsAsync()); + } + finally + { + share.DeleteAsync().Wait(); + } + } + + [TestMethod] + [Description("Calling CreateIfNotExistsAsync on a nonexistent share's root directory should result in an error 404")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileNonexistentRootDirectoryCreateIfNotExistsAsync() + { + CloudFileShare share = GetRandomShareReference(); + await share.DeleteIfExistsAsync(); + + CloudFileDirectory rootDirectory = share.GetRootDirectoryReference(); + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await rootDirectory.CreateIfNotExistsAsync(null, context), + context, + "Calling CreateIfNotExistsAsync on a nonexistent root directory should have resulted in an error 404", + HttpStatusCode.NotFound); + } + [TestMethod] [Description("Verify that a file directory's metadata can be updated")] [TestCategory(ComponentCategory.File)] diff --git a/changelog.txt b/changelog.txt index de1093c46..09e38c0e4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +Changes in 8.2 +- Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. + Changes in 8.1.1 : - All (NetStandard/NetCore): Removed Microsoft.Data.Services.Client as a temporary fix to ODataLib dependency incompatibility with NetStandard From 035a74bfc83d260a028fe09c70d4a67ab58b35cd Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Fri, 31 Mar 2017 12:40:17 -0700 Subject: [PATCH 17/37] Premium Page Blob Tier Support --- Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs | 145 ++++++++++++++++++ .../Blob/Protocol/BlobHttpResponseParsers.cs | 6 + .../Protocol/BlobHttpWebRequestFactory.cs | 25 +++ Lib/Common/Blob/BlobProperties.cs | 7 + Lib/Common/Blob/PageBlobTier.cs | 56 +++++++ .../BlobHttpResponseParsers.Common.cs | 36 +++++ Lib/Common/Blob/Protocol/ListBlobsResponse.cs | 13 ++ Lib/Common/Shared/Protocol/Constants.cs | 12 +- Lib/WindowsRuntime/Blob/CloudPageBlob.cs | 71 +++++++++ .../Protocol/BlobHttpRequestMessageFactory.cs | 22 +++ .../Blob/Protocol/BlobHttpResponseParsers.cs | 6 + .../Blob/CloudPageBlobTest.cs | 102 ++++++++++++ Test/Common/TestCategoryConstants.cs | 2 + Test/WindowsRuntime/Blob/CloudPageBlobTest.cs | 33 ++++ changelog.txt | 1 + 15 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 Lib/Common/Blob/PageBlobTier.cs diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index 722be6491..e52c4a9b4 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -2458,6 +2458,125 @@ public virtual Task StartIncrementalCopyAsync(CloudPageBlob source, Acce } #endif +#if SYNC + /// + /// Sets the tier of the blob. + /// + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request, or null. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void SetBlobTier(PageBlobTier blobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); + Executor.ExecuteSync( + this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, AsyncCallback callback, object state) + { + return this.BeginSetBlobTier(blobTier, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to set the tier of the blob. + /// + /// An that references the pending asynchronous operation. + public virtual void EndSetBlobTier(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier) + { + return this.SetBlobTierAsync(blobTier, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetBlobTier, this.EndSetBlobTier, blobTier, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.SetBlobTierAsync(blobTier, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to set the tier of the blob. + /// + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetBlobTier, this.EndSetBlobTier, blobTier, accessCondition, options, operationContext, cancellationToken); + } +#endif + /// /// Implements the Create method. /// @@ -2726,5 +2845,31 @@ internal RESTCommand CreateSnapshotImpl(IDictionary + /// Implementation method for the SetBlobTier methods. + /// + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that sets the blob tier. + private RESTCommand SetBlobTierImpl(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.SetBlobTier(uri, serverTimeout, blobTier.ToString(), accessCondition, useVersionHeader, ctx); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + + this.attributes.Properties.PageBlobTier = blobTier; + return NullType.Value; + }; + + return putCmd; + } } } diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs index c8e3baa43..6efa9bb71 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs @@ -133,6 +133,12 @@ public static BlobProperties GetProperties(HttpWebResponse response) properties.AppendBlobCommittedBlockCount = int.Parse(comittedBlockCount, CultureInfo.InvariantCulture); } + // Get the tier of the blob + string blobTierString = response.Headers[Constants.HeaderConstants.AccessTierHeader]; + PageBlobTier? pageBlobTier; + BlobHttpResponseParsers.GetBlobTier(properties.BlobType, blobTierString, out pageBlobTier); + properties.PageBlobTier = pageBlobTier; + return properties; } diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs index 2d988e467..043c9c7bf 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs @@ -399,6 +399,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, builder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); + return request; } @@ -1174,5 +1175,29 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot return request; } + + /// + /// Generates a web request to set the tier for a blob. + /// + /// A specifying the absolute URI to the blob. + /// The server timeout interval, in seconds. + /// The blob tier to set as a string. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string blobTier, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "tier"); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, builder, useVersionHeader, operationContext); + request.ApplyAccessCondition(accessCondition); + + // Add the blob tier header + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, blobTier); + + return request; + } } } diff --git a/Lib/Common/Blob/BlobProperties.cs b/Lib/Common/Blob/BlobProperties.cs index 4d26b8e8c..c03d12564 100644 --- a/Lib/Common/Blob/BlobProperties.cs +++ b/Lib/Common/Blob/BlobProperties.cs @@ -56,6 +56,7 @@ public BlobProperties(BlobProperties other) this.AppendBlobCommittedBlockCount = other.AppendBlobCommittedBlockCount; this.IsServerEncrypted = other.IsServerEncrypted; this.IsIncrementalCopy = other.IsIncrementalCopy; + this.PageBlobTier = other.PageBlobTier; } /// @@ -171,5 +172,11 @@ public BlobProperties(BlobProperties other) /// /// A bool representing if the blob is an incremental copy. public bool IsIncrementalCopy { get; internal set; } + + /// + /// Gets a value indicating the tier of the page blob. + /// + /// A object that indicates the page blob tier. + public PageBlobTier? PageBlobTier { get; internal set; } } } diff --git a/Lib/Common/Blob/PageBlobTier.cs b/Lib/Common/Blob/PageBlobTier.cs new file mode 100644 index 000000000..4eeffbade --- /dev/null +++ b/Lib/Common/Blob/PageBlobTier.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +//----------------------------------------------------------------------- + +using System; +namespace Microsoft.WindowsAzure.Storage.Blob +{ + /// + /// The tier of the page blob. + /// + public enum PageBlobTier + { + /// + /// The tier is not recognized by this version of the library. + /// + Unknown, + + /// + /// P4 Tier + /// + P4, + + /// + /// P6 Tier + /// + P6, + + /// + /// P10 Tier + /// + P10, + + /// + /// P20 Tier + /// + P20, + + /// + /// P30 Tier + /// + P30 + } +} \ No newline at end of file diff --git a/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs b/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs index f9a15fa8b..348941eb3 100644 --- a/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs +++ b/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs @@ -246,5 +246,41 @@ private static bool CheckIfTrue(string header) { return string.Equals(header, Constants.HeaderConstants.TrueHeader, StringComparison.OrdinalIgnoreCase); } + + /// + /// Determines the tier of the blob. + /// + /// A indicating the type of blob. + /// The blob tier as a string + /// A nullable . This value will be populated if the blob type is unspecified or is a page blob. + internal static void GetBlobTier(BlobType blobType, string blobTierString, out PageBlobTier? pageBlobTier) + { + pageBlobTier = null; + + if (blobType.Equals(BlobType.PageBlob)) + { + PageBlobTier pageBlobTierFromResponse; + if (Enum.TryParse(blobTierString, true, out pageBlobTierFromResponse)) + { + pageBlobTier = pageBlobTierFromResponse; + } + else + { + pageBlobTier = PageBlobTier.Unknown; + } + } + else if (blobType.Equals(BlobType.Unspecified)) + { + PageBlobTier pageBlobTierFromResponse; + if (Enum.TryParse(blobTierString, true, out pageBlobTierFromResponse)) + { + pageBlobTier = pageBlobTierFromResponse; + } + else + { + pageBlobTier = PageBlobTier.Unknown; + } + } + } } } diff --git a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs index 045e719e5..6fe866cf2 100644 --- a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs +++ b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs @@ -210,6 +210,8 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) string copyStatusDescription = null; string copyDestinationSnapshotTime = null; + string blobTierString = null; + this.reader.ReadStartElement(); while (this.reader.IsStartElement()) { @@ -344,6 +346,10 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) copyDestinationSnapshotTime = reader.ReadElementContentAsString(); break; + case Constants.AccessTierElement: + blobTierString = reader.ReadElementContentAsString(); + break; + default: reader.Skip(); break; @@ -390,6 +396,13 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) copyDestinationSnapshotTime); } + if (!string.IsNullOrEmpty(blobTierString)) + { + PageBlobTier? pageBlobTier; + BlobHttpResponseParsers.GetBlobTier(blob.Properties.BlobType, blobTierString, out pageBlobTier); + blob.Properties.PageBlobTier = pageBlobTier; + } + return new ListBlobEntry(name, blob); } diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 385f7baf8..1434f4b7b 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -507,6 +507,11 @@ static class Constants /// public const string GeoBootstrapValue = "bootstrap"; + /// + /// Constant for the blob tier. + /// + public const string AccessTierElement = "AccessTier"; + /// /// XML element for blob types. /// @@ -981,6 +986,11 @@ static HeaderConstants() /// public const string DeleteSnapshotHeader = PrefixForStorageHeader + "delete-snapshots"; + /// + /// Header for the blob tier. + /// + public const string AccessTierHeader = PrefixForStorageHeader + "access-tier"; + /// /// Header that specifies blob caching control. /// @@ -1115,7 +1125,7 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion ="2016-10-16"; + public const string TargetStorageVersion = "2016-10-16"; /// /// Specifies the file type. diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index b93a456d6..412a83a81 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -969,6 +969,52 @@ public virtual Task StartIncrementalCopyAsync(Uri sourceSnapshot, Access cancellationToken), cancellationToken); } + /// + /// Sets the tier for a blob. + /// + /// A representing the tier to set. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier) + { + return this.SetBlobTierAsync(blobTier, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Sets the tier for a blob. + /// + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.SetBlobTierAsync(blobTier, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Sets the tier for a blob. + /// + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsync( + this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } + /// /// Implements the Create method. /// @@ -1241,5 +1287,30 @@ private RESTCommand ClearPageImpl(long startOffset, long length, Acces return putCmd; } + + /// + /// Implementation method for the SetBlobTier methods. + /// + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that sets the blob tier. + private RESTCommand SetBlobTierImpl(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetBlobTier(uri, serverTimeout, blobTier.ToString(), accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + + this.attributes.Properties.PageBlobTier = blobTier; + return NullType.Value; + }; + + return putCmd; + } } } diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs index 0738485c3..c936eccfd 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs @@ -834,5 +834,27 @@ public static StorageRequestMessage GetServiceStats(Uri uri, int? timeout, Opera { return HttpRequestMessageFactory.GetServiceStats(uri, timeout, operationContext, canonicalizer, credentials); } + + /// + /// Constructs a web request to set the tier on a page blob. + /// + /// The absolute URI to the blob. + /// The server timeout interval. + /// The blob tier to set. + /// The access condition to apply to the request. + /// A web request to use to perform the operation. + public static StorageRequestMessage SetBlobTier(Uri uri, int? timeout, string blobTier, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "tier"); + + StorageRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, builder, content, operationContext, canonicalizer, credentials); + + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, blobTier); + + request.ApplyAccessCondition(accessCondition); + request.ApplySequenceNumberCondition(accessCondition); + return request; + } } } diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs index 7968acf39..a916a8852 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs @@ -124,6 +124,12 @@ public static BlobProperties GetProperties(HttpResponseMessage response) properties.AppendBlobCommittedBlockCount = int.Parse(comittedBlockCount, CultureInfo.InvariantCulture); } + // Get the tier of the blob + string blobTierString = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.AccessTierHeader); + PageBlobTier? pageBlobTier; + BlobHttpResponseParsers.GetBlobTier(properties.BlobType, blobTierString, out pageBlobTier); + properties.PageBlobTier = pageBlobTier; + return properties; } diff --git a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs index b17d0779e..33b47bcf5 100644 --- a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs @@ -3705,6 +3705,108 @@ private void ListBlobsWithIncrementalCopyImpl(int overload) container.DeleteIfExistsAsync().Wait(); } } + + [TestMethod] + [Description("Set blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetBlobTier() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + blob.Create(0); + blob.SetBlobTier(PageBlobTier.P4); + Assert.AreEqual(PageBlobTier.P4, blob.Properties.PageBlobTier); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual(PageBlobTier.P4, blob2.Properties.PageBlobTier); + + CloudPageBlob blob3 = (CloudPageBlob)container.ListBlobs().ToList().First(); + Assert.AreEqual(PageBlobTier.P4, blob3.Properties.PageBlobTier); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetBlobTierAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + IAsyncResult result; + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + result = blob.BeginCreate(0, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreate(result); + + result = blob.BeginSetBlobTier(PageBlobTier.P6, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndSetBlobTier(result); + Assert.AreEqual(PageBlobTier.P6, blob.Properties.PageBlobTier); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + result = blob2.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(PageBlobTier.P6, blob2.Properties.PageBlobTier); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Set blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetBlobTierTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + blob.CreateAsync(0).Wait(); + + blob.SetBlobTierAsync(PageBlobTier.P20).Wait(); + Assert.AreEqual(PageBlobTier.P20, blob.Properties.PageBlobTier); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(PageBlobTier.P20, blob.Properties.PageBlobTier); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif } } diff --git a/Test/Common/TestCategoryConstants.cs b/Test/Common/TestCategoryConstants.cs index df122690d..a52f8a6b6 100644 --- a/Test/Common/TestCategoryConstants.cs +++ b/Test/Common/TestCategoryConstants.cs @@ -57,5 +57,7 @@ public static class TenantTypeCategory public const string DevFabric = "DevFabric"; public const string Cloud = "Cloud"; + + public const string Premium = "Premium"; } } diff --git a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs index 848f8f215..79a33d17b 100644 --- a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs @@ -1386,5 +1386,38 @@ public async Task ListBlobsWithIncrementalCopiedBlobTestAsync() container.DeleteIfExistsAsync().Wait(); } } + + [TestMethod] + [Description("Set blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public async Task CloudPageBlobSetBlobTierAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + await blob.CreateAsync(0); + + await blob.SetBlobTierAsync(PageBlobTier.P30); + Assert.AreEqual(PageBlobTier.P30, blob.Properties.PageBlobTier); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(PageBlobTier.P30, blob2.Properties.PageBlobTier); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null); + CloudPageBlob blob3 = (CloudPageBlob)results.Results.ToList().First(); + Assert.AreEqual(PageBlobTier.P30, blob3.Properties.PageBlobTier); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } } } diff --git a/changelog.txt b/changelog.txt index b904529fb..a0875d567 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ Changes in X.X.X : - All: Support for 2016-10-16 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. - Files: Added support for creating the snapshot of a share. See the service documentation for more information on how to use this API. - Files: Added support for server side encryption. +- PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. Changes in 8.1.1 : - All (NetStandard/NetCore): Removed Microsoft.Data.Services.Client as a temporary fix to ODataLib dependency incompatibility with NetStandard From b903be329bfd802289491c450e72fb342717760c Mon Sep 17 00:00:00 2001 From: zezha-msft Date: Mon, 27 Mar 2017 17:44:01 -0700 Subject: [PATCH 18/37] 922922 Implement IfNotExists check on OpenWrite Currently the condition IfNotExists is only checked when the blob is being committed. In the case where the blob already exists, the commit will fail, but resources were wasted on upload the content. The OpenWrite call should have failed earlier. --- Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs | 21 ++++++++--- Lib/Common/AccessCondition.cs | 36 +++++++++++++++++++ Lib/Common/Blob/CloudBlockBlob.Common.cs | 13 +++++++ Lib/Common/Core/SR.cs | 1 + Lib/WindowsRuntime/Blob/CloudBlockBlob.cs | 14 +++++--- .../TestHelper.cs | 11 ++++++ .../Blob/BlobWriteStreamTest.cs | 20 +++++------ .../Blob/BlobWriteStreamTest.cs | 23 ++++++++---- Test/WindowsRuntime/TestHelper.cs | 12 +++++++ changelog.txt | 1 + 10 files changed, 123 insertions(+), 29 deletions(-) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index 319853aa1..c657ac566 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -65,7 +65,14 @@ public virtual CloudBlobStream OpenWrite(AccessCondition accessCondition = null, { try { - this.FetchAttributes(accessCondition, options, operationContext); + // If the accessCondition is IsIfNotExists, the fetch call will always return 400 + this.FetchAttributes(accessCondition.Clone().RemoveIsIfNotExistsCondition(), options, operationContext); + + // In case the blob already exists and the access condition is "IfNotExists", we should fail fast before uploading any content for the blob + if (accessCondition.IsIfNotExists) + { + throw GenerateExceptionForConflictFailure(); + } } catch (StorageException e) { @@ -154,7 +161,8 @@ public virtual ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCond if ((accessCondition != null) && accessCondition.IsConditional) { ICancellableAsyncResult result = this.BeginFetchAttributes( - accessCondition, + // If the accessCondition is IsIfNotExists, the fetch call will always return 400 + accessCondition.Clone().RemoveIsIfNotExistsCondition(), options, operationContext, ar => @@ -164,6 +172,12 @@ public virtual ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCond try { this.EndFetchAttributes(ar); + + // In case the blob already exists and the access condition is "IfNotExists", we should fail fast before uploading any content for the blob + if (accessCondition.IsIfNotExists) + { + storageAsyncResult.OnComplete(GenerateExceptionForConflictFailure()); + } } catch (StorageException e) { @@ -2635,9 +2649,6 @@ private static bool ContinueOpenWriteOnFailure(StorageException exception, Acces // If we got a 404 and the access condition was not an If-Match, continue. (We don't want an if-none-match to interfere with the case where the blob doesn't exist) if ((exception.RequestInformation.HttpStatusCode == (int)HttpStatusCode.NotFound) && string.IsNullOrEmpty(accessCondition.IfMatchETag)) return true; - // If we got a 400 and the access condition was If-None-Match-*, continue. (There is a special case: If-None-Match-*, on a blob that doesn't exist, will return a 400 on a read operation, because it's an impossible condition. - if ((exception.RequestInformation.HttpStatusCode == (int)HttpStatusCode.BadRequest) && !string.IsNullOrEmpty(accessCondition.IfNoneMatchETag) && (accessCondition.IfNoneMatchETag == "*")) return true; - // If we got a 403, continue. This is to account for the case where our credentials give write, but not read permission. if (exception.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Forbidden) return true; diff --git a/Lib/Common/AccessCondition.cs b/Lib/Common/AccessCondition.cs index 78b2bdda3..92b5fafb6 100644 --- a/Lib/Common/AccessCondition.cs +++ b/Lib/Common/AccessCondition.cs @@ -171,6 +171,42 @@ internal bool IsConditional } } + /// + /// Determines whether the access condition is IfNotExists. + /// + /// true if the access condition is a IfNotExists; otherwise, false. + internal bool IsIfNotExists + { + get + { + return string.Equals("*", this.IfNoneMatchETag, StringComparison.Ordinal); + + } + } + + /// + /// Remove the IfNotExists condition. + /// + /// The reference to the is returned, to allow chained usage + internal AccessCondition RemoveIsIfNotExistsCondition() + { + if (this.IsIfNotExists) + { + this.IfNoneMatchETag = null; + } + + return this; + } + + /// + /// Provide a shallow copy of the current access condition + /// + /// A shallow copy of the object + public AccessCondition Clone() + { + return (AccessCondition)this.MemberwiseClone(); + } + /// /// Constructs an empty access condition. /// diff --git a/Lib/Common/Blob/CloudBlockBlob.Common.cs b/Lib/Common/Blob/CloudBlockBlob.Common.cs index c6f8c6aed..e84fb6a76 100644 --- a/Lib/Common/Blob/CloudBlockBlob.Common.cs +++ b/Lib/Common/Blob/CloudBlockBlob.Common.cs @@ -27,6 +27,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using System.Text; using System.Threading; using System.Threading.Tasks; + using System.Net; /// /// Represents a blob that is uploaded as a set of blocks. @@ -294,5 +295,17 @@ internal void CheckAdjustBlockSize(long? streamLength) } } } + + /// + /// Helper method to generate a 409 Conflict exception. + /// Return a 409 error wrapped in StorageException + private static StorageException GenerateExceptionForConflictFailure() + { + RequestResult requestResult = new RequestResult(); + requestResult.HttpStatusMessage = SR.BlobAlreadyExists; + requestResult.HttpStatusCode = (int)HttpStatusCode.Conflict; + requestResult.ExtendedErrorInformation = null; + return new StorageException(requestResult, SR.BlobAlreadyExists, null /* inner */); + } } } diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index d19a7e6d4..7a669d1fd 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -35,6 +35,7 @@ internal class SR public const string BatchErrorInOperation = "Element {0} in the batch returned an unexpected response code."; public const string BinaryMessageShouldUseBase64Encoding = "EncodeMessage should be true for binary message."; public const string Blob = "blob"; + public const string BlobAlreadyExists = "The specified blob already exists."; public const string BlobDataCorrupted = "Blob data corrupted (integrity check failed), Expected value is '{0}', retrieved '{1}'"; public const string BlobEndPointNotConfigured = "No blob endpoint configured."; public const string BlobInvalidSequenceNumber = "The sequence number may not be specified for an increment operation."; diff --git a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs index e30060cff..b632aeae9 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs @@ -104,19 +104,23 @@ public virtual Task OpenWriteAsync(AccessCondition accessCondit { try { - await this.FetchAttributesAsync(accessCondition, options, operationContext, cancellationToken); + // If the accessCondition is IsIfNotExists, the fetch call will always return 400 + await this.FetchAttributesAsync(accessCondition.Clone().RemoveIsIfNotExistsCondition(), options, operationContext, cancellationToken); + + // In case the blob already exists and the access condition is "IfNotExists", we should fail fast before uploading any content for the blob + if (accessCondition.IsIfNotExists) + { + throw GenerateExceptionForConflictFailure(); + } } catch (Exception) { if ((operationContext.LastResult != null) && (((operationContext.LastResult.HttpStatusCode == (int)HttpStatusCode.NotFound) && string.IsNullOrEmpty(accessCondition.IfMatchETag)) || - (operationContext.LastResult.HttpStatusCode == (int)HttpStatusCode.Forbidden) || - (operationContext.LastResult.HttpStatusCode == (int)HttpStatusCode.BadRequest) && - (!string.IsNullOrEmpty(accessCondition.IfNoneMatchETag) && (accessCondition.IfNoneMatchETag == "*")))) + (operationContext.LastResult.HttpStatusCode == (int)HttpStatusCode.Forbidden))) { // If we got a 404 and the condition was not an If-Match OR if we got a 403, - // If we got a 400 and the access condition was If-None-Match-*, continue. (There is a special case: If-None-Match-*, on a blob that doesn't exist, will return a 400 on a read operation, because it's an impossible condition. // we should continue with the operation. } else diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/TestHelper.cs b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/TestHelper.cs index 929221a37..15c7c4b21 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/TestHelper.cs +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/TestHelper.cs @@ -103,6 +103,17 @@ internal static async Task ExpectedExceptionAsync(Func operation, Operatio { await operation(); } + catch (StorageException storageException) + { + Assert.AreEqual((int)expectedStatusCode, storageException.RequestInformation.HttpStatusCode, "Http status code is unexpected."); + if (!string.IsNullOrEmpty(requestErrorCode)) + { + Assert.IsNotNull(storageException.RequestInformation.ExtendedErrorInformation); + Assert.AreEqual(requestErrorCode, storageException.RequestInformation.ExtendedErrorInformation.ErrorCode); + } + + return; + } catch (Exception) { Assert.AreEqual((int)expectedStatusCode, operationContext.LastResult.HttpStatusCode, "Http status code is unexpected."); diff --git a/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs b/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs index 977df00cc..6ddf99758 100644 --- a/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs @@ -170,10 +170,9 @@ public void BlockBlobWriteStreamOpenWithAccessCondition() HttpStatusCode.NotModified); accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); - blobStream = existingBlob.OpenWrite(accessCondition); TestHelper.ExpectedException( - () => blobStream.Dispose(), - "BlobWriteStream.Dispose with a non-met condition should fail", + () => existingBlob.OpenWrite(accessCondition), + "OpenWrite with a non-met condition should fail", HttpStatusCode.Conflict); accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); @@ -213,7 +212,6 @@ public void BlockBlobWriteStreamOpenWithAccessCondition() "BlobWriteStream.Dispose with a non-met condition should fail", HttpStatusCode.Conflict); - blob = container.GetBlockBlobReference("blob8"); accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value); blobStream = existingBlob.OpenWrite(accessCondition); @@ -335,13 +333,12 @@ public void BlockBlobWriteStreamOpenAPMWithAccessCondition() accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); result = existingBlob.BeginOpenWrite(accessCondition, null, null, - ar => waitHandle.Set(), - null); + ar => waitHandle.Set(), + null); waitHandle.WaitOne(); - blobStream = existingBlob.EndOpenWrite(result); TestHelper.ExpectedException( - () => blobStream.Dispose(), - "BlobWriteStream.Dispose with a non-met condition should fail", + () => existingBlob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", HttpStatusCode.Conflict); accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); @@ -394,18 +391,17 @@ public void BlockBlobWriteStreamOpenAPMWithAccessCondition() blob = container.GetBlockBlobReference("blob7"); accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); - result = existingBlob.BeginOpenWrite(accessCondition, null, null, + result = blob.BeginOpenWrite(accessCondition, null, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blobStream = existingBlob.EndOpenWrite(result); + blobStream = blob.EndOpenWrite(result); blob.PutBlockList(new List()); TestHelper.ExpectedException( () => blobStream.Dispose(), "BlobWriteStream.Dispose with a non-met condition should fail", HttpStatusCode.Conflict); - blob = container.GetBlockBlobReference("blob8"); accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value); result = existingBlob.BeginOpenWrite(accessCondition, null, null, ar => waitHandle.Set(), diff --git a/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs b/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs index 1c801ded5..f324818db 100644 --- a/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs +++ b/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs @@ -202,13 +202,8 @@ await TestHelper.ExpectedExceptionAsync( HttpStatusCode.NotModified); accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); - blobStream = await existingBlob.OpenWriteAsync(accessCondition, null, context); await TestHelper.ExpectedExceptionAsync( - () => - { - blobStream.Dispose(); - return Task.FromResult(true); - }, + async () => await existingBlob.OpenWriteAsync(accessCondition, null, context), context, "BlobWriteStream.Dispose with a non-met condition should fail", HttpStatusCode.Conflict); @@ -267,6 +262,10 @@ await TestHelper.ExpectedExceptionAsync( blobStream = await existingBlob.OpenWriteAsync(accessCondition, null, context); #if NETCORE Thread.Sleep(1000); //Make the condition invalid for sure + +#else + // wait for a second so that the LastModified time of existingBlob is in the past, as the precision is in seconds + await Task.Delay(TimeSpan.FromSeconds(1)); #endif await existingBlob.SetPropertiesAsync(); await TestHelper.ExpectedExceptionAsync( @@ -616,6 +615,13 @@ public async Task BlockBlobWriteStreamFlushTestAsync() await wholeBlob.WriteAsync(buffer, 0, buffer.Length); } +#if NETCORE + // todo: Make some other better logic for this test to be reliable. + System.Threading.Thread.Sleep(500); +#else + await Task.Delay(TimeSpan.FromSeconds(1)); +#endif + Assert.AreEqual(1, opContext.RequestResults.Count); await blobStream.FlushAsync(); @@ -840,8 +846,9 @@ public async Task PageBlobWriteStreamFlushTestAsync() #if NETCORE // todo: Make some other better logic for this test to be reliable. System.Threading.Thread.Sleep(500); +#else + await Task.Delay(TimeSpan.FromSeconds(1)); #endif - Assert.AreEqual(2, opContext.RequestResults.Count); await blobStream.FlushAsync(); @@ -1012,6 +1019,8 @@ public async Task AppendBlobWriteStreamFlushTestAsync() #if NETCORE // todo: Make some other better logic for this test to be reliable. System.Threading.Thread.Sleep(500); +#else + await Task.Delay(TimeSpan.FromSeconds(1)); #endif Assert.AreEqual(2, opContext.RequestResults.Count); diff --git a/Test/WindowsRuntime/TestHelper.cs b/Test/WindowsRuntime/TestHelper.cs index 67af489ae..49cafdfce 100644 --- a/Test/WindowsRuntime/TestHelper.cs +++ b/Test/WindowsRuntime/TestHelper.cs @@ -125,6 +125,17 @@ internal static async Task ExpectedExceptionAsync(Func operation, Operatio { await operation(); } + catch (StorageException storageException) + { + Assert.AreEqual((int)expectedStatusCode, storageException.RequestInformation.HttpStatusCode, "Http status code is unexpected."); + if (!string.IsNullOrEmpty(requestErrorCode)) + { + Assert.IsNotNull(storageException.RequestInformation.ExtendedErrorInformation); + Assert.AreEqual(requestErrorCode, storageException.RequestInformation.ExtendedErrorInformation.ErrorCode); + } + + return; + } catch (Exception) { Assert.AreEqual((int)expectedStatusCode, operationContext.LastResult.HttpStatusCode, "Http status code is unexpected."); @@ -133,6 +144,7 @@ internal static async Task ExpectedExceptionAsync(Func operation, Operatio Assert.IsNotNull(operationContext.LastResult.ExtendedErrorInformation); Assert.AreEqual(requestErrorCode, operationContext.LastResult.ExtendedErrorInformation.ErrorCode); } + return; } diff --git a/changelog.txt b/changelog.txt index 09e38c0e4..c46ff2d58 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ Changes in 8.2 +- All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. Changes in 8.1.1 : From 3cb4e97d4cfce817e9f23f33cc14ae8f068a5165 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Wed, 12 Apr 2017 12:34:32 -0700 Subject: [PATCH 19/37] Fixing bug with passing modifiedOptions and addressing customer request --- Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs | 2 +- Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs index 7dd75a01f..d647e51a1 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs @@ -217,7 +217,7 @@ public virtual bool CreateIfNotExists(BlobContainerPublicAccessType accessType, try { - this.Create(accessType, requestOptions, operationContext); + this.Create(accessType, modifiedOptions, operationContext); return true; } catch (StorageException e) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index b80f34eeb..ad7234c59 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -1029,9 +1029,9 @@ private void UploadFromFileCallback(IAsyncResult asyncResult) /// An that references the pending asynchronous operation. public virtual void EndUploadFromFile(IAsyncResult asyncResult) { - if (asyncResult is CancellableAsyncResultTaskWrapper) + CancellableAsyncResultTaskWrapper cancellableAsyncResult = asyncResult as CancellableAsyncResultTaskWrapper; + if (cancellableAsyncResult != null) { - CancellableAsyncResultTaskWrapper cancellableAsyncResult = asyncResult as CancellableAsyncResultTaskWrapper; cancellableAsyncResult.Wait(); } else From d314865a8814a9189db0c4884168ab3f1a506eed Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Tue, 9 May 2017 15:30:50 -0700 Subject: [PATCH 20/37] Request encrypted check for resize api --- Lib/ClassLibraryCommon/File/CloudFile.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/ClassLibraryCommon/File/CloudFile.cs b/Lib/ClassLibraryCommon/File/CloudFile.cs index 22627cb63..8562d5bab 100644 --- a/Lib/ClassLibraryCommon/File/CloudFile.cs +++ b/Lib/ClassLibraryCommon/File/CloudFile.cs @@ -4302,6 +4302,7 @@ private RESTCommand ResizeImpl(long sizeInBytes, AccessCondition acces HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.UpdateETagLMTAndLength(resp, false); this.Properties.Length = sizeInBytes; + cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); return NullType.Value; }; From 5a915d9d66fb81e9baeec06f1b5e0c4771cbf13d Mon Sep 17 00:00:00 2001 From: asorrin-msft Date: Tue, 23 May 2017 10:54:48 -0700 Subject: [PATCH 21/37] First pass at adding unit tests that double as API documentation. (#103) Documented Blob Request Options in detail, and UploadFromFile at a high level. --- Lib/Common/Blob/BlobRequestOptions.cs | 102 ++++++++-- Lib/Common/RetryPolicies/LocationMode.cs | 16 ++ .../Blob/BlobClientEncryptionTests.cs | 31 ++-- .../Blob/BlobUploadDownloadTest.cs | 174 +++++++++++++++++- .../Blob/CloudAppendBlobTest.cs | 48 +++++ Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs | 67 +++++++ Test/Common/Core/CloudStorageAccountTests.cs | 63 ++++++- 7 files changed, 462 insertions(+), 39 deletions(-) diff --git a/Lib/Common/Blob/BlobRequestOptions.cs b/Lib/Common/Blob/BlobRequestOptions.cs index 99a85d254..4837eb669 100644 --- a/Lib/Common/Blob/BlobRequestOptions.cs +++ b/Lib/Common/Blob/BlobRequestOptions.cs @@ -243,6 +243,14 @@ internal void AssertPolicyIfRequired() /// Gets or sets the retry policy for the request. /// /// An object of type . + /// Retry policies instruct the Storage Client to retry failed requests. + /// By default, only some failures are retried. For example, connection failures and + /// throttling failures can be retried. Resource not found (404) or authentication + /// failures are not retried, because these are not likely to succeed on retry. + /// If not set, the Storage Client uses an exponential backoff retry policy, where the wait time gets + /// exponentially longer between requests, up to a total of around 30 seconds. + /// The default retry policy is recommended for most scenarios. + /// public IRetryPolicy RetryPolicy { get; set; } #if !(WINDOWS_RT || NETCORE) @@ -256,6 +264,13 @@ internal void AssertPolicyIfRequired() /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. /// /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + /// + /// RequireEncryption here refers to Client-Side Encryption. + /// If this value is set to true, all calls will fail if the data + /// is not encrypted/decrypted with an encryption policy. If this value + /// is false (the default), any data being downloaded that is not encrypted + /// will be returned as-is. + /// public bool? RequireEncryption { get; set; } /// @@ -270,9 +285,23 @@ internal void AssertPolicyIfRequired() /// for the request. /// /// - /// This option is used only by the object in the UploadFrom* methods and - /// the BlobWriteStream methods. By default, it is set to false. Set this option to true only for single writer scenarios. + /// This option is used only by the object in the UploadFrom* methods, + /// the AppendFrom* methods, and the BlobWriteStream class. By default, it is set to false. + /// Set this option to true only for single-writer scenarios. /// Setting this option to true in a multi-writer scenario may lead to corrupted blob data. + /// + /// When calling "UploadFrom*" on an append blob, the Storage Client breaks the input data + /// up into a number of data blocks, and uploads each data block with an "append block" operation. + /// Normally, an "IfAppendPositionEqual" access condition is added to the append block operation, so that the + /// upload operation will fail if some other process somewhere has appended data in the middle of this data stream. + /// However, this can result in a false failure in a very specific case. If an append operation fails with a timeout, + /// there is a chance that the operation succeeded on the service, but the "success" response did not make it back to the client. + /// In this case, the client will retry, and then get an "append position not met" failure. + /// + /// Setting this value to true results in the upload operation continuing when it sees an "append position not met" + /// failure on retry - accounting for the above possibility. However, this loses protection in the multi-writer + /// scenario - if multiple threads are uploading to the blob at once, and this value is set to true, some data + /// may be lost, because the client thinks the data was uploaded, when in fact it was the other process' data. /// public bool? AbsorbConditionalErrorsOnRetry { get; set; } @@ -280,18 +309,35 @@ internal void AssertPolicyIfRequired() /// Gets or sets the location mode of the request. /// /// A enumeration value indicating the location mode of the request. + /// The LocationMode specifies in which locations the Storage Client + /// will attempt to make the request. This is only valid for RA-GRS accounts - accounts + /// where data can be read from either the primary or the secondary endpoint. + /// public LocationMode? LocationMode { get; set; } /// - /// Gets or sets the server timeout interval for the request. + /// Gets or sets the server timeout interval for a single HTTP request. /// - /// A containing the server timeout interval for the request. + /// A containing the server timeout interval for each HTTP request. + /// + /// The server timeout is the timeout sent to the Azure Storage service + /// for each REST request made. If the API called makes multiple REST calls + /// (UploadFromStream, for example, or if the request retries), this timeout + /// will be applied separately to each request. This value is not + /// tracked or validated on the client, it is only passed to the Storage service. + /// public TimeSpan? ServerTimeout { get; set; } /// /// Gets or sets the maximum execution time across all potential retries for the request. /// /// A representing the maximum execution time for retries for the request. + /// + /// The maximum execution time is the time allotted for a single API call. + /// If the total amount of time spent in the API - across all REST requests, + /// retries, etc - exceeds this value, the client will timeout. This value + /// is only tracked on the client, it is not sent to the service. + /// public TimeSpan? MaximumExecutionTime { get @@ -308,13 +354,19 @@ public TimeSpan? MaximumExecutionTime this.maximumExecutionTime = value; } - } + } /// - /// Gets or sets the number of blocks that may be simultaneously uploaded when uploading a blob that is greater than - /// the value specified by the property in size. + /// Gets or sets the number of blocks that may be simultaneously uploaded. /// /// An integer value indicating the number of parallel blob upload operations that may proceed. + /// + /// When using the UploadFrom* methods on a blob, the blob will be broken up into blocks. Setting this + /// value limits the number of outstanding I/O "put block" requests that the library will have in-flight + /// at a given time. Default is 1 (no parallelism). Setting this value higher may result in + /// faster blob uploads, depending on the network between the client and the Azure Storage service. + /// If blobs are small (less than 256 MB), keeping this value equal to 1 is advised. + /// public int? ParallelOperationThreadCount { get @@ -360,7 +412,17 @@ public long? SingleBlobUploadThresholdInBytes /// /// Gets or sets a value to calculate and send/validate content MD5 for transactions. /// - /// Use true to calculate and send/validate content MD5 for transactions; otherwise, false. + /// Use true to calculate and send/validate content MD5 for transactions; otherwise, false. Default is false. + /// + /// The UseTransactionalMD5 option instructs the Storage Client to calculate and validate + /// the MD5 hash of individual Storage REST operations. For a given REST operation, + /// if this value is set, both the Storage Client and the Storage service will calculate + /// the MD5 hash of the transferred data, and will fail if the values do not match. + /// This value is not persisted on the service or the client. + /// This option applies to both upload and download operations. + /// Note that HTTPS does a similar check during transit. If you are using HTTPS, + /// we recommend this feature be off. + /// #if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif @@ -388,8 +450,18 @@ public bool? UseTransactionalMD5 /// /// Gets or sets a value to indicate that an MD5 hash will be calculated and stored when uploading a blob. /// - /// Use true to calculate and store an MD5 hash when uploading a blob; otherwise, false. - /// This property is not supported for the Append* APIs. + /// Use true to calculate and store an MD5 hash when uploading a blob; otherwise, false. Defaults to false. + /// This property is not supported for the Append* APIs. + /// The StoreBlobContentMD5 request option instructs the Storage Client to calculate the MD5 hash + /// of the blob content during an upload operation. This value is then stored on the + /// blob object as the Content-MD5 header. This option applies only to upload operations. + /// This is useful for validating the integrity of the blob upon later download, and + /// compatible with the Content-MD5 header as defined in the HTTP spec. If using + /// the Storage Client for later download, if the Content-MD5 header is present, + /// the MD5 hash of the content will be validated, unless "DisableContentMD5Validation" is set. + /// Note that this value is not validated on the Azure Storage service on either upload or download of data; + /// it is merely stored and returned. + /// #if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif @@ -417,8 +489,14 @@ public bool? StoreBlobContentMD5 /// /// Gets or sets a value to indicate that MD5 validation will be disabled when downloading blobs. /// - /// Use true to disable MD5 validation; false to enable MD5 validation. -#if WINDOWS_PHONE && WINDOWS_DESKTOP + /// Use true to disable MD5 validation; false to enable MD5 validation. Default is false. + /// + /// When downloading a blob, if the value already exists on the blob, the Storage service + /// will include the MD5 hash of the entire blob as a header. This option controls + /// whether or not the Storage Client will validate that MD5 hash on download. + /// See for more details. + /// +#if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif public bool? DisableContentMD5Validation diff --git a/Lib/Common/RetryPolicies/LocationMode.cs b/Lib/Common/RetryPolicies/LocationMode.cs index 98464d4cb..b2894a407 100644 --- a/Lib/Common/RetryPolicies/LocationMode.cs +++ b/Lib/Common/RetryPolicies/LocationMode.cs @@ -25,21 +25,37 @@ public enum LocationMode /// /// Requests are always sent to the primary location. /// + /// + /// If this value is used for requests that only work against a secondary location + /// (GetServiceStats, for example), the request will fail in the client. + /// PrimaryOnly, /// /// Requests are always sent to the primary location first. If a request fails, it is sent to the secondary location. /// + /// + /// If this value is used for requests that are only valid against one location, the client will + /// only target the allowed location. + /// PrimaryThenSecondary, /// /// Requests are always sent to the secondary location. /// + /// + /// If this value is used for requests that only work against a primary location + /// (create, modify, and delete APIs), the request will fail in the client. + /// SecondaryOnly, /// /// Requests are always sent to the secondary location first. If a request fails, it is sent to the primary location. /// + /// + /// If this value is used for requests that are only valid against one location, the client will + /// only target the allowed location. + /// SecondaryThenPrimary, } } diff --git a/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs b/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs index 391e44f46..19cbbd2be 100644 --- a/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs @@ -103,28 +103,31 @@ private void DoCloudBlobEncryption(BlobType type, bool partial) blob = container.GetAppendBlobReference("appendblob"); } - // Create the Key to be used for wrapping. - SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + #region sample_RequestOptions_EncryptionPolicy - // Create the resolver to be used for unwrapping. - DictionaryKeyResolver resolver = new DictionaryKeyResolver(); - resolver.Add(aesKey); + // Create the Key to be used for wrapping. + // This code creates a random encryption key. + Microsoft.Azure.KeyVault.SymmetricKey aesKey = new SymmetricKey(kid: "symencryptionkey"); // Create the encryption policy to be used for upload. - BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(key: aesKey, keyResolver: null); // Set the encryption policy on the request options. BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; - MemoryStream stream; - // Upload the encrypted contents to the blob. - using (stream = new MemoryStream(buffer)) - { - blob.UploadFromStream(stream, size, null, uploadOptions, null); + // Encrypt and upload the data to the blob. + MemoryStream stream = new MemoryStream(buffer); + blob.UploadFromStream(stream, length: size, accessCondition: null, options: uploadOptions); + + #endregion - // Ensure that the user stream is open. - Assert.IsTrue(stream.CanSeek); - } + // Ensure that the user stream is open. + Assert.IsTrue(stream.CanSeek); + stream.Dispose(); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); // Download the encrypted blob. // Create the decryption policy to be used for download. There is no need to specify the diff --git a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs index 936a03a2a..322475577 100644 --- a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob { + using Auth; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -730,6 +731,68 @@ public void CloudAppendBlobUploadDownloadFileTask() File.Delete("garbage.file"); } + [TestMethod] + [Description("Upload from file to a block blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlockBlobUploadBasicFunctionality() + { + string accountName = this.testContainer.ServiceClient.Credentials.AccountName; + string accountKey = this.testContainer.ServiceClient.Credentials.ExportBase64EncodedKey(); + string containerName = this.testContainer.Name + "copy"; + string blobName = "myBlob"; + string inputFileName = Path.GetTempFileName(); + File.WriteAllText(inputFileName, @"Sample file text here."); + + try + { + #region sample_UploadBlob_EndToEnd + // This is one common way of creating a CloudStorageAccount object. You can get + // your Storage Account Name and Key from the Azure Portal. + StorageCredentials credentials = new StorageCredentials(accountName, accountKey); + CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, useHttps: true); + + // Another common way to create a CloudStorageAccount object is to use a connection string: + // CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); + + CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); + + // This call creates a local CloudBlobContainer object, but does not make a network call + // to the Azure Storage Service. The container on the service that this object represents may + // or may not exist at this point. If it does exist, the properties will not yet have been + // popluated on this object. + CloudBlobContainer blobContainer = blobClient.GetContainerReference(containerName); + + // This makes an actual service call to the Azure Storage service. Unless this call fails, + // the container will have been created. + blobContainer.Create(); + + // This also does not make a service call, it only creates a local object. + CloudBlockBlob blob = blobContainer.GetBlockBlobReference(blobName); + + // This transfers data in the file to the blob on the service. + blob.UploadFromFile(inputFileName); + #endregion + + Assert.AreEqual(File.ReadAllText(inputFileName), blob.DownloadText()); + } + finally + { + StorageCredentials credentials = new StorageCredentials(accountName, accountKey); + CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true); + CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); + CloudBlobContainer blobContainer = blobClient.GetContainerReference(containerName); + blobContainer.DeleteIfExists(); + + if (File.Exists(inputFileName)) + { + File.Delete(inputFileName); + } + } + } + private void DoUploadDownloadFileTask(ICloudBlob blob, int fileSize) { string inputFileName = Path.GetTempFileName(); @@ -954,9 +1017,6 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() } CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); - CloudBlockBlob blob2 = container.GetBlockBlobReference("blob2"); - CloudBlockBlob blob3 = container.GetBlockBlobReference("blob3"); - CloudBlockBlob blob4 = container.GetBlockBlobReference("blob4"); blob1.StreamWriteSizeInBytes = 5 * 1024 * 1024; blob1.UploadFromFile(inputFileName, null, options, null); @@ -967,11 +1027,22 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() { TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } - - blob2.StreamWriteSizeInBytes = 5 * 1024 * 1024; + + CloudBlockBlob blob = container.GetBlockBlobReference("unittestblob"); // This one is used in the unit test samples, hence the name "blob", not "blob2". + blob.StreamWriteSizeInBytes = 5 * 1024 * 1024; + + #region sample_BlobRequestOptions_ParallelOperationThreadCount + + BlobRequestOptions parallelThreadCountOptions = new BlobRequestOptions(); + + // Allow up to four simultaneous I/O operations. + parallelThreadCountOptions.ParallelOperationThreadCount = 4; + blob.UploadFromFile(inputFileName, accessCondition: null, options: parallelThreadCountOptions, operationContext: null); + + #endregion + options.ParallelOperationThreadCount = 4; - blob2.UploadFromFile(inputFileName, null, options, null); - blob2.DownloadToFile(outputFileName, FileMode.Create, null, options, null); + blob.DownloadToFile(outputFileName, FileMode.Create, null, options, null); using (FileStream inputFileStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read), outputFileStream = new FileStream(outputFileName, FileMode.Open, FileAccess.Read)) @@ -979,6 +1050,7 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } + CloudBlockBlob blob3 = container.GetBlockBlobReference("blob3"); blob3.StreamWriteSizeInBytes = 6 * 1024 * 1024 + 1; options.ParallelOperationThreadCount = 1; blob3.UploadFromFile(inputFileName, null, options, null); @@ -990,6 +1062,7 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } + CloudBlockBlob blob4 = container.GetBlockBlobReference("blob4"); blob4.StreamWriteSizeInBytes = 6 * 1024 * 1024 + 1; options.ParallelOperationThreadCount = 3; blob4.UploadFromFile(inputFileName, null, options, null); @@ -1376,10 +1449,97 @@ private void DoUploadFromByteArrayTest(ICloudBlob blob, int bufferSize, int buff [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudBlockBlobDownloadToByteArray() { + AssertSecondaryEndpoint(); + CloudBlockBlob blob = this.testContainer.GetBlockBlobReference("blob1"); this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 0, 0); this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 1 * 512, 0); this.DoDownloadToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, 0); + + byte[] bytes = new byte[] { 1, 2, 3, 4 }; + byte[] destinationArray = new byte[4]; + blob.UploadFromByteArray(bytes, 0, bytes.Length); + + // Wait until the data has been replicated to the secondary tenant. + try + { + TestHelper.SpinUpTo30SecondsIgnoringFailures(() => blob.FetchAttributes(null, new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryOnly }, null)); + } + catch (Exception) + { + Assert.Inconclusive("Data took more than 30 seconds to replicate to the secondary; aborting test."); + } + + #region sample_RequestOptions_RetryPolicy + + // Create a Linear Retry Policy. + // This retry policy will instruct the Storage Client to retry the request in a linear fashion. + // This particular retry policy will retry the request every 20 seconds, up to a maximum of 4 retries. + BlobRequestOptions optionsWithRetryPolicy = new BlobRequestOptions() { RetryPolicy = new RetryPolicies.LinearRetry(TimeSpan.FromSeconds(20), 4) }; + + int byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: optionsWithRetryPolicy); + + // This retry policy will never retry. + optionsWithRetryPolicy = new BlobRequestOptions() { RetryPolicy = new RetryPolicies.NoRetry() }; + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: optionsWithRetryPolicy); + + #endregion + + + #region sample_RequestOptions_LocationMode + // The PrimaryOnly LocationMode directs the request and all potential retries to go to the primary endpoint. + BlobRequestOptions locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.PrimaryOnly }; + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); + + // The PrimaryThenSecondary LocationMode directs the first request to go to the primary location. + // If this request fails with a retryable error, the retry will next hit the secondary location. + // Retries will switch back and forth between primary and secondary until the request succeeds, + // or retry attempts have been exhausted. + locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.PrimaryThenSecondary }; + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); + + // The SecondaryOnly LocationMode directs the request and all potential retries to go to the secondary endpoint. + locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryOnly }; + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); + + // The SecondaryThenPrimary LocationMode directs the first request to go to the secondary location. + // If this request fails with a retryable error, the retry will next hit the primary location. + // Retries will switch back and forth between secondary and primary until the request succeeds, or retry attempts + // have been exhausted. + locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryThenPrimary }; + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); + #endregion + + #region sample_RequestOptions_ServerTimeout_MaximumExecutionTime + + BlobRequestOptions timeoutRequestOptions = new BlobRequestOptions() + { + // Each REST operation will timeout after 5 seconds. + ServerTimeout = TimeSpan.FromSeconds(5), + + // Allot 30 seconds for this API call, including retries + MaximumExecutionTime = TimeSpan.FromSeconds(30) + }; + + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: timeoutRequestOptions); + + #endregion + + bool exceptionThrown = false; + try + { + #region sample_RequestOptions_RequireEncryption + // Instruct the client library to fail if data read from the service is not encrypted. + BlobRequestOptions requireEncryptionRequestOptions = new BlobRequestOptions() { RequireEncryption = true }; + + byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: requireEncryptionRequestOptions); + #endregion + } + catch (InvalidOperationException) + { + exceptionThrown = true; + } + Assert.IsTrue(exceptionThrown); } [TestMethod] diff --git a/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs index d5209e08c..bc8124472 100644 --- a/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs @@ -3088,5 +3088,53 @@ private void CloudAppendBlobUploadFromStream(CloudBlobContainer container, int s blob.Delete(); } + + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TestAppendBlobAbsorbConditionalErrorsOnRetry() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + byte[] inputData = new byte[10*1024*1024]; + new Random().NextBytes(inputData); + + #region sample_BlobRequestOptions_AbsorbConditionalErrorsOnRetry + using (MemoryStream inputDataStream = new MemoryStream(inputData)) + { + BlobRequestOptions conditionalErrorRequestOptions = new BlobRequestOptions() { AbsorbConditionalErrorsOnRetry = true }; + + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendBlob"); + appendBlob.UploadFromStream(inputDataStream, accessCondition: null, options: conditionalErrorRequestOptions); + } + #endregion + + using (MemoryStream dataStream = new MemoryStream()) + using (MemoryStream expectedDataStream = new MemoryStream(inputData)) + { + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendBlob"); + appendBlob.DownloadToStream(dataStream); + + dataStream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(dataStream.Length, expectedDataStream.Length); + TestHelper.AssertStreamsAreEqualAtIndex( + dataStream, + expectedDataStream, + 0, + 0, + (int)dataStream.Length); + } + } + finally + { + container.DeleteIfExists(); + } + } } } \ No newline at end of file diff --git a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs index 407e72fcf..b662a8d7b 100644 --- a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs +++ b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs @@ -21,6 +21,7 @@ using System.IO; using System.Net; using System.Security.Cryptography; +using System.Text; using System.Threading; namespace Microsoft.WindowsAzure.Storage.Blob @@ -1174,5 +1175,71 @@ public void UseTransactionalMD5GetTestAPM() container.DeleteIfExists(); } } + + [TestMethod] + [Description("Test UseTransactionalMD5 flag with DownloadRangeToStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void BasicMD5FlagFunctionality() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + string testBlobString = "testBlobString"; + byte[] blobContent = Encoding.UTF8.GetBytes(testBlobString); + CloudBlockBlob testBlob = container.GetBlockBlobReference(testBlobString); + + #region sample_BlobRequestOptions_StoreBlobContentMD5 + MemoryStream sourceStream; + + // Instruct the Storage Client to calculate and store the MD5 of the blob on upload. + BlobRequestOptions optionsWithStoreBlobContentMD5 = new BlobRequestOptions() { StoreBlobContentMD5 = true }; + + using (sourceStream = new MemoryStream(blobContent)) + { + testBlob.UploadFromStream(sourceStream, accessCondition: null, options: optionsWithStoreBlobContentMD5); + } + + #endregion + + Assert.AreEqual(testBlobString, testBlob.DownloadText()); + + #region sample_BlobRequestOptions_UseTransactionalMD5 + MemoryStream targetStream; + + // Instruct the Storage Client to request and validate the Content-MD5 for individual REST operations. + BlobRequestOptions optionsWithUseTransactionalMD5 = new BlobRequestOptions() { UseTransactionalMD5 = true }; + + using (targetStream = new MemoryStream()) + { + testBlob.DownloadToStream(targetStream, accessCondition: null, options: optionsWithUseTransactionalMD5); + } + + #endregion + + Assert.AreEqual(testBlobString, Encoding.UTF8.GetString(targetStream.GetBuffer(), 0, (int)blobContent.Length)); + + #region sample_BlobRequestOptions_DisableContentMD5Validation + + // Instruct the Storage Client to skip validating the MD5 hash of the content, + BlobRequestOptions optionsWithDisableContentMD5Validation = new BlobRequestOptions() { DisableContentMD5Validation = true }; + + using (targetStream = new MemoryStream()) + { + testBlob.DownloadToStream(targetStream, accessCondition: null, options: optionsWithDisableContentMD5Validation); + } + + #endregion + + Assert.AreEqual(testBlobString, Encoding.UTF8.GetString(targetStream.GetBuffer(), 0, (int)blobContent.Length)); + } + finally + { + container.DeleteIfExists(); + } + } } } diff --git a/Test/Common/Core/CloudStorageAccountTests.cs b/Test/Common/Core/CloudStorageAccountTests.cs index d73de41d3..e32a1eb21 100644 --- a/Test/Common/Core/CloudStorageAccountTests.cs +++ b/Test/Common/Core/CloudStorageAccountTests.cs @@ -618,23 +618,74 @@ public void CloudStorageAccountClientMethods() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudStorageAccountClientUriVerify() { - StorageCredentials cred = new StorageCredentials(TestBase.TargetTenantConfig.AccountName, TestBase.TargetTenantConfig.AccountKey); - CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(cred, true); - + string myAccountName = TestBase.TargetTenantConfig.AccountName; + string myAccountKey = TestBase.TargetTenantConfig.AccountKey; + + #region sample_CloudStorageAccount_Constructor + + // Create a CloudStorageAccount object using account name and key. + // The account name should be just the name of a Storage Account, not a URI, and + // not including the suffix. The key should be a base-64 encoded string that you + // can acquire from the portal, or from the management plane. + // This will have full permissions to all operations on the account. + StorageCredentials storageCredentials = new StorageCredentials(myAccountName, myAccountKey); + CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(storageCredentials, useHttps: true); + + // Create a CloudBlobClient object from the storage account. + // This object is the root object for all operations on the + // blob service for this particular account. CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient(); + + // Get a reference to a CloudBlobContainer object in this account. + // This object can be used to create the container on the service, + // list blobs, delete the container, etc. This operation does not make a + // call to the Azure Storage service. It neither creates the container + // on the service, nor validates its existence. CloudBlobContainer container = blobClient.GetContainerReference("container1"); - Assert.AreEqual(cloudStorageAccount.BlobEndpoint.ToString() + "container1", container.Uri.ToString()); + + // Create a CloudQueueClient object from the storage account. + // This object is the root object for all operations on the + // queue service for this particular account. CloudQueueClient queueClient = cloudStorageAccount.CreateCloudQueueClient(); + + // Get a reference to a CloudQueue object in this account. + // This object can be used to create the queue on the service, + // delete the queue, add messages, etc. This operation does not + // make a call to the Azure Storage service. It neither creates + // the queue on the service, nor validates its existence. CloudQueue queue = queueClient.GetQueueReference("queue1"); - Assert.AreEqual(cloudStorageAccount.QueueEndpoint.ToString() + "queue1", queue.Uri.ToString()); + + // Create a CloudTableClient object from the storage account. + // This object is the root object for all operations on the + // table service for this particular account. CloudTableClient tableClient = cloudStorageAccount.CreateCloudTableClient(); + + // Get a reference to a CloudTable object in this account. + // This object can be used to create the table on the service, + // delete the table, insert entities, etc. This operation does + // not make a call to the Azure Storage service. It neither + // creates the table on the service, nor validates its existence. CloudTable table = tableClient.GetTableReference("table1"); - Assert.AreEqual(cloudStorageAccount.TableEndpoint.ToString() + "table1", table.Uri.ToString()); + + // Create a CloudFileClient object from the storage account. + // This object is the root object for all operations on the + // file service for this particular account. CloudFileClient fileClient = cloudStorageAccount.CreateCloudFileClient(); + + // Get a reference to a CloudFileShare object in this account. + // This object can be used to create the share on the service, + // delete the share, list files and directories, etc. This operation + // does not make a call to the Azure Storage service. It neither + // creates the share on the service, nor validates its existence. CloudFileShare share = fileClient.GetShareReference("share1"); + #endregion + + Assert.AreEqual(cloudStorageAccount.BlobEndpoint.ToString() + "container1", container.Uri.ToString()); + Assert.AreEqual(cloudStorageAccount.QueueEndpoint.ToString() + "queue1", queue.Uri.ToString()); + Assert.AreEqual(cloudStorageAccount.TableEndpoint.ToString() + "table1", table.Uri.ToString()); Assert.AreEqual(cloudStorageAccount.FileEndpoint.ToString() + "share1", share.Uri.ToString()); } From c8bb607b3f9944c9837eba27ff82072483476f66 Mon Sep 17 00:00:00 2001 From: erezvani1529 Date: Tue, 23 May 2017 14:22:58 -0700 Subject: [PATCH 22/37] Revert "First pass at adding unit tests that double as API documentation. (#103)" (#104) This reverts commit 5a915d9d66fb81e9baeec06f1b5e0c4771cbf13d --- Lib/Common/Blob/BlobRequestOptions.cs | 102 ++-------- Lib/Common/RetryPolicies/LocationMode.cs | 16 -- .../Blob/BlobClientEncryptionTests.cs | 31 ++-- .../Blob/BlobUploadDownloadTest.cs | 174 +----------------- .../Blob/CloudAppendBlobTest.cs | 48 ----- Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs | 67 ------- Test/Common/Core/CloudStorageAccountTests.cs | 63 +------ 7 files changed, 39 insertions(+), 462 deletions(-) diff --git a/Lib/Common/Blob/BlobRequestOptions.cs b/Lib/Common/Blob/BlobRequestOptions.cs index 4837eb669..99a85d254 100644 --- a/Lib/Common/Blob/BlobRequestOptions.cs +++ b/Lib/Common/Blob/BlobRequestOptions.cs @@ -243,14 +243,6 @@ internal void AssertPolicyIfRequired() /// Gets or sets the retry policy for the request. /// /// An object of type . - /// Retry policies instruct the Storage Client to retry failed requests. - /// By default, only some failures are retried. For example, connection failures and - /// throttling failures can be retried. Resource not found (404) or authentication - /// failures are not retried, because these are not likely to succeed on retry. - /// If not set, the Storage Client uses an exponential backoff retry policy, where the wait time gets - /// exponentially longer between requests, up to a total of around 30 seconds. - /// The default retry policy is recommended for most scenarios. - /// public IRetryPolicy RetryPolicy { get; set; } #if !(WINDOWS_RT || NETCORE) @@ -264,13 +256,6 @@ internal void AssertPolicyIfRequired() /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. /// /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. - /// - /// RequireEncryption here refers to Client-Side Encryption. - /// If this value is set to true, all calls will fail if the data - /// is not encrypted/decrypted with an encryption policy. If this value - /// is false (the default), any data being downloaded that is not encrypted - /// will be returned as-is. - /// public bool? RequireEncryption { get; set; } /// @@ -285,23 +270,9 @@ internal void AssertPolicyIfRequired() /// for the request. /// /// - /// This option is used only by the object in the UploadFrom* methods, - /// the AppendFrom* methods, and the BlobWriteStream class. By default, it is set to false. - /// Set this option to true only for single-writer scenarios. + /// This option is used only by the object in the UploadFrom* methods and + /// the BlobWriteStream methods. By default, it is set to false. Set this option to true only for single writer scenarios. /// Setting this option to true in a multi-writer scenario may lead to corrupted blob data. - /// - /// When calling "UploadFrom*" on an append blob, the Storage Client breaks the input data - /// up into a number of data blocks, and uploads each data block with an "append block" operation. - /// Normally, an "IfAppendPositionEqual" access condition is added to the append block operation, so that the - /// upload operation will fail if some other process somewhere has appended data in the middle of this data stream. - /// However, this can result in a false failure in a very specific case. If an append operation fails with a timeout, - /// there is a chance that the operation succeeded on the service, but the "success" response did not make it back to the client. - /// In this case, the client will retry, and then get an "append position not met" failure. - /// - /// Setting this value to true results in the upload operation continuing when it sees an "append position not met" - /// failure on retry - accounting for the above possibility. However, this loses protection in the multi-writer - /// scenario - if multiple threads are uploading to the blob at once, and this value is set to true, some data - /// may be lost, because the client thinks the data was uploaded, when in fact it was the other process' data. /// public bool? AbsorbConditionalErrorsOnRetry { get; set; } @@ -309,35 +280,18 @@ internal void AssertPolicyIfRequired() /// Gets or sets the location mode of the request. /// /// A enumeration value indicating the location mode of the request. - /// The LocationMode specifies in which locations the Storage Client - /// will attempt to make the request. This is only valid for RA-GRS accounts - accounts - /// where data can be read from either the primary or the secondary endpoint. - /// public LocationMode? LocationMode { get; set; } /// - /// Gets or sets the server timeout interval for a single HTTP request. + /// Gets or sets the server timeout interval for the request. /// - /// A containing the server timeout interval for each HTTP request. - /// - /// The server timeout is the timeout sent to the Azure Storage service - /// for each REST request made. If the API called makes multiple REST calls - /// (UploadFromStream, for example, or if the request retries), this timeout - /// will be applied separately to each request. This value is not - /// tracked or validated on the client, it is only passed to the Storage service. - /// + /// A containing the server timeout interval for the request. public TimeSpan? ServerTimeout { get; set; } /// /// Gets or sets the maximum execution time across all potential retries for the request. /// /// A representing the maximum execution time for retries for the request. - /// - /// The maximum execution time is the time allotted for a single API call. - /// If the total amount of time spent in the API - across all REST requests, - /// retries, etc - exceeds this value, the client will timeout. This value - /// is only tracked on the client, it is not sent to the service. - /// public TimeSpan? MaximumExecutionTime { get @@ -354,19 +308,13 @@ public TimeSpan? MaximumExecutionTime this.maximumExecutionTime = value; } - } + } /// - /// Gets or sets the number of blocks that may be simultaneously uploaded. + /// Gets or sets the number of blocks that may be simultaneously uploaded when uploading a blob that is greater than + /// the value specified by the property in size. /// /// An integer value indicating the number of parallel blob upload operations that may proceed. - /// - /// When using the UploadFrom* methods on a blob, the blob will be broken up into blocks. Setting this - /// value limits the number of outstanding I/O "put block" requests that the library will have in-flight - /// at a given time. Default is 1 (no parallelism). Setting this value higher may result in - /// faster blob uploads, depending on the network between the client and the Azure Storage service. - /// If blobs are small (less than 256 MB), keeping this value equal to 1 is advised. - /// public int? ParallelOperationThreadCount { get @@ -412,17 +360,7 @@ public long? SingleBlobUploadThresholdInBytes /// /// Gets or sets a value to calculate and send/validate content MD5 for transactions. /// - /// Use true to calculate and send/validate content MD5 for transactions; otherwise, false. Default is false. - /// - /// The UseTransactionalMD5 option instructs the Storage Client to calculate and validate - /// the MD5 hash of individual Storage REST operations. For a given REST operation, - /// if this value is set, both the Storage Client and the Storage service will calculate - /// the MD5 hash of the transferred data, and will fail if the values do not match. - /// This value is not persisted on the service or the client. - /// This option applies to both upload and download operations. - /// Note that HTTPS does a similar check during transit. If you are using HTTPS, - /// we recommend this feature be off. - /// + /// Use true to calculate and send/validate content MD5 for transactions; otherwise, false. #if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif @@ -450,18 +388,8 @@ public bool? UseTransactionalMD5 /// /// Gets or sets a value to indicate that an MD5 hash will be calculated and stored when uploading a blob. /// - /// Use true to calculate and store an MD5 hash when uploading a blob; otherwise, false. Defaults to false. - /// This property is not supported for the Append* APIs. - /// The StoreBlobContentMD5 request option instructs the Storage Client to calculate the MD5 hash - /// of the blob content during an upload operation. This value is then stored on the - /// blob object as the Content-MD5 header. This option applies only to upload operations. - /// This is useful for validating the integrity of the blob upon later download, and - /// compatible with the Content-MD5 header as defined in the HTTP spec. If using - /// the Storage Client for later download, if the Content-MD5 header is present, - /// the MD5 hash of the content will be validated, unless "DisableContentMD5Validation" is set. - /// Note that this value is not validated on the Azure Storage service on either upload or download of data; - /// it is merely stored and returned. - /// + /// Use true to calculate and store an MD5 hash when uploading a blob; otherwise, false. + /// This property is not supported for the Append* APIs. #if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif @@ -489,14 +417,8 @@ public bool? StoreBlobContentMD5 /// /// Gets or sets a value to indicate that MD5 validation will be disabled when downloading blobs. /// - /// Use true to disable MD5 validation; false to enable MD5 validation. Default is false. - /// - /// When downloading a blob, if the value already exists on the blob, the Storage service - /// will include the MD5 hash of the entire blob as a header. This option controls - /// whether or not the Storage Client will validate that MD5 hash on download. - /// See for more details. - /// -#if WINDOWS_PHONE && WINDOWS_DESKTOP + /// Use true to disable MD5 validation; false to enable MD5 validation. +#if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #endif public bool? DisableContentMD5Validation diff --git a/Lib/Common/RetryPolicies/LocationMode.cs b/Lib/Common/RetryPolicies/LocationMode.cs index b2894a407..98464d4cb 100644 --- a/Lib/Common/RetryPolicies/LocationMode.cs +++ b/Lib/Common/RetryPolicies/LocationMode.cs @@ -25,37 +25,21 @@ public enum LocationMode /// /// Requests are always sent to the primary location. /// - /// - /// If this value is used for requests that only work against a secondary location - /// (GetServiceStats, for example), the request will fail in the client. - /// PrimaryOnly, /// /// Requests are always sent to the primary location first. If a request fails, it is sent to the secondary location. /// - /// - /// If this value is used for requests that are only valid against one location, the client will - /// only target the allowed location. - /// PrimaryThenSecondary, /// /// Requests are always sent to the secondary location. /// - /// - /// If this value is used for requests that only work against a primary location - /// (create, modify, and delete APIs), the request will fail in the client. - /// SecondaryOnly, /// /// Requests are always sent to the secondary location first. If a request fails, it is sent to the primary location. /// - /// - /// If this value is used for requests that are only valid against one location, the client will - /// only target the allowed location. - /// SecondaryThenPrimary, } } diff --git a/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs b/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs index 19cbbd2be..391e44f46 100644 --- a/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Blob/BlobClientEncryptionTests.cs @@ -103,31 +103,28 @@ private void DoCloudBlobEncryption(BlobType type, bool partial) blob = container.GetAppendBlobReference("appendblob"); } - #region sample_RequestOptions_EncryptionPolicy - // Create the Key to be used for wrapping. - // This code creates a random encryption key. - Microsoft.Azure.KeyVault.SymmetricKey aesKey = new SymmetricKey(kid: "symencryptionkey"); + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); // Create the encryption policy to be used for upload. - BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(key: aesKey, keyResolver: null); + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); // Set the encryption policy on the request options. BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; - // Encrypt and upload the data to the blob. - MemoryStream stream = new MemoryStream(buffer); - blob.UploadFromStream(stream, length: size, accessCondition: null, options: uploadOptions); - - #endregion - - // Ensure that the user stream is open. - Assert.IsTrue(stream.CanSeek); - stream.Dispose(); + MemoryStream stream; + // Upload the encrypted contents to the blob. + using (stream = new MemoryStream(buffer)) + { + blob.UploadFromStream(stream, size, null, uploadOptions, null); - // Create the resolver to be used for unwrapping. - DictionaryKeyResolver resolver = new DictionaryKeyResolver(); - resolver.Add(aesKey); + // Ensure that the user stream is open. + Assert.IsTrue(stream.CanSeek); + } // Download the encrypted blob. // Create the decryption policy to be used for download. There is no need to specify the diff --git a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs index 322475577..936a03a2a 100644 --- a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs @@ -17,7 +17,6 @@ namespace Microsoft.WindowsAzure.Storage.Blob { - using Auth; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -731,68 +730,6 @@ public void CloudAppendBlobUploadDownloadFileTask() File.Delete("garbage.file"); } - [TestMethod] - [Description("Upload from file to a block blob")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudBlockBlobUploadBasicFunctionality() - { - string accountName = this.testContainer.ServiceClient.Credentials.AccountName; - string accountKey = this.testContainer.ServiceClient.Credentials.ExportBase64EncodedKey(); - string containerName = this.testContainer.Name + "copy"; - string blobName = "myBlob"; - string inputFileName = Path.GetTempFileName(); - File.WriteAllText(inputFileName, @"Sample file text here."); - - try - { - #region sample_UploadBlob_EndToEnd - // This is one common way of creating a CloudStorageAccount object. You can get - // your Storage Account Name and Key from the Azure Portal. - StorageCredentials credentials = new StorageCredentials(accountName, accountKey); - CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, useHttps: true); - - // Another common way to create a CloudStorageAccount object is to use a connection string: - // CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); - - CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); - - // This call creates a local CloudBlobContainer object, but does not make a network call - // to the Azure Storage Service. The container on the service that this object represents may - // or may not exist at this point. If it does exist, the properties will not yet have been - // popluated on this object. - CloudBlobContainer blobContainer = blobClient.GetContainerReference(containerName); - - // This makes an actual service call to the Azure Storage service. Unless this call fails, - // the container will have been created. - blobContainer.Create(); - - // This also does not make a service call, it only creates a local object. - CloudBlockBlob blob = blobContainer.GetBlockBlobReference(blobName); - - // This transfers data in the file to the blob on the service. - blob.UploadFromFile(inputFileName); - #endregion - - Assert.AreEqual(File.ReadAllText(inputFileName), blob.DownloadText()); - } - finally - { - StorageCredentials credentials = new StorageCredentials(accountName, accountKey); - CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true); - CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); - CloudBlobContainer blobContainer = blobClient.GetContainerReference(containerName); - blobContainer.DeleteIfExists(); - - if (File.Exists(inputFileName)) - { - File.Delete(inputFileName); - } - } - } - private void DoUploadDownloadFileTask(ICloudBlob blob, int fileSize) { string inputFileName = Path.GetTempFileName(); @@ -1017,6 +954,9 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() } CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blob2 = container.GetBlockBlobReference("blob2"); + CloudBlockBlob blob3 = container.GetBlockBlobReference("blob3"); + CloudBlockBlob blob4 = container.GetBlockBlobReference("blob4"); blob1.StreamWriteSizeInBytes = 5 * 1024 * 1024; blob1.UploadFromFile(inputFileName, null, options, null); @@ -1027,22 +967,11 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() { TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } - - CloudBlockBlob blob = container.GetBlockBlobReference("unittestblob"); // This one is used in the unit test samples, hence the name "blob", not "blob2". - blob.StreamWriteSizeInBytes = 5 * 1024 * 1024; - - #region sample_BlobRequestOptions_ParallelOperationThreadCount - - BlobRequestOptions parallelThreadCountOptions = new BlobRequestOptions(); - - // Allow up to four simultaneous I/O operations. - parallelThreadCountOptions.ParallelOperationThreadCount = 4; - blob.UploadFromFile(inputFileName, accessCondition: null, options: parallelThreadCountOptions, operationContext: null); - - #endregion - + + blob2.StreamWriteSizeInBytes = 5 * 1024 * 1024; options.ParallelOperationThreadCount = 4; - blob.DownloadToFile(outputFileName, FileMode.Create, null, options, null); + blob2.UploadFromFile(inputFileName, null, options, null); + blob2.DownloadToFile(outputFileName, FileMode.Create, null, options, null); using (FileStream inputFileStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read), outputFileStream = new FileStream(outputFileName, FileMode.Open, FileAccess.Read)) @@ -1050,7 +979,6 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } - CloudBlockBlob blob3 = container.GetBlockBlobReference("blob3"); blob3.StreamWriteSizeInBytes = 6 * 1024 * 1024 + 1; options.ParallelOperationThreadCount = 1; blob3.UploadFromFile(inputFileName, null, options, null); @@ -1062,7 +990,6 @@ public void CloudBlockBlobTestParallelUploadFromFileStream() TestHelper.AssertStreamsAreEqualFast(inputFileStream, outputFileStream); } - CloudBlockBlob blob4 = container.GetBlockBlobReference("blob4"); blob4.StreamWriteSizeInBytes = 6 * 1024 * 1024 + 1; options.ParallelOperationThreadCount = 3; blob4.UploadFromFile(inputFileName, null, options, null); @@ -1449,97 +1376,10 @@ private void DoUploadFromByteArrayTest(ICloudBlob blob, int bufferSize, int buff [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudBlockBlobDownloadToByteArray() { - AssertSecondaryEndpoint(); - CloudBlockBlob blob = this.testContainer.GetBlockBlobReference("blob1"); this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 0, 0); this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 1 * 512, 0); this.DoDownloadToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, 0); - - byte[] bytes = new byte[] { 1, 2, 3, 4 }; - byte[] destinationArray = new byte[4]; - blob.UploadFromByteArray(bytes, 0, bytes.Length); - - // Wait until the data has been replicated to the secondary tenant. - try - { - TestHelper.SpinUpTo30SecondsIgnoringFailures(() => blob.FetchAttributes(null, new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryOnly }, null)); - } - catch (Exception) - { - Assert.Inconclusive("Data took more than 30 seconds to replicate to the secondary; aborting test."); - } - - #region sample_RequestOptions_RetryPolicy - - // Create a Linear Retry Policy. - // This retry policy will instruct the Storage Client to retry the request in a linear fashion. - // This particular retry policy will retry the request every 20 seconds, up to a maximum of 4 retries. - BlobRequestOptions optionsWithRetryPolicy = new BlobRequestOptions() { RetryPolicy = new RetryPolicies.LinearRetry(TimeSpan.FromSeconds(20), 4) }; - - int byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: optionsWithRetryPolicy); - - // This retry policy will never retry. - optionsWithRetryPolicy = new BlobRequestOptions() { RetryPolicy = new RetryPolicies.NoRetry() }; - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: optionsWithRetryPolicy); - - #endregion - - - #region sample_RequestOptions_LocationMode - // The PrimaryOnly LocationMode directs the request and all potential retries to go to the primary endpoint. - BlobRequestOptions locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.PrimaryOnly }; - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); - - // The PrimaryThenSecondary LocationMode directs the first request to go to the primary location. - // If this request fails with a retryable error, the retry will next hit the secondary location. - // Retries will switch back and forth between primary and secondary until the request succeeds, - // or retry attempts have been exhausted. - locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.PrimaryThenSecondary }; - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); - - // The SecondaryOnly LocationMode directs the request and all potential retries to go to the secondary endpoint. - locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryOnly }; - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); - - // The SecondaryThenPrimary LocationMode directs the first request to go to the secondary location. - // If this request fails with a retryable error, the retry will next hit the primary location. - // Retries will switch back and forth between secondary and primary until the request succeeds, or retry attempts - // have been exhausted. - locationModeRequestOptions = new BlobRequestOptions() { LocationMode = RetryPolicies.LocationMode.SecondaryThenPrimary }; - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: locationModeRequestOptions); - #endregion - - #region sample_RequestOptions_ServerTimeout_MaximumExecutionTime - - BlobRequestOptions timeoutRequestOptions = new BlobRequestOptions() - { - // Each REST operation will timeout after 5 seconds. - ServerTimeout = TimeSpan.FromSeconds(5), - - // Allot 30 seconds for this API call, including retries - MaximumExecutionTime = TimeSpan.FromSeconds(30) - }; - - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: timeoutRequestOptions); - - #endregion - - bool exceptionThrown = false; - try - { - #region sample_RequestOptions_RequireEncryption - // Instruct the client library to fail if data read from the service is not encrypted. - BlobRequestOptions requireEncryptionRequestOptions = new BlobRequestOptions() { RequireEncryption = true }; - - byteCount = blob.DownloadToByteArray(destinationArray, index: 0, accessCondition: null, options: requireEncryptionRequestOptions); - #endregion - } - catch (InvalidOperationException) - { - exceptionThrown = true; - } - Assert.IsTrue(exceptionThrown); } [TestMethod] diff --git a/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs index bc8124472..d5209e08c 100644 --- a/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs @@ -3088,53 +3088,5 @@ private void CloudAppendBlobUploadFromStream(CloudBlobContainer container, int s blob.Delete(); } - - - [TestMethod] - [Description("Single put blob and get blob")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void TestAppendBlobAbsorbConditionalErrorsOnRetry() - { - CloudBlobContainer container = GetRandomContainerReference(); - container.Create(); - try - { - byte[] inputData = new byte[10*1024*1024]; - new Random().NextBytes(inputData); - - #region sample_BlobRequestOptions_AbsorbConditionalErrorsOnRetry - using (MemoryStream inputDataStream = new MemoryStream(inputData)) - { - BlobRequestOptions conditionalErrorRequestOptions = new BlobRequestOptions() { AbsorbConditionalErrorsOnRetry = true }; - - CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendBlob"); - appendBlob.UploadFromStream(inputDataStream, accessCondition: null, options: conditionalErrorRequestOptions); - } - #endregion - - using (MemoryStream dataStream = new MemoryStream()) - using (MemoryStream expectedDataStream = new MemoryStream(inputData)) - { - CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendBlob"); - appendBlob.DownloadToStream(dataStream); - - dataStream.Seek(0, SeekOrigin.Begin); - Assert.AreEqual(dataStream.Length, expectedDataStream.Length); - TestHelper.AssertStreamsAreEqualAtIndex( - dataStream, - expectedDataStream, - 0, - 0, - (int)dataStream.Length); - } - } - finally - { - container.DeleteIfExists(); - } - } } } \ No newline at end of file diff --git a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs index b662a8d7b..407e72fcf 100644 --- a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs +++ b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs @@ -21,7 +21,6 @@ using System.IO; using System.Net; using System.Security.Cryptography; -using System.Text; using System.Threading; namespace Microsoft.WindowsAzure.Storage.Blob @@ -1175,71 +1174,5 @@ public void UseTransactionalMD5GetTestAPM() container.DeleteIfExists(); } } - - [TestMethod] - [Description("Test UseTransactionalMD5 flag with DownloadRangeToStream")] - [TestCategory(ComponentCategory.Blob)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void BasicMD5FlagFunctionality() - { - CloudBlobContainer container = GetRandomContainerReference(); - try - { - container.Create(); - string testBlobString = "testBlobString"; - byte[] blobContent = Encoding.UTF8.GetBytes(testBlobString); - CloudBlockBlob testBlob = container.GetBlockBlobReference(testBlobString); - - #region sample_BlobRequestOptions_StoreBlobContentMD5 - MemoryStream sourceStream; - - // Instruct the Storage Client to calculate and store the MD5 of the blob on upload. - BlobRequestOptions optionsWithStoreBlobContentMD5 = new BlobRequestOptions() { StoreBlobContentMD5 = true }; - - using (sourceStream = new MemoryStream(blobContent)) - { - testBlob.UploadFromStream(sourceStream, accessCondition: null, options: optionsWithStoreBlobContentMD5); - } - - #endregion - - Assert.AreEqual(testBlobString, testBlob.DownloadText()); - - #region sample_BlobRequestOptions_UseTransactionalMD5 - MemoryStream targetStream; - - // Instruct the Storage Client to request and validate the Content-MD5 for individual REST operations. - BlobRequestOptions optionsWithUseTransactionalMD5 = new BlobRequestOptions() { UseTransactionalMD5 = true }; - - using (targetStream = new MemoryStream()) - { - testBlob.DownloadToStream(targetStream, accessCondition: null, options: optionsWithUseTransactionalMD5); - } - - #endregion - - Assert.AreEqual(testBlobString, Encoding.UTF8.GetString(targetStream.GetBuffer(), 0, (int)blobContent.Length)); - - #region sample_BlobRequestOptions_DisableContentMD5Validation - - // Instruct the Storage Client to skip validating the MD5 hash of the content, - BlobRequestOptions optionsWithDisableContentMD5Validation = new BlobRequestOptions() { DisableContentMD5Validation = true }; - - using (targetStream = new MemoryStream()) - { - testBlob.DownloadToStream(targetStream, accessCondition: null, options: optionsWithDisableContentMD5Validation); - } - - #endregion - - Assert.AreEqual(testBlobString, Encoding.UTF8.GetString(targetStream.GetBuffer(), 0, (int)blobContent.Length)); - } - finally - { - container.DeleteIfExists(); - } - } } } diff --git a/Test/Common/Core/CloudStorageAccountTests.cs b/Test/Common/Core/CloudStorageAccountTests.cs index e32a1eb21..d73de41d3 100644 --- a/Test/Common/Core/CloudStorageAccountTests.cs +++ b/Test/Common/Core/CloudStorageAccountTests.cs @@ -618,74 +618,23 @@ public void CloudStorageAccountClientMethods() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudStorageAccountClientUriVerify() { - string myAccountName = TestBase.TargetTenantConfig.AccountName; - string myAccountKey = TestBase.TargetTenantConfig.AccountKey; - - #region sample_CloudStorageAccount_Constructor - - // Create a CloudStorageAccount object using account name and key. - // The account name should be just the name of a Storage Account, not a URI, and - // not including the suffix. The key should be a base-64 encoded string that you - // can acquire from the portal, or from the management plane. - // This will have full permissions to all operations on the account. - StorageCredentials storageCredentials = new StorageCredentials(myAccountName, myAccountKey); - CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(storageCredentials, useHttps: true); - - // Create a CloudBlobClient object from the storage account. - // This object is the root object for all operations on the - // blob service for this particular account. - CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient(); + StorageCredentials cred = new StorageCredentials(TestBase.TargetTenantConfig.AccountName, TestBase.TargetTenantConfig.AccountKey); + CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(cred, true); - // Get a reference to a CloudBlobContainer object in this account. - // This object can be used to create the container on the service, - // list blobs, delete the container, etc. This operation does not make a - // call to the Azure Storage service. It neither creates the container - // on the service, nor validates its existence. + CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient(); CloudBlobContainer container = blobClient.GetContainerReference("container1"); + Assert.AreEqual(cloudStorageAccount.BlobEndpoint.ToString() + "container1", container.Uri.ToString()); - - // Create a CloudQueueClient object from the storage account. - // This object is the root object for all operations on the - // queue service for this particular account. CloudQueueClient queueClient = cloudStorageAccount.CreateCloudQueueClient(); - - // Get a reference to a CloudQueue object in this account. - // This object can be used to create the queue on the service, - // delete the queue, add messages, etc. This operation does not - // make a call to the Azure Storage service. It neither creates - // the queue on the service, nor validates its existence. CloudQueue queue = queueClient.GetQueueReference("queue1"); + Assert.AreEqual(cloudStorageAccount.QueueEndpoint.ToString() + "queue1", queue.Uri.ToString()); - - // Create a CloudTableClient object from the storage account. - // This object is the root object for all operations on the - // table service for this particular account. CloudTableClient tableClient = cloudStorageAccount.CreateCloudTableClient(); - - // Get a reference to a CloudTable object in this account. - // This object can be used to create the table on the service, - // delete the table, insert entities, etc. This operation does - // not make a call to the Azure Storage service. It neither - // creates the table on the service, nor validates its existence. CloudTable table = tableClient.GetTableReference("table1"); + Assert.AreEqual(cloudStorageAccount.TableEndpoint.ToString() + "table1", table.Uri.ToString()); - - // Create a CloudFileClient object from the storage account. - // This object is the root object for all operations on the - // file service for this particular account. CloudFileClient fileClient = cloudStorageAccount.CreateCloudFileClient(); - - // Get a reference to a CloudFileShare object in this account. - // This object can be used to create the share on the service, - // delete the share, list files and directories, etc. This operation - // does not make a call to the Azure Storage service. It neither - // creates the share on the service, nor validates its existence. CloudFileShare share = fileClient.GetShareReference("share1"); - #endregion - - Assert.AreEqual(cloudStorageAccount.BlobEndpoint.ToString() + "container1", container.Uri.ToString()); - Assert.AreEqual(cloudStorageAccount.QueueEndpoint.ToString() + "queue1", queue.Uri.ToString()); - Assert.AreEqual(cloudStorageAccount.TableEndpoint.ToString() + "table1", table.Uri.ToString()); Assert.AreEqual(cloudStorageAccount.FileEndpoint.ToString() + "share1", share.Uri.ToString()); } From d3c879fe04fb3a7cb06aa2e35d1cf1da65e65002 Mon Sep 17 00:00:00 2001 From: jofriedm-msft Date: Mon, 3 Jul 2017 21:25:34 -0700 Subject: [PATCH 23/37] Page Blob Tier Support for Create and Copy APIs (#108) * Page Blob Tier Support for Create and Copy APIs * renaming premiumBlobTier to premiumPageBlobTier --- .../Blob/CloudAppendBlob.cs | 2 +- Lib/ClassLibraryCommon/Blob/CloudBlob.cs | 56 +- Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs | 2 +- Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs | 487 +++++++++++++++--- .../Blob/Protocol/BlobHttpResponseParsers.cs | 18 +- .../Protocol/BlobHttpWebRequestFactory.cs | 39 +- Lib/Common/Blob/BlobProperties.cs | 14 +- Lib/Common/Blob/PageBlobTier.cs | 23 +- .../BlobHttpResponseParsers.Common.cs | 22 +- Lib/Common/Blob/Protocol/ListBlobsResponse.cs | 13 +- Lib/Common/Core/SR.cs | 1 + Lib/Common/Shared/Protocol/Constants.cs | 5 + Lib/WindowsRuntime/Blob/CloudAppendBlob.cs | 2 +- Lib/WindowsRuntime/Blob/CloudBlob.cs | 34 +- Lib/WindowsRuntime/Blob/CloudBlockBlob.cs | 2 +- Lib/WindowsRuntime/Blob/CloudPageBlob.cs | 210 ++++++-- .../Protocol/BlobHttpRequestMessageFactory.cs | 39 +- .../Blob/Protocol/BlobHttpResponseParsers.cs | 18 +- .../Blob/CloudPageBlobTest.cs | 379 +++++++++++++- Test/Common/Blob/BlobTestBase.Common.cs | 15 + Test/Common/TestBase.Common.cs | 12 + .../TestConfigProcess/TestConfigurations.cs | 2 + Test/Common/TestConfigurationsTemplate.xml | 1 + Test/WindowsRuntime/Blob/CloudPageBlobTest.cs | 168 +++++- changelog.txt | 2 +- 25 files changed, 1392 insertions(+), 174 deletions(-) diff --git a/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs index aa112fa10..4b5ac83ce 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs @@ -2803,7 +2803,7 @@ private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRe RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, accessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, null /* premiumPageBlobTier */, accessCondition, useVersionHeader, ctx); putCmd.SetHeaders = (r, ctx) => BlobHttpWebRequestFactory.AddMetadata(r, this.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs index 5a06f1227..d4a1ada03 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs @@ -2610,12 +2610,32 @@ public virtual Task BreakLeaseAsync(TimeSpan? breakPeriod, AccessCondi /// [DoesServiceRequest] public virtual string StartCopy(Uri source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(source, null /* premiumPageBlobTier */, sourceAccessCondition, destAccessCondition, options, operationContext); + } + + /// + /// Begins an operation to start copying another blob's contents, properties, and metadata to this blob. + /// + /// The of the source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + internal virtual string StartCopy(Uri source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) { CommonUtility.AssertNotNull("source", source); this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Executor.ExecuteSync( - this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, sourceAccessCondition, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } @@ -2631,7 +2651,7 @@ public virtual string StartCopy(Uri source, AccessCondition sourceAccessConditio [DoesServiceRequest] public virtual ICancellableAsyncResult BeginStartCopy(Uri source, AsyncCallback callback, object state) { - return this.BeginStartCopy(source, null /* sourceAccessCondition */, null /* destAccessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginStartCopy(source, null /* premiumPageBlobTier */, null /* sourceAccessCondition */, null /* destAccessCondition */, null /* options */, null /* operationContext */, callback, state); } /// @@ -2647,12 +2667,30 @@ public virtual ICancellableAsyncResult BeginStartCopy(Uri source, AsyncCallback /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginStartCopy(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(source, null /* premiumPageBlobTier */, sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another blob's contents, properties, and metadata to this blob. + /// + /// The of the source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + internal virtual ICancellableAsyncResult BeginStartCopy(Uri source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { CommonUtility.AssertNotNull("source", source); this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Executor.BeginExecuteAsync( - this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, sourceAccessCondition, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -3500,6 +3538,7 @@ private RESTCommand BreakLeaseImpl(BlobAttributes blobAttributes, Time /// The attributes. /// The URI of the source blob. /// A boolean indicating whether or not this is an incremental copy + /// A representing the tier to set. /// An object that represents the access conditions for the source object. If null, no condition is used. /// An object that represents the access conditions for the destination blob. If null, no condition is used. /// A object that specifies additional options for the request. @@ -3507,7 +3546,7 @@ private RESTCommand BreakLeaseImpl(BlobAttributes blobAttributes, Time /// A that starts to copy. /// /// sourceAccessCondition - internal RESTCommand StartCopyImpl(BlobAttributes blobAttributes, Uri source, bool incrementalCopy, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) + internal RESTCommand StartCopyImpl(BlobAttributes blobAttributes, Uri source, bool incrementalCopy, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) { if (sourceAccessCondition != null && !string.IsNullOrEmpty(sourceAccessCondition.LeaseId)) { @@ -3517,7 +3556,7 @@ internal RESTCommand StartCopyImpl(BlobAttributes blobAttributes, Uri so RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, blobAttributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.CopyFrom(uri, serverTimeout, source, incrementalCopy, sourceAccessCondition, destAccessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.CopyFrom(uri, serverTimeout, source, incrementalCopy, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, useVersionHeader, ctx); putCmd.SetHeaders = (r, ctx) => BlobHttpWebRequestFactory.AddMetadata(r, blobAttributes.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => @@ -3526,6 +3565,13 @@ internal RESTCommand StartCopyImpl(BlobAttributes blobAttributes, Uri so CloudBlob.UpdateETagLMTLengthAndSequenceNumber(blobAttributes, resp, false); CopyState state = BlobHttpResponseParsers.GetCopyAttributes(resp); blobAttributes.CopyState = state; + + if (premiumPageBlobTier.HasValue) + { + this.attributes.Properties.PremiumPageBlobTier = premiumPageBlobTier; + this.attributes.Properties.BlobTierInferred = false; + } + return state.CopyId; }; diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index ad7234c59..e70d54dbd 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -2409,7 +2409,7 @@ private RESTCommand PutBlobImpl(Stream stream, long? length, string co putCmd.SendStream = stream; putCmd.SendStreamLength = length ?? stream.Length - offset; putCmd.RecoveryAction = (cmd, ex, ctx) => RecoveryActions.SeekStream(cmd, offset); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.BlockBlob, 0, accessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.BlockBlob, 0, null /* premiumPageBlobTier */, accessCondition, useVersionHeader, ctx); putCmd.SetHeaders = (r, ctx) => BlobHttpWebRequestFactory.AddMetadata(r, this.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index e52c4a9b4..fc1e09880 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -54,6 +54,28 @@ public partial class CloudPageBlob : CloudBlob, ICloudBlob /// [DoesServiceRequest] public virtual CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.OpenWrite(size, null /* premiumPageBlobTier */, accessCondition, options, operationContext); + } + + /// + /// Opens a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. + /// + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// A object. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . + /// + [DoesServiceRequest] + internal virtual CloudBlobStream OpenWrite(long? size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, this.BlobType, this.ServiceClient, false); @@ -72,7 +94,7 @@ public virtual CloudBlobStream OpenWrite(long? size, AccessCondition accessCondi if (createNew) { - this.Create(size.Value, accessCondition, options, operationContext); + this.Create(size.Value, premiumPageBlobTier, accessCondition, options, operationContext); } else { @@ -148,6 +170,31 @@ public virtual ICancellableAsyncResult BeginOpenWrite(long? size, AsyncCallback [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] [DoesServiceRequest] public virtual ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginOpenWrite(size, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. + /// + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, + /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] + [DoesServiceRequest] + internal virtual ICancellableAsyncResult BeginOpenWrite(long? size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.attributes.AssertNoSnapshot(); bool createNew = size.HasValue; @@ -173,6 +220,7 @@ public virtual ICancellableAsyncResult BeginOpenWrite(long? size, AccessConditio #endif result = this.BeginCreate( size.Value, + premiumPageBlobTier, accessCondition, options, operationContext, @@ -358,7 +406,21 @@ public virtual Task OpenWriteAsync(long? size, AccessCondition [DoesServiceRequest] public virtual void UploadFromStream(Stream source, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { - this.UploadFromStreamHelper(source, null /* length */, accessCondition, options, operationContext); + this.UploadFromStreamHelper(source, null /* length */, null /* premiumPageBlobTier */, accessCondition, options, operationContext); + } + + /// + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void UploadFromStream(Stream source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, null /* length */, premiumPageBlobTier, accessCondition, options, operationContext); } /// @@ -372,7 +434,7 @@ public virtual void UploadFromStream(Stream source, AccessCondition accessCondit [DoesServiceRequest] public virtual void UploadFromStream(Stream source, long length, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { - this.UploadFromStreamHelper(source, length, accessCondition, options, operationContext); + this.UploadFromStreamHelper(source, length, null /* premiumPageBlobTier */, accessCondition, options, operationContext); } /// @@ -380,10 +442,26 @@ public virtual void UploadFromStream(Stream source, long length, AccessCondition /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void UploadFromStream(Stream source, long length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, length, premiumPageBlobTier, accessCondition, options, operationContext); + } + + /// + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - internal void UploadFromStreamHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + internal void UploadFromStreamHelper(Stream source, long? length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { CommonUtility.AssertNotNull("source", source); @@ -410,7 +488,7 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - using (CloudBlobStream blobStream = this.OpenWrite(length, accessCondition, modifiedOptions, operationContext)) + using (CloudBlobStream blobStream = this.OpenWrite(length, premiumPageBlobTier, accessCondition, modifiedOptions, operationContext)) { using (ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions)) { @@ -431,7 +509,7 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, AsyncCallback callback, object state) { - return this.BeginUploadFromStreamHelper(source, null /* length */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginUploadFromStreamHelper(source, null /* length */, null /* premiumPageBlobTier */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } /// @@ -447,7 +525,24 @@ public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, Asyn [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { - return this.BeginUploadFromStreamHelper(source, null /* length */, accessCondition, options, operationContext, callback, state); + return this.BeginUploadFromStreamHelper(source, null /* length */, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, null /* length */, premiumPageBlobTier, accessCondition, options, operationContext, callback, state); } /// @@ -461,7 +556,7 @@ public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, Acce [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, AsyncCallback callback, object state) { - return this.BeginUploadFromStreamHelper(source, length, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginUploadFromStreamHelper(source, length, null /* premiumPageBlobTier */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } /// @@ -478,7 +573,25 @@ public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { - return this.BeginUploadFromStreamHelper(source, length, accessCondition, options, operationContext, callback, state); + return this.BeginUploadFromStreamHelper(source, length, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, length, premiumPageBlobTier, accessCondition, options, operationContext, callback, state); } /// @@ -486,6 +599,7 @@ public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long /// /// A object providing the blob content. /// Specifies the number of bytes from the Stream source to upload from the start position. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -494,7 +608,7 @@ public virtual ICancellableAsyncResult BeginUploadFromStream(Stream source, long /// An that references the asynchronous operation. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] [DoesServiceRequest] - internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long? length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { CommonUtility.AssertNotNull("source", source); @@ -525,6 +639,7 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long ICancellableAsyncResult result = this.BeginOpenWrite( length, + premiumPageBlobTier, accessCondition, modifiedOptions, operationContext, @@ -659,7 +774,23 @@ public virtual Task UploadFromStreamAsync(Stream source, AccessCondition accessC [DoesServiceRequest] public virtual Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromStreamAsync(source, null /* premiumPageBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } /// @@ -715,7 +846,24 @@ public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCond [DoesServiceRequest] public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, length, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromStreamAsync(source, length, null /* premiumPageBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, long length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, length, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } #if SYNC @@ -728,12 +876,26 @@ public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCond /// An object that represents the context for the current operation. [DoesServiceRequest] public virtual void UploadFromFile(string path, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromFile(path, null /* premiumPageBlobTier */, accessCondition, options, operationContext); + } + + /// + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void UploadFromFile(string path, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { CommonUtility.AssertNotNull("path", path); using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - this.UploadFromStream(fileStream, accessCondition, options, operationContext); + this.UploadFromStream(fileStream, premiumPageBlobTier, accessCondition, options, operationContext); } } #endif @@ -763,6 +925,23 @@ public virtual ICancellableAsyncResult BeginUploadFromFile(string path, AsyncCal /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromFile(string path, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromFile(path, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginUploadFromFile(string path, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { CommonUtility.AssertNotNull("path", path); @@ -774,7 +953,7 @@ public virtual ICancellableAsyncResult BeginUploadFromFile(string path, AccessCo try { - ICancellableAsyncResult asyncResult = this.BeginUploadFromStream(fileStream, accessCondition, options, operationContext, this.UploadFromFileCallback, storageAsyncResult); + ICancellableAsyncResult asyncResult = this.BeginUploadFromStream(fileStream, premiumPageBlobTier, accessCondition, options, operationContext, this.UploadFromFileCallback, storageAsyncResult); storageAsyncResult.CancelDelegate = asyncResult.Cancel; return storageAsyncResult; } @@ -878,7 +1057,23 @@ public virtual Task UploadFromFileAsync(string path, AccessCondition accessCondi [DoesServiceRequest] public virtual Task UploadFromFileAsync(string path, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromFile, this.EndUploadFromFile, path, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromFileAsync(path, null /* premiumPageBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task UploadFromFileAsync(string path, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromFile, this.EndUploadFromFile, path, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } #endif @@ -894,12 +1089,28 @@ public virtual Task UploadFromFileAsync(string path, AccessCondition accessCondi /// An object that represents the context for the current operation. [DoesServiceRequest] public virtual void UploadFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromByteArray(buffer, index, count, null /* premiumPageBlobTier */, accessCondition, options, operationContext); + } + + /// + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void UploadFromByteArray(byte[] buffer, int index, int count, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { CommonUtility.AssertNotNull("buffer", buffer); using (SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count)) { - this.UploadFromStream(stream, accessCondition, options, operationContext); + this.UploadFromStream(stream, premiumPageBlobTier, accessCondition, options, operationContext); } } #endif @@ -933,11 +1144,30 @@ public virtual ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, i /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromByteArray(buffer, index, count, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index, int count, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { CommonUtility.AssertNotNull("buffer", buffer); SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); - return this.BeginUploadFromStream(stream, accessCondition, options, operationContext, callback, state); + return this.BeginUploadFromStream(stream, premiumPageBlobTier, accessCondition, options, operationContext, callback, state); } /// @@ -1007,7 +1237,25 @@ public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count [DoesServiceRequest] public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromByteArray, this.EndUploadFromByteArray, buffer, index, count, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromByteArrayAsync(buffer, index, count, null /* premiumPageBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromByteArray, this.EndUploadFromByteArray, buffer, index, count, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } #endif @@ -1022,11 +1270,26 @@ public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count /// An object that represents the context for the current operation. [DoesServiceRequest] public virtual void Create(long size, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.Create(size, null /* pageBlobTier */, accessCondition, options, operationContext); + } + + /// + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// The maximum size of the page blob, in bytes. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public virtual void Create(long size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); Executor.ExecuteSync( - this.CreateImpl(size, accessCondition, modifiedOptions), + this.CreateImpl(size, premiumPageBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } @@ -1043,7 +1306,7 @@ public virtual void Create(long size, AccessCondition accessCondition = null, Bl [DoesServiceRequest] public virtual ICancellableAsyncResult BeginCreate(long size, AsyncCallback callback, object state) { - return this.BeginCreate(size, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginCreate(size, null /* pageBlobTier */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } /// @@ -1059,10 +1322,28 @@ public virtual ICancellableAsyncResult BeginCreate(long size, AsyncCallback call /// An that references the asynchronous operation. [DoesServiceRequest] public virtual ICancellableAsyncResult BeginCreate(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginCreate(size, null /* premiumPageBlobTier */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// The maximum size of the blob, in bytes. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginCreate(long size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Executor.BeginExecuteAsync( - this.CreateImpl(size, accessCondition, modifiedOptions), + this.CreateImpl(size, premiumPageBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -1132,7 +1413,24 @@ public virtual Task CreateAsync(long size, AccessCondition accessCondition, Blob [DoesServiceRequest] public virtual Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginCreate, this.EndCreate, size, accessCondition, options, operationContext, cancellationToken); + return this.CreateAsync(size, null /* pageBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// The maximum size of the blob, in bytes. + /// A representing the tier to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task CreateAsync(long size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginCreate, this.EndCreate, size, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } #endif @@ -2214,6 +2512,26 @@ public virtual string StartCopy(CloudPageBlob source, AccessCondition sourceAcce return this.StartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); } + /// + /// Begins an operation to start copying another page blob's contents, properties, and metadata to this page blob. + /// + /// The that is the source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public virtual string StartCopy(CloudPageBlob source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudBlob.SourceBlobToUri(source), premiumPageBlobTier, sourceAccessCondition, destAccessCondition, options, operationContext); + } + /// /// Begins an operation to start an incremental copy of another page blob's contents, properties, and metadata to this page blob. /// @@ -2252,7 +2570,7 @@ public virtual string StartIncrementalCopy(Uri sourceSnapshotUri, AccessConditio this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Executor.ExecuteSync( - this.StartCopyImpl(this.attributes, sourceSnapshotUri, true /* incrementalCopy */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, sourceSnapshotUri, true /* incrementalCopy */, null /* pageBlobTier */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } @@ -2299,7 +2617,26 @@ public virtual ICancellableAsyncResult BeginStartIncrementalCopy(CloudPageBlob s [DoesServiceRequest] public virtual ICancellableAsyncResult BeginStartCopy(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { - return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + return this.BeginStartCopy(source, null /* premiumPageBlobTier */, sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another page blob's contents, properties, and metadata to this page blob. + /// + /// The that is the source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest", Justification = "Reviewed")] + [DoesServiceRequest] + public virtual ICancellableAsyncResult BeginStartCopy(CloudPageBlob source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), premiumPageBlobTier, sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); } /// @@ -2336,7 +2673,7 @@ public virtual ICancellableAsyncResult BeginStartIncrementalCopy(Uri sourceSnaps this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Executor.BeginExecuteAsync( - this.StartCopyImpl(this.attributes, sourceSnapshot, true /* incrementalCopy */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, sourceSnapshot, true /* incrementalCopy */, null /* pageBlobTier */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -2438,7 +2775,25 @@ public virtual Task StartCopyAsync(CloudPageBlob source, AccessCondition [DoesServiceRequest] public virtual Task StartCopyAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + return this.StartCopyAsync(source, null /* premiumPageBlobTier */, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another blob's contents, properties, and metadata + /// to this page blob. + /// + /// The that is the source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public virtual Task StartCopyAsync(CloudPageBlob source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); } /// @@ -2460,41 +2815,41 @@ public virtual Task StartIncrementalCopyAsync(CloudPageBlob source, Acce #if SYNC /// - /// Sets the tier of the blob. + /// Sets the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request, or null. If null, default options are applied to the request. /// An object that represents the context for the current operation. [DoesServiceRequest] - public virtual void SetBlobTier(PageBlobTier blobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + public virtual void SetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); Executor.ExecuteSync( - this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumPageBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } #endif /// - /// Begins an asynchronous operation to set the tier of the blob. + /// Begins an asynchronous operation to set the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, AsyncCallback callback, object state) + public virtual ICancellableAsyncResult BeginSetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AsyncCallback callback, object state) { - return this.BeginSetBlobTier(blobTier, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginSetPremiumBlobTier(premiumPageBlobTier, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } /// - /// Begins an asynchronous operation to set the tier of the blob. + /// Begins an asynchronous operation to set the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. @@ -2502,12 +2857,12 @@ public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, A /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + public virtual ICancellableAsyncResult BeginSetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Executor.BeginExecuteAsync( - this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumPageBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -2515,65 +2870,65 @@ public virtual ICancellableAsyncResult BeginSetBlobTier(PageBlobTier blobTier, A } /// - /// Ends an asynchronous operation to set the tier of the blob. + /// Ends an asynchronous operation to set the tier of the premium blob. /// /// An that references the pending asynchronous operation. - public virtual void EndSetBlobTier(IAsyncResult asyncResult) + public virtual void EndSetPremiumBlobTier(IAsyncResult asyncResult) { Executor.EndExecuteAsync(asyncResult); } #if TASK /// - /// Initiates an asynchronous operation to set the tier of the blob. + /// Initiates an asynchronous operation to set the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier) { - return this.SetBlobTierAsync(blobTier, CancellationToken.None); + return this.SetPremiumBlobTierAsync(premiumPageBlobTier, CancellationToken.None); } /// - /// Initiates an asynchronous operation to set the tier of the blob. + /// Initiates an asynchronous operation to set the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// A to observe while waiting for a task to complete. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier, CancellationToken cancellationToken) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginSetBlobTier, this.EndSetBlobTier, blobTier, cancellationToken); + return AsyncExtensions.TaskFromVoidApm(this.BeginSetPremiumBlobTier, this.EndSetPremiumBlobTier, premiumPageBlobTier, cancellationToken); } /// - /// Initiates an asynchronous operation to set the tier of the blob. + /// Initiates an asynchronous operation to set the tier of the premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { - return this.SetBlobTierAsync(blobTier, accessCondition, options, operationContext, CancellationToken.None); + return this.SetPremiumBlobTierAsync(premiumPageBlobTier, accessCondition, options, operationContext, CancellationToken.None); } /// - /// Initiates an asynchronous operation to set the tier of the blob. + /// Initiates an asynchronous operation to set the premium tier of the blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginSetBlobTier, this.EndSetBlobTier, blobTier, accessCondition, options, operationContext, cancellationToken); + return AsyncExtensions.TaskFromVoidApm(this.BeginSetPremiumBlobTier, this.EndSetPremiumBlobTier, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } #endif @@ -2581,15 +2936,16 @@ public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition acce /// Implements the Create method. /// /// The size in bytes. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// A that creates the blob. - private RESTCommand CreateImpl(long sizeInBytes, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand CreateImpl(long sizeInBytes, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.PageBlob, sizeInBytes, accessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.PageBlob, sizeInBytes, premiumPageBlobTier, accessCondition, useVersionHeader, ctx); putCmd.SetHeaders = (r, ctx) => BlobHttpWebRequestFactory.AddMetadata(r, this.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => @@ -2598,6 +2954,12 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = sizeInBytes; + this.attributes.Properties.PremiumPageBlobTier = premiumPageBlobTier; + if (premiumPageBlobTier.HasValue) + { + this.attributes.Properties.BlobTierInferred = false; + } + return NullType.Value; }; @@ -2849,23 +3211,24 @@ internal RESTCommand CreateSnapshotImpl(IDictionary /// Implementation method for the SetBlobTier methods. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// A that sets the blob tier. - private RESTCommand SetBlobTierImpl(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.SetBlobTier(uri, serverTimeout, blobTier.ToString(), accessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.SetBlobTier(uri, serverTimeout, premiumPageBlobTier.ToString(), accessCondition, useVersionHeader, ctx); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - this.attributes.Properties.PageBlobTier = blobTier; + this.attributes.Properties.PremiumPageBlobTier = premiumPageBlobTier; + this.attributes.Properties.BlobTierInferred = false; return NullType.Value; }; diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs index 6efa9bb71..ca5c80926 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs @@ -134,10 +134,20 @@ public static BlobProperties GetProperties(HttpWebResponse response) } // Get the tier of the blob - string blobTierString = response.Headers[Constants.HeaderConstants.AccessTierHeader]; - PageBlobTier? pageBlobTier; - BlobHttpResponseParsers.GetBlobTier(properties.BlobType, blobTierString, out pageBlobTier); - properties.PageBlobTier = pageBlobTier; + string premiumPageBlobTierInferredString = response.Headers[Constants.HeaderConstants.AccessTierInferredHeader]; + if (!string.IsNullOrEmpty(premiumPageBlobTierInferredString)) + { + properties.BlobTierInferred = Convert.ToBoolean(premiumPageBlobTierInferredString); + } + + string premiumPageBlobTierString = response.Headers[Constants.HeaderConstants.AccessTierHeader]; + PremiumPageBlobTier? premiumPageBlobTier; + BlobHttpResponseParsers.GetBlobTier(properties.BlobType, premiumPageBlobTierString, out premiumPageBlobTier); + properties.PremiumPageBlobTier = premiumPageBlobTier; + if (properties.PremiumPageBlobTier.HasValue && !properties.BlobTierInferred.HasValue) + { + properties.BlobTierInferred = false; + } return properties; } diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs index 043c9c7bf..fcc385b7e 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs @@ -139,7 +139,7 @@ public static void WriteServiceProperties(ServiceProperties properties, Stream o /// A object. public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, AccessCondition accessCondition, OperationContext operationContext) { - return BlobHttpWebRequestFactory.Put(uri, timeout, properties, blobType, pageBlobSize, accessCondition, true /* useVersionHeader */, operationContext); + return BlobHttpWebRequestFactory.Put(uri, timeout, properties, blobType, pageBlobSize, null /* premiumPageBlobTier */, accessCondition, true /* useVersionHeader */, operationContext); } /// @@ -152,11 +152,12 @@ public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties propertie /// A enumeration value. /// For a page blob, the size of the blob. This parameter is ignored /// for block blobs. + /// A representing the tier to set. /// An object that represents the condition that must be met in order for the request to proceed. /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. - public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { CommonUtility.AssertNotNull("properties", properties); @@ -203,6 +204,11 @@ public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties propertie request.Headers[Constants.HeaderConstants.BlobType] = Constants.HeaderConstants.PageBlob; request.Headers[Constants.HeaderConstants.BlobContentLengthHeader] = pageBlobSize.ToString(NumberFormatInfo.InvariantInfo); properties.Length = pageBlobSize; + + if (premiumPageBlobTier.HasValue) + { + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumPageBlobTier.Value.ToString()); + } } else if (blobType == BlobType.BlockBlob) { @@ -1023,6 +1029,24 @@ public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, AccessC /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, bool incrementalCopy, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, bool useVersionHeader, OperationContext operationContext) + { + return BlobHttpWebRequestFactory.CopyFrom(uri, timeout, source, incrementalCopy, null /* pageBlobTier */, sourceAccessCondition, destAccessCondition, useVersionHeader, operationContext); + } + + /// + /// Generates a web request to copy a blob or file to another blob. + /// + /// A specifying the absolute URI to the destination blob. + /// An integer specifying the server timeout interval. + /// A specifying the absolute URI to the source object, including any necessary authentication parameters. + /// A boolean indicating whether or not this is an incremental copy. + /// A representing the tier to set. + /// An object that represents the condition that must be met on the source object in order for the request to proceed. + /// An object that represents the condition that must be met on the destination blob in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, bool incrementalCopy, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, bool useVersionHeader, OperationContext operationContext) { CommonUtility.AssertNotNull("source", source); @@ -1037,6 +1061,11 @@ public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, bool in request.Headers.Add(Constants.HeaderConstants.CopySourceHeader, source.AbsoluteUri); + if (premiumPageBlobTier.HasValue) + { + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumPageBlobTier.Value.ToString()); + } + request.ApplyAccessCondition(destAccessCondition); request.ApplyAccessConditionToSource(sourceAccessCondition); @@ -1181,12 +1210,12 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot /// /// A specifying the absolute URI to the blob. /// The server timeout interval, in seconds. - /// The blob tier to set as a string. + /// The blob tier to set as a string. /// An object that represents the condition that must be met in order for the request to proceed. /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. - public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string blobTier, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string premiumPageBlobTier, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.Component, "tier"); @@ -1195,7 +1224,7 @@ public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string blobTier, request.ApplyAccessCondition(accessCondition); // Add the blob tier header - request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, blobTier); + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumPageBlobTier); return request; } diff --git a/Lib/Common/Blob/BlobProperties.cs b/Lib/Common/Blob/BlobProperties.cs index c03d12564..87ef88ea2 100644 --- a/Lib/Common/Blob/BlobProperties.cs +++ b/Lib/Common/Blob/BlobProperties.cs @@ -56,7 +56,7 @@ public BlobProperties(BlobProperties other) this.AppendBlobCommittedBlockCount = other.AppendBlobCommittedBlockCount; this.IsServerEncrypted = other.IsServerEncrypted; this.IsIncrementalCopy = other.IsIncrementalCopy; - this.PageBlobTier = other.PageBlobTier; + this.PremiumPageBlobTier = other.PremiumPageBlobTier; } /// @@ -174,9 +174,15 @@ public BlobProperties(BlobProperties other) public bool IsIncrementalCopy { get; internal set; } /// - /// Gets a value indicating the tier of the page blob. + /// Gets a value indicating the tier of the premium page blob. /// - /// A object that indicates the page blob tier. - public PageBlobTier? PageBlobTier { get; internal set; } + /// A object that indicates the page blob tier. + public PremiumPageBlobTier? PremiumPageBlobTier { get; internal set; } + + /// + /// Gets a value indicating if the tier of the premium page blob has been inferred. + /// + /// A bool representing if the premium blob tier has been inferred. + public bool? BlobTierInferred { get; internal set; } } } diff --git a/Lib/Common/Blob/PageBlobTier.cs b/Lib/Common/Blob/PageBlobTier.cs index 4eeffbade..228247405 100644 --- a/Lib/Common/Blob/PageBlobTier.cs +++ b/Lib/Common/Blob/PageBlobTier.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// +// // Copyright 2013 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,10 @@ namespace Microsoft.WindowsAzure.Storage.Blob { /// /// The tier of the page blob. + /// Please take a look at https://docs.microsoft.com/en-us/azure/storage/storage-premium-storage#scalability-and-performance-targets + /// for detailed information on the corresponding IOPS and throughtput per PremiumPageBlobTier. /// - public enum PageBlobTier + public enum PremiumPageBlobTier { /// /// The tier is not recognized by this version of the library. @@ -51,6 +53,21 @@ public enum PageBlobTier /// /// P30 Tier /// - P30 + P30, + + /// + /// P40 Tier + /// + P40, + + /// + /// P50 Tier + /// + P50, + + /// + /// P60 Tier + /// + P60 } } \ No newline at end of file diff --git a/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs b/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs index 348941eb3..017a81f16 100644 --- a/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs +++ b/Lib/Common/Blob/Protocol/BlobHttpResponseParsers.Common.cs @@ -252,33 +252,33 @@ private static bool CheckIfTrue(string header) /// /// A indicating the type of blob. /// The blob tier as a string - /// A nullable . This value will be populated if the blob type is unspecified or is a page blob. - internal static void GetBlobTier(BlobType blobType, string blobTierString, out PageBlobTier? pageBlobTier) + /// A nullable . This value will be populated if the blob type is unspecified or is a page blob. + internal static void GetBlobTier(BlobType blobType, string blobTierString, out PremiumPageBlobTier? premiumPageBlobTier) { - pageBlobTier = null; + premiumPageBlobTier = null; if (blobType.Equals(BlobType.PageBlob)) { - PageBlobTier pageBlobTierFromResponse; - if (Enum.TryParse(blobTierString, true, out pageBlobTierFromResponse)) + PremiumPageBlobTier premiumPageBlobTierFromResponse; + if (Enum.TryParse(blobTierString, true, out premiumPageBlobTierFromResponse)) { - pageBlobTier = pageBlobTierFromResponse; + premiumPageBlobTier = premiumPageBlobTierFromResponse; } else { - pageBlobTier = PageBlobTier.Unknown; + premiumPageBlobTier = PremiumPageBlobTier.Unknown; } } else if (blobType.Equals(BlobType.Unspecified)) { - PageBlobTier pageBlobTierFromResponse; - if (Enum.TryParse(blobTierString, true, out pageBlobTierFromResponse)) + PremiumPageBlobTier premiumPageBlobTierFromResponse; + if (Enum.TryParse(blobTierString, true, out premiumPageBlobTierFromResponse)) { - pageBlobTier = pageBlobTierFromResponse; + premiumPageBlobTier = premiumPageBlobTierFromResponse; } else { - pageBlobTier = PageBlobTier.Unknown; + premiumPageBlobTier = PremiumPageBlobTier.Unknown; } } } diff --git a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs index 6fe866cf2..e6b6e2480 100644 --- a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs +++ b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs @@ -210,7 +210,7 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) string copyStatusDescription = null; string copyDestinationSnapshotTime = null; - string blobTierString = null; + string premiumPageBlobTierString = null; this.reader.ReadStartElement(); while (this.reader.IsStartElement()) @@ -347,7 +347,7 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) break; case Constants.AccessTierElement: - blobTierString = reader.ReadElementContentAsString(); + premiumPageBlobTierString = reader.ReadElementContentAsString(); break; default: @@ -396,11 +396,12 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) copyDestinationSnapshotTime); } - if (!string.IsNullOrEmpty(blobTierString)) + if (!string.IsNullOrEmpty(premiumPageBlobTierString)) { - PageBlobTier? pageBlobTier; - BlobHttpResponseParsers.GetBlobTier(blob.Properties.BlobType, blobTierString, out pageBlobTier); - blob.Properties.PageBlobTier = pageBlobTier; + PremiumPageBlobTier? premiumPageBlobTier; + BlobHttpResponseParsers.GetBlobTier(blob.Properties.BlobType, premiumPageBlobTierString, out premiumPageBlobTier); + blob.Properties.PremiumPageBlobTier = premiumPageBlobTier; + blob.Properties.BlobTierInferred = false; } return new ListBlobEntry(name, blob); diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index d19a7e6d4..be56e1c5f 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -42,6 +42,7 @@ internal class SR public const string BlobStreamAlreadyCommitted = "Blob stream has already been committed once."; public const string BlobStreamFlushPending = "Blob stream has a pending flush operation. Please call EndFlush first."; public const string BlobStreamReadPending = "Blob stream has a pending read operation. Please call EndRead first."; + public const string BlobTierNotSupported = "Blob tier cannot be set by this API on an existing blob. Please call SetBlobTier for existing blobs."; public const string BlobTypeMismatch = "Blob type of the blob reference doesn't match blob type of the blob."; public const string BufferTooSmall = "The provided buffer is too small to fit in the blob data given the offset."; public const string BufferManagerProvidedIncorrectLengthBuffer = "The IBufferManager provided an incorrect length buffer to the stream, Expected {0}, received {1}. Buffer length should equal the value returned by IBufferManager.GetDefaultBufferSize()."; diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 1434f4b7b..66e89bf76 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -991,6 +991,11 @@ static HeaderConstants() /// public const string AccessTierHeader = PrefixForStorageHeader + "access-tier"; + /// + /// Header for the blob tier inferred. + /// + public const string AccessTierInferredHeader = PrefixForStorageHeader + "access-tier-inferred"; + /// /// Header that specifies blob caching control. /// diff --git a/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs index 11992062c..8a157fadc 100644 --- a/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs @@ -1162,7 +1162,7 @@ private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRe options.ApplyToStorageCommand(putCmd); putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { - StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, null /* pageBlobTier */, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); BlobHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); return msg; }; diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index ba601133c..d6fc24324 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -1026,11 +1026,32 @@ public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAcc /// [DoesServiceRequest] public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.StartCopyAsync(source, null /* pageBlobTier*/, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Begins an operation to start copying a blob's contents, properties, and metadata to a new blob. + /// + /// The URI of a source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + internal virtual Task StartCopyAsync(Uri source, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { CommonUtility.AssertNotNull("source", source); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( - this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, sourceAccessCondition, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, source, false /* incrementalCopy */, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -1480,12 +1501,13 @@ private RESTCommand BreakLeaseImpl(BlobAttributes attributes, TimeSpan /// The blob's attributes. /// The URI of the source blob. /// A boolean indicating whether or not this is an incremental copy + /// A representing the tier to set. /// An object that represents the access conditions for the source blob. If null, no condition is used. /// An object that represents the access conditions for the destination blob. If null, no condition is used. /// An object that specifies additional options for the request. /// A delegate for setting the BlobAttributes result. /// A that starts to copy the blob. - internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source, bool incrementalCopy, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) + internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source, bool incrementalCopy, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) { if (sourceAccessCondition != null && !string.IsNullOrEmpty(sourceAccessCondition.LeaseId)) { @@ -1497,7 +1519,7 @@ internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source options.ApplyToStorageCommand(putCmd); putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { - StorageRequestMessage msg = BlobHttpRequestMessageFactory.CopyFrom(uri, serverTimeout, source, incrementalCopy, sourceAccessCondition, destAccessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + StorageRequestMessage msg = BlobHttpRequestMessageFactory.CopyFrom(uri, serverTimeout, source, incrementalCopy, premiumPageBlobTier, sourceAccessCondition, destAccessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); BlobHttpRequestMessageFactory.AddMetadata(msg, attributes.Metadata); return msg; }; @@ -1507,6 +1529,12 @@ internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source CloudBlob.UpdateETagLMTLengthAndSequenceNumber(attributes, resp, false); CopyState state = BlobHttpResponseParsers.GetCopyAttributes(resp); attributes.CopyState = state; + this.attributes.Properties.PremiumPageBlobTier = premiumPageBlobTier; + if (premiumPageBlobTier.HasValue) + { + this.attributes.Properties.BlobTierInferred = false; + } + return state.CopyId; }; diff --git a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs index f9fc82d10..afcffd2fb 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs @@ -1003,7 +1003,7 @@ private RESTCommand PutBlobImpl(Stream stream, long? length, string co putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(cappedStream, offset, length, null /* md5 */, cmd, ctx); putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { - StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.BlockBlob, 0, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.BlockBlob, 0, null, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); BlobHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); return msg; }; diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index 412a83a81..37873c1ae 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -88,6 +88,26 @@ public virtual Task OpenWriteAsync(long? size, AccessCondition /// [DoesServiceRequest] public virtual Task OpenWriteAsync(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.OpenWriteAsync(size, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Opens a stream for writing to the blob. + /// + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A stream to be used for writing to the blob. + /// + /// To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + [DoesServiceRequest] + internal virtual Task OpenWriteAsync(long? size, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { this.attributes.AssertNoSnapshot(); bool createNew = size.HasValue; @@ -101,7 +121,7 @@ public virtual Task OpenWriteAsync(long? size, AccessCondition { if (createNew) { - await this.CreateAsync(size.Value, accessCondition, options, operationContext, cancellationToken); + await this.CreateAsync(size.Value, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); } else { @@ -183,7 +203,22 @@ public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCond [DoesServiceRequest] public virtual Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return this.UploadFromStreamAsyncHelper(source, null /*length */, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromStreamAsyncHelper(source, null /*length */, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// The stream providing the blob content. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, null /*length */, premiumBlobTier, accessCondition, options, operationContext, cancellationToken); } /// @@ -199,7 +234,24 @@ public virtual Task UploadFromStreamAsync(Stream source, AccessCondition accessC [DoesServiceRequest] public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return this.UploadFromStreamAsyncHelper(source, length, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromStreamAsyncHelper(source, length, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. + /// + /// The stream providing the blob content. + /// A representing the tier to set. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, long length, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, length, premiumBlobTier, accessCondition, options, operationContext, cancellationToken); } /// @@ -214,7 +266,7 @@ public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCond [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { - return UploadFromStreamAsyncHelper(source, length, accessCondition, options, operationContext, CancellationToken.None); + return UploadFromStreamAsyncHelper(source, length, null /* premiumBlobTier */, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -222,13 +274,14 @@ internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCon /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. + /// A representing the tier to set. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A that represents an asynchronous action. [DoesServiceRequest] - internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal Task UploadFromStreamAsyncHelper(Stream source, long? length, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { CommonUtility.AssertNotNull("source", source); @@ -259,7 +312,7 @@ internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCon return Task.Run(async () => { - using (CloudBlobStream blobStream = await this.OpenWriteAsync(length, accessCondition, options, operationContext, cancellationToken)) + using (CloudBlobStream blobStream = await this.OpenWriteAsync(length, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken)) { // We should always call AsStreamForWrite with bufferSize=0 to prevent buffering. Our // stream copier only writes 64K buffers at a time anyway, so no buffering is needed. @@ -327,6 +380,22 @@ public virtual Task UploadFromFileAsync(StorageFile source, AccessCondition acce /// A that represents an asynchronous action. [DoesServiceRequest] public virtual Task UploadFromFileAsync(StorageFile source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromFileAsync(source, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. + /// + /// The file providing the blob content. + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task UploadFromFileAsync(StorageFile source, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { CommonUtility.AssertNotNull("source", source); @@ -334,7 +403,7 @@ public virtual Task UploadFromFileAsync(StorageFile source, AccessCondition acce { using (IRandomAccessStreamWithContentType stream = await source.OpenReadAsync().AsTask(cancellationToken)) { - await this.UploadFromStreamAsync(stream.AsStream(), accessCondition, options, operationContext, cancellationToken); + await this.UploadFromStreamAsync(stream.AsStream(), premiumBlobTier, accessCondition, options, operationContext, cancellationToken); } }); } @@ -351,6 +420,22 @@ public virtual Task UploadFromFileAsync(StorageFile source, AccessCondition acce /// A that represents an asynchronous action. [DoesServiceRequest] public virtual Task UploadFromFileAsync(string path, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromFileAsync(path, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task UploadFromFileAsync(string path, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { CommonUtility.AssertNotNull("path", path); @@ -358,7 +443,7 @@ public virtual Task UploadFromFileAsync(string path, AccessCondition accessCondi { using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - await this.UploadFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + await this.UploadFromStreamAsync(stream, premiumBlobTier, accessCondition, options, operationContext, cancellationToken); } }, cancellationToken); } @@ -406,11 +491,29 @@ public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count /// A that represents an asynchronous action. [DoesServiceRequest] public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromByteArrayAsync(buffer, index, count, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { CommonUtility.AssertNotNull("buffer", buffer); SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); - return this.UploadFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + return this.UploadFromStreamAsync(stream, premiumBlobTier, accessCondition, options, operationContext, cancellationToken); } /// @@ -422,7 +525,7 @@ public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count [DoesServiceRequest] public virtual Task CreateAsync(long size) { - return this.CreateAsync(size, null /* accessCondition */, null /* options */, null /* operationContext */); + return this.CreateAsync(size, null /* premiumBlobTier */, null /* accessCondition */, null /* options */, null /* operationContext */, CancellationToken.None); } /// @@ -437,7 +540,7 @@ public virtual Task CreateAsync(long size) [DoesServiceRequest] public virtual Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { - return this.CreateAsync(size, accessCondition, options, operationContext, CancellationToken.None); + return this.CreateAsync(size, null /* premiumBlobTier */, accessCondition, options, operationContext, CancellationToken.None); } /// @@ -452,10 +555,27 @@ public virtual Task CreateAsync(long size, AccessCondition accessCondition, Blob /// A that represents an asynchronous action. [DoesServiceRequest] public virtual Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.CreateAsync(size, null /* premiumBlobTier */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// The maximum size of the page blob, in bytes. + /// A representing the tier to set. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public virtual Task CreateAsync(long size, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( - this.CreateImpl(size, accessCondition, modifiedOptions), + this.CreateImpl(size, premiumBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -922,7 +1042,28 @@ public virtual Task StartCopyAsync(CloudPageBlob source, AccessCondition [DoesServiceRequest] public virtual Task StartCopyAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + return this.StartCopyAsync(source, null /* premiumBlobTier */, sourceAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Begins an operation to start copying another page blob's contents, properties, and metadata to a new blob. + /// + /// The source blob. + /// A representing the tier to set. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public virtual Task StartCopyAsync(CloudPageBlob source, PremiumPageBlobTier? premiumBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source), premiumBlobTier, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); } /// @@ -963,7 +1104,7 @@ public virtual Task StartIncrementalCopyAsync(Uri sourceSnapshot, Access CommonUtility.AssertNotNull("sourceSnapshot", sourceSnapshot); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( - this.StartCopyImpl(this.attributes, sourceSnapshot, true /*incrementalCopy */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), + this.StartCopyImpl(this.attributes, sourceSnapshot, true /*incrementalCopy */, null /* pageBlobTier */, null /* sourceAccessCondition */, destAccessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -972,44 +1113,44 @@ public virtual Task StartIncrementalCopyAsync(Uri sourceSnapshot, Access /// /// Sets the tier for a blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// A that represents an asynchronous action. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier) { - return this.SetBlobTierAsync(blobTier, null /* accessCondition */, null /* options */, null /* operationContext */); + return this.SetPremiumBlobTierAsync(premiumBlobTier, null /* accessCondition */, null /* options */, null /* operationContext */); } /// /// Sets the tier for a blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. /// A that represents an asynchronous action. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { - return this.SetBlobTierAsync(blobTier, accessCondition, options, operationContext, CancellationToken.None); + return this.SetPremiumBlobTierAsync(premiumBlobTier, accessCondition, options, operationContext, CancellationToken.None); } /// - /// Sets the tier for a blob. + /// Sets the tier for a premium blob. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A that represents an asynchronous action. [DoesServiceRequest] - public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( - this.SetBlobTierImpl(blobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumBlobTier, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -1019,17 +1160,18 @@ public virtual Task SetBlobTierAsync(PageBlobTier blobTier, AccessCondition acce /// Implements the Create method. /// /// The size in bytes. + /// A representing the tier to set. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// A that creates the blob. - private RESTCommand CreateImpl(long sizeInBytes, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand CreateImpl(long sizeInBytes, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { - StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.PageBlob, sizeInBytes, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + StorageRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.PageBlob, sizeInBytes, premiumBlobTier, accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); BlobHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); return msg; }; @@ -1039,6 +1181,12 @@ private RESTCommand CreateImpl(long sizeInBytes, AccessCondition acces CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); cmd.CurrentResult.IsRequestServerEncrypted = HttpResponseParsers.ParseServerRequestEncrypted(resp); this.Properties.Length = sizeInBytes; + this.attributes.Properties.PremiumPageBlobTier = premiumBlobTier; + if (premiumBlobTier.HasValue) + { + this.attributes.Properties.BlobTierInferred = false; + } + return NullType.Value; }; @@ -1291,22 +1439,24 @@ private RESTCommand ClearPageImpl(long startOffset, long length, Acces /// /// Implementation method for the SetBlobTier methods. /// - /// A representing the tier to set. + /// A representing the tier to set. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// A that sets the blob tier. - private RESTCommand SetBlobTierImpl(PageBlobTier blobTier, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetBlobTier(uri, serverTimeout, blobTier.ToString(), accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetBlobTier(uri, serverTimeout, premiumBlobTier.ToString(), accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); - this.attributes.Properties.PageBlobTier = blobTier; + this.attributes.Properties.PremiumPageBlobTier = premiumBlobTier; + this.attributes.Properties.BlobTierInferred = false; + return NullType.Value; }; diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs index c936eccfd..a49fb2b6c 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs @@ -60,13 +60,14 @@ public static StorageRequestMessage AppendBlock(Uri uri, int? timeout, AccessCon /// The type of the blob. /// For a page blob, the size of the blob. This parameter is ignored /// for block blobs. + /// A representing the tier to set. /// The access condition to apply to the request. /// The HTTP entity body and content headers. /// An object that represents the context for the current operation. /// A canonicalizer that converts HTTP request data into a standard form appropriate for signing. /// A object providing credentials for the request. /// A web request to use to perform the operation. - public static StorageRequestMessage Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + public static StorageRequestMessage Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, PremiumPageBlobTier? pageBlobTier, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { if (blobType == BlobType.Unspecified) { @@ -113,6 +114,11 @@ public static StorageRequestMessage Put(Uri uri, int? timeout, BlobProperties pr request.Headers.Add(Constants.HeaderConstants.BlobType, Constants.HeaderConstants.PageBlob); request.Headers.Add(Constants.HeaderConstants.BlobContentLengthHeader, pageBlobSize.ToString(NumberFormatInfo.InvariantInfo)); properties.Length = pageBlobSize; + + if (pageBlobTier.HasValue) + { + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, pageBlobTier.Value.ToString()); + } } else if (blobType == BlobType.BlockBlob) { @@ -685,6 +691,26 @@ public static StorageRequestMessage PutPage(Uri uri, int? timeout, PageRange pag /// A object providing credentials for the request. /// A web request to use to perform the operation. public static StorageRequestMessage CopyFrom(Uri uri, int? timeout, Uri source, bool incrementalCopy, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + { + return BlobHttpRequestMessageFactory.CopyFrom(uri, timeout, source, incrementalCopy, null /* pageBlobTier */, sourceAccessCondition, destAccessCondition, content, operationContext, canonicalizer, credentials); + } + + /// + /// Generates a web request to copy a blob. + /// + /// The absolute URI to the destination blob. + /// The server timeout interval. + /// The absolute URI to the source blob, including any necessary authentication parameters. + /// A boolean indicating whether or not this is an incremental copy. + /// A representing the tier to set. + /// The access condition to apply to the source blob. + /// The access condition to apply to the destination blob. + /// The HTTP entity body and content headers. + /// An object that represents the context for the current operation. + /// A canonicalizer that converts HTTP request data into a standard form appropriate for signing. + /// A object providing credentials for the request. + /// A web request to use to perform the operation. + public static StorageRequestMessage CopyFrom(Uri uri, int? timeout, Uri source, bool incrementalCopy, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder builder = null; if (incrementalCopy) @@ -699,6 +725,11 @@ public static StorageRequestMessage CopyFrom(Uri uri, int? timeout, Uri source, request.ApplyAccessCondition(destAccessCondition); request.ApplyAccessConditionToSource(sourceAccessCondition); + if (premiumPageBlobTier.HasValue) + { + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumPageBlobTier.Value.ToString()); + } + return request; } @@ -840,17 +871,17 @@ public static StorageRequestMessage GetServiceStats(Uri uri, int? timeout, Opera /// /// The absolute URI to the blob. /// The server timeout interval. - /// The blob tier to set. + /// The blob tier to set. /// The access condition to apply to the request. /// A web request to use to perform the operation. - public static StorageRequestMessage SetBlobTier(Uri uri, int? timeout, string blobTier, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + public static StorageRequestMessage SetBlobTier(Uri uri, int? timeout, string premiumBlobTier, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.Component, "tier"); StorageRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, builder, content, operationContext, canonicalizer, credentials); - request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, blobTier); + request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumBlobTier); request.ApplyAccessCondition(accessCondition); request.ApplySequenceNumberCondition(accessCondition); diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs index a916a8852..360b54464 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs @@ -125,10 +125,20 @@ public static BlobProperties GetProperties(HttpResponseMessage response) } // Get the tier of the blob - string blobTierString = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.AccessTierHeader); - PageBlobTier? pageBlobTier; - BlobHttpResponseParsers.GetBlobTier(properties.BlobType, blobTierString, out pageBlobTier); - properties.PageBlobTier = pageBlobTier; + string premiumBlobTierInferredString = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.AccessTierInferredHeader); + if (!string.IsNullOrEmpty(premiumBlobTierInferredString)) + { + properties.BlobTierInferred = Convert.ToBoolean(premiumBlobTierInferredString); + } + + string premiumBlobTierString = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.AccessTierHeader); + PremiumPageBlobTier? premiumPageBlobTier; + BlobHttpResponseParsers.GetBlobTier(properties.BlobType, premiumBlobTierString, out premiumPageBlobTier); + properties.PremiumPageBlobTier = premiumPageBlobTier; + if (properties.PremiumPageBlobTier.HasValue && !properties.BlobTierInferred.HasValue) + { + properties.BlobTierInferred = false; + } return properties; } diff --git a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs index 33b47bcf5..a6c2134d5 100644 --- a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.IO; @@ -3707,29 +3708,237 @@ private void ListBlobsWithIncrementalCopyImpl(int overload) } [TestMethod] - [Description("Set blob tier and fetch attributes")] + [Description("Set blob tier when creating a premium page blob and fetch attributes")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.Premium)] - public void CloudPageBlobSetBlobTier() + public void CloudPageBlobSetPremiumBlobTierOnCreate() { - CloudBlobContainer container = GetRandomContainerReference(); + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); try { container.Create(); CloudPageBlob blob = container.GetPageBlobReference("blob1"); - blob.Create(0); - blob.SetBlobTier(PageBlobTier.P4); - Assert.AreEqual(PageBlobTier.P4, blob.Properties.PageBlobTier); + blob.Create(0, PremiumPageBlobTier.P40, null, null, null); + Assert.AreEqual(PremiumPageBlobTier.P40, blob.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual(PremiumPageBlobTier.P40, blob2.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob2.Properties.BlobTierInferred.Value); + + byte[] data = GetRandomBuffer(512); + + CloudPageBlob blob3 = container.GetPageBlobReference("blob3"); + blob3.UploadFromByteArray(data, 0, data.Length, PremiumPageBlobTier.P10, null, null, null); + Assert.AreEqual(PremiumPageBlobTier.P10, blob3.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob3.Properties.BlobTierInferred.Value); + + string inputFileName = "i_" + Path.GetRandomFileName(); + using (FileStream fs = new FileStream(inputFileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(data, 0, data.Length); + } + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + blob4.UploadFromFile(inputFileName, PremiumPageBlobTier.P20, null, null, null); + Assert.AreEqual(PremiumPageBlobTier.P20, blob4.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob4.Properties.BlobTierInferred.Value); + + using (MemoryStream memStream = new MemoryStream(data)) + { + CloudPageBlob blob5 = container.GetPageBlobReference("blob5"); + blob5.UploadFromStream(memStream, PremiumPageBlobTier.P30, null, null, null); + Assert.AreEqual(PremiumPageBlobTier.P30, blob5.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob5.Properties.BlobTierInferred.Value); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set blob tier when creating a premium page blob and fetch attributes - APM")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTierOnCreateAPM() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.Create(); + + IAsyncResult result; + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + result = blob.BeginCreate(0, PremiumPageBlobTier.P50, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreate(result); + Assert.AreEqual(PremiumPageBlobTier.P50, blob.Properties.PremiumPageBlobTier); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + result = blob2.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(PremiumPageBlobTier.P50, blob2.Properties.PremiumPageBlobTier); + + byte[] data = GetRandomBuffer(512); + + CloudPageBlob blob3 = container.GetPageBlobReference("blob3"); + result = blob3.BeginUploadFromByteArray(data, 0, data.Length, PremiumPageBlobTier.P10, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob3.EndUploadFromByteArray(result); + Assert.AreEqual(PremiumPageBlobTier.P10, blob3.Properties.PremiumPageBlobTier); + + string inputFileName = "i_" + Path.GetRandomFileName(); + using (FileStream fs = new FileStream(inputFileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(data, 0, data.Length); + } + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + result = blob4.BeginUploadFromFile(inputFileName, PremiumPageBlobTier.P20, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob4.EndUploadFromFile(result); + Assert.AreEqual(PremiumPageBlobTier.P20, blob4.Properties.PremiumPageBlobTier); + + using (MemoryStream memStream = new MemoryStream(data)) + { + CloudPageBlob blob5 = container.GetPageBlobReference("blob5"); + result = blob5.BeginUploadFromStream(memStream, PremiumPageBlobTier.P30, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob5.EndUploadFromStream(result); + Assert.AreEqual(PremiumPageBlobTier.P30, blob5.Properties.PremiumPageBlobTier); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Set blob tier on creating a premium page blob and fetch attributes - TASK")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTierOnCreateTask() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + blob.CreateAsync(0, PremiumPageBlobTier.P60, null, null, null, CancellationToken.None).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P60, blob.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(PremiumPageBlobTier.P60, blob2.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob2.Properties.BlobTierInferred.Value); + + byte[] data = GetRandomBuffer(512); + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + blob4.UploadFromByteArrayAsync(data, 0, data.Length, PremiumPageBlobTier.P10, null, null, null, CancellationToken.None).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P10, blob4.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob4.Properties.BlobTierInferred.Value); + + string inputFileName = "i_" + Path.GetRandomFileName(); + using (FileStream fs = new FileStream(inputFileName, FileMode.Create, FileAccess.Write)) + { + fs.WriteAsync(data, 0, data.Length).Wait(); + } + + CloudPageBlob blob5 = container.GetPageBlobReference("blob5"); + blob5.UploadFromFileAsync(inputFileName, PremiumPageBlobTier.P20, null, null, null, CancellationToken.None).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P20, blob5.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob5.Properties.BlobTierInferred.Value); + + using (MemoryStream memStream = new MemoryStream(data)) + { + CloudPageBlob blob6 = container.GetPageBlobReference("blob6"); + blob6.UploadFromStreamAsync(memStream, PremiumPageBlobTier.P30, null, null, null, CancellationToken.None).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P30, blob6.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob6.Properties.BlobTierInferred.Value); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Set premium blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTier() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.Create(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + blob.Create(1024); + Assert.IsFalse(blob.Properties.BlobTierInferred.HasValue); + blob.FetchAttributes(); + Assert.IsTrue(blob.Properties.BlobTierInferred.Value); + Assert.AreEqual(PremiumPageBlobTier.P10, blob.Properties.PremiumPageBlobTier); + + blob.SetPremiumBlobTier(PremiumPageBlobTier.P30); + Assert.AreEqual(PremiumPageBlobTier.P30, blob.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); blob2.FetchAttributes(); - Assert.AreEqual(PageBlobTier.P4, blob2.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P30, blob2.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob2.Properties.BlobTierInferred.Value); CloudPageBlob blob3 = (CloudPageBlob)container.ListBlobs().ToList().First(); - Assert.AreEqual(PageBlobTier.P4, blob3.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P30, blob3.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob3.Properties.BlobTierInferred.Value); + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + blob4.Create(125 * Constants.GB); + try + { + blob4.SetPremiumBlobTier(PremiumPageBlobTier.P6); + Assert.Fail("Expected failure when setting blob tier size to be less than content length"); + } + catch (StorageException e) + { + Assert.IsFalse(blob4.Properties.BlobTierInferred.HasValue); + Assert.AreEqual("The remote server returned an error: (409) Conflict.", e.Message); + } + + try + { + blob2.SetPremiumBlobTier(PremiumPageBlobTier.P4); + Assert.Fail("Expected failure when attempted to set the tier to a lower value than previously"); + } + catch (StorageException e) + { + Assert.AreEqual("The remote server returned an error: (409) Conflict.", e.Message); + } } finally { @@ -3738,14 +3947,14 @@ public void CloudPageBlobSetBlobTier() } [TestMethod] - [Description("Set blob tier and fetch attributes")] + [Description("Set premium blob tier and fetch attributes - APM")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.Premium)] - public void CloudPageBlobSetBlobTierAPM() + public void CloudPageBlobSetPremiumBlobTierAPM() { - CloudBlobContainer container = GetRandomContainerReference(); + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); try { container.Create(); @@ -3759,16 +3968,16 @@ public void CloudPageBlobSetBlobTierAPM() waitHandle.WaitOne(); blob.EndCreate(result); - result = blob.BeginSetBlobTier(PageBlobTier.P6, ar => waitHandle.Set(), null); + result = blob.BeginSetPremiumBlobTier(PremiumPageBlobTier.P6, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndSetBlobTier(result); - Assert.AreEqual(PageBlobTier.P6, blob.Properties.PageBlobTier); + blob.EndSetPremiumBlobTier(result); + Assert.AreEqual(PremiumPageBlobTier.P6, blob.Properties.PremiumPageBlobTier); CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); result = blob2.BeginFetchAttributes(ar => waitHandle.Set(), null); waitHandle.WaitOne(); blob2.EndFetchAttributes(result); - Assert.AreEqual(PageBlobTier.P6, blob2.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P6, blob2.Properties.PremiumPageBlobTier); } } finally @@ -3779,14 +3988,14 @@ public void CloudPageBlobSetBlobTierAPM() #if TASK [TestMethod] - [Description("Set blob tier and fetch attributes")] + [Description("Set premium blob tier and fetch attributes - TASK")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.Premium)] - public void CloudPageBlobSetBlobTierTask() + public void CloudPageBlobSetPremiumBlobTierTask() { - CloudBlobContainer container = GetRandomContainerReference(); + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); try { container.CreateAsync().Wait(); @@ -3794,12 +4003,140 @@ public void CloudPageBlobSetBlobTierTask() CloudPageBlob blob = container.GetPageBlobReference("blob1"); blob.CreateAsync(0).Wait(); - blob.SetBlobTierAsync(PageBlobTier.P20).Wait(); - Assert.AreEqual(PageBlobTier.P20, blob.Properties.PageBlobTier); + blob.SetPremiumBlobTierAsync(PremiumPageBlobTier.P20).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P20, blob.Properties.PremiumPageBlobTier); CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); blob2.FetchAttributesAsync().Wait(); - Assert.AreEqual(PageBlobTier.P20, blob.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P20, blob.Properties.PremiumPageBlobTier); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Set premium blob tier when copying from an existing blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTierOnCopy() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.Create(); + + CloudPageBlob source = container.GetPageBlobReference("source"); + source.Create(1024, PremiumPageBlobTier.P10, null, null, null); + + // copy to larger disk + CloudPageBlob copy = container.GetPageBlobReference("copy"); + Assert.IsFalse(copy.Properties.BlobTierInferred.HasValue); + + string copyId = copy.StartCopy(TestHelper.Defiddler(source), PremiumPageBlobTier.P30); + Assert.AreEqual(BlobType.PageBlob, copy.BlobType); + Assert.AreEqual(PremiumPageBlobTier.P30, copy.Properties.PremiumPageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P10, source.Properties.PremiumPageBlobTier); + Assert.IsFalse(source.Properties.BlobTierInferred.Value); + Assert.IsFalse(copy.Properties.BlobTierInferred.Value); + WaitForCopy(copy); + + CloudPageBlob copyRef = container.GetPageBlobReference("copy"); + copyRef.FetchAttributes(); + Assert.IsFalse(copyRef.Properties.BlobTierInferred.Value); + Assert.AreEqual(PremiumPageBlobTier.P30, copyRef.Properties.PremiumPageBlobTier); + + // copy where source does not have a tier + CloudPageBlob source2 = container.GetPageBlobReference("source2"); + try + { + source2.Create(1024); + CloudPageBlob copy3 = container.GetPageBlobReference("copy3"); + string copyId3 = copy3.StartCopy(TestHelper.Defiddler(source2), PremiumPageBlobTier.P60); + Assert.AreEqual(BlobType.PageBlob, copy3.BlobType); + Assert.AreEqual(PremiumPageBlobTier.P60, copy3.Properties.PremiumPageBlobTier); + Assert.IsFalse(copy3.Properties.BlobTierInferred.Value); + } + finally + { + source2.FetchAttributes(); + Assert.IsTrue(source2.Properties.BlobTierInferred.Value); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set premium blob tier when copying from an existing blob - APM")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTierOnCopyAPM() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.Create(); + + IAsyncResult result; + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudPageBlob blob = container.GetPageBlobReference("source"); + result = blob.BeginCreate(0, PremiumPageBlobTier.P10, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreate(result); + + CloudPageBlob copy = container.GetPageBlobReference("copy"); + result = copy.BeginStartCopy(TestHelper.Defiddler(blob), PremiumPageBlobTier.P30, null, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + copy.EndStartCopy(result); + + result = copy.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + copy.EndFetchAttributes(result); + Assert.AreEqual(PremiumPageBlobTier.P30, copy.Properties.PremiumPageBlobTier); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Set premium blob tier when copying from an existing blob - TASK")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public void CloudPageBlobSetPremiumBlobTierOnCopyTask() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudPageBlob blob = container.GetPageBlobReference("source"); + blob.CreateAsync(0, PremiumPageBlobTier.P4, null, null, null, CancellationToken.None).Wait(); + + CloudPageBlob copy = container.GetPageBlobReference("copy"); + copy.StartCopyAsync(blob, PremiumPageBlobTier.P60, null, null, null, null, CancellationToken.None).Wait(); + Assert.AreEqual(PremiumPageBlobTier.P4, blob.Properties.PremiumPageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P60, copy.Properties.PremiumPageBlobTier); + + CloudPageBlob copy2 = container.GetPageBlobReference("copy"); + copy2.FetchAttributesAsync().Wait(); + Assert.AreEqual(PremiumPageBlobTier.P60, copy2.Properties.PremiumPageBlobTier); } finally { diff --git a/Test/Common/Blob/BlobTestBase.Common.cs b/Test/Common/Blob/BlobTestBase.Common.cs index e13152d1a..48b573d01 100644 --- a/Test/Common/Blob/BlobTestBase.Common.cs +++ b/Test/Common/Blob/BlobTestBase.Common.cs @@ -44,6 +44,21 @@ public static CloudBlobContainer GetRandomContainerReference() return container; } + public static CloudBlobContainer GetRandomPremiumBlobContainerReference() + { + if (TestBase.PremiumBlobTenantConfig == null || TestBase.PremiumBlobStorageCredentials == null) + { + Assert.Inconclusive("A premium blob storage account must be specified to run this test."); + } + + Uri baseAddressUri = new Uri(TestBase.PremiumBlobTenantConfig.BlobServiceEndpoint); + CloudBlobClient blobClient = new CloudBlobClient(baseAddressUri, TestBase.PremiumBlobStorageCredentials); + string name = GetRandomContainerName(); + CloudBlobContainer container = blobClient.GetContainerReference(name); + + return container; + } + public static List GetBlockIdList(int count) { List blocks = new List(); diff --git a/Test/Common/TestBase.Common.cs b/Test/Common/TestBase.Common.cs index 19b7ea827..fd6e96bb7 100644 --- a/Test/Common/TestBase.Common.cs +++ b/Test/Common/TestBase.Common.cs @@ -180,15 +180,27 @@ public static void SetPayloadFormatOnDataServiceContext(TableServiceContext ctx, public static TenantConfiguration TargetTenantConfig { get; private set; } + public static TenantConfiguration PremiumBlobTenantConfig { get; private set; } + public static TenantType CurrentTenantType { get; private set; } public static StorageCredentials StorageCredentials { get; private set; } + public static StorageCredentials PremiumBlobStorageCredentials { get; private set; } + private static void Initialize(TestConfigurations configurations) { TestBase.TargetTenantConfig = configurations.TenantConfigurations.Single(config => config.TenantName == configurations.TargetTenantName); TestBase.StorageCredentials = new StorageCredentials(TestBase.TargetTenantConfig.AccountName, TestBase.TargetTenantConfig.AccountKey); TestBase.CurrentTenantType = TargetTenantConfig.TenantType; + + try + { + TestBase.PremiumBlobTenantConfig = configurations.TenantConfigurations.Single(config => config.TenantName == configurations.TargetPremiumBlobTenantName); + TestBase.PremiumBlobStorageCredentials = new StorageCredentials(TestBase.PremiumBlobTenantConfig.AccountName, TestBase.PremiumBlobTenantConfig.AccountKey); + } + catch (InvalidOperationException e) { } + #if WINDOWS_DESKTOP System.Threading.ThreadPool.SetMinThreads(100, 100); #endif diff --git a/Test/Common/TestConfigProcess/TestConfigurations.cs b/Test/Common/TestConfigProcess/TestConfigurations.cs index 9cde9f910..87f66feff 100644 --- a/Test/Common/TestConfigProcess/TestConfigurations.cs +++ b/Test/Common/TestConfigProcess/TestConfigurations.cs @@ -25,6 +25,7 @@ public class TestConfigurations { public const string DefaultTestConfigFilePath = @"TestConfigurations.xml"; public string TargetTenantName { get; private set; } + public string TargetPremiumBlobTenantName { get; private set; } public IEnumerable TenantConfigurations { get; private set; } public static TestConfigurations ReadFromXml(XDocument testConfigurationsDoc) @@ -37,6 +38,7 @@ public static TestConfigurations ReadFromXml(XElement testConfigurationsElement) { TestConfigurations result = new TestConfigurations(); result.TargetTenantName = (string)testConfigurationsElement.Element("TargetTestTenant"); + result.TargetPremiumBlobTenantName = (string)testConfigurationsElement.Element("TargetPremiumBlobTenant"); List tenantConfigurationList = new List(); foreach (XElement tenantConfigurationElement in testConfigurationsElement.Element("TenantConfigurations").Elements("TenantConfiguration")) diff --git a/Test/Common/TestConfigurationsTemplate.xml b/Test/Common/TestConfigurationsTemplate.xml index d3a7c204d..bc71c95f1 100644 --- a/Test/Common/TestConfigurationsTemplate.xml +++ b/Test/Common/TestConfigurationsTemplate.xml @@ -1,5 +1,6 @@ ProductionTenant +ProductionTenant DevStore diff --git a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs index 79a33d17b..3ec241a52 100644 --- a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs @@ -31,6 +31,8 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; +using Microsoft.WindowsAzure.Storage.Shared.Protocol; +using Windows.Storage; #endif namespace Microsoft.WindowsAzure.Storage.Blob @@ -1388,31 +1390,183 @@ public async Task ListBlobsWithIncrementalCopiedBlobTestAsync() } [TestMethod] - [Description("Set blob tier and fetch attributes")] + [Description("Set premium blob tier when creating a page blob and fetch attributes")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.Premium)] - public async Task CloudPageBlobSetBlobTierAsync() + public async Task CloudPageBlobSetPremiumBlobTierOnCreateAsync() { - CloudBlobContainer container = GetRandomContainerReference(); + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + await container.CreateAsync(); + + CloudPageBlob blob = container.GetPageBlobReference("blob1"); + await blob.CreateAsync(0, PremiumPageBlobTier.P30, null, null, null, CancellationToken.None); + Assert.AreEqual(PremiumPageBlobTier.P30, blob.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); + + CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(PremiumPageBlobTier.P30, blob2.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob2.Properties.BlobTierInferred.Value); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null); + CloudPageBlob blob3 = (CloudPageBlob)results.Results.ToList().First(); + Assert.AreEqual(PremiumPageBlobTier.P30, blob3.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob3.Properties.BlobTierInferred.Value); + + byte[] data = GetRandomBuffer(512); + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + await blob4.UploadFromByteArrayAsync(data, 0, data.Length, PremiumPageBlobTier.P10, null, null, null, CancellationToken.None); + Assert.AreEqual(PremiumPageBlobTier.P10, blob4.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob4.Properties.BlobTierInferred.Value); + + StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder; + StorageFile inputFile = await tempFolder.CreateFileAsync("input.file", CreationCollisionOption.GenerateUniqueName); + using (Stream file = await inputFile.OpenStreamForWriteAsync()) + { + await file.WriteAsync(data, 0, data.Length); + } + + CloudPageBlob blob5 = container.GetPageBlobReference("blob5"); + await blob5.UploadFromFileAsync(inputFile, PremiumPageBlobTier.P20, null, null, null, CancellationToken.None); + Assert.AreEqual(PremiumPageBlobTier.P20, blob5.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob5.Properties.BlobTierInferred.Value); + + using (MemoryStream memStream = new MemoryStream(data)) + { + CloudPageBlob blob6 = container.GetPageBlobReference("blob6"); + await blob6.UploadFromStreamAsync(memStream, PremiumPageBlobTier.P30, null, null, null, CancellationToken.None); + Assert.AreEqual(PremiumPageBlobTier.P30, blob6.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob6.Properties.BlobTierInferred.Value); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Set premium blob tier and fetch attributes")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public async Task CloudPageBlobSetPremiumBlobTierAsync() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); try { await container.CreateAsync(); CloudPageBlob blob = container.GetPageBlobReference("blob1"); await blob.CreateAsync(0); + Assert.IsFalse(blob.Properties.BlobTierInferred.HasValue); + await blob.FetchAttributesAsync(); + Assert.IsTrue(blob.Properties.BlobTierInferred.Value); - await blob.SetBlobTierAsync(PageBlobTier.P30); - Assert.AreEqual(PageBlobTier.P30, blob.Properties.PageBlobTier); + await blob.SetPremiumBlobTierAsync(PremiumPageBlobTier.P30); + Assert.AreEqual(PremiumPageBlobTier.P30, blob.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); CloudPageBlob blob2 = container.GetPageBlobReference("blob1"); await blob2.FetchAttributesAsync(); - Assert.AreEqual(PageBlobTier.P30, blob2.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P30, blob2.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob2.Properties.BlobTierInferred.Value); BlobResultSegment results = await container.ListBlobsSegmentedAsync(null); CloudPageBlob blob3 = (CloudPageBlob)results.Results.ToList().First(); - Assert.AreEqual(PageBlobTier.P30, blob3.Properties.PageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P30, blob3.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob3.Properties.BlobTierInferred.Value); + + CloudPageBlob blob4 = container.GetPageBlobReference("blob4"); + await blob4.CreateAsync(125 * Constants.GB); + try + { + await blob4.SetPremiumBlobTierAsync(PremiumPageBlobTier.P6); + Assert.Fail("Expected failure when setting blob tier size to be less than content length"); + } + catch (StorageException e) + { + Assert.AreEqual("Specified blob tier size limit cannot be less than content length.", e.Message); + Assert.IsFalse(blob4.Properties.BlobTierInferred.HasValue); + } + + try + { + await blob2.SetPremiumBlobTierAsync(PremiumPageBlobTier.P4); + Assert.Fail("Expected failure when attempted to set the tier to a lower value than previously"); + } + catch (StorageException e) + { + Assert.AreEqual("A higher blob tier has already been explicitly set.", e.Message); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Set premium blob tier when copying from an existing blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.Premium)] + public async Task CloudPageBlobSetPremiumBlobTierOnCopyAsync() + { + CloudBlobContainer container = GetRandomPremiumBlobContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudPageBlob blob = container.GetPageBlobReference("source"); + await blob.CreateAsync(1024, PremiumPageBlobTier.P6, null, null, null, CancellationToken.None); + + // copy to larger disk + CloudPageBlob copy = container.GetPageBlobReference("copy"); + await copy.StartCopyAsync(blob, PremiumPageBlobTier.P10, null, null, null, null, CancellationToken.None); + await WaitForCopyAsync(copy); + Assert.AreEqual(PremiumPageBlobTier.P6, blob.Properties.PremiumPageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P10, copy.Properties.PremiumPageBlobTier); + Assert.IsFalse(blob.Properties.BlobTierInferred.Value); + Assert.IsFalse(copy.Properties.BlobTierInferred.Value); + + CloudPageBlob copyRef = container.GetPageBlobReference("copy"); + await copyRef.FetchAttributesAsync(); + Assert.AreEqual(PremiumPageBlobTier.P10, copyRef.Properties.PremiumPageBlobTier); + Assert.IsFalse(copyRef.Properties.BlobTierInferred.Value); + + // copy where source does not have a tier + CloudPageBlob source2 = container.GetPageBlobReference("source2"); + await source2.CreateAsync(1024); + CloudPageBlob copy2 = container.GetPageBlobReference("copy2"); + await copy2.StartCopyAsync(TestHelper.Defiddler(source2), PremiumPageBlobTier.P20, null, null, null, null, CancellationToken.None); + await WaitForCopyAsync(copy2); + Assert.AreEqual(BlobType.PageBlob, copy2.BlobType); + Assert.IsNull(source2.Properties.PremiumPageBlobTier); + Assert.AreEqual(PremiumPageBlobTier.P20, copy2.Properties.PremiumPageBlobTier); + Assert.IsFalse(copy2.Properties.BlobTierInferred.Value); + + // attempt to copy to a disk too small + CloudPageBlob source3 = container.GetPageBlobReference("source3"); + source3.CreateAsync(120 * Constants.GB).Wait(); + CloudPageBlob copy3 = container.GetPageBlobReference("copy3"); + try + { + copy3.StartCopyAsync(TestHelper.Defiddler(source3), PremiumPageBlobTier.P6, null, null, null, null, CancellationToken.None).Wait(); + Assert.Fail("Expect failure when attempting to copy to too small of a disk"); + } + catch (AggregateException e) + { + Assert.AreEqual("Specified blob tier size limit cannot be less than content length.", e.InnerException.Message); + Assert.IsFalse(copy3.Properties.BlobTierInferred.HasValue); + } } finally { diff --git a/changelog.txt b/changelog.txt index a0875d567..0805d2abb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,7 @@ Changes in X.X.X : - All: Support for 2016-10-16 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. - Files: Added support for creating the snapshot of a share. See the service documentation for more information on how to use this API. - Files: Added support for server side encryption. -- PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. +- PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. Changes in 8.1.1 : - All (NetStandard/NetCore): Removed Microsoft.Data.Services.Client as a temporary fix to ODataLib dependency incompatibility with NetStandard From 0e4a886cf393809df2e2eddbeb872a00c54e8f3b Mon Sep 17 00:00:00 2001 From: jofriedm-msft Date: Wed, 5 Jul 2017 10:33:13 -0700 Subject: [PATCH 24/37] Oct16 (#110) * Page Blob Tier Support for Create and Copy APIs * renaming premiumBlobTier to premiumPageBlobTier * Bumping storage version to Apr17 --- Lib/Common/Shared/Protocol/Constants.cs | 2 +- changelog.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 66e89bf76..b7cee05c2 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -1130,7 +1130,7 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion = "2016-10-16"; + public const string TargetStorageVersion = "2017-04-17"; /// /// Specifies the file type. diff --git a/changelog.txt b/changelog.txt index 0805d2abb..1ddaf5da7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ Changes in X.X.X : -- All: Support for 2016-10-16 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. +- All: Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. - Files: Added support for creating the snapshot of a share. See the service documentation for more information on how to use this API. - Files: Added support for server side encryption. - PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. From 7df764b312074f14d7e2bc2a71c319df20fabf1d Mon Sep 17 00:00:00 2001 From: jofriedm-msft Date: Tue, 11 Jul 2017 09:51:23 -0700 Subject: [PATCH 25/37] Oct16 (#111) * Page Blob Tier Support for Create and Copy APIs * renaming premiumBlobTier to premiumPageBlobTier * Bumping storage version to Apr17 * Removing access conditions from Set Tier APIs --- Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs | 27 ++++++++----------- .../Protocol/BlobHttpWebRequestFactory.cs | 4 +-- Lib/WindowsRuntime/Blob/CloudPageBlob.cs | 17 +++++------- .../Protocol/BlobHttpRequestMessageFactory.cs | 6 +---- 4 files changed, 20 insertions(+), 34 deletions(-) diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index fc1e09880..719b5f6c0 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -2818,16 +2818,15 @@ public virtual Task StartIncrementalCopyAsync(CloudPageBlob source, Acce /// Sets the tier of the premium blob. /// /// A representing the tier to set. - /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request, or null. If null, default options are applied to the request. /// An object that represents the context for the current operation. [DoesServiceRequest] - public virtual void SetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + public virtual void SetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, BlobRequestOptions options = null, OperationContext operationContext = null) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); Executor.ExecuteSync( - this.SetBlobTierImpl(premiumPageBlobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumPageBlobTier, modifiedOptions), modifiedOptions.RetryPolicy, operationContext); } @@ -2843,26 +2842,25 @@ public virtual void SetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, [DoesServiceRequest] public virtual ICancellableAsyncResult BeginSetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AsyncCallback callback, object state) { - return this.BeginSetPremiumBlobTier(premiumPageBlobTier, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + return this.BeginSetPremiumBlobTier(premiumPageBlobTier, null /* options */, null /* operationContext */, callback, state); } /// /// Begins an asynchronous operation to set the tier of the premium blob. /// /// A representing the tier to set. - /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginSetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + public virtual ICancellableAsyncResult BeginSetPremiumBlobTier(PremiumPageBlobTier premiumPageBlobTier, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Executor.BeginExecuteAsync( - this.SetBlobTierImpl(premiumPageBlobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumPageBlobTier, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, callback, @@ -2906,29 +2904,27 @@ public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobT /// Initiates an asynchronous operation to set the tier of the premium blob. /// /// A representing the tier to set. - /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, BlobRequestOptions options, OperationContext operationContext) { - return this.SetPremiumBlobTierAsync(premiumPageBlobTier, accessCondition, options, operationContext, CancellationToken.None); + return this.SetPremiumBlobTierAsync(premiumPageBlobTier, options, operationContext, CancellationToken.None); } /// /// Initiates an asynchronous operation to set the premium tier of the blob. /// /// A representing the tier to set. - /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A object that represents the asynchronous operation. [DoesServiceRequest] - public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumPageBlobTier, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - return AsyncExtensions.TaskFromVoidApm(this.BeginSetPremiumBlobTier, this.EndSetPremiumBlobTier, premiumPageBlobTier, accessCondition, options, operationContext, cancellationToken); + return AsyncExtensions.TaskFromVoidApm(this.BeginSetPremiumBlobTier, this.EndSetPremiumBlobTier, premiumPageBlobTier, options, operationContext, cancellationToken); } #endif @@ -3212,15 +3208,14 @@ internal RESTCommand CreateSnapshotImpl(IDictionary /// A representing the tier to set. - /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. /// A that sets the blob tier. - private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumPageBlobTier, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumPageBlobTier, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.SetBlobTier(uri, serverTimeout, premiumPageBlobTier.ToString(), accessCondition, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.SetBlobTier(uri, serverTimeout, premiumPageBlobTier.ToString(), useVersionHeader, ctx); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs index fcc385b7e..3f6a19be5 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs @@ -1211,17 +1211,15 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot /// A specifying the absolute URI to the blob. /// The server timeout interval, in seconds. /// The blob tier to set as a string. - /// An object that represents the condition that must be met in order for the request to proceed. /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. - public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string premiumPageBlobTier, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + public static HttpWebRequest SetBlobTier(Uri uri, int? timeout, string premiumPageBlobTier, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.Component, "tier"); HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, builder, useVersionHeader, operationContext); - request.ApplyAccessCondition(accessCondition); // Add the blob tier header request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumPageBlobTier); diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index 37873c1ae..6ca7e7d61 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -1118,39 +1118,37 @@ public virtual Task StartIncrementalCopyAsync(Uri sourceSnapshot, Access [DoesServiceRequest] public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier) { - return this.SetPremiumBlobTierAsync(premiumBlobTier, null /* accessCondition */, null /* options */, null /* operationContext */); + return this.SetPremiumBlobTierAsync(premiumBlobTier, null /* options */, null /* operationContext */); } /// /// Sets the tier for a blob. /// /// A representing the tier to set. - /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. /// A that represents an asynchronous action. [DoesServiceRequest] - public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options, OperationContext operationContext) { - return this.SetPremiumBlobTierAsync(premiumBlobTier, accessCondition, options, operationContext, CancellationToken.None); + return this.SetPremiumBlobTierAsync(premiumBlobTier, options, operationContext, CancellationToken.None); } /// /// Sets the tier for a premium blob. /// /// A representing the tier to set. - /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request, or null. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A that represents an asynchronous action. [DoesServiceRequest] - public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { this.attributes.AssertNoSnapshot(); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.PageBlob, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( - this.SetBlobTierImpl(premiumBlobTier, accessCondition, modifiedOptions), + this.SetBlobTierImpl(premiumBlobTier, modifiedOptions), modifiedOptions.RetryPolicy, operationContext, cancellationToken), cancellationToken); @@ -1440,15 +1438,14 @@ private RESTCommand ClearPageImpl(long startOffset, long length, Acces /// Implementation method for the SetBlobTier methods. /// /// A representing the tier to set. - /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// A that sets the blob tier. - private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options) + private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options) { RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetBlobTier(uri, serverTimeout, premiumBlobTier.ToString(), accessCondition, cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetBlobTier(uri, serverTimeout, premiumBlobTier.ToString(), cnt, ctx, this.ServiceClient.GetCanonicalizer(), this.ServiceClient.Credentials); putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs index a49fb2b6c..5e89d955f 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs @@ -872,19 +872,15 @@ public static StorageRequestMessage GetServiceStats(Uri uri, int? timeout, Opera /// The absolute URI to the blob. /// The server timeout interval. /// The blob tier to set. - /// The access condition to apply to the request. /// A web request to use to perform the operation. - public static StorageRequestMessage SetBlobTier(Uri uri, int? timeout, string premiumBlobTier, AccessCondition accessCondition, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) + public static StorageRequestMessage SetBlobTier(Uri uri, int? timeout, string premiumBlobTier, HttpContent content, OperationContext operationContext, ICanonicalizer canonicalizer, StorageCredentials credentials) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.Component, "tier"); StorageRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, builder, content, operationContext, canonicalizer, credentials); - request.Headers.Add(Constants.HeaderConstants.AccessTierHeader, premiumBlobTier); - request.ApplyAccessCondition(accessCondition); - request.ApplySequenceNumberCondition(accessCondition); return request; } } From dadf65db1c6cc79e46f9100cbf253e81e625def5 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Tue, 11 Jul 2017 19:08:51 -0700 Subject: [PATCH 26/37] [Apr17][8.2] Disable ShareSnapshot --- Lib/ClassLibraryCommon/File/CloudFileShare.cs | 16 ++++----- .../DirectoryHttpWebRequestFactory.cs | 6 ++-- .../Protocol/FileHttpWebRequestFactory.cs | 10 +++--- .../Protocol/ShareHttpWebRequestFactory.cs | 35 ++++++++++--------- Lib/Common/File/CloudFile.Common.cs | 4 +-- Lib/Common/File/CloudFileClient.Common.cs | 2 +- Lib/Common/File/CloudFileDirectory.Common.cs | 4 +-- Lib/Common/File/CloudFileShare.Common.cs | 12 +++---- Lib/Common/File/Protocol/FileShareEntry.cs | 2 +- Lib/Common/File/ShareListingDetails.cs | 7 +--- Lib/WindowsRuntime/File/CloudFileShare.cs | 8 ++--- .../ShareHttpRequestMessageFactory.cs | 28 +++++++-------- changelog.txt | 3 +- global.json | 2 +- 14 files changed, 67 insertions(+), 72 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index 7e96bda7c..758f9a8ba 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -334,7 +334,7 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope /// An object that represents the context for the current operation. /// A object that is a share snapshot. [DoesServiceRequest] - public CloudFileShare Snapshot(IDictionary metadata = null, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + internal CloudFileShare Snapshot(IDictionary metadata = null, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -352,7 +352,7 @@ public CloudFileShare Snapshot(IDictionary metadata = null, Acce /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, object state) + internal ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, object state) { return this.BeginSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); } @@ -368,7 +368,7 @@ public ICancellableAsyncResult BeginSnapshot(AsyncCallback callback, object stat /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public ICancellableAsyncResult BeginSnapshot(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + internal ICancellableAsyncResult BeginSnapshot(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { this.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -385,7 +385,7 @@ public ICancellableAsyncResult BeginSnapshot(IDictionary metadat /// /// An that references the pending asynchronous operation. /// A object that is a share snapshot. - public CloudFileShare EndSnapshot(IAsyncResult asyncResult) + internal CloudFileShare EndSnapshot(IAsyncResult asyncResult) { return Executor.EndExecuteAsync(asyncResult); } @@ -396,7 +396,7 @@ public CloudFileShare EndSnapshot(IAsyncResult asyncResult) /// /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - public Task SnapshotAsync() + internal Task SnapshotAsync() { return this.SnapshotAsync(CancellationToken.None); } @@ -407,7 +407,7 @@ public Task SnapshotAsync() /// A to observe while waiting for a task to complete. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - public Task SnapshotAsync(CancellationToken cancellationToken) + internal Task SnapshotAsync(CancellationToken cancellationToken) { return AsyncExtensions.TaskFromApm(this.BeginSnapshot, this.EndSnapshot, cancellationToken); } @@ -421,7 +421,7 @@ public Task SnapshotAsync(CancellationToken cancellationToken) /// An object that represents the context for the current operation. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - public Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + internal Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { return this.SnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); } @@ -436,7 +436,7 @@ public Task SnapshotAsync(IDictionary metadata, /// A to observe while waiting for a task to complete. /// A object of type that represents the asynchronous operation. [DoesServiceRequest] - public Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { return AsyncExtensions.TaskFromApm(this.BeginSnapshot, this.EndSnapshot, metadata, accessCondition, options, operationContext, cancellationToken); } diff --git a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs index abf5ce5ae..f5e6a621c 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs @@ -105,7 +105,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); @@ -139,7 +139,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); @@ -173,7 +173,7 @@ public static HttpWebRequest List(Uri uri, int? timeout, FileListingContext list /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest List(Uri uri, int? timeout, FileListingContext listingContext, DateTimeOffset? shareSnapshot, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest List(Uri uri, int? timeout, FileListingContext listingContext, DateTimeOffset? shareSnapshot, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); DirectoryHttpWebRequestFactory.AddShareSnapshot(directoryBuilder, shareSnapshot); diff --git a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs index eaf2519a1..158cd6035 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs @@ -152,7 +152,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -186,7 +186,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -289,7 +289,7 @@ public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, lon /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, long? count, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, long? count, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if (offset.HasValue) { @@ -386,7 +386,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, AccessCondition accessCo /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. - public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); FileHttpWebRequestFactory.AddShareSnapshot(builder, shareSnapshot); @@ -442,7 +442,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? coun /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? count, bool rangeContentMD5, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? count, bool rangeContentMD5, DateTimeOffset? shareSnapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if (offset.HasValue && offset.Value < 0) { diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 70728c1c1..44d5f21f9 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -92,7 +92,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if ((snapshot != null) && (deleteSnapshotsOption != DeleteShareSnapshotsOption.None)) { @@ -143,7 +143,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); @@ -177,7 +177,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A flag indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. - public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); ShareHttpWebRequestFactory.AddShareSnapshot(shareBuilder, snapshot); @@ -259,7 +259,7 @@ public static HttpWebRequest GetStats(Uri uri, int? timeout, bool useVersionHead /// A flag indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. - public static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + internal static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder builder = new UriQueryBuilder(); builder.Add(Constants.QueryConstants.ResourceType, "share"); @@ -343,19 +343,20 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC sb.Append("metadata"); } - if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) - { - if (!started) - { - started = true; - } - else - { - sb.Append(","); - } - - sb.Append("snapshots"); - } + //TODO: Enable with ShareSnapshot + //if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) + //{ + // if (!started) + // { + // started = true; + // } + // else + // { + // sb.Append(","); + // } + + // sb.Append("snapshots"); + //} builder.Add("include", sb.ToString()); } diff --git a/Lib/Common/File/CloudFile.Common.cs b/Lib/Common/File/CloudFile.Common.cs index b8233dde5..d95af53c9 100644 --- a/Lib/Common/File/CloudFile.Common.cs +++ b/Lib/Common/File/CloudFile.Common.cs @@ -220,7 +220,7 @@ public StorageUri StorageUri /// Gets the absolute URI to the file, including query string information if the file's share is a snapshot. /// /// A specifying the absolute URI to the file, including snapshot query information if the file's share is a snapshot. - public Uri SnapshotQualifiedUri + internal Uri SnapshotQualifiedUri { get { @@ -242,7 +242,7 @@ public Uri SnapshotQualifiedUri /// /// An object of type containing the file's URIs for both the primary and secondary locations, /// including snapshot query information if the file's share is a snapshot. - public StorageUri SnapshotQualifiedStorageUri + internal StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/CloudFileClient.Common.cs b/Lib/Common/File/CloudFileClient.Common.cs index 377006fcd..bcedf5525 100644 --- a/Lib/Common/File/CloudFileClient.Common.cs +++ b/Lib/Common/File/CloudFileClient.Common.cs @@ -122,7 +122,7 @@ public virtual CloudFileShare GetShareReference(string shareName) /// A string containing the name of the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A reference to a share. - public CloudFileShare GetShareReference(string shareName, DateTimeOffset? snapshotTime) + internal CloudFileShare GetShareReference(string shareName, DateTimeOffset? snapshotTime) { CommonUtility.AssertNotNullOrEmpty("shareName", shareName); return new CloudFileShare(shareName, snapshotTime, this); diff --git a/Lib/Common/File/CloudFileDirectory.Common.cs b/Lib/Common/File/CloudFileDirectory.Common.cs index 3582e00c9..27dc68b5f 100644 --- a/Lib/Common/File/CloudFileDirectory.Common.cs +++ b/Lib/Common/File/CloudFileDirectory.Common.cs @@ -121,7 +121,7 @@ public Uri Uri /// Gets the absolute URI to the directory, including query string information if the directory's share is a snapshot. /// /// A specifying the absolute URI to the directory, including snapshot query information if the directory's share is a snapshot. - public Uri SnapshotQualifiedUri + internal Uri SnapshotQualifiedUri { get { @@ -143,7 +143,7 @@ public Uri SnapshotQualifiedUri /// /// An object of type containing the directory's URIs for both the primary and secondary locations, /// including snapshot query information if the directory's share is a snapshot. - public StorageUri SnapshotQualifiedStorageUri + internal StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/CloudFileShare.Common.cs b/Lib/Common/File/CloudFileShare.Common.cs index fa8bdd7d8..e2536ffe8 100644 --- a/Lib/Common/File/CloudFileShare.Common.cs +++ b/Lib/Common/File/CloudFileShare.Common.cs @@ -57,7 +57,7 @@ public CloudFileShare(Uri shareAddress, StorageCredentials credentials) /// The absolute URI to the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A object. - public CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + internal CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) : this(new StorageUri(shareAddress), snapshotTime, credentials) { } @@ -79,7 +79,7 @@ public CloudFileShare(StorageUri shareAddress, StorageCredentials credentials) /// The absolute URI to the share. /// A specifying the snapshot timestamp, if the share is a snapshot. /// A object. - public CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + internal CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) { CommonUtility.AssertNotNull("shareAddress", shareAddress); CommonUtility.AssertNotNull("shareAddress", shareAddress.PrimaryUri); @@ -151,13 +151,13 @@ public Uri Uri /// /// If the share is not a snapshot, the value of this property is null. /// - public DateTimeOffset? SnapshotTime { get; internal set; } + internal DateTimeOffset? SnapshotTime { get; set; } /// /// Gets a value indicating whether this share is a snapshot. /// /// true if this share is a snapshot; otherwise, false. - public bool IsSnapshot + internal bool IsSnapshot { get { @@ -169,7 +169,7 @@ public bool IsSnapshot /// Gets the absolute URI to the share, including query string information if the share is a snapshot. /// /// A specifying the absolute URI to the share, including snapshot query information if the share is a snapshot. - public Uri SnapshotQualifiedUri + internal Uri SnapshotQualifiedUri { get { @@ -191,7 +191,7 @@ public Uri SnapshotQualifiedUri /// /// An object of type containing the share's URIs for both the primary and secondary locations, /// including snapshot query information if the share is a snapshot. - public StorageUri SnapshotQualifiedStorageUri + internal StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/Common/File/Protocol/FileShareEntry.cs b/Lib/Common/File/Protocol/FileShareEntry.cs index 4cd09ff4d..6d8a5b962 100644 --- a/Lib/Common/File/Protocol/FileShareEntry.cs +++ b/Lib/Common/File/Protocol/FileShareEntry.cs @@ -69,6 +69,6 @@ internal FileShareEntry() /// Gets the share's snapshot time, if any. /// /// A specifying the snapshot timestamp, if the share is a snapshot. - public DateTimeOffset? SnapshotTime { get; internal set; } + internal DateTimeOffset? SnapshotTime { get; set; } } } diff --git a/Lib/Common/File/ShareListingDetails.cs b/Lib/Common/File/ShareListingDetails.cs index 48c1d217e..3aaed78de 100644 --- a/Lib/Common/File/ShareListingDetails.cs +++ b/Lib/Common/File/ShareListingDetails.cs @@ -35,14 +35,9 @@ public enum ShareListingDetails /// Metadata = 0x1, - /// - /// Retrieve share snapshots. - /// - Snapshots = 0x2, - /// /// Retrieve all available details. /// - All = Metadata | Snapshots + All = Metadata } } diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index 6e1d6c19e..056a6180b 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -158,7 +158,7 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope /// /// A share snapshot. [DoesServiceRequest] - public virtual Task SnapshotAsync() + internal virtual Task SnapshotAsync() { return this.SnapshotAsync(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */); } @@ -169,7 +169,7 @@ public virtual Task SnapshotAsync() /// A to observe while waiting for a task to complete. /// A share snapshot. [DoesServiceRequest] - public virtual Task SnapshotAsync(CancellationToken cancellationToken) + internal virtual Task SnapshotAsync(CancellationToken cancellationToken) { return this.SnapshotAsync(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */, cancellationToken); } @@ -183,7 +183,7 @@ public virtual Task SnapshotAsync(CancellationToken cancellation /// An object that represents the context for the current operation. /// A share snapshot. [DoesServiceRequest] - public virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { return this.SnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); } @@ -198,7 +198,7 @@ public virtual Task SnapshotAsync(IDictionary me /// A to observe while waiting for a task to complete. /// A share snapshot. [DoesServiceRequest] - public virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index 92c32ad74..04d16fdf0 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -215,20 +215,20 @@ public static StorageRequestMessage List(Uri uri, int? timeout, ListingContext l StringBuilder sb = new StringBuilder(); bool started = false; - - if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) - { - if (!started) - { - started = true; - } - else - { - sb.Append(","); - } - - sb.Append("snapshots"); - } + //TODO: Enable for ShareSnapshot + //if ((detailsIncluded & ShareListingDetails.Snapshots) == ShareListingDetails.Snapshots) + //{ + // if (!started) + // { + // started = true; + // } + // else + // { + // sb.Append(","); + // } + + // sb.Append("snapshots"); + //} if ((detailsIncluded & ShareListingDetails.Metadata) == ShareListingDetails.Metadata) { diff --git a/changelog.txt b/changelog.txt index 1ddaf5da7..123f3015e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,5 @@ -Changes in X.X.X : +Changes in 8.2.0 : - All: Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. -- Files: Added support for creating the snapshot of a share. See the service documentation for more information on how to use this API. - Files: Added support for server side encryption. - PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. diff --git a/global.json b/global.json index d43694b76..c374ea82a 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "lib", "Lib/AspNet" ], "sdk": { - "version": "1.0.0-preview2-1-003177" + "version": "1.0.0-preview2-1-003131" } } From 75e53121b6616cfad1715ea4e90afb9d6f9d1cf3 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Tue, 11 Jul 2017 19:30:18 -0700 Subject: [PATCH 27/37] [8.2]AsyncRead errStream in NetCore + Mono DelTable --StorageExtendedErrorInformation.ReadFromStream causing deadlocks in .net core asp.net application #431 https://github.com/Azure/azure-storage-net/issues/431 --Calling DeleteAsync or DeleteIfExistsAsync on table throwing error#437 https://github.com/Azure/azure-storage-net/issues/437 --- Lib/Common/Core/Util/Exceptions.cs | 2 +- Lib/Common/StorageExtendedErrorInformation.cs | 41 ++++++++++++++++++- .../TableOperationHttpRequestFactory.cs | 5 ++- changelog.txt | 1 + 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Lib/Common/Core/Util/Exceptions.cs b/Lib/Common/Core/Util/Exceptions.cs index ac4489b10..21497f99c 100644 --- a/Lib/Common/Core/Util/Exceptions.cs +++ b/Lib/Common/Core/Util/Exceptions.cs @@ -68,7 +68,7 @@ internal async static Task PopulateStorageExceptionFromHttpRes } else { - currentResult.ExtendedErrorInformation = StorageExtendedErrorInformation.ReadFromStream(errStream.AsInputStream()); + currentResult.ExtendedErrorInformation = await StorageExtendedErrorInformation.ReadFromStreamAsync(errStream.AsInputStream()); } } catch (Exception) diff --git a/Lib/Common/StorageExtendedErrorInformation.cs b/Lib/Common/StorageExtendedErrorInformation.cs index 775169899..0b4bc1205 100644 --- a/Lib/Common/StorageExtendedErrorInformation.cs +++ b/Lib/Common/StorageExtendedErrorInformation.cs @@ -26,8 +26,9 @@ namespace Microsoft.WindowsAzure.Storage using System.Collections.Generic; using System.IO; using System.Net; -#if WINDOWS_RT || NETCORE +#if WINDOWS_RT || NETCORE using System.Net.Http; + using System.Threading.Tasks; #endif using System.Xml; @@ -109,6 +110,44 @@ public static StorageExtendedErrorInformation ReadFromStream(Stream inputStream) } } +#if WINDOWS_RT || NETCORE + + /// + /// Gets the error details from an XML-formatted error stream. + /// + /// The input stream. + /// The error details. + public static async Task ReadFromStreamAsync(Stream inputStream) + { + CommonUtility.AssertNotNull("inputStream", inputStream); + + if (inputStream.CanSeek && inputStream.Length < 1) + { + return null; + } + + StorageExtendedErrorInformation extendedErrorInfo = new StorageExtendedErrorInformation(); + try + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.Async = true; + + using (XmlReader reader = XmlReader.Create(inputStream, settings)) + { + await reader.ReadAsync(); + extendedErrorInfo.ReadXml(reader); + } + + return extendedErrorInfo; + } + catch (XmlException) + { + // If there is a parsing error we cannot return extended error information + return null; + } + } +#endif + #region IXmlSerializable /// diff --git a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs index 549967b7d..91bb88e6e 100644 --- a/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs +++ b/Lib/WindowsRuntime/Table/Protocol/TableOperationHttpRequestFactory.cs @@ -75,7 +75,10 @@ internal static StorageRequestMessage BuildRequestForTableOperation(RESTComma operation.OperationType == TableOperationType.Replace || operation.OperationType == TableOperationType.Merge) { - msg.Headers.Add("If-Match", operation.Entity.ETag); + if (operation.ETag != null) + { + msg.Headers.Add("If-Match", operation.Entity.ETag); + } } // Prefer header diff --git a/changelog.txt b/changelog.txt index 123f3015e..d50ae02dc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,7 @@ Changes in 8.2.0 : - All: Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. - Files: Added support for server side encryption. - PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. +- Tables (NetCore): Fixed a bug where the empty etag added to the table delete operation would throw exception on Xamarin/Mono. Changes in 8.1.1 : - All (NetStandard/NetCore): Removed Microsoft.Data.Services.Client as a temporary fix to ODataLib dependency incompatibility with NetStandard From 5c3b00b06f7f3b1f4fdd83ae19babd1f0b38e7ff Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Tue, 23 May 2017 12:57:02 -0700 Subject: [PATCH 28/37] [.NET][8.1.2 Hotfix]Download hang/ SendStream leak -- Client would hang when facing half-open connections during reading partial response from server, in which case the maximum execution time passed as a token to the ReadAsync operation to Network stream was ignored(this issue will be tracked with .Net team separately). As a workaround we are adding a control to cancel the wait on the read operation (note that the read operation itself might still be going on but the flow will not be stopped indefinitely due to it). Stephen Toub has provided a tidy extension method in his blog post referring to a similar issue, which we are using in the TaskExtensions class(the reference is mentioned in the CR). --A memory leak where the send stream was not disposed in case of failures/retries and also for all table operations --assembly version update to 8.1.2 --- ...Azure.Storage.Shared.Protocol.Constants.cs | 2 +- ...age.Shared.Protocol.EncryptionConstants.cs | 2 +- ...Storage.Shared.Protocol.HeaderConstants.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../project.json | 3 +- .../AssemblyInfo.cs | 6 +-- .../WindowsAzure.StorageK.nuspec | 2 +- .../project.json | 2 +- .../Core/Executor/Executor.cs | 2 + .../Core/Util/AsyncStreamCopier.cs | 7 ++- .../Core/Util/TaskExtensions.cs | 45 +++++++++++++++++++ .../Table/TableBatchOperation.cs | 1 + .../Table/TableOperation.cs | 3 ++ Lib/Common/Auth/StorageCredentials.cs | 2 +- Lib/Common/Blob/BlobRequestOptions.cs | 3 +- Lib/Common/Core/Executor/ExecutionState.cs | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- .../WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsRuntime/File/CloudFile.cs | 10 ++--- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- README.md | 2 +- .../project.json | 4 +- .../project.json | 4 +- .../project.json | 4 +- .../WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- .../WindowsPhone81/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- changelog.txt | 5 +++ global.json | 2 +- 34 files changed, 109 insertions(+), 52 deletions(-) create mode 100644 Lib/ClassLibraryCommon/Core/Util/TaskExtensions.cs diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index 1851756db..ea492e9dd 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -245,7 +245,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.1"; + public const string AgentMetadataValue = ".NET 8.1.2"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index 6df306f3c..a37dbf002 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.1"; + public const string AgentMetadataValue = ".NET 8.1.2"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index 95989a461..c3aa5b1e8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/8.1.1 "; + public static readonly string UserAgent = "Azure-Storage/8.1.2 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "8.1.1"; + public const string UserAgentProductVersion = "8.1.2"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index 6d269b859..14c6d26f8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index 5f4cbe91d..e5af2d52d 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,7 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "8.1.1.0", + "version": "8.1.2.0", + "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", "dependencies": { diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 6d8522872..d5808a5c6 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] -[assembly: AssemblyInformationalVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyInformationalVersion("8.1.2.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index e35e7049a..3bd5e01fc 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.1 + 8.1.2 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index 2ec558c11..fc005f604 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "8.1.1.0", + "version": "8.1.2.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs index 83c656bd9..f94dccbed 100644 --- a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs +++ b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs @@ -508,6 +508,7 @@ private static void EndOperation(ExecutionState executionState) Logger.LogError(executionState.OperationContext, shouldRetry ? SR.TraceRetryDecisionTimeout : SR.TraceRetryDecisionPolicy, executionState.ExceptionRef.Message); // No Retry + executionState.CheckDisposeSendStream(); executionState.OnComplete(); } else @@ -572,6 +573,7 @@ private static void EndOperation(ExecutionState executionState) private static void RetryRequest(object state) { ExecutionState executionState = (ExecutionState)state; + executionState.CheckDisposeSendStream(); Logger.LogInformational(executionState.OperationContext, SR.TraceRetry); Executor.FireRetrying(executionState); Executor.InitRequest(executionState); diff --git a/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs b/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs index df098378a..095c47be8 100644 --- a/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs +++ b/Lib/ClassLibraryCommon/Core/Util/AsyncStreamCopier.cs @@ -23,6 +23,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Util using System.IO; using System.Threading; using System.Threading.Tasks; + // Class to copy streams with potentially overlapping read / writes. This uses no waithandle, extra threads, but does contain a single lock internal class AsyncStreamCopier : IDisposable { @@ -116,8 +117,6 @@ public void StartCopyStream(Action> completedDelegate, long? c { state.ReqTimedOut = timedOut; - // Note: the old logic had the following line happen on different threads for Desktop and Phone. I don't think - // we still need to do that, but I'm not sure. #if WINDOWS_DESKTOP || WINDOWS_PHONE state.Req.Abort(); #endif @@ -218,7 +217,7 @@ public async Task StartCopyStreamAsync(long? copyLength, long? maxLength) { this.cancellationTokenSourceCombined = this.cancellationTokenSourceAbort; } - + await this.StartCopyStreamAsyncHelper(copyLength, maxLength, this.cancellationTokenSourceCombined.Token).ConfigureAwait(false); } @@ -284,7 +283,7 @@ private async Task StartCopyStreamAsyncHelper(long? copyLength, long? maxLength, UpdateStreamCopyState(writeBuff, bytesCopied); - bytesCopied = await readTask.ConfigureAwait(false); + bytesCopied = await readTask.WithCancellation(token).ConfigureAwait(false); totalBytes = totalBytes + bytesCopied; CheckMaxLength(maxLength, totalBytes); diff --git a/Lib/ClassLibraryCommon/Core/Util/TaskExtensions.cs b/Lib/ClassLibraryCommon/Core/Util/TaskExtensions.cs new file mode 100644 index 000000000..a95e67ac7 --- /dev/null +++ b/Lib/ClassLibraryCommon/Core/Util/TaskExtensions.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Core.Util +{ + using System; + using System.Threading; + using System.Threading.Tasks; + +#if TASK + + internal static class TaskExtensions + { + /// + /// Extension method to add cancellation logic to non-cancellable operations. + /// + /// + /// Please refer to this post for more information: https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/ + internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register( + taskCompletionSource => ((TaskCompletionSource)taskCompletionSource).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(cancellationToken); + return await task; + } + } + +#endif +} diff --git a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs index 95d7d45d3..b56128dbc 100644 --- a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs @@ -140,6 +140,7 @@ private static RESTCommand> BatchImpl(TableBatchOperation bat { Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableBatchOperation(uri, builder, client.BufferManager, timeout, table.Name, batch, useVersionHeader, ctx, requestOptions, client.AccountName); batchCmd.SendStream = res.Item2; + batchCmd.StreamToDispose = res.Item2; return res.Item1; }; diff --git a/Lib/ClassLibraryCommon/Table/TableOperation.cs b/Lib/ClassLibraryCommon/Table/TableOperation.cs index b6e5c70dd..ce25b109d 100644 --- a/Lib/ClassLibraryCommon/Table/TableOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableOperation.cs @@ -138,6 +138,7 @@ private static RESTCommand InsertImpl(TableOperation operation, Clo { Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, useVersionHeader, ctx, requestOptions, client.AccountName); insertCmd.SendStream = res.Item2; + insertCmd.StreamToDispose = res.Item2; return res.Item1; }; @@ -176,6 +177,7 @@ private static RESTCommand MergeImpl(TableOperation operation, Clou { Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, useVersionHeader, ctx, requestOptions, client.AccountName); mergeCmd.SendStream = res.Item2; + mergeCmd.StreamToDispose = res.Item2; return res.Item1; }; @@ -197,6 +199,7 @@ private static RESTCommand ReplaceImpl(TableOperation operation, Cl { Tuple res = TableOperationHttpWebRequestFactory.BuildRequestForTableOperation(uri, builder, client.BufferManager, timeout, operation, useVersionHeader, ctx, requestOptions, client.AccountName); replaceCmd.SendStream = res.Item2; + replaceCmd.StreamToDispose = res.Item2; return res.Item1; }; diff --git a/Lib/Common/Auth/StorageCredentials.cs b/Lib/Common/Auth/StorageCredentials.cs index dd6a2ea88..5ea254f52 100644 --- a/Lib/Common/Auth/StorageCredentials.cs +++ b/Lib/Common/Auth/StorageCredentials.cs @@ -102,7 +102,7 @@ public bool IsSharedKey } /// - /// Gets the value of the shared access signature token's sig parameter. + /// Gets the value of the shared access signature token's sig parameter. /// public string SASSignature { diff --git a/Lib/Common/Blob/BlobRequestOptions.cs b/Lib/Common/Blob/BlobRequestOptions.cs index 99a85d254..6b676d452 100644 --- a/Lib/Common/Blob/BlobRequestOptions.cs +++ b/Lib/Common/Blob/BlobRequestOptions.cs @@ -39,7 +39,8 @@ public sealed class BlobRequestOptions : IRequestOptions private int? parallelOperationThreadCount; /// - /// Default is 32 MB. + /// Indicates the maximum size of a blob, in bytes, that may be uploaded as a single blob, + /// ranging from between 1 and 256 MB inclusive. The Default is 128 MB. /// private long? singleBlobUploadThresholdInBytes; diff --git a/Lib/Common/Core/Executor/ExecutionState.cs b/Lib/Common/Core/Executor/ExecutionState.cs index 41c100fa0..0b36b5cbd 100644 --- a/Lib/Common/Core/Executor/ExecutionState.cs +++ b/Lib/Common/Core/Executor/ExecutionState.cs @@ -228,7 +228,7 @@ internal bool ReqTimedOut } } - private void CheckDisposeSendStream() + internal void CheckDisposeSendStream() { RESTCommand cmd = this.RestCMD; diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 25d08f3ef..ae2f2286f 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -824,7 +824,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "8.1.1"; + public const string UserAgentProductVersion = "8.1.2"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 0c297402d..f6fa0fdad 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index e35e7049a..3bd5e01fc 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.1 + 8.1.2 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 62d948393..3a8ca1cd4 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index 96f85b39c..fc4b31e17 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index 8bb4e75b8..ce690e372 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -1250,7 +1250,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques /// A stream providing the range data. /// The offset at which to begin writing, in bytes. /// An optional hash value that will be used to set the property - /// on the file. May be null or an empty string. + /// on the file. May be null or an empty string. /// A that represents an asynchronous action. [DoesServiceRequest] public virtual Task WriteRangeAsync(Stream rangeData, long startOffset, string contentMD5) @@ -1264,7 +1264,7 @@ public virtual Task WriteRangeAsync(Stream rangeData, long startOffset, string c /// A stream providing the range data. /// The offset at which to begin writing, in bytes. /// An optional hash value that will be used to set the property - /// on the file. May be null or an empty string. + /// on the file. May be null or an empty string. /// An object that represents the access conditions for the file. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1281,7 +1281,7 @@ public virtual Task WriteRangeAsync(Stream rangeData, long startOffset, string c /// A stream providing the range data. /// The offset at which to begin writing, in bytes. /// An optional hash value that will be used to set the property - /// on the file. May be null or an empty string. + /// on the file. May be null or an empty string. /// An object that represents the access conditions for the file. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. @@ -1869,8 +1869,8 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F /// The range data. /// The start offset. /// An optional hash value that will be used to set the property - /// on the file. May be null or an empty string. - /// An object that represents the access conditions for the file. If null, no condition is used. + /// on the file. May be null or an empty string. + /// An object that represents the access conditions for the file. If null, no condition is used. /// A object that specifies additional options for the request. /// A that writes the range. private RESTCommand PutRangeImpl(Stream rangeData, long startOffset, string contentMD5, AccessCondition accessCondition, FileRequestOptions options) diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index 71cbf1f22..68abd08b7 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: ComVisible(false)] diff --git a/README.md b/README.md index 8f67912a8..bd0530183 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (8.1.1) +# Microsoft Azure Storage SDK for .NET (8.1.2) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 653792e5d..330cd4580 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "8.1.1.0", + "version": "8.1.2.0", "testRunner": "xunit", "dependencies": { @@ -16,7 +16,7 @@ "type": "platform", "version": "1.0.0" }, - "WindowsAzure.Storage": "8.1.1" + "WindowsAzure.Storage": "8.1.2" }, "imports": "dnxcore50" diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index 339bec809..b8cf2cab0 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "8.1.1.0", + "version": "8.1.2.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "8.1.1-test" + "WindowsAzure.Storage": "8.1.2-test" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index 0dfd20fb4..63cc7e491 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,11 +1,11 @@ { - "version": "8.1.1.0", + "version": "8.1.2.0", "testRunner": "xunit", "dependencies": { "xunit": "2.1.0", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Microsoft.WindowsAzure.Storage": "8.1.1.0", + "Microsoft.WindowsAzure.Storage": "8.1.2.0", "XUnitForMsTest": "1.0.0-*" }, diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 39fcf768c..6edf3c94f 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index f4ff191a1..bdeb22b03 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index c108a395d..5225eadeb 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 03520289e..a80e0ff10 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 83d42a3c6..6b8d9d43e 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.1.0")] -[assembly: AssemblyFileVersion("8.1.1.0")] +[assembly: AssemblyVersion("8.1.2.0")] +[assembly: AssemblyFileVersion("8.1.2.0")] [assembly: ComVisible(false)] diff --git a/changelog.txt b/changelog.txt index c46ff2d58..26af5a97f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,10 @@ Changes in 8.2 - All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. +Changes in 8.1.2 : +- Blobs (Desktop) : Fixed a bug where the MaximumExecutionTime was not honored, leading to infinite wait, if due to a failure, e.g., a network failure after receiving the response headers, server stopped sending partial response. +- All(Desktop) : Fixed a memory leak issue where the SendStream was not being disposed during retries, cancellations and Table Operations. + Changes in 8.1.1 : - All (NetStandard/NetCore): Removed Microsoft.Data.Services.Client as a temporary fix to ODataLib dependency incompatibility with NetStandard @@ -51,6 +55,7 @@ Changes in 8.0.0 : - Blobs: Fixed error for AcquireLease on non-existent container. - PageBlobs: Fixed error response for GetPageRangesDiff. - Blobs: Update in "If-None-Match: *" pre-condition behavior to fail for reads(previously disregarded by the service). +- Blobs: CreateIfNotExists calls on a Container will be logged by AppInsight as a failure if the container exists. The failure is ignored by the library and this change saves an extra service call in case the container doesn't exist. Changes in 7.2.1 : diff --git a/global.json b/global.json index d43694b76..f6a5a6b7a 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "lib", "Lib/AspNet" ], "sdk": { - "version": "1.0.0-preview2-1-003177" + "version": "1.0.0-preview2-003131" } } From b5760b3a6346d938c1a5a2b32a353543db4a6fdc Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Wed, 24 May 2017 07:14:47 -0700 Subject: [PATCH 29/37] update assembly versions to 8.1.3 --- ...rosoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs | 2 +- ...dowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs | 2 +- ....WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- .../Microsoft.WindowsAzure.Storage.Facade/project.json | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs | 6 +++--- .../WindowsAzure.StorageK.nuspec | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsDesktop/WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- README.md | 2 +- .../project.json | 4 ++-- .../project.json | 4 ++-- Test/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone81/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- changelog.txt | 2 +- 23 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index ea492e9dd..123603ce5 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -245,7 +245,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.2"; + public const string AgentMetadataValue = ".NET 8.1.3"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index a37dbf002..cccaf9b0b 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.2"; + public const string AgentMetadataValue = ".NET 8.1.3"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index c3aa5b1e8..47c9128d3 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/8.1.2 "; + public static readonly string UserAgent = "Azure-Storage/8.1.3 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "8.1.2"; + public const string UserAgentProductVersion = "8.1.3"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index 14c6d26f8..62fffc175 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index e5af2d52d..368d4eb62 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "8.1.2.0", + "version": "8.1.3.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index d5808a5c6..5964e9ba1 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] -[assembly: AssemblyInformationalVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyInformationalVersion("8.1.3.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index 3bd5e01fc..17aed33b3 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.2 + 8.1.3 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index fc005f604..c1b10524f 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "8.1.2.0", + "version": "8.1.3.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index ae2f2286f..49f11ae7d 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -824,7 +824,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "8.1.2"; + public const string UserAgentProductVersion = "8.1.3"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index f6fa0fdad..46bd2da39 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index 3bd5e01fc..17aed33b3 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.2 + 8.1.3 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 3a8ca1cd4..b088ce25c 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index fc4b31e17..a75c0f84a 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index 68abd08b7..b1840ce18 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: ComVisible(false)] diff --git a/README.md b/README.md index bd0530183..098b27a97 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (8.1.2) +# Microsoft Azure Storage SDK for .NET (8.1.3) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 330cd4580..2fc11a9cb 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "8.1.2.0", + "version": "8.1.3.0", "testRunner": "xunit", "dependencies": { @@ -16,7 +16,7 @@ "type": "platform", "version": "1.0.0" }, - "WindowsAzure.Storage": "8.1.2" + "WindowsAzure.Storage": "8.1.3" }, "imports": "dnxcore50" diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index b8cf2cab0..91bd54de1 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "8.1.2.0", + "version": "8.1.3.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "8.1.2-test" + "WindowsAzure.Storage": "8.1.3-test" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 6edf3c94f..3142cf80b 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index bdeb22b03..fd8aab3d9 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index 5225eadeb..f0b1536be 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index a80e0ff10..46c310842 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 6b8d9d43e..deb26d85c 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.2.0")] -[assembly: AssemblyFileVersion("8.1.2.0")] +[assembly: AssemblyVersion("8.1.3.0")] +[assembly: AssemblyFileVersion("8.1.3.0")] [assembly: ComVisible(false)] diff --git a/changelog.txt b/changelog.txt index 26af5a97f..cd632f647 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,7 @@ Changes in 8.2 - All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. -Changes in 8.1.2 : +Changes in 8.1.3 : - Blobs (Desktop) : Fixed a bug where the MaximumExecutionTime was not honored, leading to infinite wait, if due to a failure, e.g., a network failure after receiving the response headers, server stopped sending partial response. - All(Desktop) : Fixed a memory leak issue where the SendStream was not being disposed during retries, cancellations and Table Operations. From 96fd54f6316a4becab65667b51ca799ca7537205 Mon Sep 17 00:00:00 2001 From: Josh Friedman Date: Mon, 5 Jun 2017 17:22:35 -0700 Subject: [PATCH 30/37] Fixed a bug with passing modifiedOptions and addressing customer request --- Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs | 2 +- Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs index 7dd75a01f..d647e51a1 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs @@ -217,7 +217,7 @@ public virtual bool CreateIfNotExists(BlobContainerPublicAccessType accessType, try { - this.Create(accessType, requestOptions, operationContext); + this.Create(accessType, modifiedOptions, operationContext); return true; } catch (StorageException e) diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index c657ac566..e5c449dff 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -1043,9 +1043,9 @@ private void UploadFromFileCallback(IAsyncResult asyncResult) /// An that references the pending asynchronous operation. public virtual void EndUploadFromFile(IAsyncResult asyncResult) { - if (asyncResult is CancellableAsyncResultTaskWrapper) + CancellableAsyncResultTaskWrapper cancellableAsyncResult = asyncResult as CancellableAsyncResultTaskWrapper; + if (cancellableAsyncResult != null) { - CancellableAsyncResultTaskWrapper cancellableAsyncResult = asyncResult as CancellableAsyncResultTaskWrapper; cancellableAsyncResult.Wait(); } else From fe7dde06c383c70d2edae0aeeb41dee47f3f295e Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Tue, 6 Jun 2017 14:04:43 -0700 Subject: [PATCH 31/37] [8.1.4] Executor Regression Fix --- ...rosoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs | 2 +- ...dowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs | 2 +- ....WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs | 4 ++-- .../Properties/AssemblyInfo.cs | 4 ++-- .../Microsoft.WindowsAzure.Storage.Facade/project.json | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs | 6 +++--- .../WindowsAzure.StorageK.nuspec | 2 +- Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json | 2 +- Lib/ClassLibraryCommon/Core/Executor/Executor.cs | 2 -- Lib/Common/Core/Executor/ExecutionState.cs | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsDesktop/WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 ++-- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- README.md | 2 +- .../project.json | 4 ++-- .../project.json | 4 ++-- Test/WindowsDesktop/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhone81/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs | 4 ++-- Test/WindowsRuntime/Properties/AssemblyInfo.cs | 4 ++-- changelog.txt | 5 ++++- 25 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index 123603ce5..e5355fcce 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -245,7 +245,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.3"; + public const string AgentMetadataValue = ".NET 8.1.4"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index cccaf9b0b..b730600df 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.3"; + public const string AgentMetadataValue = ".NET 8.1.4"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index 47c9128d3..c1dbdbef6 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/8.1.3 "; + public static readonly string UserAgent = "Azure-Storage/8.1.4 "; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "8.1.3"; + public const string UserAgentProductVersion = "8.1.4"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index 62fffc175..ca0701e5b 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index 368d4eb62..77c506fc1 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "8.1.3.0", + "version": "8.1.4.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 5964e9ba1..570ffa4d0 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] -[assembly: AssemblyInformationalVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyInformationalVersion("8.1.4.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index 17aed33b3..ad8978088 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.3 + 8.1.4 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index c1b10524f..3e49de528 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "8.1.3.0", + "version": "8.1.4.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs index f94dccbed..83c656bd9 100644 --- a/Lib/ClassLibraryCommon/Core/Executor/Executor.cs +++ b/Lib/ClassLibraryCommon/Core/Executor/Executor.cs @@ -508,7 +508,6 @@ private static void EndOperation(ExecutionState executionState) Logger.LogError(executionState.OperationContext, shouldRetry ? SR.TraceRetryDecisionTimeout : SR.TraceRetryDecisionPolicy, executionState.ExceptionRef.Message); // No Retry - executionState.CheckDisposeSendStream(); executionState.OnComplete(); } else @@ -573,7 +572,6 @@ private static void EndOperation(ExecutionState executionState) private static void RetryRequest(object state) { ExecutionState executionState = (ExecutionState)state; - executionState.CheckDisposeSendStream(); Logger.LogInformational(executionState.OperationContext, SR.TraceRetry); Executor.FireRetrying(executionState); Executor.InitRequest(executionState); diff --git a/Lib/Common/Core/Executor/ExecutionState.cs b/Lib/Common/Core/Executor/ExecutionState.cs index 0b36b5cbd..41c100fa0 100644 --- a/Lib/Common/Core/Executor/ExecutionState.cs +++ b/Lib/Common/Core/Executor/ExecutionState.cs @@ -228,7 +228,7 @@ internal bool ReqTimedOut } } - internal void CheckDisposeSendStream() + private void CheckDisposeSendStream() { RESTCommand cmd = this.RestCMD; diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 49f11ae7d..f29397940 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -824,7 +824,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "8.1.3"; + public const string UserAgentProductVersion = "8.1.4"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 46bd2da39..13a351fbf 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index 17aed33b3..ad8978088 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.3 + 8.1.4 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index b088ce25c..ca4a97dec 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index a75c0f84a..b0ced1e86 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index b1840ce18..ff8ea710e 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: ComVisible(false)] diff --git a/README.md b/README.md index 098b27a97..8ce50b215 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (8.1.3) +# Microsoft Azure Storage SDK for .NET (8.1.4) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 2fc11a9cb..65e44150e 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "8.1.3.0", + "version": "8.1.4.0", "testRunner": "xunit", "dependencies": { @@ -16,7 +16,7 @@ "type": "platform", "version": "1.0.0" }, - "WindowsAzure.Storage": "8.1.3" + "WindowsAzure.Storage": "8.1.4" }, "imports": "dnxcore50" diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index 91bd54de1..968feb381 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "8.1.3.0", + "version": "8.1.4.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "8.1.3-test" + "WindowsAzure.Storage": "8.1.4-test" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 3142cf80b..33a3f1b17 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index fd8aab3d9..c15f65a51 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index f0b1536be..3bfbf1f8a 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 46c310842..08441752b 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index deb26d85c..4e0047a9c 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.3.0")] -[assembly: AssemblyFileVersion("8.1.3.0")] +[assembly: AssemblyVersion("8.1.4.0")] +[assembly: AssemblyFileVersion("8.1.4.0")] [assembly: ComVisible(false)] diff --git a/changelog.txt b/changelog.txt index cd632f647..d878a8950 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,10 @@ -Changes in 8.2 +Changes in 8.2.0 - All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. +Changes in 8.1.4 : +- All(Desktop): Fixed a regression in the executor caused by disposing the SendStream during parallel operations. + Changes in 8.1.3 : - Blobs (Desktop) : Fixed a bug where the MaximumExecutionTime was not honored, leading to infinite wait, if due to a failure, e.g., a network failure after receiving the response headers, server stopped sending partial response. - All(Desktop) : Fixed a memory leak issue where the SendStream was not being disposed during retries, cancellations and Table Operations. From 8251535d945fbfeda1c93e728e314586a4bb3f8a Mon Sep 17 00:00:00 2001 From: Dogu Arslan Date: Fri, 9 Jun 2017 19:31:56 +0100 Subject: [PATCH 32/37] TableEntityAdapter class and unit tests (#436) TableEntityAdapter class and unit tests * Update on code comments, typos. * Additional unit tests for struct type POCO objects. * New struct type test entity with value and reference type properties. Does not implement ITableEntity interface or inherit from TableEntity. * Updated changelog, additional remarks for api usage. * Additional remarks for api usage --- Lib/Common/Table/TableEntityAdapter.cs | 93 +++++++++++++ .../Table/TableOperationUnitTests.cs | 126 ++++++++++++++++++ Test/Common/Table/Entities/ShapeEntity.cs | 5 - Test/Common/Table/Entities/StructEntity.cs | 28 ++++ .../Table/TableOperationUnitTests.cs | 49 +++++++ changelog.txt | 1 + 6 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 Lib/Common/Table/TableEntityAdapter.cs create mode 100644 Test/Common/Table/Entities/StructEntity.cs diff --git a/Lib/Common/Table/TableEntityAdapter.cs b/Lib/Common/Table/TableEntityAdapter.cs new file mode 100644 index 000000000..2fa168f5f --- /dev/null +++ b/Lib/Common/Table/TableEntityAdapter.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// // Licensed 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. +// +// -------------------------------------------------------------------------------------------------------------------- +namespace Microsoft.WindowsAzure.Storage.Table +{ + using System.Collections.Generic; + + /// + /// Adapter class to allow reading and writing objects to Azure Table Storage without inheriting from class + /// or implementing interface. The objects can be simple POCO objects or complex objects with nested complex properties. + /// + /// The type of object to read and write to Azure Table Storage, it can be a class or a struct. + public class TableEntityAdapter : TableEntity + { + /// + /// Initializes a new instance of the class. + /// + public TableEntityAdapter() + { + } + + /// + /// Initializes a new instance of the class with the specified object. + /// + /// The object to write to Azure Table Storage. + public TableEntityAdapter(T originalEntity) + { + this.OriginalEntity = originalEntity; + } + + /// + /// Initializes a new instance of the class with the specified object, partition key and row key. + /// + /// The object to write to Azure Table Storage. + /// A string containing the partition key value for the entity. + /// A string containing the row key value for the entity. + public TableEntityAdapter(T originalEntity, string partitionKey, string rowKey) + : base(partitionKey, rowKey) + { + this.OriginalEntity = originalEntity; + } + + /// + /// The original entity that is read and written to azure table storage. + /// + public T OriginalEntity { get; set; } + + /// + /// Deserializes instance using the specified that maps property names of the + /// to typed values and stores it in the property. + /// + /// An object that maps property names to typed values. + /// An object that represents the context for the current operation. + public override void ReadEntity(IDictionary properties, OperationContext operationContext) + { + this.OriginalEntity = ConvertBack(properties, operationContext); + } + + /// + /// Serializes the of property names mapped to data values from the property. + /// + /// An object that represents the context for the current operation. + /// An object that maps string property names to typed values created by + /// serializing this table entity instance. + /// If is a simple POCO object with simple properties (primitive types, string, byte[], ...), method will create + /// objects using these properties.
+ /// Ie. A simple POCO object A with properties of B and C with this structure A->B, A->C, will be converted to key value pairs of {"B", EntityProperty(B)}, {"C", EntityProperty(C)}.
+ /// If has complex properties (and potentially these properties having complex properties of their own), method will flatten first.
+ /// Ie. An object A with a simple property of B and complex properties of C and D which have their own properties of E and F with this structure A->B, A->C->E and A->D->F, will be flattened to key value pairs of:
+ /// {"B", EntityProperty(B)}, {"C_E", EntityProperty(E)} and {"D_F", EntityProperty(F)}.
+ /// For each key value pair:
+ /// 1. The key is composed by appending the names of the properties visited from root (A) to end node property (E or F) delimited by "_".
+ /// 2. The value is the object, instantiated by the value of the end node property.
+ /// All key value pairs will be stored in the returned .
+ /// method recomposes the original object (POCO or complex) using the returned by this method and store it in property.
+ /// Properties that are marked with in the object will be ignored and not processed by this method.
+ public override IDictionary WriteEntity(OperationContext operationContext) + { + return Flatten(this.OriginalEntity, operationContext); + } + } +} diff --git a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs index 9c9d89538..55ff9222e 100644 --- a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs @@ -2887,6 +2887,64 @@ public void FlattenAndRecomposeSimpleObject() Assert.AreEqual(properties["Breadth"].Int32Value, recomposedShapeEntity.Breadth); } + [TestMethod] + [Description("Tests writing and reading simple object with TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeSimpleObjectWithTableEntityAdapter() + { + ShapeEntity shapeEntity = new ShapeEntity(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "square", 4, 4); + OperationContext operationContext = new OperationContext(); + + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(shapeEntity, shapeEntity.PartitionKey, shapeEntity.RowKey); + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + + Assert.AreEqual(5, properties.Count); + Assert.AreEqual(shapeEntity.PartitionKey, properties["PartitionKey"].StringValue); + Assert.AreEqual(shapeEntity.RowKey, properties["RowKey"].StringValue); + Assert.AreEqual(shapeEntity.Name, properties["Name"].StringValue); + Assert.AreEqual(shapeEntity.Length, properties["Length"].Int32Value); + Assert.AreEqual(shapeEntity.Breadth, properties["Breadth"].Int32Value); + + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + + Assert.AreEqual(properties["PartitionKey"].StringValue, readEntityAdapter.OriginalEntity.PartitionKey); + Assert.AreEqual(properties["RowKey"].StringValue, readEntityAdapter.OriginalEntity.RowKey); + Assert.AreEqual(properties["Name"].StringValue, readEntityAdapter.OriginalEntity.Name); + Assert.AreEqual(properties["Length"].Int32Value, readEntityAdapter.OriginalEntity.Length); + Assert.AreEqual(properties["Breadth"].Int32Value, readEntityAdapter.OriginalEntity.Breadth); + } + + [TestMethod] + [Description("Tests writing and reading complex struct with TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeComplexStructWithTableEntityAdapter() + { + StructEntity structEntity = new StructEntity { Breadth = 12, Length = 10, Name = "ComplexStructEntity" }; + + ComplexEntity originalComplexEntity = CreateComplexEntity(Guid.NewGuid().ToString(), 123); + structEntity.ComplextEntity = originalComplexEntity; + + OperationContext operationContext = new OperationContext(); + + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(structEntity, "partitionKey", "rowKey"); + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + + Assert.AreEqual(structEntity.Name, readEntityAdapter.OriginalEntity.Name); + Assert.AreEqual(structEntity.Length, readEntityAdapter.OriginalEntity.Length); + Assert.AreEqual(structEntity.Breadth, readEntityAdapter.OriginalEntity.Breadth); + ComplexEntity.AssertEquality(structEntity.ComplextEntity, readEntityAdapter.OriginalEntity.ComplextEntity); + } + [TestMethod] [Description("Flattens and recomposes complex object with flat object structure without nested properties")] [TestCategory(ComponentCategory.Table)] @@ -2917,6 +2975,24 @@ public void FlattenAndRecomposeComplexObject() } } + [TestMethod] + [Description("Tests writing and reading complex object with TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeComplexObjectWithTableEntityAdapter() + { + ComplexEntity originalComplexEntity = CreateComplexEntity(Guid.NewGuid().ToString(), 0); + OperationContext operationContext = new OperationContext(); + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(originalComplexEntity); + + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + ComplexEntity.AssertEquality(originalComplexEntity, readEntityAdapter.OriginalEntity); + } + [TestMethod] [Description("Flattens and recomposes complex object with multiple layers of nested object hierarchy, containing a mix of complex, composite and simple properties")] [TestCategory(ComponentCategory.Table)] @@ -2954,6 +3030,28 @@ public void FlattenAndRecomposeComplexObjectWithNestedComplexProperties() ComplexEntityWithNestedComplexProperties.AssertEquality(complexEntityWithNestedComplexProperties, recomposedObject); } + [TestMethod] + [Description("Tests reading and writing a complex object with multiple layers of nested object hierarchy using TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeNestedComplexObjectWithTableEntityAdapter() + { + string pk = Guid.NewGuid().ToString(); + Random rd = new Random(); + ComplexEntityWithNestedComplexProperties complexEntityWithNestedComplexProperties = CreateComplexEntityWithNestedComplexProperties(pk, rd.Next()); + OperationContext operationContext = new OperationContext(); + + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(); + writtenEntityAdapter.OriginalEntity = complexEntityWithNestedComplexProperties; + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + ComplexEntityWithNestedComplexProperties.AssertEquality(complexEntityWithNestedComplexProperties, readEntityAdapter.OriginalEntity); + } + [TestMethod] [Description("Flattening detects recursive referenced object and throws.")] [TestCategory(ComponentCategory.Table)] @@ -2979,6 +3077,34 @@ public void FlatteningDetectsRecursiveReferencedObjectsAndThrows() + " Property Type: Microsoft.WindowsAzure.Storage.Table.Entities.ComplexEntityWithNestedComplexProperties.", ex.Message); } + [TestMethod] + [Description("TableEntityAdapter detects recursive referenced object and throws.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableEntityAdapterDetectsRecursiveReferencedObjectsAndThrows() + { + string pk = Guid.NewGuid().ToString(); + Random rd = new Random(); + ComplexEntityWithNestedComplexProperties recursiveReferencedObject = CreateComplexEntityWithNestedComplexProperties(pk, rd.Next()); + + // Create a recursive reference to itself via its composite property + recursiveReferencedObject.InnerComplexEntityWithNestedComplexProperties = recursiveReferencedObject; + + OperationContext operationContext = new OperationContext(); + TableEntityAdapter writtenEntityAdapter = + new TableEntityAdapter(recursiveReferencedObject); + + // Flattening detects recursive reference and throws. + SerializationException ex = TestHelper.ExpectedException( + () => writtenEntityAdapter.WriteEntity(operationContext), + "Flatten should throw SerializationException for recursive referenced objects."); + + Assert.AreEqual("Recursive reference detected. Object Path: InnerComplexEntityWithNestedComplexProperties" + + " Property Type: Microsoft.WindowsAzure.Storage.Table.Entities.ComplexEntityWithNestedComplexProperties.", ex.Message); + } + private static ComplexEntity CreateComplexEntity(string pk, int seed) { ComplexEntity complexEntity = new ComplexEntity(pk, string.Format("{0:0000}", seed)); diff --git a/Test/Common/Table/Entities/ShapeEntity.cs b/Test/Common/Table/Entities/ShapeEntity.cs index f861a3501..b0c5d21b7 100644 --- a/Test/Common/Table/Entities/ShapeEntity.cs +++ b/Test/Common/Table/Entities/ShapeEntity.cs @@ -15,11 +15,6 @@ // //----------------------------------------------------------------------- -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - namespace Microsoft.WindowsAzure.Storage.Table.Entities { public class ShapeEntity diff --git a/Test/Common/Table/Entities/StructEntity.cs b/Test/Common/Table/Entities/StructEntity.cs new file mode 100644 index 000000000..38879ea2b --- /dev/null +++ b/Test/Common/Table/Entities/StructEntity.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed 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. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Table.Entities +{ + public struct StructEntity + { + public string Name { get; set; } + public int Length { get; set; } + public int Breadth { get; set; } + + public ComplexEntity ComplextEntity { get; set; } + } +} diff --git a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs index 56a3c3680..210b76046 100644 --- a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs +++ b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs @@ -1607,6 +1607,55 @@ public void FlattenAndRecomposeComplexObjectWithNestedComplexProperties() ComplexEntityWithNestedComplexProperties.AssertEquality(complexEntityWithNestedComplexProperties, recomposedObject); } + [TestMethod] + [Description("Tests reading and writing a complex object with multiple layers of nested object hierarchy using TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeNestedComplexObjectWithTableEntityAdapter() + { + string pk = Guid.NewGuid().ToString(); + Random rd = new Random(); + ComplexEntityWithNestedComplexProperties complexEntityWithNestedComplexProperties = CreateComplexEntityWithNestedComplexProperties(pk, rd.Next()); + OperationContext operationContext = new OperationContext(); + + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(); + writtenEntityAdapter.OriginalEntity = complexEntityWithNestedComplexProperties; + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + ComplexEntityWithNestedComplexProperties.AssertEquality(complexEntityWithNestedComplexProperties, readEntityAdapter.OriginalEntity); + } + + [TestMethod] + [Description("Tests writing and reading complex struct with TableEntityAdapter")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void FlattenAndRecomposeComplexStructWithTableEntityAdapter() + { + StructEntity structEntity = new StructEntity { Breadth = 12, Length = 10, Name = "ComplexStructEntity" }; + + ComplexEntity originalComplexEntity = CreateComplexEntity(Guid.NewGuid().ToString(), 123); + structEntity.ComplextEntity = originalComplexEntity; + + OperationContext operationContext = new OperationContext(); + + TableEntityAdapter writtenEntityAdapter = new TableEntityAdapter(structEntity, "partitionKey", "rowKey"); + IDictionary properties = writtenEntityAdapter.WriteEntity(operationContext); + + TableEntityAdapter readEntityAdapter = new TableEntityAdapter(); + readEntityAdapter.ReadEntity(properties, operationContext); + + Assert.AreEqual(structEntity.Name, readEntityAdapter.OriginalEntity.Name); + Assert.AreEqual(structEntity.Length, readEntityAdapter.OriginalEntity.Length); + Assert.AreEqual(structEntity.Breadth, readEntityAdapter.OriginalEntity.Breadth); + ComplexEntity.AssertEquality(structEntity.ComplextEntity, readEntityAdapter.OriginalEntity.ComplextEntity); + } + [TestMethod] [Description("Flattening detects recursive referenced object and throws.")] [TestCategory(ComponentCategory.Table)] diff --git a/changelog.txt b/changelog.txt index d878a8950..d97fb98fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ Changes in 8.2.0 - All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. +- Tables: Added TableEntityAdapter class to allow writing and reading simple or complex objects to Azure Table Storage without the need to implement ITableEntity interface or inherit from TableEntity class. Changes in 8.1.4 : - All(Desktop): Fixed a regression in the executor caused by disposing the SendStream during parallel operations. From 467cdd282a49b08c7747dbdfc91f398436f93a58 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Mon, 12 Jun 2017 19:36:55 +0200 Subject: [PATCH 33/37] Missing ConfigureAwait(false) in CloudFileDirectory, Exceptions and StreamExtensions (#380) --- Lib/ClassLibraryCommon/File/CloudFileDirectory.cs | 2 +- Lib/Common/Core/Util/Exceptions.cs | 2 +- Lib/Common/Core/Util/StreamExtensions.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs index d7bc0b5a0..c7a8eee80 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs @@ -301,7 +301,7 @@ private async Task CreateIfNotExistsAsyncHelper(FileRequestOptions options { // If the share does not exist, this will throw a 404, which is what we want. // This.Create() will throw the same 404 if the share does not exist, and this is not the root directory. - await this.ServiceClient.GetShareReference(this.Share.Name, this.Share.SnapshotTime).FetchAttributesAsync(null, options, context, cancellationToken); + await this.ServiceClient.GetShareReference(this.Share.Name, this.Share.SnapshotTime).FetchAttributesAsync(null, options, context, cancellationToken).ConfigureAwait(false); // If the above call did not throw an exception, then the share (and thus the root directory) already exists. return false; diff --git a/Lib/Common/Core/Util/Exceptions.cs b/Lib/Common/Core/Util/Exceptions.cs index ac4489b10..d84988039 100644 --- a/Lib/Common/Core/Util/Exceptions.cs +++ b/Lib/Common/Core/Util/Exceptions.cs @@ -61,7 +61,7 @@ internal async static Task PopulateStorageExceptionFromHttpRes try { - Stream errStream = await response.Content.ReadAsStreamAsync(); + Stream errStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); if (parseError != null) { currentResult.ExtendedErrorInformation = parseError(errStream, response, response.Content.Headers.ContentType.ToString()); diff --git a/Lib/Common/Core/Util/StreamExtensions.cs b/Lib/Common/Core/Util/StreamExtensions.cs index 732962b2e..1d6fd7517 100644 --- a/Lib/Common/Core/Util/StreamExtensions.cs +++ b/Lib/Common/Core/Util/StreamExtensions.cs @@ -296,7 +296,7 @@ internal static async Task WriteToAsync(this Stream stream, Stream toStream, break; } - readCount = await stream.ReadAsync(buffer, 0, bytesToRead, token); + readCount = await stream.ReadAsync(buffer, 0, bytesToRead, token).ConfigureAwait(false); if (bytesRemaining.HasValue) { @@ -305,7 +305,7 @@ internal static async Task WriteToAsync(this Stream stream, Stream toStream, if (readCount > 0) { - await toStream.WriteAsync(buffer, 0, readCount, token); + await toStream.WriteAsync(buffer, 0, readCount, token).ConfigureAwait(false); // Update the StreamDescriptor after the bytes are successfully committed to the output stream if (streamCopyState != null) @@ -342,7 +342,7 @@ internal static async Task WriteToAsync(this Stream stream, Stream toStream, // Streams opened with AsStreamForWrite extension need to be flushed // to write all buffered data to the underlying Windows Runtime stream. - await toStream.FlushAsync(); + await toStream.FlushAsync().ConfigureAwait(false); if (streamCopyState != null && streamCopyState.Md5HashRef != null) { From 127cab87c01790d4689106b25dfea8de4faea06b Mon Sep 17 00:00:00 2001 From: "Keith Farmer (MSFT)" Date: Mon, 3 Jul 2017 10:56:22 -0700 Subject: [PATCH 34/37] Task 1192950: Connection string improvements (#477) * Task 1192950: Adds SAS + Name to the cases of connection string parsing where endpoints are inferred; adds specification of secondary endpoints where primary is also specified; cleans up code and adds unit tests * Revert change to StorageCredentials, move AccountName to CloudStorageAccount for SAS case * update changelog for connection string changes * review feedback * review feedback * Make DefaultEndpointsProtocol optional; Rewrite ValidCredentials; Remove obsolete tests * review feedback --- Lib/Common/CloudStorageAccount.cs | 365 ++++++++++++----- Test/Common/Core/CloudStorageAccountTests.cs | 388 ++++++++++++++----- changelog.txt | 2 + 3 files changed, 566 insertions(+), 189 deletions(-) diff --git a/Lib/Common/CloudStorageAccount.cs b/Lib/Common/CloudStorageAccount.cs index ebc9e9290..ed87ad537 100644 --- a/Lib/Common/CloudStorageAccount.cs +++ b/Lib/Common/CloudStorageAccount.cs @@ -35,6 +35,7 @@ namespace Microsoft.WindowsAzure.Storage using System.Linq; using AccountSetting = System.Collections.Generic.KeyValuePair>; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using ConnectionStringFilter = System.Func, System.Collections.Generic.IDictionary>; /// /// Represents a Microsoft Azure Storage account. @@ -111,6 +112,26 @@ static bool UseV1MD5 /// internal const string FileEndpointSettingString = "FileEndpoint"; + /// + /// The setting name for a custom blob storage secondary endpoint. + /// + internal const string BlobSecondaryEndpointSettingString = "BlobSecondaryEndpoint"; + + /// + /// The setting name for a custom queue secondary endpoint. + /// + internal const string QueueSecondaryEndpointSettingString = "QueueSecondaryEndpoint"; + + /// + /// The setting name for a custom table storage secondary endpoint. + /// + internal const string TableSecondaryEndpointSettingString = "TableSecondaryEndpoint"; + + /// + /// The setting name for a custom file storage secondary endpoint. + /// + internal const string FileSecondaryEndpointSettingString = "FileSecondaryEndpoint"; + /// /// The setting name for a custom storage endpoint suffix. /// @@ -211,6 +232,26 @@ static bool UseV1MD5 ///
private static readonly AccountSetting FileEndpointSetting = Setting(FileEndpointSettingString, IsValidUri); + /// + /// Validator for the BlobSecondaryEndpoint setting. Must be a valid Uri. + /// + private static readonly AccountSetting BlobSecondaryEndpointSetting = Setting(BlobSecondaryEndpointSettingString, IsValidUri); + + /// + /// Validator for the QueueSecondaryEndpoint setting. Must be a valid Uri. + /// + private static readonly AccountSetting QueueSecondaryEndpointSetting = Setting(QueueSecondaryEndpointSettingString, IsValidUri); + + /// + /// Validator for the TableSecondaryEndpoint setting. Must be a valid Uri. + /// + private static readonly AccountSetting TableSecondaryEndpointSetting = Setting(TableSecondaryEndpointSettingString, IsValidUri); + + /// + /// Validator for the FileSecondaryEndpoint setting. Must be a valid Uri. + /// + private static readonly AccountSetting FileSecondaryEndpointSetting = Setting(FileSecondaryEndpointSettingString, IsValidUri); + /// /// Validator for the EndpointSuffix setting. Must be a valid Uri. /// @@ -458,6 +499,11 @@ public Uri FileEndpoint /// A object. public StorageCredentials Credentials { get; private set; } + /// + /// Private record of the account name for use in ToString(bool). + /// + private string accountName; + /// /// Parses a connection string and returns a created /// from the connection string. @@ -688,6 +734,11 @@ public string ToString(bool exportSecrets) listOfSettings.Add(this.Credentials.ToString(exportSecrets)); } + if (!string.IsNullOrWhiteSpace(this.accountName) && (this.Credentials != null ? string.IsNullOrWhiteSpace(this.Credentials.AccountName) : true)) + { + listOfSettings.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", AccountNameSettingString, this.accountName)); + } + return string.Join(";", listOfSettings); } @@ -756,19 +807,35 @@ internal static bool ParseImpl(string connectionString, out CloudStorageAccount IDictionary settings = ParseStringIntoSettings(connectionString, error); // malformed settings string + if (settings == null) { accountInformation = null; return false; } + + // helper method + + Func settingOrDefault = + (key) => + { + string result = default(string); + + settings.TryGetValue(key, out result); + + return result; + }; + // devstore case + if (MatchesSpecification( settings, AllRequired(UseDevelopmentStorageSetting), Optional(DevelopmentStorageProxyUriSetting))) { - string proxyUri = null; + string proxyUri; + if (settings.TryGetValue(DevelopmentStorageProxyUriSettingString, out proxyUri)) { accountInformation = GetDevelopmentStorageAccount(new Uri(proxyUri)); @@ -779,59 +846,116 @@ internal static bool ParseImpl(string connectionString, out CloudStorageAccount } accountInformation.Settings = ValidCredentials(settings); + return true; } - // automatic case - if (MatchesSpecification( - settings, - AllRequired(DefaultEndpointsProtocolSetting, AccountNameSetting, AccountKeySetting), - Optional(BlobEndpointSetting, QueueEndpointSetting, TableEndpointSetting, FileEndpointSetting, AccountKeyNameSetting, EndpointSuffixSetting))) + // non-devstore case + + ConnectionStringFilter endpointsOptional = + Optional( + BlobEndpointSetting, BlobSecondaryEndpointSetting, + QueueEndpointSetting, QueueSecondaryEndpointSetting, + TableEndpointSetting, TableSecondaryEndpointSetting, + FileEndpointSetting, FileSecondaryEndpointSetting + ); + + ConnectionStringFilter primaryEndpointRequired = + AtLeastOne( + BlobEndpointSetting, + QueueEndpointSetting, + TableEndpointSetting, + FileEndpointSetting + ); + + ConnectionStringFilter secondaryEndpointsOptional = + Optional( + BlobSecondaryEndpointSetting, + QueueSecondaryEndpointSetting, + TableSecondaryEndpointSetting, + FileSecondaryEndpointSetting + ); + + ConnectionStringFilter automaticEndpointsMatchSpec = + MatchesExactly(MatchesAll( + MatchesOne( + MatchesAll(AllRequired(AccountKeySetting), Optional(AccountKeyNameSetting)), // Key + Name, Endpoints optional + AllRequired(SharedAccessSignatureSetting) // SAS + Name, Endpoints optional + ), + AllRequired(AccountNameSetting), // Name required to automatically create URIs + endpointsOptional, + Optional(DefaultEndpointsProtocolSetting, EndpointSuffixSetting) + )); + + ConnectionStringFilter explicitEndpointsMatchSpec = + MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared + ValidCredentials, + primaryEndpointRequired, + secondaryEndpointsOptional + )); + + bool matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec); + bool matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec); + + if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec) { - string blobEndpoint = null; - settings.TryGetValue(BlobEndpointSettingString, out blobEndpoint); - - string queueEndpoint = null; - settings.TryGetValue(QueueEndpointSettingString, out queueEndpoint); - - string tableEndpoint = null; - settings.TryGetValue(TableEndpointSettingString, out tableEndpoint); - - string fileEndpoint = null; - settings.TryGetValue(FileEndpointSettingString, out fileEndpoint); - - accountInformation = new CloudStorageAccount( - GetCredentials(settings), - blobEndpoint != null ? new StorageUri(new Uri(blobEndpoint)) : ConstructBlobEndpoint(settings), - queueEndpoint != null ? new StorageUri(new Uri(queueEndpoint)) : ConstructQueueEndpoint(settings), - tableEndpoint != null ? new StorageUri(new Uri(tableEndpoint)) : ConstructTableEndpoint(settings), - fileEndpoint != null ? new StorageUri(new Uri(fileEndpoint)) : ConstructFileEndpoint(settings)); - - string endpointSuffix = null; - if (settings.TryGetValue(EndpointSuffixSettingString, out endpointSuffix)) + if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(DefaultEndpointsProtocolSettingString)) { - accountInformation.EndpointSuffix = endpointSuffix; + settings.Add(DefaultEndpointsProtocolSettingString, "https"); } - accountInformation.Settings = ValidCredentials(settings); - return true; - } - - // explicit case - if (MatchesSpecification( - settings, - AtLeastOne(BlobEndpointSetting, QueueEndpointSetting, TableEndpointSetting, FileEndpointSetting), - ValidCredentials)) - { - Uri blobUri = !settings.ContainsKey(BlobEndpointSettingString) || settings[BlobEndpointSettingString] == null ? null : new Uri(settings[BlobEndpointSettingString]); - Uri queueUri = !settings.ContainsKey(QueueEndpointSettingString) || settings[QueueEndpointSettingString] == null ? null : new Uri(settings[QueueEndpointSettingString]); - Uri tableUri = !settings.ContainsKey(TableEndpointSettingString) || settings[TableEndpointSettingString] == null ? null : new Uri(settings[TableEndpointSettingString]); - Uri fileUri = !settings.ContainsKey(FileEndpointSettingString) || settings[FileEndpointSettingString] == null ? null : new Uri(settings[FileEndpointSettingString]); - - accountInformation = new CloudStorageAccount(GetCredentials(settings), blobUri, queueUri, tableUri, fileUri); - - accountInformation.Settings = ValidCredentials(settings); - return true; + string blobEndpoint = settingOrDefault(BlobEndpointSettingString); + string queueEndpoint = settingOrDefault(QueueEndpointSettingString); + string tableEndpoint = settingOrDefault(TableEndpointSettingString); + string fileEndpoint = settingOrDefault(FileEndpointSettingString); + string blobSecondaryEndpoint = settingOrDefault(BlobSecondaryEndpointSettingString); + string queueSecondaryEndpoint = settingOrDefault(QueueSecondaryEndpointSettingString); + string tableSecondaryEndpoint = settingOrDefault(TableSecondaryEndpointSettingString); + string fileSecondaryEndpoint = settingOrDefault(FileSecondaryEndpointSettingString); + + // if secondary is specified, primary must also be specified + + Func isValidEndpointPair = + (primary, secondary) => + !String.IsNullOrWhiteSpace(primary) + || /* primary is null, and... */ String.IsNullOrWhiteSpace(secondary); + + Func, StorageUri>, StorageUri> createStorageUri = + (primary, secondary, factory) => + !String.IsNullOrWhiteSpace(secondary) && !String.IsNullOrWhiteSpace(primary) + ? new StorageUri(new Uri(primary), new Uri(secondary)) + : !String.IsNullOrWhiteSpace(primary) + ? new StorageUri(new Uri(primary)) + : matchesAutomaticEndpointsSpec && factory != null + ? factory(settings) + : new StorageUri(null) + ; + + if ( + isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint) + && isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint) + && isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint) + && isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint) + ) + { + accountInformation = + new CloudStorageAccount( + GetCredentials(settings), + createStorageUri(blobEndpoint, blobSecondaryEndpoint, ConstructBlobEndpoint), + createStorageUri(queueEndpoint, queueSecondaryEndpoint, ConstructQueueEndpoint), + createStorageUri(tableEndpoint, tableSecondaryEndpoint, ConstructTableEndpoint), + createStorageUri(fileEndpoint, fileSecondaryEndpoint, ConstructFileEndpoint) + ) + { + DefaultEndpoints = (blobEndpoint == null && queueEndpoint == null && tableEndpoint == null && fileEndpoint == null), + EndpointSuffix = settingOrDefault(EndpointSuffixSettingString), + Settings = ValidCredentials(settings) + }; + + accountInformation.accountName = settingOrDefault(AccountNameSettingString); + + return true; + } } // not valid @@ -951,7 +1075,7 @@ private static bool IsValidDomain(string settingValue) /// /// A list of settings that must be present. /// The remaining settings or null if the filter's requirement is not satisfied. - private static Func, IDictionary> AllRequired(params AccountSetting[] requiredSettings) + private static ConnectionStringFilter AllRequired(params AccountSetting[] requiredSettings) { return (settings) => { @@ -979,7 +1103,7 @@ private static Func, IDictionary> Al ///
/// A list of settings that are optional. /// The remaining settings or null if the filter's requirement is not satisfied. - private static Func, IDictionary> Optional(params AccountSetting[] optionalSettings) + private static ConnectionStringFilter Optional(params AccountSetting[] optionalSettings) { return (settings) => { @@ -1004,7 +1128,7 @@ private static Func, IDictionary> Op /// A list of settings of which one must be present. /// The remaining settings or null if the filter's requirement is not satisfied. [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] - private static Func, IDictionary> AtLeastOne(params AccountSetting[] atLeastOneSettings) + private static ConnectionStringFilter AtLeastOne(params AccountSetting[] atLeastOneSettings) { return (settings) => { @@ -1026,67 +1150,116 @@ private static Func, IDictionary> At } /// - /// Settings filter that ensures that a valid combination of credentials is present. + /// Settings filter that ensures that none of the specified settings are present. /// + /// A list of settings of which one must not be present. /// The remaining settings or null if the filter's requirement is not satisfied. - private static IDictionary ValidCredentials(IDictionary settings) + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] + private static ConnectionStringFilter None(params AccountSetting[] atLeastOneSettings) { - string accountName; - string accountKey; - string accountKeyName; - string sharedAccessSignature; - IDictionary result = new Dictionary(settings); - - if (settings.TryGetValue(AccountNameSettingString, out accountName) && - !AccountNameSetting.Value(accountName)) + return (settings) => { - return null; - } + IDictionary result = new Dictionary(settings); + bool foundOne = false; - if (settings.TryGetValue(AccountKeySettingString, out accountKey) && - !AccountKeySetting.Value(accountKey)) - { - return null; - } + foreach (AccountSetting requirement in atLeastOneSettings) + { + string value; + if (result.TryGetValue(requirement.Key, out value) && requirement.Value(value)) + { + foundOne = true; + } + } - if (settings.TryGetValue(AccountKeyNameSettingString, out accountKeyName) && - !AccountKeyNameSetting.Value(accountKeyName)) - { - return null; - } + return foundOne ? null : result; + }; + } - if (settings.TryGetValue(SharedAccessSignatureSettingString, out sharedAccessSignature) && - !SharedAccessSignatureSetting.Value(sharedAccessSignature)) + /// + /// Settings filter that ensures that all of the specified filters match. + /// + /// A list of filters of which all must match. + /// The remaining settings or null if the filter's requirement is not satisfied. + private static ConnectionStringFilter MatchesAll(params ConnectionStringFilter[] filters) + { + return (settings) => { - return null; - } + IDictionary result = new Dictionary(settings); - result.Remove(AccountNameSettingString); - result.Remove(AccountKeySettingString); - result.Remove(AccountKeyNameSettingString); - result.Remove(SharedAccessSignatureSettingString); + foreach (ConnectionStringFilter filter in filters) + { + if (result == null) + { + break; + } + + result = filter(result); + } - // AccountAndKey - if (accountName != null && accountKey != null && sharedAccessSignature == null) - { return result; - } + }; + } - // SharedAccessSignature - if (accountName == null && accountKey == null && accountKeyName == null && sharedAccessSignature != null) + /// + /// Settings filter that ensures that exactly one filter matches. + /// + /// A list of filters of which exactly one must match. + /// The remaining settings or null if the filter's requirement is not satisfied. + private static ConnectionStringFilter MatchesOne(params ConnectionStringFilter[] filters) + { + return (settings) => { - return result; - } + IDictionary[] results = + filters + .Select(filter => filter(new Dictionary(settings))) + .Where(result => result != null) + .Take(2) + .ToArray(); + + if (results.Length != 1) + { + return null; + } + else + { + return results.First(); + } + }; + } - // Anonymous - if (accountName == null && accountKey == null && accountKeyName == null && sharedAccessSignature == null) + /// + /// Settings filter that ensures that the specified filter is an exact match. + /// + /// A list of filters of which ensures that the specified filter is an exact match. + /// The remaining settings or null if the filter's requirement is not satisfied. + private static ConnectionStringFilter MatchesExactly(ConnectionStringFilter filter) + { + return (settings) => { - return result; - } + IDictionary results = filter(settings); - return null; + if (results == null || results.Any()) + { + return null; + } + else + { + return results; + } + }; } + /// + /// Settings filter that ensures that a valid combination of credentials is present. + /// + /// The remaining settings or null if the filter's requirement is not satisfied. + private static ConnectionStringFilter ValidCredentials = + MatchesOne( + MatchesAll(AllRequired(AccountNameSetting, AccountKeySetting), Optional(AccountKeyNameSetting), None(SharedAccessSignatureSetting)), // AccountAndKey + MatchesAll(AllRequired(SharedAccessSignatureSetting), Optional(AccountNameSetting), None(AccountKeySetting, AccountKeyNameSetting)), // SharedAccessSignature (AccountName optional) + None(AccountNameSetting, AccountKeySetting, AccountKeyNameSetting, SharedAccessSignatureSetting) // Anonymous + ); + /// /// Tests to see if a given list of settings matches a set of filters exactly. /// @@ -1099,9 +1272,9 @@ private static IDictionary ValidCredentials(IDictionary private static bool MatchesSpecification( IDictionary settings, - params Func, IDictionary>[] constraints) + params ConnectionStringFilter[] constraints) { - foreach (Func, IDictionary> constraint in constraints) + foreach (ConnectionStringFilter constraint in constraints) { IDictionary remainingSettings = constraint(settings); @@ -1145,7 +1318,7 @@ private static StorageCredentials GetCredentials(IDictionary set return new StorageCredentials(accountName, accountKey, accountKeyName); } - if (accountName == null && accountKey == null && accountKeyName == null && sharedAccessSignature != null) + if (accountKey == null && accountKeyName == null && sharedAccessSignature != null) { return new StorageCredentials(sharedAccessSignature); } diff --git a/Test/Common/Core/CloudStorageAccountTests.cs b/Test/Common/Core/CloudStorageAccountTests.cs index d73de41d3..0a9f46ddc 100644 --- a/Test/Common/Core/CloudStorageAccountTests.cs +++ b/Test/Common/Core/CloudStorageAccountTests.cs @@ -534,44 +534,325 @@ public void CloudStorageAccountEndpointSuffixWithBlob() [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] public void CloudStorageAccountConnectionStringRoundtrip() { + string[] accountKeyParams = new[] + { + TestBase.TargetTenantConfig.AccountName, + TestBase.TargetTenantConfig.AccountKey, + "fake.endpoint.suffix", + "https://primary.endpoint/", + "https://secondary.endpoint/" + }; + + string[] accountSasParams = new[] + { + TestBase.TargetTenantConfig.AccountName, + "sasTest", + "fake.endpoint.suffix", + "https://primary.endpoint/", + "https://secondary.endpoint/" + }; + + // account key + string accountString1 = string.Format( "DefaultEndpointsProtocol=http;AccountName={0};AccountKey={1};EndpointSuffix={2};", - TestBase.TargetTenantConfig.AccountName, - TestBase.TargetTenantConfig.AccountKey, - "fake.endpoint.suffix"); + accountKeyParams); string accountString2 = string.Format( "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};", - TestBase.TargetTenantConfig.AccountName, - TestBase.TargetTenantConfig.AccountKey); + accountKeyParams); string accountString3 = string.Format( - "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};QueueEndpoint={2}", - TestBase.TargetTenantConfig.AccountName, - TestBase.TargetTenantConfig.AccountKey, - "https://alternate.queue.endpoint/"); + "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};QueueEndpoint={3}", + accountKeyParams); string accountString4 = string.Format( "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2};QueueEndpoint={3}", - TestBase.TargetTenantConfig.AccountName, - TestBase.TargetTenantConfig.AccountKey, - "fake.endpoint.suffix", - "https://alternate.queue.endpoint/"); + accountKeyParams); connectionStringRoundtripHelper(accountString1); connectionStringRoundtripHelper(accountString2); connectionStringRoundtripHelper(accountString3); connectionStringRoundtripHelper(accountString4); + + string accountString5 = + string.Format( + "AccountName={0};AccountKey={1};EndpointSuffix={2};", + accountKeyParams); + + string accountString6 = + string.Format( + "AccountName={0};AccountKey={1};", + accountKeyParams); + + string accountString7 = + string.Format( + "AccountName={0};AccountKey={1};QueueEndpoint={3}", + accountKeyParams); + + string accountString8 = + string.Format( + "AccountName={0};AccountKey={1};EndpointSuffix={2};QueueEndpoint={3}", + accountKeyParams); + + connectionStringRoundtripHelper(accountString5); + connectionStringRoundtripHelper(accountString6); + connectionStringRoundtripHelper(accountString7); + connectionStringRoundtripHelper(accountString8); + + // shared access + + string accountString9 = + string.Format( + "DefaultEndpointsProtocol=http;AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};", + accountSasParams); + + string accountString10 = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};", + accountSasParams); + + string accountString11 = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};QueueEndpoint={3}", + accountSasParams); + + string accountString12 = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};QueueEndpoint={3}", + accountSasParams); + + connectionStringRoundtripHelper(accountString9); + connectionStringRoundtripHelper(accountString10); + connectionStringRoundtripHelper(accountString11); + connectionStringRoundtripHelper(accountString12); + + string accountString13 = + string.Format( + "AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};", + accountSasParams); + + string accountString14 = + string.Format( + "AccountName={0};SharedAccessSignature={1};", + accountSasParams); + + string accountString15 = + string.Format( + "AccountName={0};SharedAccessSignature={1};QueueEndpoint={3}", + accountSasParams); + + string accountString16 = + string.Format( + "AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};QueueEndpoint={3}", + accountSasParams); + + connectionStringRoundtripHelper(accountString13); + connectionStringRoundtripHelper(accountString14); + connectionStringRoundtripHelper(accountString15); + connectionStringRoundtripHelper(accountString16); + + // shared access no account name + + string accountString17 = + string.Format( + "SharedAccessSignature={1};QueueEndpoint={3}", + accountSasParams); + + connectionStringRoundtripHelper(accountString17); + } + + [TestMethod] + [Description("Regular account with HTTP")] + [TestCategory(ComponentCategory.Core)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudStorageAccountConnectionStringExpectedExceptions() + { + string[][] endpointCombinations = new[] + { + new[] { "BlobEndpoint={3}", "BlobSecondaryEndpoint={4}", "BlobEndpoint={3};BlobSecondaryEndpoint={4}" }, + new[] { "QueueEndpoint={3}", "QueueSecondaryEndpoint={4}", "QueueEndpoint={3};QueueSecondaryEndpoint={4}" }, + new[] { "TableEndpoint={3}", "TableSecondaryEndpoint={4}", "TableEndpoint={3};TableSecondaryEndpoint={4}" }, + new[] { "FileEndpoint={3}", "FileSecondaryEndpoint={4}", "FileEndpoint={3};FileSecondaryEndpoint={4}" } + }; + + string[] accountKeyParams = new[] + { + TestBase.TargetTenantConfig.AccountName, + TestBase.TargetTenantConfig.AccountKey, + "fake.endpoint.suffix", + "https://primary.endpoint/", + "https://secondary.endpoint/" + }; + + string[] accountSasParams = new[] + { + TestBase.TargetTenantConfig.AccountName, + "sasTest", + "fake.endpoint.suffix", + "https://primary.endpoint/", + "https://secondary.endpoint/" + }; + + foreach (string[] endpointCombination in endpointCombinations) + { + // account key + + string accountStringKeyPrimary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[0], + accountKeyParams + ); + + string accountStringKeySecondary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[1], + accountKeyParams + ); + + + string accountStringKeyPrimarySecondary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[2], + accountKeyParams + ); + + + CloudStorageAccount.Parse(accountStringKeyPrimary); // no exception expected + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringKeySecondary), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringKeyPrimarySecondary); // no exception expected + + // account key, no default protocol + + string accountStringKeyNoDefaultProtocolPrimary = + string.Format( + "AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[0], + accountKeyParams + ); + + string accountStringKeyNoDefaultProtocolSecondary = + string.Format( + "AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[1], + accountKeyParams + ); + + + string accountStringKeyNoDefaultProtocolPrimarySecondary = + string.Format( + "AccountName={0};AccountKey={1};EndpointSuffix={2};" + endpointCombination[2], + accountKeyParams + ); + + + CloudStorageAccount.Parse(accountStringKeyNoDefaultProtocolPrimary); // no exception expected + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringKeyNoDefaultProtocolSecondary), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringKeyNoDefaultProtocolPrimarySecondary); // no exception expected + + // SAS + + string accountStringSasPrimary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[0], + accountSasParams + ); + + string accountStringSasSecondary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[1], + accountSasParams + ); + + string accountStringSasPrimarySecondary = + string.Format( + "DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[2], + accountSasParams + ); + + CloudStorageAccount.Parse(accountStringSasPrimary); // no exception expected + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringSasSecondary), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringSasPrimarySecondary); // no exception expected + + // SAS, no default protocol + + string accountStringSasNoDefaultProtocolPrimary = + string.Format( + "AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[0], + accountSasParams + ); + + string accountStringSasNoDefaultProtocolSecondary = + string.Format( + "AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[1], + accountSasParams + ); + + string accountStringSasNoDefaultProtocolPrimarySecondary = + string.Format( + "AccountName={0};SharedAccessSignature={1};EndpointSuffix={2};" + endpointCombination[2], + accountSasParams + ); + + CloudStorageAccount.Parse(accountStringSasNoDefaultProtocolPrimary); // no exception expected + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringSasNoDefaultProtocolSecondary), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringSasNoDefaultProtocolPrimarySecondary); // no exception expected + + // SAS without AccountName + + string accountStringSasNoNameNoEndpoint = + string.Format( + "SharedAccessSignature={1}", + accountSasParams + ); + + string accountStringSasNoNamePrimary = + string.Format( + "SharedAccessSignature={1};" + endpointCombination[0], + accountSasParams + ); + + string accountStringSasNoNameSecondary = + string.Format( + "SharedAccessSignature={1};" + endpointCombination[1], + accountSasParams + ); + + string accountStringSasNoNamePrimarySecondary = + string.Format( + "SharedAccessSignature={1};" + endpointCombination[2], + accountSasParams + ); + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringSasNoNameNoEndpoint), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringSasNoNamePrimary); // no exception expected + + TestHelper.ExpectedException(() => CloudStorageAccount.Parse(accountStringSasNoNameSecondary), "connection string parse", "No valid combination of account information found."); + + CloudStorageAccount.Parse(accountStringSasNoNamePrimarySecondary); // no exception expected + } } private void connectionStringRoundtripHelper(string accountString) { CloudStorageAccount originalAccount = CloudStorageAccount.Parse(accountString); - CloudStorageAccount copiedAccount = CloudStorageAccount.Parse(originalAccount.ToString(true)); + + string copiedAccountString = originalAccount.ToString(true); + + CloudStorageAccount copiedAccount = CloudStorageAccount.Parse(copiedAccountString); // make sure it round trips this.AccountsAreEqual(originalAccount, copiedAccount); @@ -765,19 +1046,6 @@ public void CloudStorageAccountDefaultCloudRoundtrip() Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); } - [TestMethod] - [Description("ToString method for custom endpoints should return the same connection string")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountExplicitCloudRoundtrip() - { - string accountString = "BlobEndpoint=https://blobs/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - [TestMethod] [Description("ToString method for anonymous credentials should return the same connection string")] [TestCategory(ComponentCategory.Core)] @@ -795,72 +1063,6 @@ public void CloudStorageAccountAnonymousRoundtrip() AccountsAreEqual(account, CloudStorageAccount.Parse(account.ToString(true))); } - [TestMethod] - [Description("Parse method should ignore empty values")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountEmptyValues() - { - string accountString = ";BlobEndpoint=http://blobs/;;AccountName=test;;AccountKey=abc=;"; - string validAccountString = "BlobEndpoint=http://blobs/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(validAccountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - - [TestMethod] - [Description("ToString method with custom blob endpoint should return the same connection string")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountJustBlobToString() - { - string accountString = "BlobEndpoint=http://blobs/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - - [TestMethod] - [Description("ToString method with custom queue endpoint should return the same connection string")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountJustQueueToString() - { - string accountString = "QueueEndpoint=http://queue/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - - [TestMethod] - [Description("ToString method with custom table endpoint should return the same connection string")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountJustTableToString() - { - string accountString = "TableEndpoint=http://table/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - - [TestMethod] - [Description("ToString method with custom file endpoint should return the same connection string")] - [TestCategory(ComponentCategory.Core)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudStorageAccountJustFileToString() - { - string accountString = "FileEndpoint=http://file/;AccountName=test;AccountKey=abc="; - - Assert.AreEqual(accountString, CloudStorageAccount.Parse(accountString).ToString(true)); - } - [TestMethod] [Description("Exporting account key should be possible both as a byte array and a Base64 encoded string")] [TestCategory(ComponentCategory.Core)] diff --git a/changelog.txt b/changelog.txt index d97fb98fb..6faff3a68 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,8 @@ Changes in 8.2.0 - All: Fixed a bug where calling OpenWrite with an IfNotExists access condition on an existing block blob only fails when the blob is committed, now it fails with error 409 as soon as OpenWrite is called. - Files (WinRT/NetCore): Calling CreateIfNotExistsAsync on a root directory no longer throws the error 405. It returns false if the share exists, or throws 404 if not. +- All: Connection string support expanded to allow AccountName to be specified with SharedAccessSignature and DefaultEndpointsProtocol. In this case, SharedAccessSignature is used for credentials, while having both DefaultEndpointsProtocol and AccountName allows the library to infer a set of default endpoints. Additionally, we have added support for BlobSecondaryEndpoint, QueueSecondaryEndpoint, TableSecondaryEndpoint, and FileSecondaryEndpoint. Specifying a secondary endpoint requires the specification of the corresponding primary. +- All: The use of DefaultEndpointsProtocol in a connection string is now optional in the case where endpoints would be automatically generated; if missing, a value of https will be inferred. When the parsed account settings in such a case are used to generate a connection string, the value of DefaultEndpointsProtocol will be explicitly included. - Tables: Added TableEntityAdapter class to allow writing and reading simple or complex objects to Azure Table Storage without the need to implement ITableEntity interface or inherit from TableEntity class. Changes in 8.1.4 : From ae8548949095c663b058c2f4bdd95a8f81a307d4 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Thu, 13 Jul 2017 10:32:34 -0700 Subject: [PATCH 35/37] 8.2 Apr17 facade changes -- Facade changes for premium blob tier/FileEncHeaders/Connectionstring improvements and TableEntityAdapter -- Minor bug fixes for async read stream in RT and build failures in tests after merge --- ...ft.WindowsAzure.Storage.AccessCondition.cs | 16 +++++++ ...indowsAzure.Storage.Blob.BlobProperties.cs | 10 +++++ ...oft.WindowsAzure.Storage.Blob.CloudBlob.cs | 2 +- ...WindowsAzure.Storage.Blob.CloudPageBlob.cs | 42 ++++++++++++++++--- ...sAzure.Storage.Blob.PremiumPageBlobTier.cs | 17 ++++++++ ...e.Blob.Protocol.BlobHttpResponseParsers.cs | 4 ++ ...indowsAzure.Storage.CloudStorageAccount.cs | 14 ++++++- .../Microsoft.WindowsAzure.Storage.Core.SR.cs | 2 + ...oft.WindowsAzure.Storage.File.CloudFile.cs | 4 ++ ...wsAzure.Storage.File.CloudFileDirectory.cs | 4 +- ...Storage.File.DeleteShareSnapshotsOption.cs | 10 +++++ ...re.Storage.File.FileDirectoryProperties.cs | 5 +++ ...indowsAzure.Storage.File.FileProperties.cs | 5 +++ ...sAzure.Storage.File.FileShareProperties.cs | 10 +---- ...Azure.Storage.Shared.Protocol.Constants.cs | 3 +- ...Storage.Shared.Protocol.HeaderConstants.cs | 8 ++-- ...e.Storage.Table.Protocol.TableConstants.cs | 2 +- ...sAzure.Storage.Table.TableEntityAdapter.cs | 34 +++++++++++++++ Lib/Common/StorageExtendedErrorInformation.cs | 5 +++ Lib/WindowsRuntime/File/CloudFile.cs | 10 ++--- Lib/WindowsRuntime/File/CloudFileDirectory.cs | 6 +-- Test/WindowsRuntime/Blob/CloudPageBlobTest.cs | 5 ++- Test/WindowsRuntime/File/CloudFileTest.cs | 3 +- 23 files changed, 188 insertions(+), 33 deletions(-) create mode 100644 Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.PremiumPageBlobTier.cs create mode 100644 Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.DeleteShareSnapshotsOption.cs create mode 100644 Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.TableEntityAdapter.cs diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.AccessCondition.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.AccessCondition.cs index 3ae54634b..870ab7e06 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.AccessCondition.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.AccessCondition.cs @@ -75,6 +75,22 @@ internal bool IsConditional } } + internal bool IsIfNotExists + { + get + { + throw new System.NotImplementedException(); + } + } + + internal AccessCondition RemoveIsIfNotExistsCondition() + { + throw new System.NotImplementedException(); + } + public AccessCondition Clone() + { + throw new System.NotImplementedException(); + } public static AccessCondition GenerateEmptyCondition() { throw new System.NotImplementedException(); diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.BlobProperties.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.BlobProperties.cs index e6f91de8d..847062d25 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.BlobProperties.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.BlobProperties.cs @@ -89,6 +89,16 @@ public bool IsIncrementalCopy get; internal set; } + public Microsoft.WindowsAzure.Storage.Blob.PremiumPageBlobTier? PremiumPageBlobTier + { + get; internal set; + } + + public bool? BlobTierInferred + { + get; internal set; + } + public BlobProperties() { throw new System.NotImplementedException(); diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudBlob.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudBlob.cs index dabe23817..4f7327161 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudBlob.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudBlob.cs @@ -502,7 +502,7 @@ private RESTCommand BreakLeaseImpl(BlobAttributes attributes, TimeSpan { throw new System.NotImplementedException(); } - internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source, bool incrementalCopy, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) + internal RESTCommand StartCopyImpl(BlobAttributes attributes, Uri source, bool incrementalCopy, PremiumPageBlobTier? premiumPageBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options) { throw new System.NotImplementedException(); } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.cs index 1f9dcaac0..c38812223 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.cs @@ -102,12 +102,18 @@ public virtual Task UploadFromStreamAsync(Stream source, AccessCondition accessC { throw new System.NotImplementedException(); } - [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { throw new System.NotImplementedException(); } - [DoesServiceRequest] + public virtual Task UploadFromStreamAsync(Stream source, long length, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) { throw new System.NotImplementedException(); @@ -122,7 +128,10 @@ public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count { throw new System.NotImplementedException(); } - [DoesServiceRequest] + public virtual Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task CreateAsync(long size) { throw new System.NotImplementedException(); @@ -137,7 +146,10 @@ public virtual Task CreateAsync(long size, AccessCondition accessCondition, Blob { throw new System.NotImplementedException(); } - [DoesServiceRequest] + public virtual Task CreateAsync(long size, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task ResizeAsync(long size) { throw new System.NotImplementedException(); @@ -256,11 +268,27 @@ public virtual Task StartCopyAsync(CloudPageBlob source, AccessCondition { throw new System.NotImplementedException(); } + public virtual Task StartCopyAsync(CloudPageBlob source, PremiumPageBlobTier? premiumBlobTier, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task StartIncrementalCopyAsync(CloudPageBlob sourceSnapshot, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { throw new System.NotImplementedException(); } - private RESTCommand CreateImpl(long sizeInBytes, AccessCondition accessCondition, BlobRequestOptions options) + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier) + { + throw new System.NotImplementedException(); + } + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options, OperationContext operationContext) + { + throw new System.NotImplementedException(); + } + public virtual Task SetPremiumBlobTierAsync(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + private RESTCommand CreateImpl(long sizeInBytes, PremiumPageBlobTier? premiumBlobTier, AccessCondition accessCondition, BlobRequestOptions options) { throw new System.NotImplementedException(); } @@ -288,6 +316,10 @@ private RESTCommand ClearPageImpl(long startOffset, long length, Acces { throw new System.NotImplementedException(); } + private RESTCommand SetBlobTierImpl(PremiumPageBlobTier premiumBlobTier, BlobRequestOptions options) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.PremiumPageBlobTier.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.PremiumPageBlobTier.cs new file mode 100644 index 000000000..c5b0ae825 --- /dev/null +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.PremiumPageBlobTier.cs @@ -0,0 +1,17 @@ + +namespace Microsoft.WindowsAzure.Storage.Blob +{ +public enum PremiumPageBlobTier +{ + Unknown, + P4, + P6, + P10, + P20, + P30, + P40, + P50, + P60, +} + +} \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpResponseParsers.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpResponseParsers.cs index 8e041e2ed..781144c10 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpResponseParsers.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpResponseParsers.cs @@ -46,6 +46,10 @@ private static bool CheckIfTrue(string header) { throw new System.NotImplementedException(); } + internal static void GetBlobTier(BlobType blobType, string blobTierString, out PremiumPageBlobTier? premiumPageBlobTier) + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.CloudStorageAccount.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.CloudStorageAccount.cs index fd150718b..e3264ac3e 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.CloudStorageAccount.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.CloudStorageAccount.cs @@ -237,7 +237,19 @@ private static Func, IDictionary> At { throw new System.NotImplementedException(); } - private static IDictionary ValidCredentials(IDictionary settings) + private static Func, IDictionary> None(params KeyValuePair>[] atLeastOneSettings) + { + throw new System.NotImplementedException(); + } + private static Func, IDictionary> MatchesAll(params Func, IDictionary>[] filters) + { + throw new System.NotImplementedException(); + } + private static Func, IDictionary> MatchesOne(params Func, IDictionary>[] filters) + { + throw new System.NotImplementedException(); + } + private static Func, IDictionary> MatchesExactly(Func, IDictionary> filter) { throw new System.NotImplementedException(); } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Core.SR.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Core.SR.cs index e25bb4f79..1e7069ee9 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Core.SR.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Core.SR.cs @@ -16,6 +16,7 @@ internal class SR public const string BatchErrorInOperation = "Element {0} in the batch returned an unexpected response code."; public const string BinaryMessageShouldUseBase64Encoding = "EncodeMessage should be true for binary message."; public const string Blob = "blob"; + public const string BlobAlreadyExists = "The specified blob already exists."; public const string BlobDataCorrupted = "Blob data corrupted (integrity check failed), Expected value is '{0}', retrieved '{1}'"; public const string BlobEndPointNotConfigured = "No blob endpoint configured."; public const string BlobInvalidSequenceNumber = "The sequence number may not be specified for an increment operation."; @@ -23,6 +24,7 @@ internal class SR public const string BlobStreamAlreadyCommitted = "Blob stream has already been committed once."; public const string BlobStreamFlushPending = "Blob stream has a pending flush operation. Please call EndFlush first."; public const string BlobStreamReadPending = "Blob stream has a pending read operation. Please call EndRead first."; + public const string BlobTierNotSupported = "Blob tier cannot be set by this API on an existing blob. Please call SetBlobTier for existing blobs."; public const string BlobTypeMismatch = "Blob type of the blob reference doesn't match blob type of the blob."; public const string BufferTooSmall = "The provided buffer is too small to fit in the blob data given the offset."; public const string BufferManagerProvidedIncorrectLengthBuffer = "The IBufferManager provided an incorrect length buffer to the stream, Expected {0}, received {1}. Buffer length should equal the value returned by IBufferManager.GetDefaultBufferSize()."; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFile.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFile.cs index b323a4401..3fa6de7ff 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFile.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFile.cs @@ -58,6 +58,10 @@ public FileProperties Properties { throw new System.NotImplementedException(); } + internal set + { + throw new System.NotImplementedException(); + } } public IDictionary Metadata diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileDirectory.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileDirectory.cs index 3cceef70a..940207cc3 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileDirectory.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileDirectory.cs @@ -34,7 +34,7 @@ public StorageUri StorageUri get; private set; } - public Uri SnapshotQualifiedUri + internal Uri SnapshotQualifiedUri { get { @@ -42,7 +42,7 @@ public Uri SnapshotQualifiedUri } } - public StorageUri SnapshotQualifiedStorageUri + internal StorageUri SnapshotQualifiedStorageUri { get { diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.DeleteShareSnapshotsOption.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.DeleteShareSnapshotsOption.cs new file mode 100644 index 000000000..b7f27aea6 --- /dev/null +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.DeleteShareSnapshotsOption.cs @@ -0,0 +1,10 @@ + +namespace Microsoft.WindowsAzure.Storage.File +{ +internal enum DeleteShareSnapshotsOption +{ + None, + IncludeSnapshots, +} + +} \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties.cs index 8b74e7e08..3f571d8f8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileDirectoryProperties.cs @@ -12,6 +12,11 @@ public DateTimeOffset? LastModified { get; internal set; } + + public bool IsServerEncrypted + { + get; internal set; + } } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileProperties.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileProperties.cs index ae830feda..53758a76d 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileProperties.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileProperties.cs @@ -49,6 +49,11 @@ public DateTimeOffset? LastModified get; internal set; } + public bool IsServerEncrypted + { + get; internal set; + } + public FileProperties() { throw new System.NotImplementedException(); diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileShareProperties.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileShareProperties.cs index 61d64c7b1..a6ae7d3dd 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileShareProperties.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.FileShareProperties.cs @@ -1,4 +1,3 @@ -using Microsoft.WindowsAzure.Storage.Core.Util; using System; namespace Microsoft.WindowsAzure.Storage.File { @@ -17,14 +16,7 @@ public DateTimeOffset? LastModified public int? Quota { - get - { - throw new System.NotImplementedException(); - } - set - { - throw new System.NotImplementedException(); - } + get; set; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs index e5355fcce..27b85a1d8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.cs @@ -98,6 +98,7 @@ internal static class Constants public const string GeoUnavailableValue = "unavailable"; public const string GeoLiveValue = "live"; public const string GeoBootstrapValue = "bootstrap"; + public const string AccessTierElement = "AccessTier"; public const string BlobTypeElement = "BlobType"; public const string LeaseStatusElement = "LeaseStatus"; public const string LeaseStateElement = "LeaseState"; @@ -245,7 +246,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.4"; + public const string AgentMetadataValue = ".NET 8.2.0"; } } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index c1dbdbef6..c94b2fc5c 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,9 +4,9 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/8.1.4 "; + public static readonly string UserAgent = "Azure-Storage/8.2.0 " + Constants.HeaderConstants.UserAgentComment; public const string UserAgentProductName = "Azure-Storage"; - public const string UserAgentProductVersion = "8.1.4"; + public const string UserAgentProductVersion = "8.2.0"; public const string PrefixForStorageHeader = "x-ms-"; public const string TrueHeader = "true"; public const string FalseHeader = "false"; @@ -38,6 +38,8 @@ public static class HeaderConstants public const string BlobType = "x-ms-blob-type"; public const string SnapshotHeader = "x-ms-snapshot"; public const string DeleteSnapshotHeader = "x-ms-delete-snapshots"; + public const string AccessTierHeader = "x-ms-access-tier"; + public const string AccessTierInferredHeader = "x-ms-access-tier-inferred"; public const string BlobCacheControlHeader = "x-ms-blob-cache-control"; public const string BlobContentDispositionRequestHeader = "x-ms-blob-content-disposition"; public const string BlobContentEncodingHeader = "x-ms-blob-content-encoding"; @@ -64,7 +66,7 @@ public static class HeaderConstants public const string ClientRequestIdHeader = "x-ms-client-request-id"; public const string BlobPublicAccess = "x-ms-blob-public-access"; public const string RangeHeaderFormat = "bytes={0}-{1}"; - public const string TargetStorageVersion = "2016-05-31"; + public const string TargetStorageVersion = "2017-04-17"; public const string File = "File"; public const string PageBlob = "PageBlob"; public const string BlockBlob = "BlockBlob"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.Protocol.TableConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.Protocol.TableConstants.cs index af5a459e4..87b3297e4 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.Protocol.TableConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.Protocol.TableConstants.cs @@ -5,7 +5,7 @@ namespace Microsoft.WindowsAzure.Storage.Table.Protocol internal static class TableConstants { public static readonly DateTimeOffset MinDateTime = new DateTimeOffset(1601, 1, 1, 0, 0, 0, TimeSpan.Zero); - internal static ODataVersion ODataProtocolVersion = (ODataVersion) 2; + internal static ODataVersion ODataProtocolVersion = ODataVersion.V3; public const int TableServiceBatchMaximumOperations = 100; public const string TableServicePrefixForTableContinuation = "x-ms-continuation-"; public const string TableServiceNextPartitionKey = "NextPartitionKey"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.TableEntityAdapter.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.TableEntityAdapter.cs new file mode 100644 index 000000000..0a7ba441d --- /dev/null +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Table.TableEntityAdapter.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +namespace Microsoft.WindowsAzure.Storage.Table +{ +public class TableEntityAdapter : TableEntity +{ + public T OriginalEntity + { + get; set; + } + + public TableEntityAdapter() + { + throw new System.NotImplementedException(); + } + public TableEntityAdapter(T originalEntity) + { + throw new System.NotImplementedException(); + } + public TableEntityAdapter(T originalEntity, string partitionKey, string rowKey) + : base(partitionKey, rowKey) + { + throw new System.NotImplementedException(); + } + public override void ReadEntity(IDictionary properties, OperationContext operationContext) + { + throw new System.NotImplementedException(); + } + public override IDictionary WriteEntity(OperationContext operationContext) + { + throw new System.NotImplementedException(); + } +} + +} \ No newline at end of file diff --git a/Lib/Common/StorageExtendedErrorInformation.cs b/Lib/Common/StorageExtendedErrorInformation.cs index 0b4bc1205..f6becff77 100644 --- a/Lib/Common/StorageExtendedErrorInformation.cs +++ b/Lib/Common/StorageExtendedErrorInformation.cs @@ -76,6 +76,11 @@ public static StorageExtendedErrorInformation ReadFromStream(IInputStream inputS { return ReadFromStream(inputStream.AsStreamForRead()); } + + public static async Task ReadFromStreamAsync(IInputStream inputStream) + { + return await ReadFromStreamAsync(inputStream.AsStreamForRead()); + } #endif /// diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index 0f0642cec..0a581d9b0 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -884,7 +884,7 @@ public virtual Task CreateAsync(long size, AccessCondition accessCondition, File [DoesServiceRequest] public virtual Task CreateAsync(long size, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.CreateImpl(size, accessCondition, modifiedOptions), @@ -1237,7 +1237,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques [DoesServiceRequest] public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetMetadataImpl(accessCondition, modifiedOptions), @@ -1392,7 +1392,7 @@ public virtual Task ClearRangeAsync(long startOffset, long length, AccessConditi [DoesServiceRequest] public virtual Task ClearRangeAsync(long startOffset, long length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.ClearRangeImpl(startOffset, length, accessCondition, modifiedOptions), @@ -1481,7 +1481,7 @@ public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAcc [DoesServiceRequest] public virtual Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsync( this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), @@ -1543,7 +1543,7 @@ public virtual Task AbortCopyAsync(string copyId, AccessCondition accessConditio [DoesServiceRequest] public virtual Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), diff --git a/Lib/WindowsRuntime/File/CloudFileDirectory.cs b/Lib/WindowsRuntime/File/CloudFileDirectory.cs index 30a5c2269..ef2495bb6 100644 --- a/Lib/WindowsRuntime/File/CloudFileDirectory.cs +++ b/Lib/WindowsRuntime/File/CloudFileDirectory.cs @@ -65,7 +65,7 @@ public virtual Task CreateAsync(FileRequestOptions options, OperationContext ope [DoesServiceRequest] public virtual Task CreateAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async() => await Executor.ExecuteAsyncNullReturn( this.CreateDirectoryImpl(modifiedOptions), @@ -182,7 +182,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.DeleteDirectoryImpl(accessCondition, modifiedOptions), @@ -473,7 +473,7 @@ public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileReques [DoesServiceRequest] public virtual Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - this.share.AssertNoSnapshot(); + this.Share.AssertNoSnapshot(); FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.SetMetadataImpl(accessCondition, modifiedOptions), diff --git a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs index 3ec241a52..729541008 100644 --- a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs @@ -31,10 +31,11 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; -using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Windows.Storage; #endif +using Microsoft.WindowsAzure.Storage.Shared.Protocol; + namespace Microsoft.WindowsAzure.Storage.Blob { [TestClass] @@ -1424,6 +1425,7 @@ public async Task CloudPageBlobSetPremiumBlobTierOnCreateAsync() Assert.AreEqual(PremiumPageBlobTier.P10, blob4.Properties.PremiumPageBlobTier); Assert.IsFalse(blob4.Properties.BlobTierInferred.Value); +#if !NETCORE StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder; StorageFile inputFile = await tempFolder.CreateFileAsync("input.file", CreationCollisionOption.GenerateUniqueName); using (Stream file = await inputFile.OpenStreamForWriteAsync()) @@ -1435,6 +1437,7 @@ public async Task CloudPageBlobSetPremiumBlobTierOnCreateAsync() await blob5.UploadFromFileAsync(inputFile, PremiumPageBlobTier.P20, null, null, null, CancellationToken.None); Assert.AreEqual(PremiumPageBlobTier.P20, blob5.Properties.PremiumPageBlobTier); Assert.IsFalse(blob5.Properties.BlobTierInferred.Value); +#endif using (MemoryStream memStream = new MemoryStream(data)) { diff --git a/Test/WindowsRuntime/File/CloudFileTest.cs b/Test/WindowsRuntime/File/CloudFileTest.cs index 597c3ccd2..bbb3e198b 100644 --- a/Test/WindowsRuntime/File/CloudFileTest.cs +++ b/Test/WindowsRuntime/File/CloudFileTest.cs @@ -29,9 +29,10 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; -using Microsoft.WindowsAzure.Storage.Core; #endif +using Microsoft.WindowsAzure.Storage.Core; + namespace Microsoft.WindowsAzure.Storage.File { [TestClass] From a89262e6e7ff030647c823984685013457a60221 Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Thu, 13 Jul 2017 10:42:52 -0700 Subject: [PATCH 36/37] [8.2][apr17v1][Sharesnapshot]Disable DelSnpshOptns --- Lib/ClassLibraryCommon/File/CloudFileShare.cs | 18 +++++++++--------- Lib/Common/File/DeleteShareSnapshotsOption.cs | 2 +- Lib/WindowsRuntime/File/CloudFileShare.cs | 4 ++-- .../WindowsRuntime/File/CloudFileClientTest.cs | 3 ++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index 758f9a8ba..08df94dd9 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -463,7 +463,7 @@ public virtual void Delete(AccessCondition accessCondition = null, FileRequestOp /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. [DoesServiceRequest] - public virtual void Delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + internal virtual void Delete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); Executor.ExecuteSync( @@ -480,11 +480,11 @@ public virtual void Delete(DeleteShareSnapshotsOption deleteSnapshotsOption, Acc /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, object state) + internal virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, object state) { return this.BeginDelete(DeleteShareSnapshotsOption.None, null /* accessCondition */, null /* options */, null /*operationContext */, callback, state); } - + /// /// Begins an asynchronous operation to delete a share. /// @@ -495,7 +495,7 @@ public virtual ICancellableAsyncResult BeginDelete(AsyncCallback callback, objec /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginDelete(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + internal virtual ICancellableAsyncResult BeginDelete(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { return this.BeginDelete(DeleteShareSnapshotsOption.None, accessCondition, options, operationContext, callback, state); } @@ -511,7 +511,7 @@ public virtual ICancellableAsyncResult BeginDelete(AccessCondition accessConditi /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginDelete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + internal virtual ICancellableAsyncResult BeginDelete(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -591,7 +591,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti /// A to observe while waiting for a task to complete. /// A object that represents the current operation. [DoesServiceRequest] - public virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { return AsyncExtensions.TaskFromVoidApm(this.BeginDelete, this.EndDelete, deleteSnapshotsOption, accessCondition, options, operationContext, cancellationToken); } @@ -620,7 +620,7 @@ public virtual bool DeleteIfExists(AccessCondition accessCondition = null, FileR /// An object that represents the context for the current operation. /// true if the share did not already exist and was created; otherwise false. [DoesServiceRequest] - public virtual bool DeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + internal virtual bool DeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); @@ -706,7 +706,7 @@ public virtual ICancellableAsyncResult BeginDeleteIfExists(AccessCondition acces /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. [DoesServiceRequest] - public virtual ICancellableAsyncResult BeginDeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + internal virtual ICancellableAsyncResult BeginDeleteIfExists(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); @@ -878,7 +878,7 @@ public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, F /// A to observe while waiting for a task to complete. /// A object that represents the current operation. [DoesServiceRequest] - public virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, + internal virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { return AsyncExtensions.TaskFromApm(this.BeginDeleteIfExists, this.EndDeleteIfExists, deleteSnapshotsOption, accessCondition, options, operationContext, cancellationToken); diff --git a/Lib/Common/File/DeleteShareSnapshotsOption.cs b/Lib/Common/File/DeleteShareSnapshotsOption.cs index 6639d00c4..53727e969 100644 --- a/Lib/Common/File/DeleteShareSnapshotsOption.cs +++ b/Lib/Common/File/DeleteShareSnapshotsOption.cs @@ -20,7 +20,7 @@ namespace Microsoft.WindowsAzure.Storage.File /// /// The set of options describing delete operation. /// - public enum DeleteShareSnapshotsOption + internal enum DeleteShareSnapshotsOption { /// /// Delete the share only. If the share has snapshots, this option will result in an error from the service. diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index 056a6180b..af723158f 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -251,7 +251,7 @@ public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOpti /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. [DoesServiceRequest] - public virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( @@ -306,7 +306,7 @@ public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, F /// A to observe while waiting for a task to complete. /// true if the share already existed and was deleted; otherwise, false. [DoesServiceRequest] - public virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + internal virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); diff --git a/Test/WindowsRuntime/File/CloudFileClientTest.cs b/Test/WindowsRuntime/File/CloudFileClientTest.cs index 37b5d3713..f69ebd718 100644 --- a/Test/WindowsRuntime/File/CloudFileClientTest.cs +++ b/Test/WindowsRuntime/File/CloudFileClientTest.cs @@ -404,7 +404,8 @@ public async Task CloudFileClientServerTimeoutAsync() Assert.IsNull(timeout); } - [TestMethod] + //TODO: Enable for ShareSnapshot release + //[TestMethod] [Description("Test list shares with a snapshot")] [TestCategory(ComponentCategory.File)] [TestCategory(TestTypeCategory.UnitTest)] From 9f93c79cf349b8dd501cf2f68a5a842a8b9f269a Mon Sep 17 00:00:00 2001 From: Elham Rezvani Date: Thu, 13 Jul 2017 11:18:04 -0700 Subject: [PATCH 37/37] [8.2]Update assembly versions for release --- ...indowsAzure.Storage.File.CloudFileShare.cs | 45 +++++++------------ ...age.Shared.Protocol.EncryptionConstants.cs | 2 +- ...Storage.Shared.Protocol.HeaderConstants.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../project.json | 2 +- .../AssemblyInfo.cs | 6 +-- .../WindowsAzure.StorageK.nuspec | 2 +- .../project.json | 2 +- Lib/Common/Shared/Protocol/Constants.cs | 2 +- Lib/WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- .../WindowsAzure.Storage.nuspec | 2 +- Lib/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs | 4 +- Lib/WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- README.md | 8 ++-- .../project.json | 4 +- .../project.json | 4 +- .../WindowsDesktop/Properties/AssemblyInfo.cs | 4 +- Test/WindowsPhone/Properties/AssemblyInfo.cs | 4 +- .../WindowsPhone81/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../WindowsRuntime/Properties/AssemblyInfo.cs | 4 +- changelog.txt | 2 +- 23 files changed, 55 insertions(+), 68 deletions(-) diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileShare.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileShare.cs index 01b244960..c59e5c20e 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileShare.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.File.CloudFileShare.cs @@ -35,12 +35,12 @@ public StorageUri StorageUri get; private set; } - public DateTimeOffset? SnapshotTime + internal DateTimeOffset? SnapshotTime { - get; internal set; + get; set; } - public bool IsSnapshot + internal bool IsSnapshot { get { @@ -48,7 +48,7 @@ public bool IsSnapshot } } - public Uri SnapshotQualifiedUri + internal Uri SnapshotQualifiedUri { get { @@ -56,7 +56,7 @@ public Uri SnapshotQualifiedUri } } - public StorageUri SnapshotQualifiedStorageUri + internal StorageUri SnapshotQualifiedStorageUri { get { @@ -89,7 +89,7 @@ public CloudFileShare(Uri shareAddress, StorageCredentials credentials) { throw new System.NotImplementedException(); } - public CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + internal CloudFileShare(Uri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) : this(new StorageUri(shareAddress), snapshotTime, credentials) { throw new System.NotImplementedException(); @@ -99,7 +99,7 @@ public CloudFileShare(StorageUri shareAddress, StorageCredentials credentials) { throw new System.NotImplementedException(); } - public CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) + internal CloudFileShare(StorageUri shareAddress, DateTimeOffset? snapshotTime, StorageCredentials credentials) { throw new System.NotImplementedException(); } @@ -142,51 +142,38 @@ public virtual Task CreateIfNotExistsAsync(FileRequestOptions options, Ope { throw new System.NotImplementedException(); } - internal virtual Task SnapshotAsync() - { - throw new System.NotImplementedException(); - } - internal virtual Task SnapshotAsync(CancellationToken cancellationToken) - { - throw new System.NotImplementedException(); - } - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) - { - throw new System.NotImplementedException(); - } - internal virtual Task SnapshotAsync(IDictionary metadata, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) - { - throw new System.NotImplementedException(); - } public virtual Task DeleteAsync() { throw new System.NotImplementedException(); } - [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { throw new System.NotImplementedException(); } - [DoesServiceRequest] public virtual Task DeleteAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { throw new System.NotImplementedException(); } - [DoesServiceRequest] + internal virtual Task DeleteAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } public virtual Task DeleteIfExistsAsync() { throw new System.NotImplementedException(); } - [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { throw new System.NotImplementedException(); } - [DoesServiceRequest] public virtual Task DeleteIfExistsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { throw new System.NotImplementedException(); } + internal virtual Task DeleteIfExistsAsync(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } [DoesServiceRequest] public virtual Task ExistsAsync() { @@ -300,7 +287,7 @@ internal RESTCommand SnapshotImpl(IDictionary me { throw new System.NotImplementedException(); } - private RESTCommand DeleteShareImpl(AccessCondition accessCondition, FileRequestOptions options) + private RESTCommand DeleteShareImpl(DeleteShareSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, FileRequestOptions options) { throw new System.NotImplementedException(); } diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs index b730600df..e592a5474 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.EncryptionConstants.cs @@ -10,7 +10,7 @@ public static class EncryptionConstants public const string TableEncryptionKeyDetails = "_ClientEncryptionMetadata1"; public const string TableEncryptionPropertyDetails = "_ClientEncryptionMetadata2"; public const string AgentMetadataKey = "EncryptionLibrary"; - public const string AgentMetadataValue = ".NET 8.1.4"; + public const string AgentMetadataValue = ".NET 8.2.0"; } } \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs index c94b2fc5c..ecd4a72ed 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/FacadeLib/Microsoft.WindowsAzure.Storage.Shared.Protocol.HeaderConstants.cs @@ -4,7 +4,7 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol public static class HeaderConstants { - public static readonly string UserAgent = "Azure-Storage/8.2.0 " + Constants.HeaderConstants.UserAgentComment; + public static readonly string UserAgent = "Azure-Storage/8.2.0 "; public const string UserAgentProductName = "Azure-Storage"; public const string UserAgentProductVersion = "8.2.0"; public const string PrefixForStorageHeader = "x-ms-"; diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs index ca0701e5b..b46320c0a 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/Properties/AssemblyInfo.cs @@ -31,8 +31,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: InternalsVisibleTo( "Microsoft.WindowsAzure.Storage.Facade.Portable, PublicKey=" + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json index 77c506fc1..86badded8 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage.Facade/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage", - "version": "8.1.4.0", + "version": "8.2.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 570ffa4d0..73e6dcc41 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -34,9 +34,9 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] -[assembly: AssemblyInformationalVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] +[assembly: AssemblyInformationalVersion("8.2.0.0")] [assembly: InternalsVisibleTo( diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index ad8978088..281b44c4e 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.4 + 8.2.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index 3e49de528..fa7fd77ca 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "8.1.4.0", + "version": "8.2.0.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for NetCore", diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 3b6365979..76aeec775 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -829,7 +829,7 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "8.1.4"; + public const string UserAgentProductVersion = "8.2.0"; /// /// Master Microsoft Azure Storage header prefix. diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 13a351fbf..0f2f0edc0 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index ad8978088..281b44c4e 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 8.1.4 + 8.2.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index ca4a97dec..5b59e89d4 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index b0ced1e86..86af8096c 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index ff8ea710e..bc6dbcfb3 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: ComVisible(false)] diff --git a/README.md b/README.md index 8ce50b215..55c04f0ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (8.1.4) +# Microsoft Azure Storage SDK for .NET (8.2.0) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. @@ -25,7 +25,7 @@ complete Azure SDK, please see the [Microsoft Azure .NET Developer Center](http: The complete Microsoft Azure SDK can be downloaded from the [Microsoft Azure Downloads Page](http://azure.microsoft.com/en-us/downloads/?sdk=net) and ships with support for building deployment packages, integrating with tooling, rich command line tooling, and more. -Please review [Get started with Azure Storage in five minutes](http://azure.microsoft.com/en-us/documentation/articles/storage-getting-started-guide/) if you are not familiar with Azure Storage. +Please review [Get started with Azure Storage](https://docs.microsoft.com/en-us/azure/storage/storage-dotnet-how-to-use-blobs) if you are not familiar with Azure Storage. For the best development experience, developers should use the official Microsoft NuGet packages for libraries. NuGet packages are regularly updated with new functionality and hotfixes. @@ -56,13 +56,13 @@ Through the bait and switch technique, the reference assembly enables other port ## Use with the Azure Storage Emulator - The Client Library uses a particular Storage Service version. In order to use the Storage Client Library with the Storage Emulator, a corresponding minimum version of the Azure Storage Emulator must be used. Older versions of the Storage Emulator do not have the necessary code to successfully respond to new requests. -- Currently, the minimum version of the Azure Storage Emulator needed for this library is 4.6. If you encounter a `VersionNotSupportedByEmulator` (400 Bad Request) error, please [update the Storage Emulator.](https://azure.microsoft.com/en-us/downloads/) +- Currently, the minimum version of the Azure Storage Emulator needed for this library is 5.4. If you encounter a `VersionNotSupportedByEmulator` (400 Bad Request) error, please [update the Storage Emulator.](https://azure.microsoft.com/en-us/downloads/) ## Download & Install The Storage Client Library ships with the Microsoft Azure SDK for .NET and also on NuGet. You'll find the latest version and hotfixes on NuGet via the `WindowsAzure.Storage` package. -This version of the Storage Client Library ships with the storage version 2016-05-31. +This version of the Storage Client Library ships with the storage version 2017-04-17. ### Via Git diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json index 65e44150e..6f16b99da 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.NetCore.Test/project.json @@ -1,6 +1,6 @@ { "title": "Microsoft.WindowsAzure.Storage.Facade.NetCore.Test", - "version": "8.1.4.0", + "version": "8.2.0.0", "testRunner": "xunit", "dependencies": { @@ -16,7 +16,7 @@ "type": "platform", "version": "1.0.0" }, - "WindowsAzure.Storage": "8.1.4" + "WindowsAzure.Storage": "8.2.0-facade" }, "imports": "dnxcore50" diff --git a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json index 968feb381..0b2cb2dd0 100644 --- a/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json +++ b/Test/AspNet/Facade/Microsoft.WindowsAzure.Storage.Facade.Portable/project.json @@ -1,8 +1,8 @@ { - "version": "8.1.4.0", + "version": "8.2.0.0", "supports": {}, "dependencies": { - "WindowsAzure.Storage": "8.1.4-test" + "WindowsAzure.Storage": "8.2.0-facade" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile259": {} diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 33a3f1b17..50c916f88 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index c15f65a51..da28f7c38 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index 3bfbf1f8a..1daa57784 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 08441752b..7d551a478 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 4e0047a9c..00b73c605 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,7 +25,7 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.1.4.0")] -[assembly: AssemblyFileVersion("8.1.4.0")] +[assembly: AssemblyVersion("8.2.0.0")] +[assembly: AssemblyFileVersion("8.2.0.0")] [assembly: ComVisible(false)] diff --git a/changelog.txt b/changelog.txt index e0d976a1f..f845f0c70 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ Changes in 8.2.0 -- All: Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version X.X. +- All: Support for 2017-04-17 REST version. Please see our REST API documentation and blogs for information about the related added features. If you are using the Storage Emulator, please update to Emulator version 5.4. - Files: Added support for server side encryption. - PageBlobs: For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. - Tables (NetCore): Fixed a bug where the empty etag added to the table delete operation would throw exception on Xamarin/Mono.