Merge remote-tracking branch 'upstream/master' into ignore

pull/22983/head
Donna Dionne 5 years ago
commit 12821db7f8
  1. 2
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 2
      .github/ISSUE_TEMPLATE/cleanup_request.md
  3. 2
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 2
      .github/ISSUE_TEMPLATE/question.md
  5. 2
      .github/pull_request_template.md
  6. 2
      .github/stale.yml
  7. 24
      CMakeLists.txt
  8. 8
      src/core/ext/filters/http/client_authority_filter.cc
  9. 4
      src/core/ext/transport/chttp2/transport/writing.cc
  10. 4
      src/core/lib/security/security_connector/ssl_utils.cc
  11. 3
      src/csharp/Grpc.IntegrationTesting.XdsClient/.gitignore
  12. 24
      src/csharp/Grpc.IntegrationTesting.XdsClient/Grpc.IntegrationTesting.XdsClient.csproj
  13. 31
      src/csharp/Grpc.IntegrationTesting.XdsClient/Program.cs
  14. 29
      src/csharp/Grpc.IntegrationTesting.XdsClient/Properties/AssemblyInfo.cs
  15. 300
      src/csharp/Grpc.IntegrationTesting/XdsInteropClient.cs
  16. 134
      src/csharp/Grpc.IntegrationTesting/XdsInteropClientTest.cs
  17. 6
      src/csharp/Grpc.sln
  18. 3
      src/csharp/tests.json
  19. 37
      src/php/ext/grpc/php_grpc.c
  20. 208
      src/ruby/pb/test/xds_client.rb
  21. 24
      templates/CMakeLists.txt.template
  22. 70
      test/cpp/end2end/xds_end2end_test.cc
  23. 25
      tools/internal_ci/linux/grpc_xds_csharp.cfg
  24. 26
      tools/internal_ci/linux/grpc_xds_csharp.sh
  25. 59
      tools/internal_ci/linux/grpc_xds_csharp_test_in_docker.sh
  26. 25
      tools/internal_ci/linux/grpc_xds_ruby.cfg
  27. 26
      tools/internal_ci/linux/grpc_xds_ruby.sh
  28. 60
      tools/internal_ci/linux/grpc_xds_ruby_test_in_docker.sh
  29. 3
      tools/interop_matrix/client_matrix.py

@ -2,7 +2,7 @@
name: Report a bug
about: Create a report to help us improve
labels: kind/bug, priority/P2
assignees: nicolasnoble
assignees: yashykt
---

@ -2,7 +2,7 @@
name: Request a cleanup
about: Suggest a cleanup in our repository
labels: kind/internal cleanup, priority/P2
assignees: nicolasnoble
assignees: yashykt
---

@ -2,7 +2,7 @@
name: Request a feature
about: Suggest an idea for this project
labels: kind/enhancement, priority/P2
assignees: nicolasnoble
assignees: yashykt
---

@ -2,7 +2,7 @@
name: Ask a question
about: Ask a question
labels: kind/question, priority/P3
assignees: nicolasnoble
assignees: yashykt
---

@ -8,4 +8,4 @@ If you know who should review your pull request, please remove the mentioning be
-->
@nicolasnoble
@yashykt

@ -1,7 +1,7 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 30
daysUntilStale: 90
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.

