Skip to content

Commit

Permalink
Basic Connect support for service registration on the agent (#216)
Browse files Browse the repository at this point in the history
* AgentService class:
Added properties.
AgentServiceRegistration class:
Added properties.
Added classes:
AgentServiceConnect, AgentServiceProxy.
Added tests.

* Fix

* Add ServiceKind class
Add Tests

* Formatting
  • Loading branch information
firerain-fd authored May 10, 2023
1 parent 0e5c279 commit 0bc4948
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 0 deletions.
84 changes: 84 additions & 0 deletions Consul.Test/AgentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,5 +709,89 @@ public async Task Agent_Register_UseAliasCheck()
Assert.Equal(HealthStatus.Passing, checks.Response[check2Id].Status);
Assert.Equal("All checks passing.", checks.Response[check2Id].Output);
}

[Fact]
public async Task Agent_Service_Register_With_Connect()
{
// Arrange
var destinationServiceID = KVTest.GenerateTestKeyName();
var destinationServiceRegistrationParameters = new AgentServiceRegistration
{
ID = destinationServiceID,
Name = destinationServiceID,
Port = 8000,
Check = new AgentServiceCheck
{
TTL = TimeSpan.FromSeconds(15)
},
Connect = new AgentServiceConnect
{
SidecarService = new AgentServiceRegistration
{
Port = 8001
}
}
};

var sourceServiceID = KVTest.GenerateTestKeyName();
var sourceServiceRegistrationParameters = new AgentServiceRegistration
{
ID = sourceServiceID,
Name = sourceServiceID,
Port = 9000,
Check = new AgentServiceCheck
{
TTL = TimeSpan.FromSeconds(15)
},
Tags = new string[] { "tag1", "tag2" },
Connect = new AgentServiceConnect
{
SidecarService = new AgentServiceRegistration
{
Port = 9001,
Proxy = new AgentServiceProxy
{
Upstreams = new AgentServiceProxyUpstream[] { new AgentServiceProxyUpstream { DestinationName = destinationServiceID, LocalBindPort = 9002 } }
}
}
}
};

// Act
await _client.Agent.ServiceRegister(destinationServiceRegistrationParameters);
await _client.Agent.ServiceRegister(sourceServiceRegistrationParameters);

// Assert
var services = await _client.Agent.Services();

// Assert SourceService
var sourceProxyServiceID = $"{sourceServiceID}-sidecar-proxy";
Assert.Contains(sourceServiceID, services.Response.Keys);
Assert.Contains(sourceProxyServiceID, services.Response.Keys);
AgentService sourceProxyService = services.Response[sourceProxyServiceID];
Assert.Equal(sourceServiceRegistrationParameters.Tags, sourceProxyService.Tags);
Assert.Equal(sourceServiceRegistrationParameters.Connect.SidecarService.Port, sourceProxyService.Port);
Assert.Equal(sourceServiceID, sourceProxyService.Proxy.DestinationServiceName);
Assert.Equal(sourceServiceID, sourceProxyService.Proxy.DestinationServiceID);
Assert.Equal("127.0.0.1", sourceProxyService.Proxy.LocalServiceAddress);
Assert.Equal(sourceServiceRegistrationParameters.Port, sourceProxyService.Proxy.LocalServicePort);
Assert.Equal(ServiceKind.ConnectProxy, sourceProxyService.Kind);
Assert.Single(sourceProxyService.Proxy.Upstreams);
Assert.Equal(sourceServiceRegistrationParameters.Connect.SidecarService.Proxy.Upstreams[0].DestinationName, sourceProxyService.Proxy.Upstreams[0].DestinationName);
Assert.Equal(sourceServiceRegistrationParameters.Connect.SidecarService.Proxy.Upstreams[0].LocalBindPort, sourceProxyService.Proxy.Upstreams[0].LocalBindPort);

// Assert DestinationService
var destinationProxyServiceID = $"{destinationServiceID}-sidecar-proxy";
Assert.Contains(destinationServiceID, services.Response.Keys);
Assert.Contains(destinationProxyServiceID, services.Response.Keys);
AgentService destinationProxyService = services.Response[destinationProxyServiceID];
Assert.Equal(destinationServiceRegistrationParameters.Connect.SidecarService.Port, destinationProxyService.Port);
Assert.Equal(destinationServiceID, destinationProxyService.Proxy.DestinationServiceName);
Assert.Equal(destinationServiceID, destinationProxyService.Proxy.DestinationServiceID);
Assert.Equal("127.0.0.1", destinationProxyService.Proxy.LocalServiceAddress);
Assert.Equal(destinationServiceRegistrationParameters.Port, destinationProxyService.Proxy.LocalServicePort);
Assert.Null(destinationProxyService.Proxy.Upstreams);
Assert.Equal(ServiceKind.ConnectProxy, destinationProxyService.Kind);
}
}
}
62 changes: 62 additions & 0 deletions Consul.Test/ServiceKindUnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// -----------------------------------------------------------------------
// <copyright file="ServiceKindUnitTests.cs" company="G-Research Limited">
// Copyright 2020 G-Research Limited
//
// 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.
// </copyright>
// -----------------------------------------------------------------------

using System.Collections.Generic;
using Xunit;

