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

Fixed SpatialPath serialization and compatibility with older index versions #614

Merged
merged 9 commits into from
Aug 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Microsoft.Azure.Cosmos/src/CosmosIndexJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

internal sealed class CosmosIndexJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Index).IsAssignableFrom(objectType);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType != typeof(Index))
{
return null;
}

JToken indexToken = JToken.Load(reader);

if (indexToken.Type == JTokenType.Null)
{
return null;
}

if (indexToken.Type != JTokenType.Object)
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, Documents.RMResources.InvalidIndexSpecFormat));
}

JToken indexKindToken = indexToken[Documents.Constants.Properties.IndexKind];
if (indexKindToken == null || indexKindToken.Type != JTokenType.String)
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, Documents.RMResources.InvalidIndexSpecFormat));
}

IndexKind indexKind = IndexKind.Hash;
if (Enum.TryParse(indexKindToken.Value<string>(), out indexKind))
{
object index = null;
switch (indexKind)
{
case IndexKind.Hash:
index = new HashIndex();
break;
case IndexKind.Range:
index = new RangeIndex();
break;
case IndexKind.Spatial:
index = new SpatialIndex();
break;
default:
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, Documents.RMResources.InvalidIndexKindValue, indexKind));
}

serializer.Populate(indexToken.CreateReader(), index);
return index;
}
else
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, Documents.RMResources.InvalidIndexKindValue, indexKindToken.Value<string>()));
}
}

