Skip to content

Commit

Permalink
Support for multiple endpoints..
Browse files Browse the repository at this point in the history
  • Loading branch information
jordansjones committed Apr 27, 2015
1 parent 242e7bb commit f3ba582
Show file tree
Hide file tree
Showing 51 changed files with 1,596 additions and 134 deletions.
16 changes: 16 additions & 0 deletions source/Draft/Draft-Net45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@
<Compile Include="Converters.cs" />
<Compile Include="Configuration\ClientConfig.cs" />
<Compile Include="Configuration\IEtcdClientConfig.cs" />
<Compile Include="Endpoints\EndpointAvailability.cs" />
<Compile Include="Endpoints\EndpointPool.cs" />
<Compile Include="Endpoints\EndpointPool.Builder.cs" />
<Compile Include="Endpoints\Endpoint.cs" />
<Compile Include="Endpoints\EndpointRoutingStrategy.cs" />
<Compile Include="Endpoints\EndpointVerificationStrategy.cs" />
<Compile Include="Endpoints\RoutingStrategy.ConsistentKeyHashing.cs" />
<Compile Include="Endpoints\RoutingStrategy.First.cs" />
<Compile Include="Endpoints\RoutingStrategy.Random.cs" />
<Compile Include="Endpoints\RoutingStrategy.RoundRobin.cs" />
<Compile Include="Endpoints\VerificationStrategy.None.cs" />
<Compile Include="Endpoints\VerificationStrategy.All.cs" />
<Compile Include="Endpoints\VerificationStrategy.Any.cs" />
<Compile Include="Exceptions\ClientInternalException.cs" />
<Compile Include="Exceptions\DirectoryNotEmptyException.cs" />
<Compile Include="Exceptions\EtcdException.cs" />
Expand Down Expand Up @@ -104,7 +117,10 @@
<Compile Include="Extensions\FormBodyBuilder.Extensions.cs" />
<Compile Include="Extensions\ValueConverter.RequestExtensions.cs" />
<Compile Include="Extensions\ValueConverter.ResponseExtensions.cs" />
<Compile Include="Extensions\VerificationStrategyExtensions.cs" />
<Compile Include="Helpers\FormBodyBuilder.cs" />
<Compile Include="Helpers\IRandom.cs" />
<Compile Include="Helpers\StaticRandom.cs" />
<Compile Include="Json\EtcdErrorCodeConverter.cs" />
<Compile Include="Requests\Cluster\CreateMemberRequest.cs" />
<Compile Include="Requests\Cluster\DeleteMemberRequest.cs" />
Expand Down
3 changes: 2 additions & 1 deletion source/Draft/Draft-Net45.csproj.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internal/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cstatistics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=responses/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=vendor/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=vendor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=vendor_005Cmurmurhash/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
87 changes: 87 additions & 0 deletions source/Draft/Endpoints/Endpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Linq;

namespace Draft.Endpoints
{
/// <summary>
/// Represents a verified etcd endpoint.
/// </summary>
public sealed class Endpoint : IEquatable<Endpoint>
{

/// <summary>
/// Initializes a new <see cref="Endpoint" /> class with the specified <paramref name="availability" /> and
/// <paramref name="uri" />.
/// </summary>
/// <param name="uri">The etcd endpoint uri.</param>
/// <param name="availability">The etcd endpoint availability.</param>
public Endpoint(Uri uri, EndpointAvailability availability)
{
Uri = uri;
Availability = availability;
}

/// <summary>
/// <see cref="EndpointAvailability" /> value of this etcd endpoint.
/// </summary>
public EndpointAvailability Availability { get; private set; }

/// <summary>
/// Is <c>true</c> when <see cref="Availability" /><c> == </c><see cref="EndpointAvailability.Online" />.
/// </summary>
public bool IsOnline
{
get { return Availability == EndpointAvailability.Online; }
}

/// <summary>
/// <see cref="Uri" /> value of this etcd endpoint.
/// </summary>
public Uri Uri { get; private set; }

/// <summary>
/// Determines whether the specified <see cref="Endpoint" /> is equal to the current <see cref="Endpoint" />.
/// </summary>
public bool Equals(Endpoint other)
{
if (ReferenceEquals(null, other)) { return false; }
if (ReferenceEquals(this, other)) { return true; }
return Availability == other.Availability && Equals(Uri, other.Uri);
}

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) { return false; }
if (ReferenceEquals(this, obj)) { return true; }
return obj is Endpoint && Equals((Endpoint) obj);
}

