Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Internal] Change Feed: Adds change feed retention policy to container configuration #2020

Merged
merged 37 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2095e5b
initial contract
ealsur Nov 19, 2020
d80f7d4
serialization
ealsur Nov 19, 2020
3c28509
serialization tests
ealsur Nov 19, 2020
493cada
Merge branch 'master' into users/ealsur/cpff
ealsur Nov 19, 2020
57c6ca4
Fluent support
ealsur Nov 19, 2020
e8b17ed
emulator tests
ealsur Nov 19, 2020
475cc19
contract
ealsur Nov 19, 2020
2f702f0
Merge branch 'master' into users/ealsur/cpff
ealsur Nov 19, 2020
adf5e57
Fixing test
ealsur Nov 19, 2020
06299a9
example
ealsur Nov 19, 2020
dcc4479
example
ealsur Nov 19, 2020
3a6cbb1
contract update
ealsur Nov 19, 2020
d87919f
test
ealsur Nov 19, 2020
d3ecedf
Merge branch 'master' into users/ealsur/cpff
ealsur Nov 23, 2020
460a3f3
Merge branch 'master' into users/ealsur/cpff
ealsur Dec 2, 2020
35ca791
Merge branch 'master' into users/ealsur/cpff
j82w Dec 2, 2020
e695529
Merge branch 'master' into users/ealsur/cpff
ealsur Dec 14, 2020
2ed8b5f
Removing converter
ealsur Dec 14, 2020
8b657a2
Contract refresh
ealsur Dec 14, 2020
f85c2e3
test minimum value
ealsur Dec 14, 2020
1206059
Merge branch 'master' into users/ealsur/cpff
ealsur Dec 15, 2020
a9526cb
Ignoring until new emulator is released
ealsur Dec 15, 2020
c4951c6
ChangeFeedPolicy empty by default
ealsur Dec 17, 2020
83976e8
Merge branch 'master' into users/ealsur/cpff
ealsur Dec 17, 2020
de62254
Addressing comments
ealsur Dec 17, 2020
54d51a5
Rename
ealsur Dec 17, 2020
a5eed26
Contract update
ealsur Dec 17, 2020
5174533
Comments
ealsur Dec 17, 2020
3596887
Reverting default and exposing disabled property
ealsur Dec 18, 2020
9971532
Merge branch 'master' into users/ealsur/cpff
ealsur Jan 5, 2021
8829c86
Enabling test
ealsur Jan 5, 2021
5d4e548
Disabling test
ealsur Jan 5, 2021
1c2ffba
Merge branch 'master' into users/ealsur/cpff
ealsur Jan 6, 2021
3961e94
Refactoring fluent
ealsur Jan 6, 2021
d79af4d
Merge branch 'master' into users/ealsur/cpff
ealsur Jan 6, 2021
dc67985
Marking as internal
ealsur Jan 6, 2021
f648f2d
Merge branch 'users/ealsur/cpff' of https://github.com/Azure/azure-co…
ealsur Jan 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Fluent
{
using System;

/// <summary>
/// <see cref="ChangeFeedPolicy"/> fluent definition.
/// </summary>
#if PREVIEW
ealsur marked this conversation as resolved.
Show resolved Hide resolved
public
#else
internal
#endif
class ChangeFeedPolicyDefinition
{
private readonly ContainerBuilder parent;
private readonly Action<ChangeFeedPolicy> attachCallback;
private TimeSpan changeFeedPolicyRetention;

internal ChangeFeedPolicyDefinition(
ContainerBuilder parent,
Action<ChangeFeedPolicy> attachCallback)
{
this.parent = parent;
ealsur marked this conversation as resolved.
Show resolved Hide resolved
this.attachCallback = attachCallback;
}

/// <summary>
/// Defines the path used to resolve LastWrtierWins resolution mode <see cref="ConflictResolutionPolicy"/>.
/// </summary>
/// <param name="retention"> Indicates for how long operation logs have to be retained. <see cref="ChangeFeedPolicy.RetentionDuration"/>.</param>
/// <returns>An instance of the current <see cref="ChangeFeedPolicyDefinition"/>.</returns>
public ChangeFeedPolicyDefinition WithRetentionDuration(TimeSpan retention)
{
if (retention == null)
{
throw new ArgumentNullException(nameof(retention));
}

this.changeFeedPolicyRetention = retention;

return this;
}

/// <summary>
/// Applies the current definition to the parent.
/// </summary>
/// <returns>An instance of the parent.</returns>
public ContainerBuilder Attach()
{
ChangeFeedPolicy resolutionPolicy = new ChangeFeedPolicy();
if (this.changeFeedPolicyRetention != null)
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
resolutionPolicy.RetentionDuration = this.changeFeedPolicyRetention;
}

this.attachCallback(resolutionPolicy);
return this.parent;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal ConflictResolutionDefinition(
/// Defines the path used to resolve LastWrtierWins resolution mode <see cref="ConflictResolutionPolicy"/>.
/// </summary>
/// <param name="conflictResolutionPath"> sets the path which is present in each item in the Azure Cosmos DB service for last writer wins conflict-resolution. <see cref="ConflictResolutionPolicy.ResolutionPath"/>.</param>
/// <returns>An instance of the current <see cref="UniqueKeyDefinition"/>.</returns>
/// <returns>An instance of the current <see cref="ConflictResolutionDefinition"/>.</returns>
public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflictResolutionPath)
{
if (string.IsNullOrEmpty(conflictResolutionPath))
Expand All @@ -46,7 +46,7 @@ public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflict
/// </summary>
/// <param name="conflictResolutionProcedure"> Sets the stored procedure's name to be used for conflict-resolution.</param>
/// <remarks>The stored procedure can be created later on, but needs to honor the name specified here.</remarks>
/// <returns>An instance of the current <see cref="UniqueKeyDefinition"/>.</returns>
/// <returns>An instance of the current <see cref="ConflictResolutionDefinition"/>.</returns>
/// <example>
/// This example below creates a <see cref="Container"/> with a Conflict Resolution policy that uses a stored procedure to resolve conflicts:
/// <code language="c#">
Expand Down
27 changes: 27 additions & 0 deletions Microsoft.Azure.Cosmos/src/Fluent/Settings/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ContainerBuilder : ContainerDefinition<ContainerBuilder>
private readonly Uri containerUri;
private UniqueKeyPolicy uniqueKeyPolicy;
private ConflictResolutionPolicy conflictResolutionPolicy;
private ChangeFeedPolicy changeFeedPolicy;

/// <summary>
/// Creates an instance for unit-testing
Expand Down Expand Up @@ -60,6 +61,22 @@ public ConflictResolutionDefinition WithConflictResolution()
(conflictPolicy) => this.AddConflictResolution(conflictPolicy));
}

/// <summary>
/// Defined the change feed policy for this Azure Cosmos container
/// </summary>
/// <returns>An instance of <see cref="ChangeFeedPolicyDefinition"/>.</returns>
#if PREVIEW
public
#else
internal
#endif
ChangeFeedPolicyDefinition WithChangeFeedPolicy()
{
return new ChangeFeedPolicyDefinition(
this,
(changeFeedPolicy) => this.AddChangeFeedPolicy(changeFeedPolicy));
}

/// <summary>
/// Creates a container with the current fluent definition.
/// </summary>
Expand Down Expand Up @@ -156,6 +173,11 @@ public async Task<ContainerResponse> CreateIfNotExistsAsync(
containerProperties.ConflictResolutionPolicy = this.conflictResolutionPolicy;
}

if (this.changeFeedPolicy != null)
{
containerProperties.ChangeFeedPolicy = this.changeFeedPolicy;
}

return containerProperties;
}

