Skip to content

feat: BigTable Conformance Tests GRPC Proxy Implementation #14367

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

Merged
merged 1 commit into from
May 9, 2025

Conversation

cy-yun
Copy link
Contributor

@cy-yun cy-yun commented Mar 1, 2025

@cy-yun cy-yun self-assigned this Mar 1, 2025
@cy-yun cy-yun requested review from a team as code owners March 1, 2025 02:59
builder.GrpcChannelOptions = (securityOptions.UseSsl && securityOptions.SslEndpointOverride is not null)
? GrpcChannelOptions.Empty.WithCustomOption("grpc.ssl_target_name_override", securityOptions.SslEndpointOverride)
: GrpcChannelOptions.Empty; ;
builder.GoogleCredential = accessToken is not null ? GoogleCredential.FromAccessToken(accessToken) : GoogleCredential.GetApplicationDefault();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

don't set if accessToken is null

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

Looking good! I'll leave some more comments later.


private CloudBigtableV2TestProxyImpl() => _idClientMap = new();

private static void CheckArgument(bool condition, string message, ServerCallContext context)
Copy link
Contributor

Choose a reason for hiding this comment

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

GaxPreconditions.CheckArgument

if (request.PerOperationTimeout is not null)
{
Expiration exp = Expiration.FromTimeout(TimeSpan.FromSeconds(request.PerOperationTimeout.Seconds));
settings.CallSettings = CallSettings.FromExpiration(exp);
Copy link
Contributor

Choose a reason for hiding this comment

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

Per operation

Comment on lines 104 to 105
GcpCallInvoker callInvoker = builder.CreateGcpCallInvoker();
builder.CallInvoker = callInvoker;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of doing this, after calling builder.BuildAsync you may have access to something called LastCreatedChannel.

@cy-yun cy-yun assigned amanda-tarafa and unassigned cy-yun Mar 5, 2025
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch from 19750ac to c26bd13 Compare March 5, 2025 00:40
Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

I've left a few more comments. Happy to chat about them.


private CloudBigtableV2TestProxyImpl() => _idClientMap = new();

private static ChannelCredentials GetChannelCredentials(bool encrypted, string rootCertsPem) => encrypted ? new SslCredentials(rootCertsPem) : ChannelCredentials.Insecure;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: We usually put helper methods (private usually), both static and instance, at the end of the class definition.


namespace Google.Cloud.Bigtable.V2.ConformanceTests;

public class CloudBigtableV2TestProxyImpl : CloudBigtableV2TestProxy.CloudBigtableV2TestProxyBase
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
public class CloudBigtableV2TestProxyImpl : CloudBigtableV2TestProxy.CloudBigtableV2TestProxyBase
public sealed class CloudBigtableV2TestProxyImpl : CloudBigtableV2TestProxy.CloudBigtableV2TestProxyBase


public static CloudBigtableV2TestProxyImpl Create() => new();

protected class CbtClient
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
protected class CbtClient
private class CbtClient


private void OverrideTimeoutSetting(long timeout, BigtableServiceApiSettings settings)
{
settings.CheckAndMutateRowSettings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout)));
Copy link
Contributor

Choose a reason for hiding this comment

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

CallSettings and Expiration are immutable (I'm pretty sure), so something like this should be fine:

Suggested change
settings.CheckAndMutateRowSettings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout)));
var settings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout)));
settings.CheckAndMutateRowSettings = settings;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it. I had it like that initially but changed it to this implementation bc I was worried they might be mutable.

{
string accessToken = request.SecurityOptions.AccessToken;
builder.Endpoint = dataTarget;
builder.ChannelCredentials = GetChannelCredentials(securityOptions.UseSsl, securityOptions.SslRootCertsPem); ;
Copy link
Contributor

Choose a reason for hiding this comment

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

We cannot have ChannelCredentials and GoogleCredentials at the same time, the builder does not allow it.

If we have cases like those, we need to build the ChannelCredential to include the access token. I can show you how.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you please link code samples or docs?

{
string clientId = request.ClientId;
GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided", context);
bool removed = await Task.Run(() => _idClientMap.Remove(clientId)).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to run this asynchronously. Just call the remove method normally and then...

throw new RpcException(context.Status);
}
context.Status = new Status(StatusCode.OK, "RemoveClient succeeded");
return new RemoveClientResponse();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return new RemoveClientResponse();
return Task.FromResult(new RemoveClientResponse());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah this is nice thank you!

return new CloseClientResponse();
}