public override bool CanWrite
{
get
{
return false;
}
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Resource/Settings/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// Base class for IndexingPolicy Indexes in the Azure Cosmos DB service, you should use a concrete Index like HashIndex or RangeIndex.
/// </summary>
[JsonConverter(typeof(IndexJsonConverter))]
[JsonConverter(typeof(CosmosIndexJsonConverter))]
internal abstract class Index
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos
/// </summary>
public sealed class SpatialPath
{
[JsonProperty(PropertyName = Constants.Properties.Types, ItemConverterType = typeof(StringEnumConverter))]
private Collection<SpatialType> spatialTypesInternal;

/// <summary>
Expand All @@ -27,6 +26,7 @@ public sealed class SpatialPath
/// <summary>
/// Path's spatial type
/// </summary>
[JsonProperty(PropertyName = Constants.Properties.Types, ItemConverterType = typeof(StringEnumConverter))]
public Collection<SpatialType> SpatialTypes
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ public async Task TestWithRunningProcessor()
[TestMethod]
public async Task TestWithFixedLeaseContainer()
{
await CosmosItemTests.CreateNonPartitionedContainer(
this.database.Id,
await NonPartitionedContainerHelper.CreateNonPartitionedContainer(
this.database,
"fixedLeases");

Container fixedLeasesContainer = this.cosmosClient.GetContainer(this.database.Id, "fixedLeases");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ public class CosmosItemTests : BaseCosmosClientHelper
private Container Container = null;
private ContainerProperties containerSettings = null;

private static readonly string utc_date = DateTime.UtcNow.ToString("r");

private static readonly string PreNonPartitionedMigrationApiVersion = "2018-09-17";
private static readonly string nonPartitionItemId = "fixed-Container-Item";

private static readonly string undefinedPartitionItemId = "undefined-partition-Item";

[TestInitialize]
Expand Down Expand Up @@ -1263,9 +1259,12 @@ public async Task ReadNonPartitionItemAsync()
ContainerCore fixedContainer = null;
try
{
fixedContainer = await this.CreateNonPartitionedContainer("ReadNonPartition" + Guid.NewGuid());
await this.CreateItemInNonPartitionedContainer(fixedContainer, nonPartitionItemId);
await this.CreateUndefinedPartitionItem((ContainerCore)this.Container);
fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer(
this.database,
"ReadNonPartition" + Guid.NewGuid());

await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, nonPartitionItemId);
await NonPartitionedContainerHelper.CreateUndefinedPartitionItem((ContainerCore)this.Container, undefinedPartitionItemId);

ContainerResponse containerResponse = await fixedContainer.ReadContainerAsync();
Assert.IsTrue(containerResponse.Resource.PartitionKey.Paths.Count > 0);
Expand Down Expand Up @@ -1390,13 +1389,15 @@ public async Task MigrateDataInNonPartitionContainer()
ContainerCore fixedContainer = null;
try
{
fixedContainer = await this.CreateNonPartitionedContainer("ItemTestMigrateData" + Guid.NewGuid().ToString());
fixedContainer = await NonPartitionedContainerHelper.CreateNonPartitionedContainer(
this.database,
"ItemTestMigrateData" + Guid.NewGuid().ToString());

const int ItemsToCreate = 4;
// Insert a few items with no Partition Key
for (int i = 0; i < ItemsToCreate; i++)
{
await this.CreateItemInNonPartitionedContainer(fixedContainer, Guid.NewGuid().ToString());
await NonPartitionedContainerHelper.CreateItemInNonPartitionedContainer(fixedContainer, Guid.NewGuid().ToString());
}

// Read the container metadata
Expand Down Expand Up @@ -1645,124 +1646,6 @@ private static async Task ExecuteReadFeedAsync(Container container, HttpStatusCo
}
}

private async Task<ContainerCore> CreateNonPartitionedContainer(string id)
{
await CosmosItemTests.CreateNonPartitionedContainer(
this.database.Id,
id);

return (ContainerCore)this.cosmosClient.GetContainer(this.database.Id, id);
}

internal static async Task CreateNonPartitionedContainer(
string dbName,
string containerName,
string indexingPolicyString = null)
{
string authKey = ConfigurationManager.AppSettings["MasterKey"];
string endpoint = ConfigurationManager.AppSettings["GatewayEndpoint"];
//Creating non partition Container, rest api used instead of .NET SDK api as it is not supported anymore.
HttpClient client = new System.Net.Http.HttpClient();
Uri baseUri = new Uri(endpoint);
string verb = "POST";
string resourceType = "colls";
string resourceId = string.Format("dbs/{0}", dbName);
string resourceLink = string.Format("dbs/{0}/colls", dbName);
client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", CosmosItemTests.PreNonPartitionedMigrationApiVersion);

string authHeader = CosmosItemTests.GenerateMasterKeyAuthorizationSignature(verb, resourceId, resourceType, authKey, "master", "1.0");

client.DefaultRequestHeaders.Add("authorization", authHeader);
DocumentCollection documentCollection = new DocumentCollection()
{
Id = containerName
};
if (indexingPolicyString != null)
{
documentCollection.IndexingPolicy = JsonConvert.DeserializeObject<IndexingPolicy>(indexingPolicyString);
}
string containerDefinition = documentCollection.ToString();
StringContent containerContent = new StringContent(containerDefinition);
Uri requestUri = new Uri(baseUri, resourceLink);
HttpResponseMessage response = await client.PostAsync(requestUri.ToString(), containerContent);
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode, response.ToString());
}

private async Task CreateItemInNonPartitionedContainer(ContainerCore container, string itemId)
{
string authKey = ConfigurationManager.AppSettings["MasterKey"];
string endpoint = ConfigurationManager.AppSettings["GatewayEndpoint"];
//Creating non partition Container item.
HttpClient client = new System.Net.Http.HttpClient();
Uri baseUri = new Uri(endpoint);
string verb = "POST";
string resourceType = "docs";
string resourceLink = string.Format("dbs/{0}/colls/{1}/docs", this.database.Id, container.Id);
string authHeader = CosmosItemTests.GenerateMasterKeyAuthorizationSignature(verb, container.LinkUri.OriginalString, resourceType, authKey, "master", "1.0");

client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", CosmosItemTests.PreNonPartitionedMigrationApiVersion);
client.DefaultRequestHeaders.Add("authorization", authHeader);

string itemDefinition = JsonConvert.SerializeObject(ToDoActivity.CreateRandomToDoActivity(id: itemId));
{
StringContent itemContent = new StringContent(itemDefinition);
Uri requestUri = new Uri(baseUri, resourceLink);
HttpResponseMessage response = await client.PostAsync(requestUri.ToString(), itemContent);
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode, response.ToString());
}
}

private async Task CreateUndefinedPartitionItem(ContainerCore container)
{
string authKey = ConfigurationManager.AppSettings["MasterKey"];
string endpoint = ConfigurationManager.AppSettings["GatewayEndpoint"];
//Creating undefined partition key item, rest api used instead of .NET SDK api as it is not supported anymore.
HttpClient client = new System.Net.Http.HttpClient();
Uri baseUri = new Uri(endpoint);
client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", CosmosItemTests.PreNonPartitionedMigrationApiVersion);
client.DefaultRequestHeaders.Add("x-ms-documentdb-partitionkey", "[{}]");

//Creating undefined partition Container item.
string verb = "POST";
string resourceType = "docs";
string resourceId = container.LinkUri.OriginalString;
string resourceLink = string.Format("dbs/{0}/colls/{1}/docs", this.database.Id, container.Id);
string authHeader = CosmosItemTests.GenerateMasterKeyAuthorizationSignature(verb, resourceId, resourceType, authKey, "master", "1.0");

client.DefaultRequestHeaders.Remove("authorization");
client.DefaultRequestHeaders.Add("authorization", authHeader);

var payload = new { id = undefinedPartitionItemId, user = undefinedPartitionItemId };
string itemDefinition = JsonConvert.SerializeObject(payload);
StringContent itemContent = new StringContent(itemDefinition);
Uri requestUri = new Uri(baseUri, resourceLink);
await client.PostAsync(requestUri.ToString(), itemContent);
}

private static string GenerateMasterKeyAuthorizationSignature(string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion)
{
System.Security.Cryptography.HMACSHA256 hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };

string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceId,
utc_date.ToLowerInvariant(),
""
);

byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);

return System.Web.HttpUtility.UrlEncode(string.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
}

public class ToDoActivityAfterMigration
{
public string id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ private async Task<Container> CreateNonPartitionedContainer(
Microsoft.Azure.Cosmos.IndexingPolicy indexingPolicy = null)
{
string containerName = Guid.NewGuid().ToString() + "container";
await CosmosItemTests.CreateNonPartitionedContainer(
this.database.Id,
await NonPartitionedContainerHelper.CreateNonPartitionedContainer(
this.database,
containerName,
indexingPolicy == null ? null : JsonConvert.SerializeObject(indexingPolicy));

Expand Down
Loading