-
Notifications
You must be signed in to change notification settings - Fork 384
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
Conversation
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(); |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
....Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/CloudBigtableV2TestProxyImpl.cs
Outdated
Show resolved
Hide resolved
|
||
private CloudBigtableV2TestProxyImpl() => _idClientMap = new(); | ||
|
||
private static void CheckArgument(bool condition, string message, ServerCallContext context) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per operation
GcpCallInvoker callInvoker = builder.CreateGcpCallInvoker(); | ||
builder.CallInvoker = callInvoker; |
There was a problem hiding this comment.
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
.
19750ac
to
c26bd13
Compare
There was a problem hiding this 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; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
public class CloudBigtableV2TestProxyImpl : CloudBigtableV2TestProxy.CloudBigtableV2TestProxyBase | |
public sealed class CloudBigtableV2TestProxyImpl : CloudBigtableV2TestProxy.CloudBigtableV2TestProxyBase |
|
||
public static CloudBigtableV2TestProxyImpl Create() => new(); | ||
|
||
protected class CbtClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected class CbtClient | |
private class CbtClient |
|
||
private void OverrideTimeoutSetting(long timeout, BigtableServiceApiSettings settings) | ||
{ | ||
settings.CheckAndMutateRowSettings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout))); |
There was a problem hiding this comment.
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:
settings.CheckAndMutateRowSettings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout))); | |
var settings = CallSettings.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(timeout))); | |
settings.CheckAndMutateRowSettings = settings; |
There was a problem hiding this comment.
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); ; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return new RemoveClientResponse(); | |
return Task.FromResult(new RemoveClientResponse()); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
....Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/CloudBigtableV2TestProxyImpl.cs
Outdated
Show resolved
Hide resolved
c26bd13
to
fe00441
Compare
debafb2
to
c959e81
Compare
2d57ed3
to
4e3d862
Compare
7a2461f
to
ab1f633
Compare
ab1f633
to
25487ef
Compare
aa420fd
to
005eb5a
Compare
There was a problem hiding this 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.
apis/Google.Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Program.cs
Show resolved
Hide resolved
string clientId = request.ClientId; | ||
string projectId = request.ProjectId; | ||
string instanceId = request.InstanceId; | ||
string dataTarget = request.DataTarget; | ||
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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:
GaxPreconditions.CheckArgument(clientId is not ("" or null), "ClientId", "client id must be provided"); | |
GaxPreconditions.NullOrEmpty(clientId, nameof(request.ClientId), "client id must be provided"); |
There was a problem hiding this comment.
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.
...gle.Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/appsettings.Development.json
Outdated
Show resolved
Hide resolved
<PackageReference Include="Grpc.AspNetCore" VersionOverride="2.40.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" VersionOverride="2.3.0" /> |
There was a problem hiding this comment.
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: ****
<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" /> |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
apis/Google.Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/appsettings.json
Show resolved
Hide resolved
apis/Google.Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Program.cs
Outdated
Show resolved
Hide resolved
apis/Google.Cloud.Bigtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Program.cs
Outdated
Show resolved
Hide resolved
|| (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. |
There was a problem hiding this comment.
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.
005eb5a
to
fc17128
Compare
There was a problem hiding this 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.
string clientId = request.ClientId; | ||
string projectId = request.ProjectId; | ||
string instanceId = request.InstanceId; | ||
string dataTarget = request.DataTarget; | ||
CreateClientRequest.Types.SecurityOptions securityOptions = request.SecurityOptions; |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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).
...gtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Services/CloudBigtableV2TestProxyImpl.cs
Outdated
Show resolved
Hide resolved
...gtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Services/CloudBigtableV2TestProxyImpl.cs
Outdated
Show resolved
Hide resolved
...gtable.V2/Google.Cloud.Bigtable.V2.ConformanceTests/Services/CloudBigtableV2TestProxyImpl.cs
Outdated
Show resolved
Hide resolved
|
||
public override async Task<ExecuteQueryResult> ExecuteQuery(ExecuteQueryRequest request, ServerCallContext context) | ||
{ | ||
CbtClient cbtClient = GetClient(request.ClientId, context); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
6df3c05
to
8d6c7a8
Compare
5c30538
to
fc854ae
Compare
@@ -14,13 +14,10 @@ | |||
# limitations under the License. | |||
|
|||
set -eo pipefail | |||
set +e |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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" /> |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
# 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#!/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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# limitations under the License. | |
# limitations under the License. | |
#!/bin/bash |
|
||
ChannelCredentials GetChannelCredentials() | ||
{ | ||
// securityOptions.UseSsl, securityOptions.SslRootCertsPem, securityOptions.AccessToken |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gotcha
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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());
context.Status = new Status(StatusCode.NotFound, $"Client {clientId} not found."); | ||
throw new RpcException(context.Status); |
There was a problem hiding this comment.
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.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. That is correct.
each &mutateRowsAction represents a row. You will see that, in other tests, there are multiple of those defined. The delay is defined per row.
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]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]; | |
} |
3761781
to
aa27489
Compare
aa27489
to
c49a4d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing!
Reference: https://github.com/googleapis/java-bigtable/blob/main/test-proxy/src/main/java/com/google/cloud/bigtable/testproxy/CbtTestProxy.java
Implement conformance tests for BigTable.