Skip to content

Commit

Permalink
feat: Multi-db support in Datastore.
Browse files Browse the repository at this point in the history
  • Loading branch information
anuragsrivstv committed Nov 14, 2023
1 parent eecc6fa commit 614575d
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Cloud.ClientTesting;
using Grpc.Core;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -68,6 +69,35 @@ public void Lookup_NamespaceOnly()
Assert.Equal("bar", (string)entity["foo"]);
}

[Fact(Skip = "Multiple databases are available only in preview right now.")]
public async Task MultiDb_InsertLookupDelete()
{
await _fixture.RunWithTemporaryDatabaseAsync(databaseId =>
{
var db = _fixture.CreateDatastoreDbWithDatabase(databaseId);
var keyFactory = db.CreateKeyFactory("test_dbid");
var entities = new[]
{
new Entity { Key = keyFactory.CreateKey("x"), ["description"] = "predefined_key" },
new Entity { Key = keyFactory.CreateIncompleteKey(), ["description"] = "incomplete_key" }
};

var insertedKey = db.Insert(entities);

Assert.Null(insertedKey[0]); // Insert with predefined key
Assert.NotNull(insertedKey[1]); // Insert with incomplete key
Assert.Equal(insertedKey[1], entities[1].Key); // Inserted key is propagated into entity

var lookupKey = new Key { PartitionId = new PartitionId { ProjectId = _fixture.ProjectId, NamespaceId = _fixture.NamespaceId, DatabaseId = databaseId }, Path = { insertedKey[1].Path } };
var fetchedEntity = db.Lookup(lookupKey);
Assert.NotNull(fetchedEntity);
Assert.Equal("incomplete_key", fetchedEntity["description"]);

db.Delete(lookupKey);
Assert.Null(db.Lookup(lookupKey));
});
}

[Fact]
public async Task Lookup_NoPartition()
{
Expand All @@ -85,7 +115,7 @@ public async Task Lookup_NoPartition()
var lookupKey = new Key { Path = { insertedKey.Path } };
var result = db.Lookup(lookupKey);
Assert.NotNull(result);
Assert.Equal("bar", (string)entity["foo"]);
Assert.Equal("bar", (string) entity["foo"]);

// And the same lookup asynchronously...
Assert.Equal(result, await db.LookupAsync(lookupKey));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 Google Inc. All Rights Reserved.
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,12 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Google.Api.Gax;
using Google.Api.Gax.ResourceNames;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Http;
using Google.Cloud.ClientTesting;
using Google.Cloud.Firestore.Admin.V1;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using static Google.Cloud.Firestore.Admin.V1.Database.Types;

namespace Google.Cloud.Datastore.V1.IntegrationTests
{
Expand All @@ -31,12 +37,35 @@ public sealed class DatastoreFixture : CloudProjectFixtureBase, ICollectionFixtu
private const int RetryCount = 10;
private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(3);

private readonly HttpClient _httpClient = new();
private FirestoreAdminClient _firestoreAdminClient;

internal bool RunningOnEmulator { get; }

// Creating databases and indexes can take a while.
// We don't want to wait *forever* (which would be the behavior of default poll settings)
// but we need to have a timeout of more than a minute.
private static readonly PollSettings AdminOperationPollSettings =
new PollSettings(expiration: Expiration.FromTimeout(TimeSpan.FromMinutes(5)), delay: TimeSpan.FromSeconds(5));

public string NamespaceId { get; }
public PartitionId PartitionId => new PartitionId { ProjectId = ProjectId, NamespaceId = NamespaceId };

public DatastoreFixture()
{
NamespaceId = IdGenerator.FromDateTime(prefix: "test-");
RunningOnEmulator = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DATASTORE_EMULATOR_HOST"));

//yyyScope used for the REST API to create databases.

string scope = "https://www.googleapis.com/auth/datastore";
string credentialsPath = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS");

// Initalize credentials to be used with REST API calls.
GoogleCredential googleCredential = GoogleCredential.FromFile(credentialsPath).CreateScoped(scope);
_httpClient = new HttpClientFactory()
.CreateHttpClient(new CreateHttpClientArgs { Initializers = { googleCredential } });
_httpClient.BaseAddress = new Uri("https://firestore.googleapis.com");
}

public override void Dispose()
Expand Down Expand Up @@ -99,5 +128,82 @@ public DatastoreDb CreateDatastoreDb(string namespaceId = null)
};
return builder.Build();
}

public DatastoreDb CreateDatastoreDbWithDatabase(string databaseId)
{
var builder = new DatastoreDbBuilder
{
ProjectId = ProjectId,
NamespaceId = NamespaceId,
DatabaseId = databaseId ?? "",
EmulatorDetection = EmulatorDetection.EmulatorOrProduction
};
return builder.Build();
}

public async Task RunWithTemporaryDatabaseAsync(Action<string> testFunction)
{
var databaseId = IdGenerator.FromDateTime(prefix: "test-db-");
await CreateDatabaseAsync(databaseId);

try
{
testFunction(databaseId);
}
finally
{
// Cleanup the test database.
try
{
await DeleteDatabaseAsync(databaseId);
}
catch (Exception)
{
// Silently ignore errors to prevent tests from failing.
}
}
}

/// <summary>
/// Creates a new Datastore database using Firestore Admin Client.
/// </summary>
public async Task CreateDatabaseAsync(string databaseId)
{
var pr = new ProjectName(ProjectId);
// Creating a new database is not supported on Datastore emulator.
Assert.False(RunningOnEmulator);
var client = await GetFirestoreAdminClientAsync();
var operation = await client.CreateDatabaseAsync(
new ProjectName(ProjectId),
new Database { LocationId = "us-east1", Type = DatabaseType.DatastoreMode },
databaseId);
await operation.PollUntilCompletedAsync(AdminOperationPollSettings);
Console.WriteLine($"Success creating database {databaseId}");

}

private async Task<FirestoreAdminClient> GetFirestoreAdminClientAsync()
{
if (_firestoreAdminClient == null)
{
_firestoreAdminClient = await FirestoreAdminClient.CreateAsync();
}
return _firestoreAdminClient;
}

private async Task DeleteDatabaseAsync(string databaseId)
{
var deleteDbUrl = $"/v1/projects/{ProjectId}/databases/{databaseId}";
try
{
var response = await _httpClient.DeleteAsync(deleteDbUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
Console.WriteLine($"Success deleting database {databaseId}");
}
catch (Exception ex)
{
Console.WriteLine($"Failure deleting database {databaseId}. Reason: {ex.Message}");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Api.Gax.Grpc.Testing" Version="[4.4.0, 5.0.0)" />
<PackageReference Include="Google.Cloud.Firestore.Admin.V1" Version="3.3.0" />
<ProjectReference Include="..\..\..\tools\Google.Cloud.ClientTesting\Google.Cloud.ClientTesting.csproj" />
<ProjectReference Include="..\Google.Cloud.Datastore.V1\Google.Cloud.Datastore.V1.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
Expand Down
Loading

0 comments on commit 614575d

Please sign in to comment.