public override async Task<RemoveClientResponse> RemoveClient(RemoveClientRequest request, ServerCallContext context)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public override async Task<RemoveClientResponse> RemoveClient(RemoveClientRequest request, ServerCallContext context)
public override Task<RemoveClientResponse> RemoveClient(RemoveClientRequest request, ServerCallContext context)

{
string clientId = request.ClientId;
GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided", context);
bool removed = await Task.Run(() => _idClientMap.Remove(clientId)).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

One question here is, we don't shutdown clients that we remove?
And we don't remove clients that we shutdown?

That may be the case, and the test runner could simply be calling Close and then Remove, but let's double check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copied the Java implementation for this.

@cy-yun cy-yun force-pushed the big-table-conformance-tests branch from c26bd13 to fe00441 Compare March 7, 2025 23:58
@cy-yun cy-yun changed the title feat: Initial implementation for BigTable conformance tests with CreateClient, RemoveClient, CloseClient, and ReadRow feat: BigTable Conformance Tests GRPC Proxy Implementation Mar 7, 2025
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 3 times, most recently from debafb2 to c959e81 Compare March 11, 2025 22:21
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 9 times, most recently from 2d57ed3 to 4e3d862 Compare March 21, 2025 22:22
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 5 times, most recently from 7a2461f to ab1f633 Compare March 24, 2025 18:35
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch from ab1f633 to 25487ef Compare April 2, 2025 19:26
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 2 times, most recently from aa420fd to 005eb5a Compare April 21, 2025 20:56
Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

I've done a first pass. I'll continue tomorrow, fine if you want to wait until I'm done.

The changes in CloudBigtableV2TestProxyImpl apply to all methods there, not just the one where I left them.

Comment on lines 45 to 50
string clientId = request.ClientId;
string projectId = request.ProjectId;
string instanceId = request.InstanceId;
string dataTarget = request.DataTarget;
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would just use the request.ClientId etc. here, instead of having variables.

Copy link
Contributor

Choose a reason for hiding this comment

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

This applies to all other methods.

string dataTarget = request.DataTarget;
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions;

GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided");
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to validate the data that the test proxy sends us?

If yes, then we have something like the following, if I remember right:

Suggested change
GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided");
GaxPreconditions.NullOrEmpty(clientId, nameof(request.ClientId), "client id must be provided");

Copy link
Contributor

Choose a reason for hiding this comment

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

This applies to all other methods.

<PackageReference Include="Grpc.AspNetCore" VersionOverride="2.40.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" VersionOverride="2.3.0" />
Copy link
Contributor

Choose a reason for hiding this comment

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

These versions need to be added in Directory.Packages.props and here you'd only need: ****

Suggested change
<PackageReference Include="Grpc.AspNetCore" VersionOverride="2.40.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" VersionOverride="2.3.0" />
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure if I understand this feedback!

Copy link
Contributor

Choose a reason for hiding this comment

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

We have centralized package management on this repo, so versions should be in https://github.com/googleapis/google-cloud-dotnet/blob/main/Directory.Packages.props, and here you only need to reference the package, with no version as in my code suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it. I understand now

|| (securityOptions.SslRootCertsPem is not ("" or null))), "SecurityOptions",
"security_options.ssl_root_certs_pem must be provided if security_options.use_ssl is true");

#pragma warning disable CS8604 // Possible null reference argument.
Copy link
Contributor

Choose a reason for hiding this comment

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

When you remove nullable from the csproj, you can remove all of these.