Expand All @@ -180,5 +202,10 @@ private void AddConflictResolution(ConflictResolutionPolicy conflictResolutionPo

this.conflictResolutionPolicy = conflictResolutionPolicy;
}

private void AddChangeFeedPolicy(ChangeFeedPolicy changeFeedPolicy)
{
this.changeFeedPolicy = changeFeedPolicy;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;

/// <summary>
/// Represents the change feed policy configuration for a container in the Azure Cosmos DB service.
/// </summary>
/// <example>
/// The example below creates a new container with a custom change feed policy.
/// <code language="c#">
/// <![CDATA[
/// ContainerProperties containerProperties = new ContainerProperties("MyCollection", "/country");
/// containerProperties.ChangeFeedPolicy.RetentionDuration = TimeSpan.FromMinutes(5);
///
/// CosmosContainerResponse containerCreateResponse = await client.GetDatabase("dbName").CreateContainerAsync(containerProperties, 5000);
/// ContainerProperties createdContainerProperties = containerCreateResponse.Container;
/// ]]>
/// </code>
/// </example>
/// <seealso cref="ContainerProperties"/>
#if PREVIEW
public
#else
internal
#endif
sealed class ChangeFeedPolicy
{
[JsonProperty(PropertyName = Constants.Properties.LogRetentionDuration, NullValueHandling = NullValueHandling.Ignore)]
private int? retentionDurationInMinutes;

/// <summary>
/// Gets or sets a value that indicates for how long operation logs have to be retained.
/// </summary>
/// <remarks>
/// Minimum granularity supported is minutes.
/// </remarks>
/// <value>
/// Value is in TimeSpan.
/// </value>
[JsonIgnore]
public TimeSpan RetentionDuration
{
get
{
if (!this.retentionDurationInMinutes.HasValue)
{
return TimeSpan.Zero;
}

return TimeSpan.FromMinutes(this.retentionDurationInMinutes.Value);
}
set
{
if (value.Seconds > 0)
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentOutOfRangeException(nameof(this.RetentionDuration), "Retention duration's minimum granularity is minutes.");
ealsur marked this conversation as resolved.
Show resolved Hide resolved
}

this.retentionDurationInMinutes = (int)value.TotalMinutes;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public class ContainerProperties
{
private static readonly char[] partitionKeyTokenDelimeter = new char[] { '/' };

[JsonProperty(PropertyName = Constants.Properties.ChangeFeedPolicy, NullValueHandling = NullValueHandling.Ignore)]
private ChangeFeedPolicy changeFeedPolicyInternal;

[JsonProperty(PropertyName = Constants.Properties.IndexingPolicy, NullValueHandling = NullValueHandling.Ignore)]
private IndexingPolicy indexingPolicyInternal;

Expand Down Expand Up @@ -261,6 +264,33 @@ public IndexingPolicy IndexingPolicy
}
}

/// <summary>
/// Gets the <see cref="ChangeFeedPolicy"/> associated with the container from the Azure Cosmos DB service.
/// </summary>
/// <value>
/// The change feed policy associated with the container.
/// </value>
[JsonIgnore]
#if PREVIEW
public
#else
internal
#endif
ChangeFeedPolicy ChangeFeedPolicy
{
get
{
if (this.changeFeedPolicyInternal == null)
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
this.changeFeedPolicyInternal = new ChangeFeedPolicy();
}

return this.changeFeedPolicyInternal;
}

set => this.changeFeedPolicyInternal = value;
ealsur marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Gets the <see cref="GeospatialConfig"/> associated with the collection from the Azure Cosmos DB service.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,39 @@ await databaseForConflicts.DefineContainer(containerName, partitionKeyPath)
}
}