@ -153,14 +153,28 @@ if(WIN32)
endif()
# Use C99 standard
set(CMAKE_C_STANDARD 99)
if (NOT DEFINED CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Add c++11 flags
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
else()
if (CMAKE_CXX_STANDARD LESS 11)
message(FATAL_ERROR "CMAKE_CXX_STANDARD is less than 11, please specify at least SET(CMAKE_CXX_STANDARD 11)")
endif()
endif()
if (NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
if (NOT DEFINED CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
if (NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
if(MSVC)

@ -53,13 +53,13 @@ void client_authority_start_transport_stream_op_batch(
channel_data* chand = static_cast<channel_data*>(elem->channel_data);
call_data* calld = static_cast<call_data*>(elem->call_data);
// Handle send_initial_metadata.
auto* initial_metadata =
batch->payload->send_initial_metadata.send_initial_metadata;
// If the initial metadata doesn't already contain :authority, add it.
if (batch->send_initial_metadata &&
initial_metadata->idx.named.authority == nullptr) {
batch->payload->send_initial_metadata.send_initial_metadata->idx.named
.authority == nullptr) {
grpc_error* error = grpc_metadata_batch_add_head(
initial_metadata, &calld->authority_storage,
batch->payload->send_initial_metadata.send_initial_metadata,
&calld->authority_storage,
GRPC_MDELEM_REF(chand->default_authority_mdelem), GRPC_BATCH_AUTHORITY);
if (error != GRPC_ERROR_NONE) {
grpc_transport_stream_op_batch_finish_with_failure(batch, error,

@ -81,7 +81,9 @@ static void maybe_initiate_ping(grpc_chttp2_transport* t) {
(t->keepalive_permit_without_calls == 0 &&
grpc_chttp2_stream_map_size(&t->stream_map) == 0)
? 7200 * GPR_MS_PER_SEC
: t->ping_policy.min_sent_ping_interval_without_data;
: (t->ping_policy.min_sent_ping_interval_without_data +
GPR_MS_PER_SEC); /* A second is added to deal with network delays
and timing imprecision */
grpc_millis next_allowed_ping =
t->ping_state.last_ping_sent_time + next_allowed_ping_interval;

@ -222,7 +222,7 @@ int grpc_ssl_cmp_target_name(absl::string_view target_name,
return overridden_target_name.compare(other_overridden_target_name);
}
static bool isSpiffeId(absl::string_view uri) {
static bool IsSpiffeId(absl::string_view uri) {
// Return false without logging for a non-spiffe uri scheme.
if (!absl::StartsWith(uri, "spiffe://")) {
return false;
@ -291,7 +291,7 @@ grpc_core::RefCountedPtr<grpc_auth_context> grpc_ssl_peer_to_auth_context(
prop->value.data, prop->value.length);
} else if (strcmp(prop->name, TSI_X509_URI_PEER_PROPERTY) == 0) {
absl::string_view spiffe_id(prop->value.data, prop->value.length);
if (isSpiffeId(spiffe_id)) {
if (IsSpiffeId(spiffe_id)) {
spiffe_data = prop->value.data;
spiffe_length = prop->value.length;
spiffe_id_count += 1;

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Grpc.Core\Common.csproj.include" />
<PropertyGroup>
<TargetFrameworks>net45;netcoreapp2.1</TargetFrameworks>
<OutputType>Exe</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Grpc.Core.Api\Version.cs" />
</ItemGroup>
</Project>

@ -0,0 +1,31 @@
#region Copyright notice and license
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// 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.
#endregion
using System;
using Grpc.IntegrationTesting;
namespace Grpc.IntegrationTesting.XdsClient
{
class Program
{
public static void Main(string[] args)
{
XdsInteropClient.Run(args);
}
}
}

@ -0,0 +1,29 @@
#region Copyright notice and license
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// 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.
#endregion
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Grpc.IntegrationTesting.XdsClient")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Google Inc. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

@ -0,0 +1,300 @@
#region Copyright notice and license
// Copyright 2020 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// 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.
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using Grpc.Core;
using Grpc.Core.Logging;
using Grpc.Core.Internal;
using Grpc.Testing;
namespace Grpc.IntegrationTesting
{
public class XdsInteropClient
{
internal class ClientOptions
{
[Option("num_channels", Default = 1)]
public int NumChannels { get; set; }
[Option("qps", Default = 1)]
// The desired QPS per channel.
public int Qps { get; set; }
[Option("server", Default = "localhost:8080")]
public string Server { get; set; }
[Option("stats_port", Default = 8081)]
public int StatsPort { get; set; }
[Option("rpc_timeout_sec", Default = 30)]
public int RpcTimeoutSec { get; set; }
[Option("print_response", Default = false)]
public bool PrintResponse { get; set; }
}
ClientOptions options;
StatsWatcher statsWatcher = new StatsWatcher();
// make watcher accessible by tests
internal StatsWatcher StatsWatcher => statsWatcher;
internal XdsInteropClient(ClientOptions options)
{
this.options = options;
}
public static void Run(string[] args)
{
GrpcEnvironment.SetLogger(new ConsoleLogger());
var parserResult = Parser.Default.ParseArguments<ClientOptions>(args)
.WithNotParsed(errors => Environment.Exit(1))
.WithParsed(options =>
{
var xdsInteropClient = new XdsInteropClient(options);
xdsInteropClient.RunAsync().Wait();
});
}
private async Task RunAsync()
{
var server = new Server
{
Services = { LoadBalancerStatsService.BindService(new LoadBalancerStatsServiceImpl(statsWatcher)) }
};
string host = "0.0.0.0";
server.Ports.Add(host, options.StatsPort, ServerCredentials.Insecure);
Console.WriteLine($"Running server on {host}:{options.StatsPort}");
server.Start();
var cancellationTokenSource = new CancellationTokenSource();
await RunChannelsAsync(cancellationTokenSource.Token);
await server.ShutdownAsync();
}
// method made internal to make it runnable by tests
internal async Task RunChannelsAsync(CancellationToken cancellationToken)
{
var channelTasks = new List<Task>();
for (int channelId = 0; channelId < options.NumChannels; channelId++)
{
var channelTask = RunSingleChannelAsync(channelId, cancellationToken);
channelTasks.Add(channelTask);
}
for (int channelId = 0; channelId < options.NumChannels; channelId++)
{
await channelTasks[channelId];
}
}
private async Task RunSingleChannelAsync(int channelId, CancellationToken cancellationToken)
{
Console.WriteLine($"Starting channel {channelId}");
var channel = new Channel(options.Server, ChannelCredentials.Insecure);
var client = new TestService.TestServiceClient(channel);
var inflightTasks = new List<Task>();
long rpcsStarted = 0;
var stopwatch = Stopwatch.StartNew();
while (!cancellationToken.IsCancellationRequested)
{
inflightTasks.Add(RunSingleRpcAsync(client, cancellationToken));
rpcsStarted++;
// only cleanup calls that have already completed, calls that are still inflight will be cleaned up later.
await CleanupCompletedTasksAsync(inflightTasks);
Console.WriteLine($"Currently {inflightTasks.Count} in-flight RPCs");
// if needed, wait a bit before we start the next RPC.
int nextDueInMillis = (int) Math.Max(0, (1000 * rpcsStarted / options.Qps) - stopwatch.ElapsedMilliseconds);
if (nextDueInMillis > 0)
{
await Task.Delay(nextDueInMillis);
}
}
stopwatch.Stop();
Console.WriteLine($"Shutting down channel {channelId}");
await channel.ShutdownAsync();
Console.WriteLine($"Channel shutdown {channelId}");
}
private async Task RunSingleRpcAsync(TestService.TestServiceClient client, CancellationToken cancellationToken)
{
long rpcId = statsWatcher.RpcIdGenerator.Increment();
try
{
Console.WriteLine($"Starting RPC {rpcId}.");
var response = await client.UnaryCallAsync(new SimpleRequest(),
new CallOptions(cancellationToken: cancellationToken, deadline: DateTime.UtcNow.AddSeconds(options.RpcTimeoutSec)));
statsWatcher.OnRpcComplete(rpcId, response.Hostname);
if (options.PrintResponse)
{
Console.WriteLine($"Got response {response}");
}
Console.WriteLine($"RPC {rpcId} succeeded ");
}
catch (RpcException ex)
{
statsWatcher.OnRpcComplete(rpcId, null);
Console.WriteLine($"RPC {rpcId} failed: {ex}");
}
}
private async Task CleanupCompletedTasksAsync(List<Task> tasks)
{
var toRemove = new List<Task>();
foreach (var task in tasks)
{
if (task.IsCompleted)
{
// awaiting tasks that have already completed should be instantaneous
await task;
}
toRemove.Add(task);
}
foreach (var task in toRemove)
{
tasks.Remove(task);
}
}
}
internal class StatsWatcher
{
private readonly object myLock = new object();
private readonly AtomicCounter rpcIdGenerator = new AtomicCounter(0);
private long? firstAcceptedRpcId;
private int numRpcsWanted;
private int rpcsCompleted;
private int rpcsNoHostname;
private Dictionary<string, int> rpcsByHostname;
public AtomicCounter RpcIdGenerator => rpcIdGenerator;
public StatsWatcher()
{
Reset();
}
public void OnRpcComplete(long rpcId, string responseHostname)
{
lock (myLock)
{
if (!firstAcceptedRpcId.HasValue || rpcId < firstAcceptedRpcId || rpcId >= firstAcceptedRpcId + numRpcsWanted)
{
return;
}
if (string.IsNullOrEmpty(responseHostname))
{
rpcsNoHostname ++;
}
else
{
if (!rpcsByHostname.ContainsKey(responseHostname))
{
rpcsByHostname[responseHostname] = 0;
}
rpcsByHostname[responseHostname] += 1;
}
rpcsCompleted += 1;
if (rpcsCompleted >= numRpcsWanted)
{
Monitor.Pulse(myLock);
}
}
}
public void Reset()
{
lock (myLock)
{
firstAcceptedRpcId = null;
numRpcsWanted = 0;
rpcsCompleted = 0;
rpcsNoHostname = 0;
rpcsByHostname = new Dictionary<string, int>();
}
}
public LoadBalancerStatsResponse WaitForRpcStatsResponse(int rpcsWanted, int timeoutSec)
{
lock (myLock)
{
if (firstAcceptedRpcId.HasValue)
{
throw new InvalidOperationException("StateWatcher is already collecting stats.");
}
// we are only interested in the next numRpcsWanted RPCs
firstAcceptedRpcId = rpcIdGenerator.Count + 1;
numRpcsWanted = rpcsWanted;
var deadline = DateTime.UtcNow.AddSeconds(timeoutSec);
while (true)
{
var timeoutMillis = Math.Max((int)(deadline - DateTime.UtcNow).TotalMilliseconds, 0);
if (!Monitor.Wait(myLock, timeoutMillis) || rpcsCompleted >= rpcsWanted)
{
// we collected enough RPCs, or timed out waiting
var response = new LoadBalancerStatsResponse { NumFailures = rpcsNoHostname };
response.RpcsByPeer.Add(rpcsByHostname);
Reset();
return response;
}
}
}
}
}
/// <summary>
/// Implementation of LoadBalancerStatsService server
/// </summary>
internal class LoadBalancerStatsServiceImpl : LoadBalancerStatsService.LoadBalancerStatsServiceBase
{
StatsWatcher statsWatcher;
public LoadBalancerStatsServiceImpl(StatsWatcher statsWatcher)
{
this.statsWatcher = statsWatcher;
}
public override async Task<LoadBalancerStatsResponse> GetClientStats(LoadBalancerStatsRequest request, ServerCallContext context)
{
// run as a task to avoid blocking
var response = await Task.Run(() => statsWatcher.WaitForRpcStatsResponse(request.NumRpcs, request.TimeoutSec));
Console.WriteLine($"Returning stats {response} (num of requested RPCs: {request.NumRpcs})");
return response;
}
}
}

@ -0,0 +1,134 @@
#region Copyright notice and license
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// 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.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Utils;
using Grpc.Testing;
using NUnit.Framework;
namespace Grpc.IntegrationTesting
{
public class XdsInteropClientTest
{
const string Host = "localhost";
BackendServiceImpl backendService;
Server backendServer;
Server lbStatsServer;
Channel lbStatsChannel;
LoadBalancerStatsService.LoadBalancerStatsServiceClient lbStatsClient;
XdsInteropClient xdsInteropClient;
[OneTimeSetUp]
public void Init()
{
backendService = new BackendServiceImpl();
// Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
backendServer = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
{
Services = { TestService.BindService(backendService) },
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
};
backendServer.Start();
xdsInteropClient = new XdsInteropClient(new XdsInteropClient.ClientOptions
{
NumChannels = 1,
Qps = 1,
RpcTimeoutSec = 10,
Server = $"{Host}:{backendServer.Ports.Single().BoundPort}",
});
// Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
lbStatsServer = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
{
Services = { LoadBalancerStatsService.BindService(new LoadBalancerStatsServiceImpl(xdsInteropClient.StatsWatcher)) },
Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
};
lbStatsServer.Start();
int port = lbStatsServer.Ports.Single().BoundPort;
lbStatsChannel = new Channel(Host, port, ChannelCredentials.Insecure);
lbStatsClient = new LoadBalancerStatsService.LoadBalancerStatsServiceClient(lbStatsChannel);
}
[OneTimeTearDown]
public void Cleanup()
{
lbStatsChannel.ShutdownAsync().Wait();
lbStatsServer.ShutdownAsync().Wait();
backendServer.ShutdownAsync().Wait();
}
[Test]
public async Task SmokeTest()
{
string backendName = "backend1";
backendService.UnaryHandler = (request, context) =>
{
return Task.FromResult(new SimpleResponse { Hostname = backendName});
};
var cancellationTokenSource = new CancellationTokenSource();
var runChannelsTask = xdsInteropClient.RunChannelsAsync(cancellationTokenSource.Token);
var stats = await lbStatsClient.GetClientStatsAsync(new LoadBalancerStatsRequest
{
NumRpcs = 5,
TimeoutSec = 10,
}, deadline: DateTime.UtcNow.AddSeconds(30));
Assert.AreEqual(0, stats.NumFailures);
Assert.AreEqual(backendName, stats.RpcsByPeer.Keys.Single());
Assert.AreEqual(5, stats.RpcsByPeer[backendName]);
await Task.Delay(100);
var stats2 = await lbStatsClient.GetClientStatsAsync(new LoadBalancerStatsRequest
{
NumRpcs = 3,
TimeoutSec = 10,
}, deadline: DateTime.UtcNow.AddSeconds(30));
Assert.AreEqual(0, stats2.NumFailures);
Assert.AreEqual(backendName, stats2.RpcsByPeer.Keys.Single());
Assert.AreEqual(3, stats2.RpcsByPeer[backendName]);
cancellationTokenSource.Cancel();
await runChannelsTask;
}
public class BackendServiceImpl : TestService.TestServiceBase
{
public UnaryServerMethod<SimpleRequest, SimpleResponse> UnaryHandler { get; set; }
public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
{
return UnaryHandler(request, context);
}
}
}
}

@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Tools", "Grpc.Tools\Gr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Tools.Tests", "Grpc.Tools.Tests\Grpc.Tools.Tests.csproj", "{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.IntegrationTesting.XdsClient", "Grpc.IntegrationTesting.XdsClient\Grpc.IntegrationTesting.XdsClient.csproj", "{7306313A-4853-4CFF-B913-0FCB1A497449}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -135,6 +137,10 @@ Global
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Release|Any CPU.Build.0 = Release|Any CPU
{7306313A-4853-4CFF-B913-0FCB1A497449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7306313A-4853-4CFF-B913-0FCB1A497449}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7306313A-4853-4CFF-B913-0FCB1A497449}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7306313A-4853-4CFF-B913-0FCB1A497449}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -70,7 +70,8 @@
"Grpc.IntegrationTesting.MetadataCredentialsTest",
"Grpc.IntegrationTesting.RunnerClientServerTest",
"Grpc.IntegrationTesting.SslCredentialsTest",
"Grpc.IntegrationTesting.UnobservedTaskExceptionTest"
"Grpc.IntegrationTesting.UnobservedTaskExceptionTest",
"Grpc.IntegrationTesting.XdsInteropClientTest"
],
"Grpc.Reflection.Tests": [
"Grpc.Reflection.Tests.ReflectionClientServerTest",

@ -162,35 +162,30 @@ void destroy_grpc_channels() {
PHP_GRPC_HASH_FOREACH_END()
}
void restart_channels() {
zval *data;
PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
php_grpc_zend_resource *rsrc =
(php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
if (rsrc == NULL) {
break;
}
channel_persistent_le_t* le = rsrc->ptr;
wrapped_grpc_channel wrapped_channel;
wrapped_channel.wrapper = le->channel;
grpc_channel_wrapper *channel = wrapped_channel.wrapper;
create_new_channel(&wrapped_channel, channel->target, channel->args,
channel->creds);
gpr_mu_unlock(&channel->mu);
PHP_GRPC_HASH_FOREACH_END()
}
void prefork() {
acquire_persistent_locks();
}
// Clean all channels in the persistent list
// Called at post fork
void php_grpc_clean_persistent_list(TSRMLS_D) {
zend_hash_clean(&grpc_persistent_list);
zend_hash_destroy(&grpc_persistent_list);
zend_hash_clean(&grpc_target_upper_bound_map);
zend_hash_destroy(&grpc_target_upper_bound_map);
}
void postfork_child() {
TSRMLS_FETCH();
// loop through persistent list and destroy all underlying grpc_channel objs
destroy_grpc_channels();
release_persistent_locks();
// clean all channels in the persistent list
php_grpc_clean_persistent_list(TSRMLS_C);
// clear completion queue
grpc_php_shutdown_completion_queue(TSRMLS_C);
@ -205,10 +200,6 @@ void postfork_child() {
// restart grpc_core
grpc_init();
grpc_php_init_completion_queue(TSRMLS_C);
// re-create grpc_channel and point wrapped to it
// unlock wrapped grpc channel mutex
restart_channels();
}
void postfork_parent() {

@ -0,0 +1,208 @@
#!/usr/bin/env ruby
# Copyright 2015 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
# This is the xDS interop test Ruby client. This is meant to be run by
# the run_xds_tests.py test runner.
#
# Usage: $ tools/run_tests/run_xds_tests.py --test_case=... ...
# --client_cmd="path/to/xds_client.rb --server=<hostname> \
# --stats_port=<port> \
# --qps=<qps>"
# These lines are required for the generated files to load grpc
this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib')
pb_dir = File.dirname(this_dir)
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
$LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir)
require 'optparse'
require 'logger'
require_relative '../../lib/grpc'
require 'google/protobuf'
require_relative '../src/proto/grpc/testing/empty_pb'
require_relative '../src/proto/grpc/testing/messages_pb'
require_relative '../src/proto/grpc/testing/test_services_pb'
# Some global variables to be shared by server and client
$watchers = Array.new
$watchers_mutex = Mutex.new
$watchers_cv = ConditionVariable.new
$shutdown = false
# RubyLogger defines a logger for gRPC based on the standard ruby logger.
module RubyLogger
def logger
LOGGER
end
LOGGER = Logger.new(STDOUT)
LOGGER.level = Logger::INFO
end
# GRPC is the general RPC module
module GRPC
# Inject the noop #logger if no module-level logger method has been injected.
extend RubyLogger
end
# creates a test stub
def create_stub(opts)
address = "#{opts.server}"
GRPC.logger.info("... connecting insecurely to #{address}")
Grpc::Testing::TestService::Stub.new(
address,
:this_channel_is_insecure,
)
end
# This implements LoadBalancerStatsService required by the test runner
class TestTarget < Grpc::Testing::LoadBalancerStatsService::Service
include Grpc::Testing
def get_client_stats(req, _call)
finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) +
req['timeout_sec']
watcher = {}
$watchers_mutex.synchronize do
watcher = {
"rpcs_by_peer" => Hash.new(0),
"rpcs_needed" => req['num_rpcs'],
"no_remote_peer" => 0
}
$watchers << watcher
seconds_remaining = finish_time -
Process.clock_gettime(Process::CLOCK_MONOTONIC)
while watcher['rpcs_needed'] > 0 && seconds_remaining > 0
$watchers_cv.wait($watchers_mutex, seconds_remaining)
seconds_remaining = finish_time -
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
$watchers.delete_at($watchers.index(watcher))
end
LoadBalancerStatsResponse.new(
rpcs_by_peer: watcher['rpcs_by_peer'],
num_failures: watcher['no_remote_peer'] + watcher['rpcs_needed']
);
end
end
# send 1 rpc every 1/qps second
def run_test_loop(stub, target_seconds_between_rpcs, fail_on_failed_rpcs)
include Grpc::Testing
req = SimpleRequest.new()
target_next_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
while !$shutdown
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
sleep_seconds = target_next_start - now
if sleep_seconds < 0
GRPC.logger.info("ruby xds: warning, rpc takes too long to finish. " \
"If you consistently see this, the qps is too high.")
else
sleep(sleep_seconds)
end
target_next_start += target_seconds_between_rpcs
begin
resp = stub.unary_call(req)
remote_peer = resp.hostname
rescue GRPC::BadStatus => e
remote_peer = ""
GRPC.logger.info("ruby xds: rpc failed:|#{e.message}|, " \
"this may or may not be expected")
if fail_on_failed_rpcs
raise e
end
end
$watchers_mutex.synchronize do
$watchers.each do |watcher|
watcher['rpcs_needed'] -= 1
if remote_peer.strip.empty?
watcher['no_remote_peer'] += 1
else
watcher['rpcs_by_peer'][remote_peer] += 1
end
end
$watchers_cv.broadcast
end
end
end
# Args is used to hold the command line info.
Args = Struct.new(:fail_on_failed_rpcs, :num_channels,
:server, :stats_port, :qps)
# validates the command line options, returning them as a Hash.
def parse_args
args = Args.new
args['fail_on_failed_rpcs'] = false
args['num_channels'] = 1
OptionParser.new do |opts|
opts.on('--fail_on_failed_rpcs BOOL', ['false', 'true']) do |v|
args['fail_on_failed_rpcs'] = v == 'true'
end
opts.on('--num_channels CHANNELS', 'number of channels') do |v|
args['num_channels'] = v.to_i
end
opts.on('--server SERVER_HOST', 'server hostname') do |v|
GRPC.logger.info("ruby xds: server address is #{v}")
args['server'] = v
end
opts.on('--stats_port STATS_PORT', 'stats port') do |v|
GRPC.logger.info("ruby xds: stats port is #{v}")
args['stats_port'] = v
end
opts.on('--qps QPS', 'qps') do |v|
GRPC.logger.info("ruby xds: qps is #{v}")
args['qps'] = v
end
end.parse!
args
end
def main
opts = parse_args
# This server hosts the LoadBalancerStatsService
host = "0.0.0.0:#{opts['stats_port']}"
s = GRPC::RpcServer.new
s.add_http2_port(host, :this_port_is_insecure)
s.handle(TestTarget)
server_thread = Thread.new {
# run the server until the main test runner terminates this process
s.run_till_terminated_or_interrupted(['TERM'])
}
# The client just sends unary rpcs continuously in a regular interval
stub = create_stub(opts)
target_seconds_between_rpcs = (1.0 / opts['qps'].to_f)
client_threads = Array.new
opts['num_channels'].times {
client_threads << Thread.new {
run_test_loop(stub, target_seconds_between_rpcs,
opts['fail_on_failed_rpcs'])
}
}
server_thread.join
$shutdown = true
client_threads.each { |thd| thd.join }
end
if __FILE__ == $0
main
end

@ -243,15 +243,29 @@
endif()
# Use C99 standard
set(CMAKE_C_STANDARD 99)
if (NOT DEFINED CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Add c++11 flags
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
else()
if (CMAKE_CXX_STANDARD LESS 11)
message(FATAL_ERROR "CMAKE_CXX_STANDARD is less than 11, please specify at least SET(CMAKE_CXX_STANDARD 11)")
endif()
endif()
if (NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
if (NOT DEFINED CMAKE_CXX_EXTENSIONS)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
## Some libraries are shared even with BUILD_SHARED_LIBRARIES=OFF
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
if (NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
endif()
list(APPEND CMAKE_MODULE_PATH "<%text>${CMAKE_CURRENT_SOURCE_DIR}</%text>/cmake/modules")
if(MSVC)

@ -2991,6 +2991,76 @@ TEST_P(LdsRdsTest, XdsRoutingWeightedCluster) {
gpr_unsetenv("GRPC_XDS_EXPERIMENTAL_ROUTING");
}
TEST_P(LdsRdsTest, RouteActionWeightedTargetDefaultRoute) {
const char* kNewCluster1Name = "new_cluster_1";
const char* kNewCluster2Name = "new_cluster_2";
const size_t kNumEchoRpcs = 1000;
const size_t kWeight75 = 75;
const size_t kWeight25 = 25;
SetNextResolution({});
SetNextResolutionForLbChannelAllBalancers();
// Populate new EDS resources.
AdsServiceImpl::EdsResourceArgs args({
{"locality0", GetBackendPorts(0, 1)},
});
AdsServiceImpl::EdsResourceArgs args1({
{"locality0", GetBackendPorts(1, 2)},
});
AdsServiceImpl::EdsResourceArgs args2({
{"locality0", GetBackendPorts(2, 3)},
});
balancers_[0]->ads_service()->SetEdsResource(
AdsServiceImpl::BuildEdsResource(args));
balancers_[0]->ads_service()->SetEdsResource(
AdsServiceImpl::BuildEdsResource(args1, kNewCluster1Name));
balancers_[0]->ads_service()->SetEdsResource(
AdsServiceImpl::BuildEdsResource(args2, kNewCluster2Name));
// Populate new CDS resources.
Cluster new_cluster1 = balancers_[0]->ads_service()->default_cluster();
new_cluster1.set_name(kNewCluster1Name);
balancers_[0]->ads_service()->SetCdsResource(new_cluster1);
Cluster new_cluster2 = balancers_[0]->ads_service()->default_cluster();
new_cluster2.set_name(kNewCluster2Name);
balancers_[0]->ads_service()->SetCdsResource(new_cluster2);
// Populating Route Configurations for LDS.
RouteConfiguration new_route_config =
balancers_[0]->ads_service()->default_route_config();
auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("");
auto* weighted_cluster1 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster1->set_name(kNewCluster1Name);
weighted_cluster1->mutable_weight()->set_value(kWeight75);
auto* weighted_cluster2 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster2->set_name(kNewCluster2Name);
weighted_cluster2->mutable_weight()->set_value(kWeight25);
route1->mutable_route()
->mutable_weighted_clusters()
->mutable_total_weight()
->set_value(kWeight75 + kWeight25);
SetRouteConfiguration(0, new_route_config);
WaitForAllBackends(1, 3);
CheckRpcSendOk(kNumEchoRpcs);
// Make sure RPCs all go to the correct backend.
EXPECT_EQ(0, backends_[0]->backend_service()->request_count());
const int weight_75_request_count =
backends_[1]->backend_service()->request_count();
const int weight_25_request_count =
backends_[2]->backend_service()->request_count();
const double kErrorTolerance = 0.2;
EXPECT_THAT(weight_75_request_count,
::testing::AllOf(::testing::Ge(kNumEchoRpcs * kWeight75 / 100 *
(1 - kErrorTolerance)),
::testing::Le(kNumEchoRpcs * kWeight75 / 100 *
(1 + kErrorTolerance))));
EXPECT_THAT(weight_25_request_count,
::testing::AllOf(::testing::Ge(kNumEchoRpcs * kWeight25 / 100 *
(1 - kErrorTolerance)),
::testing::Le(kNumEchoRpcs * kWeight25 / 100 *
(1 + kErrorTolerance))));
}
TEST_P(LdsRdsTest, XdsRoutingWeightedClusterUpdateWeights) {
gpr_setenv("GRPC_XDS_EXPERIMENTAL_ROUTING", "true");
const char* kNewCluster1Name = "new_cluster_1";

@ -0,0 +1,25 @@
# Copyright 2020 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
# Config file for the internal CI (in protobuf text format)
# Location of the continuous shell script in repository.
build_file: "grpc/tools/internal_ci/linux/grpc_xds_csharp.sh"
timeout_mins: 90
action {
define_artifacts {
regex: "**/*sponge_log.*"
regex: "github/grpc/reports/**"
}
}

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Copyright 2017 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
set -ex
# change to grpc repo root
cd $(dirname $0)/../../..
source tools/internal_ci/helper_scripts/prepare_build_linux_rc
export DOCKERFILE_DIR=tools/dockerfile/test/csharp_stretch_x64
export DOCKER_RUN_SCRIPT=tools/internal_ci/linux/grpc_xds_csharp_test_in_docker.sh
export OUTPUT_DIR=reports
exec tools/run_tests/dockerize/build_and_run_docker.sh

@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Copyright 2020 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
set -ex -o igncr || set -ex
mkdir -p /var/local/git
git clone /var/local/jenkins/grpc /var/local/git/grpc
(cd /var/local/jenkins/grpc/ && git submodule foreach 'cd /var/local/git/grpc \
&& git submodule update --init --reference /var/local/jenkins/grpc/${name} \
${name}')
cd /var/local/git/grpc
VIRTUAL_ENV=$(mktemp -d)
virtualenv "$VIRTUAL_ENV"
PYTHON="$VIRTUAL_ENV"/bin/python
"$PYTHON" -m pip install --upgrade pip
"$PYTHON" -m pip install --upgrade grpcio grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
# Prepare generated Python code.
TOOLS_DIR=tools/run_tests
PROTO_SOURCE_DIR=src/proto/grpc/testing
PROTO_DEST_DIR="$TOOLS_DIR"/"$PROTO_SOURCE_DIR"
mkdir -p "$PROTO_DEST_DIR"
touch "$TOOLS_DIR"/src/__init__.py
touch "$TOOLS_DIR"/src/proto/__init__.py
touch "$TOOLS_DIR"/src/proto/grpc/__init__.py
touch "$TOOLS_DIR"/src/proto/grpc/testing/__init__.py
"$PYTHON" -m grpc_tools.protoc \
--proto_path=. \
--python_out="$TOOLS_DIR" \
--grpc_python_out="$TOOLS_DIR" \
"$PROTO_SOURCE_DIR"/test.proto \
"$PROTO_SOURCE_DIR"/messages.proto \
"$PROTO_SOURCE_DIR"/empty.proto
python tools/run_tests/run_tests.py -l csharp -c opt --build_only
GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,cds_lb,eds_lb,priority_lb,weighted_target_lb,lrs_lb "$PYTHON" \
tools/run_tests/run_xds_tests.py \
--test_case=all \
--project_id=grpc-testing \
--source_image=projects/grpc-testing/global/images/xds-test-server \
--path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \
--gcp_suffix=$(date '+%s') \
--verbose \
--client_cmd='dotnet exec src/csharp/Grpc.IntegrationTesting.XdsClient/bin/Release/netcoreapp2.1/Grpc.IntegrationTesting.XdsClient.dll -- --server=xds-experimental:///{server_uri} --stats_port={stats_port} --qps={qps}'

@ -0,0 +1,25 @@
# Copyright 2020 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
# Config file for the internal CI (in protobuf text format)
# Location of the continuous shell script in repository.
build_file: "grpc/tools/internal_ci/linux/grpc_xds_ruby.sh"
timeout_mins: 90
action {
define_artifacts {
regex: "**/*sponge_log.*"
regex: "github/grpc/reports/**"
}
}

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Copyright 2017 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
set -ex
# change to grpc repo root
cd $(dirname $0)/../../..
source tools/internal_ci/helper_scripts/prepare_build_linux_rc
export DOCKERFILE_DIR=tools/dockerfile/test/ruby_jessie_x64
export DOCKER_RUN_SCRIPT=tools/internal_ci/linux/grpc_xds_ruby_test_in_docker.sh
export OUTPUT_DIR=reports
exec tools/run_tests/dockerize/build_and_run_docker.sh

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Copyright 2020 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# 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.
set -ex -o igncr || set -ex
mkdir -p /var/local/git
git clone /var/local/jenkins/grpc /var/local/git/grpc
(cd /var/local/jenkins/grpc/ && git submodule foreach 'cd /var/local/git/grpc \
&& git submodule update --init --reference /var/local/jenkins/grpc/${name} \
${name}')
cd /var/local/git/grpc
VIRTUAL_ENV=$(mktemp -d)
virtualenv "$VIRTUAL_ENV"
PYTHON="$VIRTUAL_ENV"/bin/python
"$PYTHON" -m pip install --upgrade pip
"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
# Prepare generated Python code.
TOOLS_DIR=tools/run_tests
PROTO_SOURCE_DIR=src/proto/grpc/testing
PROTO_DEST_DIR="$TOOLS_DIR"/"$PROTO_SOURCE_DIR"
mkdir -p "$PROTO_DEST_DIR"
touch "$TOOLS_DIR"/src/__init__.py
touch "$TOOLS_DIR"/src/proto/__init__.py
touch "$TOOLS_DIR"/src/proto/grpc/__init__.py
touch "$TOOLS_DIR"/src/proto/grpc/testing/__init__.py
"$PYTHON" -m grpc_tools.protoc \
--proto_path=. \
--python_out="$TOOLS_DIR" \
--grpc_python_out="$TOOLS_DIR" \
"$PROTO_SOURCE_DIR"/test.proto \
"$PROTO_SOURCE_DIR"/messages.proto \
"$PROTO_SOURCE_DIR"/empty.proto
(cd src/ruby && bundle && rake compile)
GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,cds_lb,eds_lb,priority_lb,weighted_target_lb,lrs_lb "$PYTHON" \
tools/run_tests/run_xds_tests.py \
--test_case=all \
--project_id=grpc-testing \
--source_image=projects/grpc-testing/global/images/xds-test-server \
--path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \
--gcp_suffix=$(date '+%s') \
--only_stable_gcp_apis \
--verbose \
--client_cmd='ruby src/ruby/pb/test/xds_client.rb --server=xds-experimental:///{server_uri} --stats_port={stats_port} --qps={qps}'

@ -216,7 +216,8 @@ LANG_RELEASE_MATRIX = {
('v1.24.0', ReleaseInfo()),
('v1.25.0', ReleaseInfo()),
('v1.26.1', ReleaseInfo()),
('v1.27.1', ReleaseInfo()),
('v1.27.2', ReleaseInfo()),
('v1.28.0', ReleaseInfo()),
]),
'python':
OrderedDict([

Loading…
Cancel
Save