@cy-yun cy-yun force-pushed the big-table-conformance-tests branch from 005eb5a to fc17128 Compare April 24, 2025 18:07
Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

A few more changes on the proxy implementation but nothing huge.

Comment on lines 45 to 50
string clientId = request.ClientId;
string projectId = request.ProjectId;
string instanceId = request.InstanceId;
string dataTarget = request.DataTarget;
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions;
Copy link
Contributor

Choose a reason for hiding this comment

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

This applies to all other methods.

string dataTarget = request.DataTarget;
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions;

GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided");
Copy link
Contributor

Choose a reason for hiding this comment

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

This applies to all other methods.

import "google/rpc/status.proto";

option go_package = "./testproxypb";
option csharp_namespace = "google.bigtable.testproxy";
Copy link
Contributor

@amanda-tarafa amanda-tarafa Apr 24, 2025

Choose a reason for hiding this comment

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

We chatted about this, but it shouldn't be a problem to change the csharp_namespace. The Go code shouldn't be depending on that. We cannot change the other languages packages/namespaces, but ours should be fine. (Unless they are generating the C# code for these protos which I doubt it).


public override async Task<ExecuteQueryResult> ExecuteQuery(ExecuteQueryRequest request, ServerCallContext context)
{
CbtClient cbtClient = GetClient(request.ClientId, context);
Copy link
Contributor

Choose a reason for hiding this comment

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

Following BitTable's advice let's throw Unimplemented exception here and skip all the execute query tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can do that. But if I remove the CloseClient implementation and throw NotImplementedException, the entire conformance test will fail. So I will only get rid of the ExecuteQuery implementation

@amanda-tarafa amanda-tarafa force-pushed the big-table-conformance-tests branch from 6df3c05 to 8d6c7a8 Compare April 24, 2025 23:30
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 10 times, most recently from 5c30538 to fc854ae Compare April 30, 2025 01:23
@@ -14,13 +14,10 @@
# limitations under the License.

set -eo pipefail
set +e
Copy link
Contributor Author

Choose a reason for hiding this comment

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

left this here because the github action will fail without it

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, but that's what we want, that's what -e does, fails the script if there's any error (see https://gist.github.com/mohanpedala/1e2ff5661761d3abd0385e8223e16425?permalink_comment_id=3945021)

There's some error handling below, when executing the tests, so maybe we want to do set +e before running the tests and then set -e right after so that any other errors also make the action fail.

Copy link
Contributor Author

@cy-yun cy-yun May 7, 2025

Choose a reason for hiding this comment

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

so something like this?

pushd .
cd cloud-bigtable-clients-test/tests
# Cookie, RetryInfo, ExecuteQuery, ReverseScans and FeatureGap are known failures of new features that we don't yet support.
# CloseClient we don't support as expected, but we support it in a valid manner.
# For the others we have issues to investigate, see comments in b/372509076 .
set +e
eval "go test -v -proxy_addr=:7238 ${configFlag} -skip _Retry_WithRoutingCookie\|_Retry_WithRetryInfo\|_CloseClient\|_ReverseScans\|TestFeatureGap\|TestExecuteQuery\|TestReadRows_NoRetry_ErrorAfterLastRow\|TestReadRows_Retry_PausedScan\|TestReadRows_Retry_LastScannedRow_Reverse\|TestReadRow_Generic_DeadlineExceeded"
returnCode=$?
set -e
popd

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

A few changes but mostly tyding things up.

This is great!

<PackageReference Include="Grpc.AspNetCore" VersionOverride="2.40.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" VersionOverride="2.3.0" />
Copy link
Contributor

Choose a reason for hiding this comment

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

We have centralized package management on this repo, so versions should be in https://github.com/googleapis/google-cloud-dotnet/blob/main/Directory.Packages.props, and here you only need to reference the package, with no version as in my code suggestion.

import "google/rpc/status.proto";

option go_package = "./testproxypb";
option csharp_namespace = "google.bigtable.testproxy";
Copy link
Contributor

Choose a reason for hiding this comment

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

Unresolving this one, it shouldn't be a problem to change the csharp_namespace, as that only affects the C# generated code, and it's unlikely to be referenced by the test runner.

Comment on lines 36 to 37
# Cookie, RetryInfo, ExecuteQuery, CloseClient, ReverseScans and FeatureGap are known failures of new features
# or, in the case of CloseClient a feature that we are looking into how to support.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Cookie, RetryInfo, ExecuteQuery, CloseClient, ReverseScans and FeatureGap are known failures of new features
# or, in the case of CloseClient a feature that we are looking into how to support.
# Cookie, RetryInfo, ExecuteQuery, ReverseScans and FeatureGap are known failures of new features that we don't yet support.
# CloseClient we don't support as expected, but we support it in a valid manner.
# For the others we have issuess to investigate, see b/372509076.

@@ -0,0 +1,53 @@
#!/bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#!/bin/bash

# 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# limitations under the License.
# limitations under the License.
#!/bin/bash


ChannelCredentials GetChannelCredentials()
{
// securityOptions.UseSsl, securityOptions.SslRootCertsPem, securityOptions.AccessToken
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove commented line (I think that was my leftover)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gotcha

Comment on lines 119 to 132
CbtClient cbtClient = GetClient(request.ClientId, context);
try
{
if (cbtClient.LastCreatedChannel is not null)
{
await cbtClient.LastCreatedChannel.ShutdownAsync();
}
}
catch (Exception e)
{
context.Status = new Status(StatusCode.Internal, e.Message, e);
throw new RpcException(context.Status);
}
return new CloseClientResponse();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it that if you throw UnimplementedException here all tests fail? Do you think CloseClient is being called after testing each of the others?

If that's the case, let's till do this a no-op, do not store the channel or close it. Just keep this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. CloseClient is being called after each test. I will remove the implementation and just leave

return Task.FromResult(new CloseClientResponse());

Comment on lines 141 to 142
context.Status = new Status(StatusCode.NotFound, $"Client {clientId} not found.");
throw new RpcException(context.Status);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should work.

Suggested change
context.Status = new Status(StatusCode.NotFound, $"Client {clientId} not found.");
throw new RpcException(context.Status);
throw new RpcException(new Status(StatusCode.NotFound, $"Client {clientId} not found."));

if (e.StatusCode == StatusCode.DeadlineExceeded)
{
int i = 0;
foreach (Google.Cloud.Bigtable.V2.MutateRowsRequest.Types.Entry entry in request.Request.Entries)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that the library could return a different code for each row? Let's check with the BT team.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. That is correct.

https://github.com/googleapis/cloud-bigtable-clients-test/blob/main/tests/mutaterows_test.go#L196

each &mutateRowsAction represents a row. You will see that, in other tests, there are multiple of those defined. The delay is defined per row.

Comment on lines 337 to 312
private CbtClient GetClient(string clientId, ServerCallContext context)
{
if (!s_clientMap.ContainsKey(clientId))
{
context.Status = new Status(StatusCode.NotFound, $"Client {clientId} not found.");
throw new RpcException(context.Status);
}

return s_clientMap[clientId];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private CbtClient GetClient(string clientId, ServerCallContext context)
{
if (!s_clientMap.ContainsKey(clientId))
{
context.Status = new Status(StatusCode.NotFound, $"Client {clientId} not found.");
throw new RpcException(context.Status);
}
return s_clientMap[clientId];
}
private CbtClient GetClient(string clientId)
{
if (!s_clientMap.ContainsKey(clientId))
{
throw new RpcException(new Status(StatusCode.NotFound, $"Client {clientId} not found."));
}
return s_clientMap[clientId];
}

@cy-yun cy-yun force-pushed the big-table-conformance-tests branch 8 times, most recently from 3761781 to aa27489 Compare May 7, 2025 21:44
@cy-yun cy-yun force-pushed the big-table-conformance-tests branch from aa27489 to c49a4d1 Compare May 9, 2025 18:32
Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

Amazing!

@cy-yun cy-yun merged commit de572c5 into googleapis:main May 9, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants