aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Data.Caching.Extensions/src/Clustering
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-07-13 13:20:25 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-07-13 13:20:25 -0400
commit2f674e79d42e7d36225fa9ac7ecefbc5bc62d325 (patch)
treec58999489f5391bc044e7a9bb3e557afe2860415 /lib/VNLib.Data.Caching.Extensions/src/Clustering
parent1a8ab1457244d15b19ddcc94958f645f5ec2abc7 (diff)
Checkpoint, kind of working clustering
Diffstat (limited to 'lib/VNLib.Data.Caching.Extensions/src/Clustering')
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheClientConfiguration.cs115
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheDiscoveryFailureException.cs44
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeAdvertisment.cs96
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeConfiguration.cs110
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/ICacheDiscoveryErrorHandler.cs41
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryCollection.cs62
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryEnumerator.cs41
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/Clustering/NodeDiscoveryCollection.cs156
8 files changed, 665 insertions, 0 deletions
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheClientConfiguration.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheClientConfiguration.cs
new file mode 100644
index 0000000..1c1997e
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheClientConfiguration.cs
@@ -0,0 +1,115 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: CacheClientConfiguration.cs
+*
+* CacheClientConfiguration.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+
+ /// <summary>
+ /// A fluent api configuration object for configuring a <see cref="FBMClient"/>
+ /// to connect to cache servers.
+ /// </summary>
+ public class CacheClientConfiguration
+ {
+ /// <summary>
+ /// Stores available cache servers to be used for discovery, and connections
+ /// </summary>
+ public INodeDiscoveryCollection NodeCollection { get; } = new NodeDiscoveryCollection();
+
+ /// <summary>
+ /// The authentication manager to use for signing and verifying messages to and from the cache servers
+ /// </summary>
+ public ICacheAuthManager AuthManager { get; private set; }
+
+ /// <summary>
+ /// The error handler to use for handling errors that occur during the discovery process
+ /// </summary>
+ public ICacheDiscoveryErrorHandler? ErrorHandler { get; private set; }
+
+ /// <summary>
+ /// Specifies if all connections should use TLS
+ /// </summary>
+ public bool UseTls { get; private set; }
+
+ internal Uri[]? WellKnownNodes { get; set; }
+
+ /// <summary>
+ /// Specifies the JWT authentication manager to use for signing and verifying JWTs
+ /// </summary>
+ /// <param name="manager">The authentication manager</param>
+ /// <returns>Chainable fluent object</returns>
+ public CacheClientConfiguration WithAuthenticator(ICacheAuthManager manager)
+ {
+ AuthManager = manager;
+ return this;
+ }
+
+ /// <summary>
+ /// Specifies if all connections should be using TLS
+ /// </summary>
+ /// <param name="useTls">A value that indicates if connections should use TLS</param>
+ public CacheClientConfiguration WithTls(bool useTls)
+ {
+ UseTls = useTls;
+ return this;
+ }
+
+ /// <summary>
+ /// Specifies the initial cache peers to connect to
+ /// </summary>
+ /// <param name="peers">The collection of servers to discover peers from and connect to</param>
+ /// <returns>Chainable fluent object</returns>
+ public CacheClientConfiguration WithInitialPeers(IEnumerable<Uri> peers)
+ {
+ //Check null
+ _ = peers ?? throw new ArgumentNullException(nameof(peers));
+
+ //Store peer array
+ WellKnownNodes = peers.ToArray();
+
+ if (WellKnownNodes.Any(p => !p.IsAbsoluteUri))
+ {
+ WellKnownNodes = null;
+ throw new ArgumentException("All discoverable node uris must be in absolute form");
+ }
+
+ return this;
+ }
+
+ /// <summary>
+ /// Specifies the error handler to use for handling errors that occur during the discovery process
+ /// </summary>
+ /// <param name="handler">The error handler to use during a discovery</param>
+ /// <returns>Chainable fluent object</returns>
+ public CacheClientConfiguration WithErrorHandler(ICacheDiscoveryErrorHandler handler)
+ {
+ ErrorHandler = handler;
+ return this;
+ }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheDiscoveryFailureException.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheDiscoveryFailureException.cs
new file mode 100644
index 0000000..84d611e
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheDiscoveryFailureException.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: CacheDiscoveryFailureException.cs
+*
+* CacheDiscoveryFailureException.cs is part of VNLib.Data.Caching.Extensions which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// Raised when an error occurs during cache discovery
+ /// </summary>
+ public class CacheDiscoveryFailureException : Exception
+ {
+ public CacheDiscoveryFailureException(string message) : base(message)
+ { }
+
+ public CacheDiscoveryFailureException(string message, Exception innerException) : base(message, innerException)
+ { }
+
+ public CacheDiscoveryFailureException()
+ { }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeAdvertisment.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeAdvertisment.cs
new file mode 100644
index 0000000..e81d506
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeAdvertisment.cs
@@ -0,0 +1,96 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: CacheNodeAdvertisment.cs
+*
+* CacheNodeAdvertisment.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text.Json.Serialization;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// Represents a node that can be advertised to clients
+ /// </summary>
+ public class CacheNodeAdvertisment : IEquatable<CacheNodeAdvertisment>
+ {
+ /// <summary>
+ /// The endpoint for clients to connect to to access the cache
+ /// </summary>
+ [JsonIgnore]
+ public Uri? ConnectEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets the address for clients to connect to to discover other discovertable nodes
+ /// </summary>
+ [JsonIgnore]
+ public Uri? DiscoveryEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets the unique identifier for this node
+ /// </summary>
+ [JsonPropertyName("iss")]
+ public string NodeId { get; set; }
+
+ [JsonPropertyName("url")]
+ public string? url
+ {
+ get => ConnectEndpoint?.ToString();
+ set => ConnectEndpoint = value == null ? null : new Uri(value);
+ }
+
+ [JsonPropertyName("dis")]
+ public string? dis
+ {
+ get => DiscoveryEndpoint?.ToString();
+ set => DiscoveryEndpoint = value == null ? null : new Uri(value);
+ }
+
+ /// <summary>
+ /// Determines if the given node is equal to this node, by comparing the node ids
+ /// </summary>
+ /// <param name="obj">The other node advertisment to compare</param>
+ /// <returns>True if the nodes are equal, false otherwise</returns>
+ public override bool Equals(object? obj) => obj is CacheNodeAdvertisment ad && Equals(ad);
+
+ /// <summary>
+ /// Gets the hash code for this node, based on the node id
+ /// </summary>
+ /// <returns>The instance hash-code</returns>
+ public override int GetHashCode() => string.GetHashCode(NodeId, StringComparison.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// Determines if the given node is equal to this node, by comparing the node ids
+ /// </summary>
+ /// <param name="other">The other node advertisment to compare</param>
+ /// <returns>True if the nodes are equal, false otherwise</returns>
+ public bool Equals(CacheNodeAdvertisment? other) => string.Equals(NodeId, other?.NodeId, StringComparison.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// Formats a string representation of this node
+ /// </summary>
+ /// <returns>The formatted information string</returns>
+ public override string ToString()
+ {
+ return $"NodeId: {NodeId} Connect: {url}, Discover?: {dis}";
+ }
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeConfiguration.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeConfiguration.cs
new file mode 100644
index 0000000..6b7ab48
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/CacheNodeConfiguration.cs
@@ -0,0 +1,110 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: CacheNodeConfiguration.cs
+*
+* CacheNodeConfiguration.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+
+ /// <summary>
+ /// A cache configuration for cache servers (nodes)
+ /// </summary>
+ public class CacheNodeConfiguration : CacheClientConfiguration
+ {
+ /// <summary>
+ /// The address for clients to connect to
+ /// </summary>
+ public Uri? ConnectEndpoint { get; private set; }
+
+ /// <summary>
+ /// Whether or not to advertise ourself to peer nodes
+ /// </summary>
+ public bool BroadcastAdverisment { get; private set; }
+
+ /// <summary>
+ /// Define the endpoint for clients to connect to to discover
+ /// other discovertable nodes
+ /// </summary>
+ public Uri? DiscoveryEndpoint { get; private set; }
+
+ /// <summary>
+ /// Gets the configuration for this node as an advertisment
+ /// </summary>
+ public CacheNodeAdvertisment Advertisment
+ {
+ get
+ {
+ return new CacheNodeAdvertisment()
+ {
+ DiscoveryEndpoint = DiscoveryEndpoint,
+ ConnectEndpoint = ConnectEndpoint,
+ NodeId = NodeId
+ };
+ }
+ }
+
+ /// <summary>
+ /// Sets the full address of our cache endpoint for clients to connect to
+ /// </summary>
+ /// <param name="connectUri">The uri clients will attempt to connect to</param>
+ public CacheNodeConfiguration WithCacheEndpoint(Uri connectUri)
+ {
+ ConnectEndpoint = connectUri;
+ return this;
+ }
+
+ /// <summary>
+ /// Enables or disables the advertisement of this node to other nodes
+ /// </summary>
+ /// <param name="discoveryEndpoint">The absolute endpoint clients will use to connect to</param>
+ public CacheNodeConfiguration EnableAdvertisment(Uri? discoveryEndpoint)
+ {
+ BroadcastAdverisment = discoveryEndpoint != null;
+ DiscoveryEndpoint = discoveryEndpoint;
+ return this;
+ }
+
+ ///<inheritdoc/>
+ public string NodeId { get; private set; } = null!;
+
+ /// <summary>
+ /// Specifies the current server's cluster node id. If this
+ /// is a server connection attempting to listen for changes on the
+ /// remote server, this id must be set and unique
+ /// </summary>
+ /// <param name="nodeId">The cluster node id of the current server</param>
+ /// <returns>Chainable fluent object</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public CacheNodeConfiguration WithNodeId(string nodeId)
+ {
+ NodeId = nodeId ?? throw new ArgumentNullException(nameof(nodeId));
+
+ //Update the node id in the node collection
+ (NodeCollection as NodeDiscoveryCollection)!.SetSelfId(nodeId);
+
+ return this;
+ }
+
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/ICacheDiscoveryErrorHandler.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/ICacheDiscoveryErrorHandler.cs
new file mode 100644
index 0000000..984ce3d
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/ICacheDiscoveryErrorHandler.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: ClientCacheConfiguration.cs
+*
+* ClientCacheConfiguration.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// Represents an type that will handle errors that occur during the discovery process
+ /// </summary>
+ public interface ICacheDiscoveryErrorHandler
+ {
+ /// <summary>
+ /// Invoked when an error occurs during the discovery process
+ /// </summary>
+ /// <param name="errorNode">The node that the error occured on</param>
+ /// <param name="ex">The exception that caused the invocation</param>
+ void OnDiscoveryError(CacheNodeAdvertisment errorNode, Exception ex);
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryCollection.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryCollection.cs
new file mode 100644
index 0000000..1f4d154
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryCollection.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: INodeDiscoveryCollection.cs
+*
+* INodeDiscoveryCollection.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+
+using System.Collections.Generic;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// Represents a collection of discovered nodes
+ /// </summary>
+#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
+ public interface INodeDiscoveryCollection
+#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
+ {
+ /// <summary>
+ /// Begins a new discovery and gets an enumerator for the discovery process
+ /// </summary>
+ /// <returns>An enumerator that simplifies discovery of unique nodes</returns>
+ INodeDiscoveryEnumerator BeginDiscovery();
+
+ /// <summary>
+ /// Begins a new discovery and gets an enumerator for the discovery process
+ /// </summary>
+ /// <param name="initialPeers">An initial collection of peers to add to the enumeration</param>
+ /// <returns>An enumerator that simplifies discovery of unique nodes</returns>
+ INodeDiscoveryEnumerator BeginDiscovery(IEnumerable<CacheNodeAdvertisment> initialPeers);
+
+ /// <summary>
+ /// Gets a snapshot of all discovered nodes in the current collection.
+ /// </summary>
+ /// <returns>The current collection of notes</returns>
+ CacheNodeAdvertisment[] GetAllNodes();
+
+ /// <summary>
+ /// Completes a discovery process and updates the collection with the results
+ /// </summary>
+ /// <param name="enumerator">The enumerator used to collect discovered nodes</param>
+ void CompleteDiscovery(INodeDiscoveryEnumerator enumerator);
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryEnumerator.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryEnumerator.cs
new file mode 100644
index 0000000..677088a
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/INodeDiscoveryEnumerator.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: INodeDiscoveryEnumerator.cs
+*
+* INodeDiscoveryEnumerator.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+
+using System.Collections.Generic;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// A custom enumerator for the node discovery process
+ /// </summary>
+ public interface INodeDiscoveryEnumerator : IEnumerator<CacheNodeAdvertisment>
+ {
+ /// <summary>
+ /// Adds the specified peer to the collection of discovered peers
+ /// </summary>
+ /// <param name="discoveredPeers">The peer collection</param>
+ void OnPeerDiscoveryComplete(IEnumerable<CacheNodeAdvertisment> discoveredPeers);
+ }
+}
diff --git a/lib/VNLib.Data.Caching.Extensions/src/Clustering/NodeDiscoveryCollection.cs b/lib/VNLib.Data.Caching.Extensions/src/Clustering/NodeDiscoveryCollection.cs
new file mode 100644
index 0000000..b0e53e1
--- /dev/null
+++ b/lib/VNLib.Data.Caching.Extensions/src/Clustering/NodeDiscoveryCollection.cs
@@ -0,0 +1,156 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Extensions
+* File: NodeDiscoveryCollection.cs
+*
+* NodeDiscoveryCollection.cs is part of VNLib.Data.Caching.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.Extensions is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace VNLib.Data.Caching.Extensions.Clustering
+{
+ /// <summary>
+ /// Represents a collection of available cache nodes from a discovery process
+ /// </summary>
+ public sealed class NodeDiscoveryCollection : INodeDiscoveryCollection
+ {
+ private string? _selfId;
+ private LinkedList<CacheNodeAdvertisment> _peers;
+
+ /// <summary>
+ /// Initializes a new empty <see cref="NodeDiscoveryCollection"/>
+ /// </summary>
+ public NodeDiscoveryCollection()
+ {
+ _peers = new();
+ }
+
+ /// <summary>
+ /// Manually adds nodes to the collection that were not discovered through the discovery process
+ /// </summary>
+ /// <param name="nodes">The nodes to add</param>
+ public void AddManualNodes(IEnumerable<CacheNodeAdvertisment> nodes)
+ {
+ //Get only the nodes that are not already in the collection
+ IEnumerable<CacheNodeAdvertisment> newPeers = nodes.Except(_peers);
+
+ //Add them to the end of the collection
+ foreach (CacheNodeAdvertisment peer in newPeers)
+ {
+ _peers.AddLast(peer);
+ }
+ }
+
+ /// <summary>
+ /// Sets the id of the current node, so it can be excluded from discovery
+ /// </summary>
+ /// <param name="selfId">The id of the current node to exclude</param>
+ public void SetSelfId(string? selfId) => _selfId = selfId;
+
+ ///<inheritdoc/>
+ public INodeDiscoveryEnumerator BeginDiscovery()
+ {
+ return new NodeEnumerator(new(), _selfId);
+ }
+
+ ///<inheritdoc/>
+ public INodeDiscoveryEnumerator BeginDiscovery(IEnumerable<CacheNodeAdvertisment> initialPeers)
+ {
+ //Init new enumerator with the initial peers
+ return new NodeEnumerator(new(initialPeers), _selfId);
+ }
+
+ ///<inheritdoc/>
+ public void CompleteDiscovery(INodeDiscoveryEnumerator enumerator)
+ {
+ _ = enumerator ?? throw new ArgumentNullException(nameof(enumerator));
+
+ //Capture all nodes from the enumerator and store them as our current peers
+ _peers = (enumerator as NodeEnumerator)!.Peers;
+ }
+
+ ///<inheritdoc/>
+ public CacheNodeAdvertisment[] GetAllNodes()
+ {
+ //Capture all current peers
+ return _peers.ToArray();
+ }
+
+ private sealed record class NodeEnumerator(LinkedList<CacheNodeAdvertisment> Peers, string? SelfNodeId) : INodeDiscoveryEnumerator
+ {
+ private bool isInit;
+
+ //Keep track of the current node in the collection so we can move down the list
+ private LinkedListNode<CacheNodeAdvertisment>? _currentNode;
+
+ public CacheNodeAdvertisment Current => _currentNode?.Value;
+ object IEnumerator.Current => _currentNode?.Value;
+
+
+ ///<inheritdoc/>
+ public bool MoveNext()
+ {
+ if (!isInit)
+ {
+ _currentNode = Peers.First;
+ isInit = true;
+ }
+ else
+ {
+ //Move to the next peer in the collection
+ _currentNode = _currentNode?.Next;
+ }
+
+ return _currentNode?.Value != null;
+ }
+
+ ///<inheritdoc/>
+ public void OnPeerDiscoveryComplete(IEnumerable<CacheNodeAdvertisment> discoveredPeers)
+ {
+ //Get only the peers from the discovery that are not already in the collection, or ourselves
+ IEnumerable<CacheNodeAdvertisment> newPeers = discoveredPeers.Except(Peers);
+
+ if (!string.IsNullOrWhiteSpace(SelfNodeId))
+ {
+ //remove ourselves from the list
+ newPeers = newPeers.Where(p => !SelfNodeId.Equals(p.NodeId, StringComparison.OrdinalIgnoreCase));
+ }
+
+ //Add them to the end of the collection
+ foreach (CacheNodeAdvertisment ad in newPeers)
+ {
+ Peers.AddLast(ad);
+ }
+ }
+
+ public void Reset()
+ {
+ //Go to the first node
+ _currentNode = Peers.First;
+ }
+
+ public void Dispose()
+ { }
+ }
+ }
+}