[TestMethod]
ealsur marked this conversation as resolved.
Show resolved Hide resolved
[Ignore] // Ignored until updated emulator is released
public async Task TestChangeFeedPolicy()
{
Database databaseForChangeFeed = await this.cosmosClient.CreateDatabaseAsync("changeFeedRetentionContainerTest",
cancellationToken: this.cancellationToken);

try
{
string containerName = "changeFeedRetentionContainerTest";
string partitionKeyPath = "/users";
TimeSpan retention = TimeSpan.FromMinutes(10);

ContainerResponse containerResponse =
await databaseForChangeFeed.DefineContainer(containerName, partitionKeyPath)
.WithChangeFeedPolicy()
.WithRetentionDuration(retention)
.Attach()
.CreateAsync();

Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
Assert.AreEqual(containerName, containerResponse.Resource.Id);
Assert.AreEqual(partitionKeyPath, containerResponse.Resource.PartitionKey.Paths.First());
ContainerProperties containerSettings = containerResponse.Resource;
Assert.IsNotNull(containerSettings.ChangeFeedPolicy);
Assert.AreEqual(retention.TotalMinutes, containerSettings.ChangeFeedPolicy.RetentionDuration.TotalMinutes);
}
finally
{
await databaseForChangeFeed.DeleteAsync();
}
}