/// <summary>
/// Serves as the default hash function.
/// </summary>
public override int GetHashCode()
{
unchecked { return ((int) Availability * 397) ^ (Uri != null ? Uri.GetHashCode() : 0); }
}

/// <summary>
/// Determines whether two specified <see cref="Endpoint" /> have the same value.
/// </summary>
public static bool operator ==(Endpoint left, Endpoint right)
{
return Equals(left, right);
}

/// <summary>
/// Determines whether two specified <see cref="Endpoint" /> have different values.
/// </summary>
public static bool operator !=(Endpoint left, Endpoint right)
{
return !Equals(left, right);
}

}
}
29 changes: 29 additions & 0 deletions source/Draft/Endpoints/EndpointAvailability.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq;

namespace Draft.Endpoints
{
/// <summary>
/// Etcd endpoint availability indicators
/// </summary>
public enum EndpointAvailability
{

/// <summary>
/// Endpoint availability is not known.
/// </summary>
Unknown,

/// <summary>
/// Endpoint is known to be correct and online.
/// </summary>
Online,

/// <summary>
/// Endpoint is known to be incorrect or offline.
/// </summary>
Offline


}
}
103 changes: 103 additions & 0 deletions source/Draft/Endpoints/EndpointPool.Builder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Threading.Tasks;

using Draft.Exceptions;

namespace Draft.Endpoints
{
public sealed partial class EndpointPool
{

/// <summary>
/// Initializes a new instance of the <see cref="EndpointPool.Builder" /> class.
/// </summary>
public static Builder Build()
{
return new Builder();
}

/// <summary>
/// Helper class used to build a pool of etcd endpoints
/// </summary>
public sealed class Builder
{

private EndpointRoutingStrategy _routingStrategy;

private EndpointVerificationStrategy _verificationStrategy;

internal EndpointRoutingStrategy RoutingStrategy
{
get { return _routingStrategy ?? EndpointRoutingStrategy.Default; }
}

internal EndpointVerificationStrategy VerificationStrategy
{
get { return _verificationStrategy ?? EndpointVerificationStrategy.Default; }
}

/// <summary>
/// Verifies the passed <paramref name="uris" /> first to ensure that they are <see cref="Uri.IsAbsoluteUri" />. Then
/// leverages the defined <see cref="EndpointVerificationStrategy" />. Finally creates an <see cref="EndpointPool" />
/// with
/// the defined <see cref="EndpointRoutingStrategy" />.
/// </summary>
/// <param name="uris">Etcd endpoint uris to use.</param>
/// <exception cref="ArgumentNullException">Passed <paramref name="uris" /> is null or empty.</exception>
/// <exception cref="ArgumentException">One or more <paramref name="uris" /> is not an absolute <see cref="Uri" />.</exception>
/// <exception cref="InvalidHostException">
/// May be thrown depending on the chosen
/// <see cref="EndpointVerificationStrategy" />.
/// </exception>
public async Task<EndpointPool> VerifyAndBuild(params Uri[] uris)
{
if (uris == null || !uris.Any())
{
throw new ArgumentNullException("uris", "You must supply at least 1 Uri");
}
var invalidUris = uris.Where(x => !x.IsAbsoluteUri).ToList();
if (invalidUris.Any())
{
throw new ArgumentException(
string.Format("The following Uri(s) are not valid absolute Uri(s): '{0}'", string.Join(", ", invalidUris)),
"uris"
);
}
var endpoints = await VerificationStrategy.Verify(uris);

return new EndpointPool(endpoints, RoutingStrategy);
}

/// <summary>
/// Defines the type of endpoint routing to use. Defaults to <see cref="EndpointRoutingStrategy.First" />.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="routingStrategy" /> is <c>null</c>.</exception>
public Builder WithRoutingStrategy(EndpointRoutingStrategy routingStrategy)
{
if (routingStrategy == null)
{
throw new ArgumentNullException("routingStrategy");
}
_routingStrategy = routingStrategy;
return this;
}

/// <summary>
/// Defines the type of endpoint verification to use. Defaults to <see cref="EndpointVerificationStrategy.None" />.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="verificationStrategy" /> is <c>null</c>.</exception>
public Builder WithVerificationStrategy(EndpointVerificationStrategy verificationStrategy)
{
if (verificationStrategy == null)
{
throw new ArgumentNullException("verificationStrategy");
}
_verificationStrategy = verificationStrategy;
return this;
}

}

}
}
41 changes: 41 additions & 0 deletions source/Draft/Endpoints/EndpointPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;