namespace Consul.Test
{
public class ServiceKindUnitTests
{
public static IList<object[]> TryParseTestCases => new List<object[]>
{
new object[] { null, true, null },
new object[] { "", true, null },
new object[] { " ", true, null },
new object[] { " ", true, null },
new object[] { "invalidvalud", false, null },
new object[] { "connect-proxy", true, ServiceKind.ConnectProxy },
new object[] { "Connect-proxy", true, ServiceKind.ConnectProxy },
new object[] { "mesh-gateway", true, ServiceKind.MeshGateway },
new object[] { "Mesh-gateway", true, ServiceKind.MeshGateway },
new object[] { "terminating-gateway", true, ServiceKind.TerminatingGateway },
new object[] { "Terminating-gateway", true, ServiceKind.TerminatingGateway },
new object[] { "ingress-gateway", true, ServiceKind.IngressGateway },
new object[] { "Ingress-gateway", true, ServiceKind.IngressGateway },
};

[Fact]
public void Test_Equals()
{
Assert.Equal(ServiceKind.IngressGateway, ServiceKind.IngressGateway);
Assert.Equal(ServiceKind.IngressGateway, (object)"ingress-gateway");
Assert.NotEqual(ServiceKind.IngressGateway, ServiceKind.ConnectProxy);
Assert.NotEqual(ServiceKind.IngressGateway, (object)"connect-proxy");
}

[Theory]
[MemberData(nameof(TryParseTestCases))]
public void TryParse(string value, bool expectedPredicateResult, ServiceKind expected)
{
bool actualPredicateResult = ServiceKind.TryParse(value, out ServiceKind actual);

Assert.Equal(expectedPredicateResult, actualPredicateResult);
Assert.Equal(expected, actual);
}
}
}
108 changes: 108 additions & 0 deletions Consul/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -139,6 +141,64 @@ public class AgentService
public IDictionary<string, ServiceTaggedAddress> TaggedAddresses { get; set; }
public bool EnableTagOverride { get; set; }
public IDictionary<string, string> Meta { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // If the Proxy property is serialized to have null value, a protocol error occurs when registering the service through the catalog (catalog/register) during an http request.
public AgentServiceProxy Proxy { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public ServiceKind Kind { get; set; }
}

/// <summary>
/// ServiceKind specifies the type of service.
/// </summary>
[TypeConverter(typeof(ServiceKindTypeConverter))]
public class ServiceKind : IEquatable<ServiceKind>
{
static IReadOnlyDictionary<string, ServiceKind> Map { get; } = new Dictionary<string, ServiceKind>(StringComparer.OrdinalIgnoreCase)
{
{ "connect-proxy", new ServiceKind("connect-proxy") },
{ "mesh-gateway", new ServiceKind("mesh-gateway") },
{ "terminating-gateway", new ServiceKind("terminating-gateway") },
{ "ingress-gateway", new ServiceKind("ingress-gateway") },
};

public static ServiceKind ConnectProxy => Map["connect-proxy"];
public static ServiceKind MeshGateway => Map["mesh-gateway"];
public static ServiceKind TerminatingGateway => Map["terminating-gateway"];
public static ServiceKind IngressGateway => Map["ingress-gateway"];

string Value { get; }

ServiceKind(string value) => Value = value;

public override bool Equals(object obj) => obj is ServiceKind typedObject ? Equals(typedObject) : Value.Equals(obj.ToString(), StringComparison.OrdinalIgnoreCase);

public bool Equals(ServiceKind other) => ReferenceEquals(this, other);

public override int GetHashCode() => Value.GetHashCode();

public override string ToString() => Value.ToString();

public static bool TryParse(string value, out ServiceKind result)
{
result = null;

if (string.IsNullOrWhiteSpace(value))
{
return true;
}

return Map.TryGetValue(value, out result);
}
}

class ServiceKindTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string);
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => ServiceKind.TryParse(value?.ToString(), out ServiceKind result) ? result : throw new NotSupportedException();
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) => value is null ? string.Empty : value.ToString();
}

/// <summary>
Expand Down Expand Up @@ -198,6 +258,12 @@ public class AgentServiceRegistration

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, ServiceTaggedAddress> TaggedAddresses { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public AgentServiceConnect Connect { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public AgentServiceProxy Proxy { get; set; }
}

/// <summary>
Expand All @@ -209,6 +275,48 @@ public class AgentCheckRegistration : AgentServiceCheck
public string ServiceID { get; set; }
}

/// <summary>
/// AgentServiceConnect specifies the configuration for Connect
/// </summary>
public class AgentServiceConnect
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public AgentServiceRegistration SidecarService { get; set; }
}

/// <summary>
/// AgentServiceProxy specifies the configuration for a Connect service proxy instance. This is only valid if Kind defines a proxy or gateway.
/// </summary>
public class AgentServiceProxy
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string DestinationServiceID { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int LocalServicePort { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string LocalServiceAddress { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string DestinationServiceName { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public AgentServiceProxyUpstream[] Upstreams { get; set; }
}

/// <summary>
/// AgentServiceProxyUpstream specifies the upstream service for which the proxy should create a listener.
/// </summary>
public class AgentServiceProxyUpstream
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string DestinationName { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int LocalBindPort { get; set; }
}

/// <summary>
/// AgentServiceCheck is used to create an associated check for a service
/// </summary>
Expand Down

0 comments on commit 0bc4948

Please sign in to comment.