[TestMethod]
public async Task WithIndexingPolicy()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.ChangeFeedPolicy;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
"System.TimeSpan get_RetentionDuration()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "System.TimeSpan get_RetentionDuration();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"System.TimeSpan RetentionDuration[Newtonsoft.Json.JsonIgnoreAttribute()]": {
"Type": "Property",
"Attributes": [
"JsonIgnoreAttribute"
],
"MethodInfo": "System.TimeSpan RetentionDuration;CanRead:True;CanWrite:True;System.TimeSpan get_RetentionDuration();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_RetentionDuration(System.TimeSpan);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Void .ctor()": {
"Type": "Constructor",
"Attributes": [],
"MethodInfo": "[Void .ctor(), Void .ctor()]"
},
"Void set_RetentionDuration(System.TimeSpan)": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Void set_RetentionDuration(System.TimeSpan);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
}
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.ChangeFeedProcessorState;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
Expand Down Expand Up @@ -251,6 +279,29 @@
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.ContainerProperties;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
"Microsoft.Azure.Cosmos.ChangeFeedPolicy ChangeFeedPolicy[Newtonsoft.Json.JsonIgnoreAttribute()]": {
"Type": "Property",
"Attributes": [
"JsonIgnoreAttribute"
],
"MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedPolicy ChangeFeedPolicy;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedPolicy get_ChangeFeedPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_ChangeFeedPolicy(Microsoft.Azure.Cosmos.ChangeFeedPolicy);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Microsoft.Azure.Cosmos.ChangeFeedPolicy get_ChangeFeedPolicy()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedPolicy get_ChangeFeedPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Void set_ChangeFeedPolicy(Microsoft.Azure.Cosmos.ChangeFeedPolicy)": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Void set_ChangeFeedPolicy(Microsoft.Azure.Cosmos.ChangeFeedPolicy);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
}
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.CosmosClient;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
Expand Down Expand Up @@ -339,6 +390,33 @@
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
"Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition WithRetentionDuration(System.TimeSpan)": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition WithRetentionDuration(System.TimeSpan);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
"Microsoft.Azure.Cosmos.Fluent.ContainerBuilder Attach()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.Fluent.ContainerBuilder Attach();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
}
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.Fluent.ContainerBuilder;Microsoft.Azure.Cosmos.Fluent.ContainerDefinition`1[[Microsoft.Azure.Cosmos.Fluent.ContainerBuilder, ]];IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
"Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition WithChangeFeedPolicy()": {
"Type": "Method",
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.Fluent.ChangeFeedPolicyDefinition WithChangeFeedPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
}
},
"NestedTypes": {}
},
"Microsoft.Azure.Cosmos.Linq.CosmosLinqExtensions;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
Expand Down
Loading