From fdffe3bb2c492549119ff9460021378c08bc0c34 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Thu, 19 Oct 2023 09:08:16 +0100 Subject: [PATCH 01/11] Added Support for Retrieving Nodes that support Mesh Capable Service --- Consul.Test/CatalogTest.cs | 31 +++++++++++++++++++++ Consul/Catalog.cs | 39 +++++++++++++++++++++++++++ Consul/Interfaces/ICatalogEndpoint.cs | 4 +++ 3 files changed, 74 insertions(+) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 15142e9a4..4853e8e5e 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -174,6 +174,37 @@ public async Task Catalog_GetTaggedAddressesService() Assert.True(services.Response[0].ServiceTaggedAddresses.ContainsKey("lan")); } + [Fact] + public async Task Catalog_GetNodesForMeshCapableService() + { + var svcID = KVTest.GenerateTestKeyName(); + var registration = new CatalogRegistration + { + Datacenter = "dc1", + Node = "foobar", + Address = "192.168.10.10", + Service = new AgentService + { + ID = svcID, + Service = "redis", + Tags = new[] { "master", "v1" }, + Port = 8000, + TaggedAddresses = new Dictionary + { + {"lan", new ServiceTaggedAddress {Address = "127.0.0.1", Port = 80}}, + {"wan", new ServiceTaggedAddress {Address = "192.168.10.10", Port = 8000}} + } + } + }; + + await _client.Catalog.Register(registration); + + var services = await _client.Catalog.NodesForMeshCapableService("redis"); + + Assert.True(services.Response.Length==0); + + } + [Fact] public async Task Catalog_EnableTagOverride() { diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 7d1fb59bb..5d5d2f351 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Consul.Filtering; using Newtonsoft.Json; namespace Consul @@ -237,6 +238,44 @@ public Task> Service(string service, string tag, Q return req.Execute(ct); } + /// + /// Returns the nodes providing a mesh-capable service in a given datacenter. + /// + /// The service ID + /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing + /// + public Task> NodesForMeshCapableService(string service, CancellationToken ct = default) + { + return NodesForMeshCapableService(service,QueryOptions.Default, null, ct); + } + + /// + /// Returns the nodes providing a mesh-capable service in a given datacenter. + /// + /// The service ID + /// Specifies the expression used to filter the queries results prior to returning the data + /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing + /// + public Task> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default) + { + return NodesForMeshCapableService(service, QueryOptions.Default, filter, ct); + } + + /// + /// Returns the nodes providing a mesh-capable service in a given datacenter. + /// + /// The service ID + /// Customized Query options + /// Specifies the expression used to filter the queries results prior to returning the data + /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing + /// + public Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default) + { + var req = _client.Get(string.Format("/v1/catalog/connect/{0}", service), q, filter); + + return req.Execute(ct); + } + /// /// Node is used to query for service information about a single node /// diff --git a/Consul/Interfaces/ICatalogEndpoint.cs b/Consul/Interfaces/ICatalogEndpoint.cs index 1b71bb5dd..a0eef7c7e 100644 --- a/Consul/Interfaces/ICatalogEndpoint.cs +++ b/Consul/Interfaces/ICatalogEndpoint.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Consul.Filtering; namespace Consul { @@ -44,5 +45,8 @@ public interface ICatalogEndpoint Task> Service(string service, string tag, QueryOptions q, CancellationToken ct = default); Task>> Services(CancellationToken ct = default); Task>> Services(QueryOptions q, CancellationToken ct = default); + Task> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default); + Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default); + Task> NodesForMeshCapableService(string service, CancellationToken ct = default); } } From 47f71d6c11da038bb69ade21dee860817e470829 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Thu, 19 Oct 2023 09:29:07 +0100 Subject: [PATCH 02/11] fix: dotnet format --- Consul.Test/CatalogTest.cs | 2 +- Consul/Catalog.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 4853e8e5e..fef218acd 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -201,7 +201,7 @@ public async Task Catalog_GetNodesForMeshCapableService() var services = await _client.Catalog.NodesForMeshCapableService("redis"); - Assert.True(services.Response.Length==0); + Assert.True(services.Response.Length == 0); } diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 5d5d2f351..ee25840d3 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -246,7 +246,7 @@ public Task> Service(string service, string tag, Q /// public Task> NodesForMeshCapableService(string service, CancellationToken ct = default) { - return NodesForMeshCapableService(service,QueryOptions.Default, null, ct); + return NodesForMeshCapableService(service, QueryOptions.Default, null, ct); } /// From b71e6f829e5d0e9ee299268c0e943000ebd3dc97 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Thu, 19 Oct 2023 11:20:37 +0100 Subject: [PATCH 03/11] Update Consul/Catalog.cs Co-authored-by: Winston H. <56998716+winstxnhdw@users.noreply.github.com> --- Consul/Catalog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index ee25840d3..99a5b7bef 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -243,7 +243,7 @@ public Task> Service(string service, string tag, Q /// /// The service ID /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing - /// + /// A list of service instances public Task> NodesForMeshCapableService(string service, CancellationToken ct = default) { return NodesForMeshCapableService(service, QueryOptions.Default, null, ct); From 6c2e853f9e4d6c15c08c1344c8036c994a2bf612 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Thu, 19 Oct 2023 11:21:01 +0100 Subject: [PATCH 04/11] Update Consul/Catalog.cs Co-authored-by: Winston H. <56998716+winstxnhdw@users.noreply.github.com> --- Consul/Catalog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 99a5b7bef..9d4675c7a 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -255,7 +255,7 @@ public Task> NodesForMeshCapableService(string ser /// The service ID /// Specifies the expression used to filter the queries results prior to returning the data /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing - /// + /// A list of service instances public Task> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default) { return NodesForMeshCapableService(service, QueryOptions.Default, filter, ct); From 318941f60fae62918dfd805709d265e8380c59f9 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Thu, 19 Oct 2023 11:31:13 +0100 Subject: [PATCH 05/11] Update Consul/Catalog.cs Co-authored-by: Winston H. <56998716+winstxnhdw@users.noreply.github.com> --- Consul/Catalog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 9d4675c7a..96de1e52f 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -268,7 +268,7 @@ public Task> NodesForMeshCapableService(string ser /// Customized Query options /// Specifies the expression used to filter the queries results prior to returning the data /// Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing - /// + /// A list of service instances public Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default) { var req = _client.Get(string.Format("/v1/catalog/connect/{0}", service), q, filter); From 26e10cf67c4b1fadfc635623e48e0603f047b1ba Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Tue, 28 Nov 2023 16:25:04 +0100 Subject: [PATCH 06/11] added support for mesh capabilities. --- Consul.Test/CatalogTest.cs | 34 ++++++++++++++-------------------- Consul.Test/test_config.json | 5 ++++- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index fef218acd..a70411550 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -178,31 +178,25 @@ public async Task Catalog_GetTaggedAddressesService() public async Task Catalog_GetNodesForMeshCapableService() { var svcID = KVTest.GenerateTestKeyName(); - var registration = new CatalogRegistration + var registration = new AgentServiceRegistration { - Datacenter = "dc1", - Node = "foobar", - Address = "192.168.10.10", - Service = new AgentService + Name = svcID, + Port = 8000, + Connect = new AgentServiceConnect { - ID = svcID, - Service = "redis", - Tags = new[] { "master", "v1" }, - Port = 8000, - TaggedAddresses = new Dictionary + + SidecarService = new AgentServiceRegistration { - {"lan", new ServiceTaggedAddress {Address = "127.0.0.1", Port = 80}}, - {"wan", new ServiceTaggedAddress {Address = "192.168.10.10", Port = 8000}} - } - } + Name = "sidecar", + Port = 9000, + }, + }, }; + await _client.Agent.ServiceRegister(registration); - await _client.Catalog.Register(registration); - - var services = await _client.Catalog.NodesForMeshCapableService("redis"); - - Assert.True(services.Response.Length == 0); - + var services = await _client.Catalog.NodesForMeshCapableService(registration.Name); + Assert.NotEmpty(services.Response); + Assert.Equal(services.Response[0].ServiceID, registration.Name + "-sidecar-proxy"); } [Fact] diff --git a/Consul.Test/test_config.json b/Consul.Test/test_config.json index 7ca62e1fe..74cc6c0ef 100644 --- a/Consul.Test/test_config.json +++ b/Consul.Test/test_config.json @@ -11,6 +11,9 @@ } }, "enable_script_checks": true, + "connect": { + "enabled": true + }, "encrypt": "d8wu8CSUrqgtjVsvcBPmhQ==", - "enable_central_service_config": true + "enable_central_service_config": true } From db7ffebc106088d00286dd9a4d9f681cb3685d90 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Tue, 28 Nov 2023 21:37:12 +0100 Subject: [PATCH 07/11] added support for servicesfornodes --- Consul.Test/CatalogTest.cs | 50 +++++++++++++++++++++++ Consul/Catalog.cs | 57 +++++++++++++++++++++++++++ Consul/Interfaces/ICatalogEndpoint.cs | 2 + 3 files changed, 109 insertions(+) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index a70411550..9fe461eb9 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -263,5 +264,54 @@ public async Task Catalog_EnableTagOverride() Assert.True(services.Response[0].ServiceEnableTagOverride); } } + + [Fact] + public async Task Catalog_ServicesForNodes() + { + var svcID = KVTest.GenerateTestKeyName(); + var svcID2 = KVTest.GenerateTestKeyName(); + var registration1 = new CatalogRegistration + { + Datacenter = "dc1", + Node = "foobar", + Address = "192.168.10.10", + Service = new AgentService + { + ID = svcID, + Service = "redis", + Tags = new[] { "master", "v1" }, + Port = 8000, + TaggedAddresses = new Dictionary + { + {"lan", new ServiceTaggedAddress {Address = "127.0.0.1", Port = 80}}, + {"wan", new ServiceTaggedAddress {Address = "192.168.10.10", Port = 8000}} + } + } + }; + var registration2 = new CatalogRegistration + { + Datacenter = "dc1", + Node = "foobar2", + Address = "192.168.10.11", + Service = new AgentService + { + ID = svcID2, + Service = "redis", + Tags = new[] { "master", "v2" }, + Port = 8000, + TaggedAddresses = new Dictionary + { + { "lan", new ServiceTaggedAddress { Address = "127.0.0.1", Port = 81 } }, + { "wan", new ServiceTaggedAddress { Address = "192.168.10.10", Port = 8001 } } + } + } + }; + + await _client.Catalog.Register(registration1); + await _client.Catalog.Register(registration2); + var services = await _client.Catalog.ServicesForNodes(registration1.Node, new QueryOptions { Datacenter = registration1.Datacenter }); + Assert.Contains(services.Response.Services, n => n.ID == svcID); + Assert.DoesNotContain(services.Response.Services, n => n.ID == svcID2); + } } } diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 96de1e52f..b605377d2 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -85,6 +85,40 @@ public class CatalogDeregistration public string CheckID { get; set; } } + public class NodeService + { + public NodeInfo Node { get; set; } + public List Services { get; set; } + } + + public class NodeInfo + { + public string ID { get; set; } + public string Node { get; set; } + public string Address { get; set; } + public string Datacenter { get; set; } + public Dictionary TaggedAddresses { get; set; } + public Dictionary Meta { get; set; } + } + + public class ServiceInfo + { + public string ID { get; set; } + public string Service { get; set; } + public string[] Tags { get; set; } + public Dictionary Meta { get; set; } + public int Port { get; set; } + public string Namespace { get; set; } + public Dictionary TaggedAddresses { get; set; } + } + + public class ServiceAddress + { + public string Address { get; set; } + public int Port { get; set; } + } + + /// /// Catalog can be used to query the Catalog endpoints /// @@ -298,6 +332,29 @@ public Task> Node(string node, QueryOptions q, Cancella { return _client.Get(string.Format("/v1/catalog/node/{0}", node), q).Execute(ct); } + + /// + /// ServicesForNode is used to query for the services provided by a node + /// + /// Node Name + /// CancellationToken + /// Node Services + public Task> ServicesForNodes(string node, CancellationToken ct = default) + { + return ServicesForNodes(node, QueryOptions.Default, ct); + } + + /// + /// ServicesForNode is used to query for the services provided by a node + /// + /// Node Name + /// Query Parameters + /// Cancellation Token + /// Node Services + public Task> ServicesForNodes(string node, QueryOptions q, CancellationToken ct = default) + { + return _client.Get(string.Format("/v1/catalog/node-services/{0}", node), q).Execute(ct); + } } public partial class ConsulClient : IConsulClient diff --git a/Consul/Interfaces/ICatalogEndpoint.cs b/Consul/Interfaces/ICatalogEndpoint.cs index a0eef7c7e..34b9065f3 100644 --- a/Consul/Interfaces/ICatalogEndpoint.cs +++ b/Consul/Interfaces/ICatalogEndpoint.cs @@ -48,5 +48,7 @@ public interface ICatalogEndpoint Task> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default); Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default); Task> NodesForMeshCapableService(string service, CancellationToken ct = default); + Task> ServicesForNodes(string node, QueryOptions q, CancellationToken ct = default); + Task> ServicesForNodes(string node, CancellationToken ct = default); } } From ddaec58daf7f1bf15962a8c25fcf70f981cb9e2c Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Tue, 28 Nov 2023 21:43:29 +0100 Subject: [PATCH 08/11] name fix --- Consul.Test/CatalogTest.cs | 2 +- Consul/Catalog.cs | 6 +++--- Consul/Interfaces/ICatalogEndpoint.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 9fe461eb9..150f24d86 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -309,7 +309,7 @@ public async Task Catalog_ServicesForNodes() await _client.Catalog.Register(registration1); await _client.Catalog.Register(registration2); - var services = await _client.Catalog.ServicesForNodes(registration1.Node, new QueryOptions { Datacenter = registration1.Datacenter }); + var services = await _client.Catalog.ServicesForNode(registration1.Node, new QueryOptions { Datacenter = registration1.Datacenter }); Assert.Contains(services.Response.Services, n => n.ID == svcID); Assert.DoesNotContain(services.Response.Services, n => n.ID == svcID2); } diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index b605377d2..096ab67ec 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -339,9 +339,9 @@ public Task> Node(string node, QueryOptions q, Cancella /// Node Name /// CancellationToken /// Node Services - public Task> ServicesForNodes(string node, CancellationToken ct = default) + public Task> ServicesForNode(string node, CancellationToken ct = default) { - return ServicesForNodes(node, QueryOptions.Default, ct); + return ServicesForNode(node, QueryOptions.Default, ct); } /// @@ -351,7 +351,7 @@ public Task> ServicesForNodes(string node, Cancellation /// Query Parameters /// Cancellation Token /// Node Services - public Task> ServicesForNodes(string node, QueryOptions q, CancellationToken ct = default) + public Task> ServicesForNode(string node, QueryOptions q, CancellationToken ct = default) { return _client.Get(string.Format("/v1/catalog/node-services/{0}", node), q).Execute(ct); } diff --git a/Consul/Interfaces/ICatalogEndpoint.cs b/Consul/Interfaces/ICatalogEndpoint.cs index 34b9065f3..7e7eec77e 100644 --- a/Consul/Interfaces/ICatalogEndpoint.cs +++ b/Consul/Interfaces/ICatalogEndpoint.cs @@ -48,7 +48,7 @@ public interface ICatalogEndpoint Task> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default); Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default); Task> NodesForMeshCapableService(string service, CancellationToken ct = default); - Task> ServicesForNodes(string node, QueryOptions q, CancellationToken ct = default); - Task> ServicesForNodes(string node, CancellationToken ct = default); + Task> ServicesForNode(string node, QueryOptions q, CancellationToken ct = default); + Task> ServicesForNode(string node, CancellationToken ct = default); } } From 7a916ddd696e5cbf839b844486cfeb5ff06fa4fb Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Wed, 29 Nov 2023 10:43:08 +0100 Subject: [PATCH 09/11] resolved --- Consul.Test/CatalogTest.cs | 5 ++++- Consul/Catalog.cs | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 150f24d86..87d44efb1 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NuGet.Versioning; using Xunit; namespace Consul.Test @@ -265,9 +266,11 @@ public async Task Catalog_EnableTagOverride() } } - [Fact] + [SkippableFact] public async Task Catalog_ServicesForNodes() { + var cutOffVersion = SemanticVersion.Parse("1.7.0"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `logjson` is only supported from Consul {cutOffVersion}"); var svcID = KVTest.GenerateTestKeyName(); var svcID2 = KVTest.GenerateTestKeyName(); var registration1 = new CatalogRegistration diff --git a/Consul/Catalog.cs b/Consul/Catalog.cs index 096ab67ec..b8fc54bb6 100644 --- a/Consul/Catalog.cs +++ b/Consul/Catalog.cs @@ -118,7 +118,6 @@ public class ServiceAddress public int Port { get; set; } } - /// /// Catalog can be used to query the Catalog endpoints /// @@ -305,9 +304,7 @@ public Task> NodesForMeshCapableService(string ser /// A list of service instances public Task> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default) { - var req = _client.Get(string.Format("/v1/catalog/connect/{0}", service), q, filter); - - return req.Execute(ct); + return _client.Get(string.Format("/v1/catalog/connect/{0}", service), q, filter).Execute(ct); } /// From 2d16cda4a240ca7fd59fbfcb892c0cf03d93c8fa Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Wed, 29 Nov 2023 10:56:49 +0100 Subject: [PATCH 10/11] edit --- Consul.Test/CatalogTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 87d44efb1..6b8a848b8 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -271,6 +271,7 @@ public async Task Catalog_ServicesForNodes() { var cutOffVersion = SemanticVersion.Parse("1.7.0"); Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `logjson` is only supported from Consul {cutOffVersion}"); + var svcID = KVTest.GenerateTestKeyName(); var svcID2 = KVTest.GenerateTestKeyName(); var registration1 = new CatalogRegistration From 89e15f80c90b85194d07729ab05c51c30a5510f0 Mon Sep 17 00:00:00 2001 From: Samson Amaugo Date: Wed, 29 Nov 2023 11:06:25 +0100 Subject: [PATCH 11/11] edited reason for skipping --- Consul.Test/CatalogTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Consul.Test/CatalogTest.cs b/Consul.Test/CatalogTest.cs index 6b8a848b8..c00ba396f 100644 --- a/Consul.Test/CatalogTest.cs +++ b/Consul.Test/CatalogTest.cs @@ -270,7 +270,7 @@ public async Task Catalog_EnableTagOverride() public async Task Catalog_ServicesForNodes() { var cutOffVersion = SemanticVersion.Parse("1.7.0"); - Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `logjson` is only supported from Consul {cutOffVersion}"); + Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `ServicesForNodes` is only supported from Consul {cutOffVersion}"); var svcID = KVTest.GenerateTestKeyName(); var svcID2 = KVTest.GenerateTestKeyName();