using Flurl;

namespace Draft.Endpoints
{
/// <summary>
/// Pool of Etcd Endpoints
/// </summary>
public sealed partial class EndpointPool
{

internal EndpointPool(IEnumerable<Endpoint> endpoints, EndpointRoutingStrategy routingStrategy)
{
AllEndpoints = endpoints.ToList();
RoutingStrategy = routingStrategy;
}

internal List<Endpoint> AllEndpoints { get; private set; }

internal Endpoint[] OnlineEndpoints
{
get { return AllEndpoints.Where(x => x.IsOnline).ToArray(); }
}

internal EndpointRoutingStrategy RoutingStrategy { get; private set; }

internal Url GetEndpointUrl(params string[] pathParts)
{
pathParts = pathParts ?? new string[0];

var keyPath = string.Join("/", pathParts.Select(x => x.TrimStart('/').TrimEnd('/')));

return RoutingStrategy.Select(keyPath, OnlineEndpoints).Uri.ToUrl()
.AppendPathSegment(keyPath);
}

}
}
69 changes: 69 additions & 0 deletions source/Draft/Endpoints/EndpointRoutingStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Linq;

namespace Draft.Endpoints
{
/// <summary>
/// Represents a strategy for selecting which etcd endpoint to use for http calls.
/// </summary>
public abstract class EndpointRoutingStrategy
{

internal static EndpointRoutingStrategy Default
{
get { return First; }
}

/// <summary>
/// Executes the strategy against the passed <paramref name="key" /> and <paramref name="endpoints" />.
/// </summary>
/// <param name="key">The etcd key for the current http call.</param>
/// <param name="endpoints">The <see cref="EndpointAvailability.Online" /> etcd endpoints.</param>
/// <returns>The <see cref="Endpoint" /> to use in the current http call.</returns>
public abstract Endpoint Select(string key, Endpoint[] endpoints);

#region Built-in strategies

private static readonly Lazy<EndpointRoutingStrategy> LazyConsistenKeyHashing = new Lazy<EndpointRoutingStrategy>(() => new RoutingStrategyConsistentKeyHashing());

private static readonly Lazy<EndpointRoutingStrategy> LazyFirst = new Lazy<EndpointRoutingStrategy>(() => new RoutingStrategyFirst());

private static readonly Lazy<EndpointRoutingStrategy> LazyRandom = new Lazy<EndpointRoutingStrategy>(() => new RoutingStrategyRandom());

private static readonly Lazy<EndpointRoutingStrategy> LazyRoundRobin = new Lazy<EndpointRoutingStrategy>(() => new RoutingStrategyRoundRobin());

// /// <summary>
// /// Uses a consistent hashing algorithm on the etcd key to select the <see cref="Endpoint" />.
// /// </summary>
// public static EndpointRoutingStrategy ConsistentKeyHashing
// {
// get { return LazyConsistenKeyHashing.Value; }
// }

/// <summary>
/// Uses the first <see cref="Endpoint" />.
/// </summary>
public static EndpointRoutingStrategy First
{
get { return LazyFirst.Value; }
}

/// <summary>
/// Uses a randomly selected <see cref="Endpoint" />.
/// </summary>
public static EndpointRoutingStrategy Random
{
get { return LazyRandom.Value; }
}

/// <summary>
/// Uses a round-robin patter for selecting the <see cref="Endpoint" />.
/// </summary>
public static EndpointRoutingStrategy RoundRobin
{
get { return LazyRoundRobin.Value; }
}

#endregion
}
}
Loading

0 comments on commit f3ba582

Please sign in to comment.