Skip to content
Merged
79 changes: 79 additions & 0 deletions Consul.Test/CatalogTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NuGet.Versioning;
using Xunit;

namespace Consul.Test
Expand Down Expand Up @@ -174,6 +176,31 @@ 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 AgentServiceRegistration
{
Name = svcID,
Port = 8000,
Connect = new AgentServiceConnect
{

SidecarService = new AgentServiceRegistration
{
Name = "sidecar",
Port = 9000,
},
},
};
await _client.Agent.ServiceRegister(registration);

var services = await _client.Catalog.NodesForMeshCapableService(registration.Name);
Assert.NotEmpty(services.Response);
Assert.Equal(services.Response[0].ServiceID, registration.Name + "-sidecar-proxy");
}

[Fact]
public async Task Catalog_EnableTagOverride()
{
Expand Down Expand Up @@ -238,5 +265,57 @@ public async Task Catalog_EnableTagOverride()
Assert.True(services.Response[0].ServiceEnableTagOverride);
}
}

[SkippableFact]
public async Task Catalog_ServicesForNodes()
{
var cutOffVersion = SemanticVersion.Parse("1.7.0");
Skip.If(AgentVersion < cutOffVersion, $"Current version is {AgentVersion}, but `ServicesForNodes` is only supported from Consul {cutOffVersion}");

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<string, ServiceTaggedAddress>
{
{"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<string, ServiceTaggedAddress>
{
{ "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.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);
}
}
}
5 changes: 4 additions & 1 deletion Consul.Test/test_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
}
},
"enable_script_checks": true,
"connect": {
"enabled": true
},
"encrypt": "d8wu8CSUrqgtjVsvcBPmhQ==",
"enable_central_service_config": true
"enable_central_service_config": true
}
93 changes: 93 additions & 0 deletions Consul/Catalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Consul.Filtering;
using Newtonsoft.Json;

namespace Consul
Expand Down Expand Up @@ -84,6 +85,39 @@ public class CatalogDeregistration
public string CheckID { get; set; }
}

public class NodeService
{
public NodeInfo Node { get; set; }
public List<ServiceInfo> 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<string, string> TaggedAddresses { get; set; }
public Dictionary<string, string> Meta { get; set; }
}

public class ServiceInfo
{
public string ID { get; set; }
public string Service { get; set; }
public string[] Tags { get; set; }
public Dictionary<string, string> Meta { get; set; }
public int Port { get; set; }
public string Namespace { get; set; }
public Dictionary<string, ServiceAddress> TaggedAddresses { get; set; }
}

public class ServiceAddress
{
public string Address { get; set; }
public int Port { get; set; }
}

/// <summary>
/// Catalog can be used to query the Catalog endpoints
/// </summary>
Expand Down Expand Up @@ -237,6 +271,42 @@ public Task<QueryResult<CatalogService[]>> Service(string service, string tag, Q
return req.Execute(ct);
}

/// <summary>
/// Returns the nodes providing a mesh-capable service in a given datacenter.
/// </summary>
/// <param name="service">The service ID</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A list of service instances</returns>
public Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, CancellationToken ct = default)
{
return NodesForMeshCapableService(service, QueryOptions.Default, null, ct);
}

/// <summary>
/// Returns the nodes providing a mesh-capable service in a given datacenter.
/// </summary>
/// <param name="service">The service ID</param>
/// <param name="filter">Specifies the expression used to filter the queries results prior to returning the data</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A list of service instances</returns>
public Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default)
{
return NodesForMeshCapableService(service, QueryOptions.Default, filter, ct);
}

/// <summary>
/// Returns the nodes providing a mesh-capable service in a given datacenter.
/// </summary>
/// <param name="service">The service ID</param>
/// <param name="q">Customized Query options</param>
/// <param name="filter">Specifies the expression used to filter the queries results prior to returning the data</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A list of service instances</returns>
public Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default)
{
return _client.Get<CatalogService[]>(string.Format("/v1/catalog/connect/{0}", service), q, filter).Execute(ct);
}

/// <summary>
/// Node is used to query for service information about a single node
/// </summary>
Expand All @@ -259,6 +329,29 @@ public Task<QueryResult<CatalogNode>> Node(string node, QueryOptions q, Cancella
{
return _client.Get<CatalogNode>(string.Format("/v1/catalog/node/{0}", node), q).Execute(ct);
}

/// <summary>
/// ServicesForNode is used to query for the services provided by a node
/// </summary>
/// <param name="node">Node Name</param>
/// <param name="ct">CancellationToken</param>
/// <returns>Node Services</returns>
public Task<QueryResult<NodeService>> ServicesForNode(string node, CancellationToken ct = default)
{
return ServicesForNode(node, QueryOptions.Default, ct);
}

/// <summary>
/// ServicesForNode is used to query for the services provided by a node
/// </summary>
/// <param name="node">Node Name</param>
/// <param name="q">Query Parameters</param>
/// <param name="ct">Cancellation Token</param>
/// <returns>Node Services</returns>
public Task<QueryResult<NodeService>> ServicesForNode(string node, QueryOptions q, CancellationToken ct = default)
{
return _client.Get<NodeService>(string.Format("/v1/catalog/node-services/{0}", node), q).Execute(ct);
}
}

public partial class ConsulClient : IConsulClient
Expand Down
6 changes: 6 additions & 0 deletions Consul/Interfaces/ICatalogEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Consul.Filtering;

namespace Consul
{
Expand All @@ -44,5 +45,10 @@ public interface ICatalogEndpoint
Task<QueryResult<CatalogService[]>> Service(string service, string tag, QueryOptions q, CancellationToken ct = default);
Task<QueryResult<Dictionary<string, string[]>>> Services(CancellationToken ct = default);
Task<QueryResult<Dictionary<string, string[]>>> Services(QueryOptions q, CancellationToken ct = default);
Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, Filter filter, CancellationToken ct = default);
Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, QueryOptions q, Filter filter, CancellationToken ct = default);
Task<QueryResult<CatalogService[]>> NodesForMeshCapableService(string service, CancellationToken ct = default);
Task<QueryResult<NodeService>> ServicesForNode(string node, QueryOptions q, CancellationToken ct = default);
Task<QueryResult<NodeService>> ServicesForNode(string node, CancellationToken ct = default);
}
}