diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.csproj b/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.csproj
new file mode 100644
index 0000000..8cf522d
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.csproj
@@ -0,0 +1,86 @@
+
+
+
+
+ Release
+ AnyCPU
+ {1255EBD3-0340-4AF6-A5F2-3D97D8709546}
+ Library
+ Properties
+ Amazon.ElastiCacheCluster
+ Amazon.ElastiCacheCluster
+ v3.5
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+
+
+ prompt
+ 4
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+
+
+ prompt
+ 4
+ true
+ bin\Release\Amazon.ElastiCacheCluster.XML
+
+
+ true
+
+
+ awskey.snk
+
+
+
+ ..\packages\EnyimMemcached.2.12\lib\net35\Enyim.Caching.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.nuspec b/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.nuspec
new file mode 100644
index 0000000..a96b2fe
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Amazon.ElastiCacheCluster.nuspec
@@ -0,0 +1,20 @@
+
+
+
+ $id$
+ $version$
+ $title$
+ $author$
+ $author$
+ http://media.amazonwebservices.com/aws_singlebox_01.png
+ http://aws.amazon.com/apache2.0
+ https://github.com/awslabs/elasticache-cluster-config-net
+ false
+ $description$
+ Copyright 2014
+ AWS Amazon cloud elasticache clusterclient aws amazon ElastiCacheCluster elasticacheclusterconfig cluster config clusterconfig elasticachenetclusterclient ElastiCache
+
+
+
+
+
\ No newline at end of file
diff --git a/Amazon.ElastiCacheCluster/ClusterClient.cs b/Amazon.ElastiCacheCluster/ClusterClient.cs
new file mode 100644
index 0000000..a545d9c
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/ClusterClient.cs
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Enyim.Caching;
+
+namespace Amazon.ElastiCacheCluster
+{
+ ///
+ /// Used to instantiate MemcachedClients with auto discovery enabled.
+ /// Only use these for easy creation because the ability to get information from the config object is lost
+ ///
+ public static class ClusterClient
+ {
+ ///
+ /// Creates a MemcachedClient using the settings found in the app.config section "clusterclient"
+ ///
+ /// A new MemcachedClient configured for auto discovery
+ public static MemcachedClient CreateClient()
+ {
+ return new MemcachedClient(new ElastiCacheClusterConfig());
+ }
+
+ ///
+ /// Creates a MemcachedClient using the settings found in the app.config section specified
+ ///
+ /// A section in app.config that has a endpoint field
+ /// A new MemcachedClient configured for auto discovery
+ public static MemcachedClient CreateClient(string section)
+ {
+ return new MemcachedClient(new ElastiCacheClusterConfig(section));
+ }
+
+ ///
+ /// Creates a MemcachedClient using the default settings with the endpoint and port specified
+ ///
+ /// The url for the cluster endpoint containing .cfg.
+ /// The port to access the cluster on
+ /// A new MemcachedClient configured for auto discovery
+ public static MemcachedClient CreateClient(string endpoint, int port)
+ {
+ return new MemcachedClient(new ElastiCacheClusterConfig(endpoint, port));
+ }
+
+ ///
+ /// Creates a MemcachedClient using the Client config provided
+ ///
+ /// The config to instantiate the client with
+ /// A new MemcachedClient configured for auto discovery
+ public static MemcachedClient CreateClient(ElastiCacheClusterConfig config)
+ {
+ return new MemcachedClient(config);
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/ClusterConfigSettings.cs b/Amazon.ElastiCacheCluster/ClusterConfigSettings.cs
new file mode 100644
index 0000000..faea95e
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/ClusterConfigSettings.cs
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Configuration;
+using Enyim.Caching.Configuration;
+using Enyim.Caching.Memcached;
+using Amazon.ElastiCacheCluster.Factories;
+
+namespace Amazon.ElastiCacheCluster
+{
+ ///
+ /// A config settings object used to configure the client config
+ ///
+ public class ClusterConfigSettings : ConfigurationSection
+ {
+ ///
+ /// An object that produces nodes for the Discovery Node, mainly used for testing
+ ///
+ public IConfigNodeFactory NodeFactory { get; set; }
+
+ #region Constructors
+
+ ///
+ /// For config manager
+ ///
+ public ClusterConfigSettings() { }
+
+ ///
+ /// Used to initialize a setup with a host and port
+ ///
+ /// Cluster hostname
+ /// Cluster port
+ public ClusterConfigSettings(string hostname, int port)
+ {
+ if (string.IsNullOrEmpty(hostname))
+ throw new ArgumentNullException("hostname");
+ if (port <= 0)
+ throw new ArgumentException("Port cannot be less than or equal to zero");
+
+ this.ClusterEndPoint.HostName = hostname;
+ this.ClusterEndPoint.Port = port;
+ }
+
+ #endregion
+
+ #region Config Settings
+
+ ///
+ /// Class containing information about the cluster host and port
+ ///
+ [ConfigurationProperty("endpoint", IsRequired = true)]
+ public Endpoint ClusterEndPoint
+ {
+ get { return (Endpoint)base["endpoint"]; }
+ set { base["endpoint"] = value; }
+ }
+
+ ///
+ /// Class containing information about the node configuration
+ ///
+ [ConfigurationProperty("node", IsRequired = false)]
+ public NodeSettings ClusterNode
+ {
+ get { return (NodeSettings)base["node"]; }
+ set { base["node"] = value; }
+ }
+
+ ///
+ /// Class containing information about the poller configuration
+ ///
+ [ConfigurationProperty("poller", IsRequired = false)]
+ public PollerSettings ClusterPoller
+ {
+ get { return (PollerSettings)base["poller"]; }
+ set { base["poller"] = value; }
+ }
+
+ ///
+ /// Endpoint that contains the hostname and port for auto discovery
+ ///
+ public class Endpoint : ConfigurationElement
+ {
+ ///
+ /// The hostname of the cluster containing ".cfg."
+ ///
+ [ConfigurationProperty("hostname", IsRequired = true)]
+ public String HostName
+ {
+ get
+ {
+ return (String)this["hostname"];
+ }
+ set
+ {
+ this["hostname"] = value;
+ }
+ }
+
+ ///
+ /// The port of the endpoint
+ ///
+ [ConfigurationProperty("port", IsRequired = true)]
+ public int Port
+ {
+ get
+ {
+ return (int)this["port"];
+ }
+ set
+ {
+ this["port"] = value;
+ }
+ }
+ }
+
+ ///
+ /// Settings used for the discovery node
+ ///
+ public class NodeSettings : ConfigurationElement
+ {
+ ///
+ /// How many tries the node should use to get a config
+ ///
+ [ConfigurationProperty("nodeTries", DefaultValue = -1, IsRequired = false)]
+ public int NodeTries
+ {
+ get { return (int)base["nodeTries"]; }
+ set { base["nodeTries"] = value; }
+ }
+
+ ///
+ /// The delay between tries for the config in miliseconds
+ ///
+ [ConfigurationProperty("nodeDelay", DefaultValue = -1, IsRequired = false)]
+ public int NodeDelay
+ {
+ get { return (int)base["nodeDelay"]; }
+ set { base["nodeDelay"] = value; }
+ }
+ }
+
+ ///
+ /// Settins used for the configuration poller
+ ///
+ public class PollerSettings : ConfigurationElement
+ {
+ ///
+ /// The delay between polls in miliseconds
+ ///
+ [ConfigurationProperty("intervalDelay", DefaultValue = -1, IsRequired = false)]
+ public int IntervalDelay
+ {
+ get { return (int)base["intervalDelay"]; }
+ set { base["intervalDelay"] = value; }
+ }
+ }
+
+ #endregion
+
+ #region MemcachedConfig
+
+ ///
+ /// Gets or sets the configuration of the socket pool.
+ ///
+ [ConfigurationProperty("socketPool", IsRequired = false)]
+ public SocketPoolElement SocketPool
+ {
+ get { return (SocketPoolElement)base["socketPool"]; }
+ set { base["socketPool"] = value; }
+ }
+
+ ///
+ /// Gets or sets the configuration of the authenticator.
+ ///
+ [ConfigurationProperty("authentication", IsRequired = false)]
+ public AuthenticationElement Authentication
+ {
+ get { return (AuthenticationElement)base["authentication"]; }
+ set { base["authentication"] = value; }
+ }
+
+ ///
+ /// Gets or sets the which will be used to assign items to Memcached nodes.
+ ///
+ [ConfigurationProperty("locator", IsRequired = false)]
+ public ProviderElement NodeLocator
+ {
+ get { return (ProviderElement)base["locator"]; }
+ set { base["locator"] = value; }
+ }
+
+ ///
+ /// Gets or sets the which will be used to convert item keys for Memcached.
+ ///
+ [ConfigurationProperty("keyTransformer", IsRequired = false)]
+ public ProviderElement KeyTransformer
+ {
+ get { return (ProviderElement)base["keyTransformer"]; }
+ set { base["keyTransformer"] = value; }
+ }
+
+ ///
+ /// Gets or sets the which will be used serialzie or deserialize items.
+ ///
+ [ConfigurationProperty("transcoder", IsRequired = false)]
+ public ProviderElement Transcoder
+ {
+ get { return (ProviderElement)base["transcoder"]; }
+ set { base["transcoder"] = value; }
+ }
+
+ ///
+ /// Gets or sets the which will be used monitor the performance of the client.
+ ///
+ [ConfigurationProperty("performanceMonitor", IsRequired = false)]
+ public ProviderElement PerformanceMonitor
+ {
+ get { return (ProviderElement)base["performanceMonitor"]; }
+ set { base["performanceMonitor"] = value; }
+ }
+
+ ///
+ /// Gets or sets the type of the communication between client and server.
+ ///
+ [ConfigurationProperty("protocol", IsRequired = false, DefaultValue = MemcachedProtocol.Binary)]
+ public MemcachedProtocol Protocol
+ {
+ get { return (MemcachedProtocol)base["protocol"]; }
+ set { base["protocol"] = value; }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/ConfigurationPoller.cs b/Amazon.ElastiCacheCluster/ConfigurationPoller.cs
new file mode 100644
index 0000000..fcdc217
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/ConfigurationPoller.cs
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Timers;
+
+namespace Amazon.ElastiCacheCluster
+{
+ ///
+ /// A poller used to reconfigure the client servers when updates occur to the cluster configuration
+ ///
+ internal class ConfigurationPoller
+ {
+ private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(ConfigurationPoller));
+
+ #region Defaults
+
+ // Poll once every minute
+ private static readonly int DEFAULT_INTERVAL_DELAY = 60000;
+
+ #endregion
+
+ private Timer timer;
+ private int intervalDelay;
+ private ElastiCacheClusterConfig config;
+
+ #region Constructors
+
+ ///
+ /// Creates a poller for Auto Discovery with the default intervals
+ ///
+ /// The memcached client to update servers for
+ public ConfigurationPoller(ElastiCacheClusterConfig config)
+ : this(config, DEFAULT_INTERVAL_DELAY) { }
+
+ ///
+ /// Creates a poller for Auto Discovery with the defined itnerval, delay, tries, and try delay for polling
+ ///
+ /// The memcached client to update servers for
+ /// The amount of time between polling operations in miliseconds
+ public ConfigurationPoller(ElastiCacheClusterConfig config, int intervalDelay)
+ {
+ this.intervalDelay = intervalDelay < 0 ? DEFAULT_INTERVAL_DELAY : intervalDelay;
+ this.config = config;
+
+ this.timer = new Timer(this.intervalDelay);
+ this.timer.Elapsed += this.pollOnTimedEvent;
+ }
+
+ #endregion
+
+ #region Polling Methods
+
+ internal void StartTimer()
+ {
+ log.Debug("Starting timer");
+ this.pollOnTimedEvent(null, null);
+ this.timer.Start();
+ }
+
+ ///
+ /// Used by the poller's timer to update the cluster configuration if a new version is available
+ ///
+ internal void pollOnTimedEvent(Object source, ElapsedEventArgs e)
+ {
+ log.Debug("Polling...");
+ try
+ {
+ var oldVersion = config.DiscoveryNode.ClusterVersion;
+ var endPoints = config.DiscoveryNode.GetEndPointList();
+ if (oldVersion != config.DiscoveryNode.ClusterVersion)
+ {
+ this.config.Pool.UpdateLocator(endPoints);
+ }
+ }
+ catch
+ {
+ try
+ {
+ config.DiscoveryNode.ResolveEndPoint();
+
+ var oldVersion = config.DiscoveryNode.ClusterVersion;
+ var endPoints = config.DiscoveryNode.GetEndPointList();
+ if (oldVersion != config.DiscoveryNode.ClusterVersion)
+ {
+ this.config.Pool.UpdateLocator(endPoints);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new TimeoutException("Could not retrieve cluster configuration after updating endpoint. " + ex.Message);
+ }
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Disposes the background thread that is used for polling the configs
+ ///
+ public void StopPolling()
+ {
+ log.Debug("Destroying poller thread");
+ if (this.timer != null)
+ this.timer.Dispose();
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/DiscoveryNode.cs b/Amazon.ElastiCacheCluster/DiscoveryNode.cs
new file mode 100644
index 0000000..23ddde0
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/DiscoveryNode.cs
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using System.Net;
+using System.Configuration;
+using Enyim.Caching.Configuration;
+using Enyim.Caching.Memcached;
+using Amazon.ElastiCacheCluster.Helpers;
+using Amazon.ElastiCacheCluster.Operations;
+using Amazon.ElastiCacheCluster.Pools;
+using Enyim.Caching.Memcached.Results;
+using Enyim.Caching.Memcached.Protocol;
+
+namespace Amazon.ElastiCacheCluster
+{
+ ///
+ /// A class that manages the discovery of endpoints inside of an ElastiCache cluster
+ ///
+ public class DiscoveryNode
+ {
+ #region Static ReadOnlys
+
+ private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(DiscoveryNode));
+
+ internal static readonly int DEFAULT_TRY_COUNT = 5;
+ internal static readonly int DEFAULT_TRY_DELAY = 1000;
+
+ #endregion
+
+ ///
+ /// The version of memcached running on the Nodes
+ ///
+ public Version NodeVersion { get; private set; }
+
+ ///
+ /// The version of the cluster configuration
+ ///
+ public int ClusterVersion { get; private set; }
+
+ ///
+ /// The number of nodes running inside of the cluster
+ ///
+ public int NodesInCluster { get { return this.nodes.Count; } }
+
+ #region Private Fields
+
+ private IPEndPoint EndPoint;
+
+ private IMemcachedNode Node;
+
+ private ElastiCacheClusterConfig config;
+
+ private List nodes = new List();
+
+ private ConfigurationPoller poller;
+
+ private string hostname;
+ private int port;
+
+ private int tries;
+ private int delay;
+
+ private Object nodesLock, endpointLock, clusterLock;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// The node used to discover endpoints in an ElastiCache cluster
+ ///
+ /// The client discovery node is contained within
+ /// The host name of the cluster with .cfg. in name
+ /// The port of the cluster
+ /// The config of the client to access the SocketPool
+ internal DiscoveryNode(ElastiCacheClusterConfig config, string hostname, int port)
+ : this(config, hostname, port, DEFAULT_TRY_COUNT, DEFAULT_TRY_DELAY) { }
+
+ ///
+ /// The node used to discover endpoints in an ElastiCache cluster
+ ///
+ /// The client discovery node is contained within
+ /// The host name of the cluster with .cfg. in name
+ /// The port of the cluster
+ /// The config of the client to access the SocketPool
+ /// The number of tries for requesting config info
+ /// The time, in miliseconds, to wait between tries
+ internal DiscoveryNode(ElastiCacheClusterConfig config, string hostname, int port, int tries, int delay)
+ {
+ #region Param Checks
+
+ if (config == null)
+ throw new ArgumentNullException("config");
+ if (string.IsNullOrEmpty(hostname))
+ throw new ArgumentNullException("hostname");
+ if (port <= 0)
+ throw new ArgumentException("Port cannot be 0 or less");
+ if (tries < 1)
+ throw new ArgumentException("Must atleast try once");
+ if (delay < 0)
+ throw new ArgumentException("The delay can't be negative");
+ if (hostname.IndexOf(".cfg", StringComparison.OrdinalIgnoreCase) < 0)
+ throw new ArgumentException("The hostname is not able to use Auto Discovery");
+
+ #endregion
+
+ #region Setting Members
+
+ this.hostname = hostname;
+ this.port = port;
+ this.config = config;
+ this.ClusterVersion = 0;
+ this.tries = tries;
+ this.delay = delay;
+
+ this.clusterLock = new Object();
+ this.endpointLock = new Object();
+ this.nodesLock = new Object();
+
+ #endregion
+
+ this.ResolveEndPoint();
+ this.GetNodeVersion();
+ }
+
+ #endregion
+
+ #region Poller Methods
+
+ ///
+ /// Used to start a poller that checks for changes in the cluster client configuration
+ ///
+ internal void StartPoller()
+ {
+ this.config.Pool.UpdateLocator(new List(new IPEndPoint[] { this.EndPoint }));
+ this.poller = new ConfigurationPoller(this.config);
+ this.poller.StartTimer();
+ }
+
+ ///
+ /// Used to start a poller that checks for changes in the cluster client configuration
+ ///
+ /// Time between pollings, in miliseconds
+ internal void StartPoller(int intervalDelay)
+ {
+ this.poller = new ConfigurationPoller(this.config, intervalDelay);
+ this.poller.StartTimer();
+ }
+
+ #endregion
+
+ #region Config Info
+
+ ///
+ /// Parses the string NodeConfig into a list of IPEndPoints for configuration
+ ///
+ /// A list of IPEndPoints for config to use
+ internal List GetEndPointList()
+ {
+ try
+ {
+ var endpoints = AddrUtil.HashEndPointList(this.GetNodeConfig());
+
+ lock (nodesLock)
+ {
+ this.nodes.Clear();
+
+ foreach (var point in endpoints)
+ {
+ this.nodes.Add(this.config.nodeFactory.CreateNode(point, this.config.SocketPool));
+ }
+ }
+
+ return endpoints;
+ }
+ catch (Exception ex)
+ {
+ throw ex;
+ }
+ }
+
+ ///
+ /// Gets the Node configuration from "config get cluster" if it's new or "get AmazonElastiCache:cluster" if it's older than
+ /// 1.4.14
+ ///
+ /// A string in the format "hostname1|ip1|port1 hostname2|ip2|port2 ..."
+ internal string GetNodeConfig()
+ {
+ var tries = this.tries;
+ var nodeVersion = this.GetNodeVersion();
+ var older = new Version("1.4.14");
+ var waiting = true;
+ string message = "";
+ string[] items = null;
+
+ IGetOperation command = nodeVersion.CompareTo(older) < 0 ?
+ command = new GetOperation("AmazonElastiCache:cluster") :
+ command = new ConfigGetOperation("cluster");
+
+ while (waiting && tries > 0)
+ {
+ tries--;
+ try
+ {
+ lock (nodesLock)
+ {
+ // This avoids timing out from requesting the config from the endpoint
+ foreach (var node in this.nodes.ToArray())
+ {
+ try
+ {
+ var result = node.Execute(command);
+
+ if (result.Success)
+ {
+ var configCommand = command as IConfigOperation;
+ items = Encoding.UTF8.GetString(configCommand.ConfigResult.Data.Array, configCommand.ConfigResult.Data.Offset, configCommand.ConfigResult.Data.Count).Split('\n');
+ waiting = false;
+ break;
+ }
+ else
+ {
+ message = result.Message;
+ }
+ }
+ catch (Exception ex)
+ {
+ message = ex.Message;
+ }
+ }
+ }
+
+ if (waiting)
+ System.Threading.Thread.Sleep(this.delay);
+
+ }
+ catch (Exception ex)
+ {
+ message = ex.Message;
+ System.Threading.Thread.Sleep(this.delay);
+ }
+ }
+
+ if (waiting)
+ {
+ throw new TimeoutException(String.Format("Could not get config of version " + this.NodeVersion.ToString() + ". Tries: {0} Delay: {1}. " + message, this.tries, this.delay));
+ }
+
+ lock (clusterLock)
+ {
+ if (this.ClusterVersion < Convert.ToInt32(items[0]))
+ this.ClusterVersion = Convert.ToInt32(items[0]);
+ }
+ return items[1];
+ }
+
+ ///
+ /// Finds the version of Memcached the Elasticache setup is running on
+ ///
+ /// Version of memcahced running on nodes
+ internal Version GetNodeVersion()
+ {
+ if (this.NodeVersion != null)
+ {
+ return this.NodeVersion;
+ }
+
+ if (!string.IsNullOrEmpty(this.Node.ToString()) && this.Node.ToString().Equals("TestingAWSInternal"))
+ {
+ this.NodeVersion = new Version("1.4.14");
+ return this.NodeVersion;
+ }
+
+ IStatsOperation statcommand = new Enyim.Caching.Memcached.Protocol.Text.StatsOperation(null);
+ var statresult = this.Node.Execute(statcommand);
+
+ string version;
+ if (statcommand.Result.TryGetValue("version", out version))
+ {
+ this.NodeVersion = new Version(version);
+ return this.NodeVersion;
+ }
+ else
+ {
+ log.Error("Could not call stats on Node endpoint");
+ throw new CommandNotSupportedException("The node does not have a version in stats.");
+ }
+ }
+
+ ///
+ /// Tries to resolve the endpoint ip, used if the connection fails
+ ///
+ /// The resolved endpoint as an ip and port
+ internal IPEndPoint ResolveEndPoint()
+ {
+ IPHostEntry entry = null;
+ var waiting = true;
+ var tryCount = this.tries;
+ string message = "";
+
+ while (tryCount > 0 && waiting)
+ {
+ try
+ {
+ tryCount--;
+ entry = Dns.GetHostEntry(hostname);
+ if (entry.AddressList.Length > 0)
+ {
+ waiting = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ message = ex.Message;
+ System.Threading.Thread.Sleep(this.delay);
+ }
+ }
+
+ if (waiting || entry == null)
+ {
+ log.Error("Could not resolve hostname to ip");
+ throw new TimeoutException(String.Format("Could not resolve hostname to Ip after trying the specified amount: {0}. " + message, this.tries));
+ }
+
+ lock (endpointLock)
+ {
+ this.EndPoint = new IPEndPoint(entry.AddressList[0], port);
+ }
+
+ lock (nodesLock)
+ {
+ try
+ {
+ this.Node.Dispose();
+ }
+ catch { }
+ this.Node = this.config.nodeFactory.CreateNode(this.EndPoint, this.config.SocketPool);
+ this.nodes.Clear();
+ this.nodes.Add(this.Node);
+ }
+ return this.EndPoint;
+ }
+
+ #endregion
+
+ ///
+ /// Stops the current poller
+ ///
+ public void Dispose()
+ {
+ if (this.poller != null)
+ this.poller.StopPolling();
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/ElastiCacheClusterConfig.cs b/Amazon.ElastiCacheCluster/ElastiCacheClusterConfig.cs
new file mode 100644
index 0000000..65f384f
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/ElastiCacheClusterConfig.cs
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Enyim.Caching.Memcached;
+using Enyim.Reflection;
+using Enyim.Caching.Memcached.Protocol.Binary;
+using Enyim.Caching.Configuration;
+using Amazon.ElastiCacheCluster.Pools;
+using Amazon.ElastiCacheCluster.Factories;
+using System.Configuration;
+
+namespace Amazon.ElastiCacheCluster
+{
+ ///
+ /// Configuration class for auto discovery
+ ///
+ public class ElastiCacheClusterConfig : IMemcachedClientConfiguration
+ {
+ // these are lazy initialized in the getters
+ private Type nodeLocator;
+ private ITranscoder transcoder;
+ private IMemcachedKeyTransformer keyTransformer;
+
+ internal ClusterConfigSettings setup;
+ internal AutoServerPool Pool;
+ internal IConfigNodeFactory nodeFactory;
+
+ ///
+ /// The node used to check the cluster's configuration
+ ///
+ public DiscoveryNode DiscoveryNode { get; private set; }
+
+ #region Constructors
+
+ ///
+ /// Initializes a MemcahcedClient config with auto discovery enabled from the app.config clusterclient section
+ ///
+ public ElastiCacheClusterConfig()
+ : this(null as ClusterConfigSettings) { }
+
+ ///
+ /// Initializes a MemcahcedClient config with auto discovery enabled from the app.config with the specified section
+ ///
+ /// The section to get config settings from
+ public ElastiCacheClusterConfig(string section)
+ : this(ConfigurationManager.GetSection(section) as ClusterConfigSettings) { }
+
+ ///
+ /// Initializes a MemcahcedClient config with auto discovery enabled
+ ///
+ /// The hostname of the cluster containing ".cfg."
+ /// The port to connect to for communication
+ public ElastiCacheClusterConfig(string hostname, int port)
+ : this(new ClusterConfigSettings(hostname, port)) { }
+
+ ///
+ /// Initializes a MemcahcedClient config with auto discovery enabled using the setup provided
+ ///
+ /// The setup to get conifg settings from
+ public ElastiCacheClusterConfig(ClusterConfigSettings setup)
+ {
+ if (setup == null)
+ {
+ try
+ {
+ setup = ConfigurationManager.GetSection("clusterclient") as ClusterConfigSettings;
+ if (setup == null)
+ {
+ throw new ConfigurationErrorsException("Could not instantiate from app.config, setup was null");
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigurationErrorsException("Could not instantiate from app.config\n" + ex.Message);
+ }
+ }
+
+ if (setup.ClusterEndPoint == null)
+ throw new ArgumentException("Cluster Settings are null");
+ if (String.IsNullOrEmpty(setup.ClusterEndPoint.HostName))
+ throw new ArgumentNullException("hostname");
+ if (setup.ClusterEndPoint.Port <= 0)
+ throw new ArgumentException("Port cannot be 0 or less");
+
+ this.setup = setup;
+ this.Servers = new List();
+
+ this.Protocol = setup.Protocol;
+
+ if (setup.KeyTransformer == null)
+ this.KeyTransformer = new DefaultKeyTransformer();
+ else
+ this.KeyTransformer = setup.KeyTransformer.CreateInstance() ?? new DefaultKeyTransformer();
+
+ this.SocketPool = (ISocketPoolConfiguration)setup.SocketPool ?? new SocketPoolConfiguration();
+ this.Authentication = (IAuthenticationConfiguration)setup.Authentication ?? new AuthenticationConfiguration();
+
+ this.nodeFactory = setup.NodeFactory ?? new DefaultConfigNodeFactory();
+
+ if (setup.ClusterEndPoint.HostName.IndexOf(".cfg", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ if (setup.ClusterNode != null)
+ {
+ var _tries = setup.ClusterNode.NodeTries > 0 ? setup.ClusterNode.NodeTries : DiscoveryNode.DEFAULT_TRY_COUNT;
+ var _delay = setup.ClusterNode.NodeDelay >= 0 ? setup.ClusterNode.NodeDelay : DiscoveryNode.DEFAULT_TRY_DELAY;
+ this.DiscoveryNode = new DiscoveryNode(this, setup.ClusterEndPoint.HostName, setup.ClusterEndPoint.Port, _tries, _delay);
+ }
+ else
+ this.DiscoveryNode = new DiscoveryNode(this, setup.ClusterEndPoint.HostName, setup.ClusterEndPoint.Port);
+ }
+ else
+ {
+ throw new ArgumentException("The provided endpoint does not support auto discovery");
+ }
+ }
+
+ #endregion
+
+ #region Members
+
+ ///
+ /// Gets a list of each representing a Memcached server in the pool.
+ ///
+ public IList Servers { get; private set; }
+
+ ///
+ /// Gets the configuration of the socket pool.
+ ///
+ public ISocketPoolConfiguration SocketPool { get; private set; }
+
+ ///
+ /// Gets the authentication settings.
+ ///
+ public IAuthenticationConfiguration Authentication { get; set; }
+
+ ///
+ /// Gets or sets the which will be used to convert item keys for Memcached.
+ ///
+ public IMemcachedKeyTransformer KeyTransformer
+ {
+ get { return this.keyTransformer ?? (this.keyTransformer = new DefaultKeyTransformer()); }
+ set { this.keyTransformer = value; }
+ }
+
+ ///
+ /// Gets or sets the Type of the which will be used to assign items to Memcached nodes.
+ ///
+ /// If both and are assigned then the latter takes precedence.
+ public Type NodeLocator
+ {
+ get { return this.nodeLocator; }
+ set
+ {
+ ConfigurationHelper.CheckForInterface(value, typeof(IMemcachedNodeLocator));
+ this.nodeLocator = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the NodeLocatorFactory instance which will be used to create a new IMemcachedNodeLocator instances.
+ ///
+ /// If both and are assigned then the latter takes precedence.
+ public IProviderFactory NodeLocatorFactory { get; set; }
+
+ ///
+ /// Gets or sets the which will be used serialize or deserialize items.
+ ///
+ public ITranscoder Transcoder
+ {
+ get { return this.transcoder ?? (this.transcoder = new DefaultTranscoder()); }
+ set { this.transcoder = value; }
+ }
+
+ ///
+ /// Gets or sets the instance which will be used monitor the performance of the client.
+ ///
+ public IPerformanceMonitor PerformanceMonitor { get; set; }
+
+ ///
+ /// Gets or sets the type of the communication between client and server.
+ ///
+ public MemcachedProtocol Protocol { get; set; }
+
+ #endregion
+
+ #region [ interface ]
+
+ IList IMemcachedClientConfiguration.Servers
+ {
+ get { return this.Servers; }
+ }
+
+ ISocketPoolConfiguration IMemcachedClientConfiguration.SocketPool
+ {
+ get { return this.SocketPool; }
+ }
+
+ IAuthenticationConfiguration IMemcachedClientConfiguration.Authentication
+ {
+ get { return this.Authentication; }
+ }
+
+ IMemcachedKeyTransformer IMemcachedClientConfiguration.CreateKeyTransformer()
+ {
+ return this.KeyTransformer;
+ }
+
+ IMemcachedNodeLocator IMemcachedClientConfiguration.CreateNodeLocator()
+ {
+ var f = this.NodeLocatorFactory;
+ if (f != null) return f.Create();
+
+ return this.NodeLocator == null
+ ? new DefaultNodeLocator()
+ : (IMemcachedNodeLocator)FastActivator.Create(this.NodeLocator);
+ }
+
+ ITranscoder IMemcachedClientConfiguration.CreateTranscoder()
+ {
+ return this.Transcoder;
+ }
+
+ IServerPool IMemcachedClientConfiguration.CreatePool()
+ {
+ switch (this.Protocol)
+ {
+ case MemcachedProtocol.Text:
+ this.Pool = new AutoServerPool(this, new Enyim.Caching.Memcached.Protocol.Text.TextOperationFactory());
+ break;
+ case MemcachedProtocol.Binary:
+ this.Pool = new AutoBinaryPool(this);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("Unknown protocol: " + (int)this.Protocol);
+ }
+
+ return this.Pool;
+ }
+
+ IPerformanceMonitor IMemcachedClientConfiguration.CreatePerformanceMonitor()
+ {
+ return this.PerformanceMonitor;
+ }
+
+ #endregion
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Factories/DefaultConfigNodeFactory.cs b/Amazon.ElastiCacheCluster/Factories/DefaultConfigNodeFactory.cs
new file mode 100644
index 0000000..055ce72
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Factories/DefaultConfigNodeFactory.cs
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using Enyim.Caching.Configuration;
+using Enyim.Caching.Memcached;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Amazon.ElastiCacheCluster.Factories
+{
+ internal class DefaultConfigNodeFactory : IConfigNodeFactory
+ {
+ public IMemcachedNode CreateNode(IPEndPoint endpoint, ISocketPoolConfiguration config)
+ {
+ return new MemcachedNode(endpoint, config);
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Factories/IConfigNodeFactory.cs b/Amazon.ElastiCacheCluster/Factories/IConfigNodeFactory.cs
new file mode 100644
index 0000000..421f855
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Factories/IConfigNodeFactory.cs
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using Enyim.Caching.Configuration;
+using Enyim.Caching.Memcached;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Amazon.ElastiCacheCluster.Factories
+{
+ public interface IConfigNodeFactory
+ {
+ IMemcachedNode CreateNode(IPEndPoint endpoint, ISocketPoolConfiguration config);
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Helpers/AddrUtil.cs b/Amazon.ElastiCacheCluster/Helpers/AddrUtil.cs
new file mode 100644
index 0000000..7de2d27
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Helpers/AddrUtil.cs
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Amazon.ElastiCacheCluster.Helpers
+{
+ ///
+ /// A class used to parse configs of Auto Discovery
+ ///
+ internal static class AddrUtil
+ {
+ ///
+ /// Creates a list of endpoints from a string returned in the config request
+ ///
+ /// Format: host1|ip1|port1 host2|ip2|port2 ...
+ /// List of the endpoints parsed to ip:port endpoints for connections
+ public static List HashEndPointList(string endpoints)
+ {
+ List list = new List();
+ foreach (var node in endpoints.Split(' '))
+ {
+ string[] parts = node.Split('|');
+ IPAddress ip;
+ if (IPAddress.TryParse(parts[1], out ip))
+ {
+ list.Add(new IPEndPoint(ip, Convert.ToInt32(parts[2])));
+ }
+ }
+ return list;
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Helpers/TextSocketHelper.cs b/Amazon.ElastiCacheCluster/Helpers/TextSocketHelper.cs
new file mode 100644
index 0000000..aaa0bbe
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Helpers/TextSocketHelper.cs
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.IO;
+using System.Text;
+using System.Collections.Generic;
+using Enyim.Caching.Memcached;
+
+namespace Amazon.ElastiCacheCluster.Helpers
+{
+ internal static class TextSocketHelper
+ {
+ private const string GenericErrorResponse = "ERROR";
+ private const string ClientErrorResponse = "CLIENT_ERROR ";
+ private const string ServerErrorResponse = "SERVER_ERROR ";
+ private const int ErrorResponseLength = 13;
+
+ ///
+ /// Signifies the string that is used to end a command
+ ///
+ public const string CommandTerminator = "\r\n";
+
+ private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(TextSocketHelper));
+
+ ///
+ /// Reads the response of the server.
+ ///
+ /// The data sent by the memcached server.
+ /// The server did not sent a response or an empty line was returned.
+ /// The server did not specified any reason just returned the string ERROR. - or - The server returned a SERVER_ERROR, in this case the Message of the exception is the message returned by the server.
+ /// The server did not recognize the request sent by the client. The Message of the exception is the message returned by the server.
+ internal static string ReadResponse(PooledSocket socket)
+ {
+ string response = TextSocketHelper.ReadLine(socket);
+
+ if (log.IsDebugEnabled)
+ log.Debug("Received response: " + response);
+
+ if (String.IsNullOrEmpty(response))
+ throw new MemcachedClientException("Empty response received.");
+
+ if (String.Compare(response, GenericErrorResponse, StringComparison.Ordinal) == 0)
+ throw new NotSupportedException("Operation is not supported by the server or the request was malformed. If the latter please report the bug to the developers.");
+
+ if (response.Length >= ErrorResponseLength)
+ {
+ if (String.Compare(response, 0, ClientErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
+ {
+ throw new MemcachedClientException(response.Remove(0, ErrorResponseLength));
+ }
+ else if (String.Compare(response, 0, ServerErrorResponse, 0, ErrorResponseLength, StringComparison.Ordinal) == 0)
+ {
+ throw new MemcachedException(response.Remove(0, ErrorResponseLength));
+ }
+ }
+
+ return response;
+ }
+
+
+ ///
+ /// Reads a line from the socket. A line is terninated by \r\n.
+ ///
+ ///
+ private static string ReadLine(PooledSocket socket)
+ {
+ MemoryStream ms = new MemoryStream(50);
+
+ bool gotR = false;
+ //byte[] buffer = new byte[1];
+
+ int data;
+
+ while (true)
+ {
+ data = socket.ReadByte();
+
+ if (data == 13)
+ {
+ gotR = true;
+ continue;
+ }
+
+ if (gotR)
+ {
+ if (data == 10)
+ break;
+
+ ms.WriteByte(13);
+
+ gotR = false;
+ }
+
+ ms.WriteByte((byte)data);
+ }
+
+ string retval = Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)ms.Length);
+
+ if (log.IsDebugEnabled)
+ log.Debug("ReadLine: " + retval);
+
+ return retval;
+ }
+
+ ///
+ /// Gets the bytes representing the specified command. returned buffer can be used to streamline multiple writes into one Write on the Socket
+ /// using the
+ ///
+ /// The command to be converted.
+ /// The buffer containing the bytes representing the command. The command must be terminated by \r\n.
+ /// The Nagle algorithm is disabled on the socket to speed things up, so it's recommended to convert a command into a buffer
+ /// and use the to send the command and the additional buffers in one transaction.
+ internal unsafe static IList> GetCommandBuffer(string value)
+ {
+ var data = new ArraySegment(Encoding.ASCII.GetBytes(value));
+
+ return new ArraySegment[] { data };
+ }
+
+ ///
+ /// Gets the bytes representing the specified command. Returns buffer in the provided list.
+ ///
+ /// The command to be converted.
+ /// The list to store the buffer in.
+ /// The buffer containing the bytes representing the command. The command must be terminated by \r\n.
+ internal unsafe static IList> GetCommandBuffer(string value, IList> list)
+ {
+ var data = new ArraySegment(Encoding.ASCII.GetBytes(value));
+
+ list.Add(data);
+
+ return list;
+ }
+
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Operations/ConfigGetOperation.cs b/Amazon.ElastiCacheCluster/Operations/ConfigGetOperation.cs
new file mode 100644
index 0000000..1468512
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Operations/ConfigGetOperation.cs
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using Enyim.Caching.Memcached.Protocol;
+using Enyim.Caching.Memcached.Protocol.Text;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Enyim.Caching.Memcached.Results;
+using Enyim.Caching.Memcached.Results.Extensions;
+using Enyim.Caching.Memcached;
+using Amazon.ElastiCacheCluster.Helpers;
+
+namespace Amazon.ElastiCacheCluster.Operations
+{
+ ///
+ /// Used to get auto discovery information from ElastiCache endpoints version 1.4.14 or higher
+ ///
+ internal class ConfigGetOperation : SingleItemOperation, IGetOperation, IConfigOperation
+ {
+ private CacheItem result;
+
+ ///
+ /// Creates a config get for ElastiCache
+ ///
+ ///
+ public ConfigGetOperation(string key) : base(key) { }
+
+ protected override IList> GetBuffer()
+ {
+ var command = "config get " + this.Key + TextSocketHelper.CommandTerminator;
+
+ return TextSocketHelper.GetCommandBuffer(command);
+ }
+
+ protected override Enyim.Caching.Memcached.Results.IOperationResult ReadResponse(PooledSocket socket)
+ {
+ string description = TextSocketHelper.ReadResponse(socket);
+
+ if (String.Compare(description, "END", StringComparison.Ordinal) == 0)
+ return null;
+
+ if (description.Length < 7 || String.Compare(description, 0, "CONFIG ", 0, 7, StringComparison.Ordinal) != 0)
+ throw new MemcachedClientException("No CONFIG response received.\r\n" + description);
+
+ string[] parts = description.Split(' ');
+
+ /****** Format ********
+ *
+ * CONFIG
+ * 0 1 2 3
+ *
+ */
+
+ ushort flags = UInt16.Parse(parts[2], CultureInfo.InvariantCulture);
+ int length = Int32.Parse(parts[3], CultureInfo.InvariantCulture);
+
+ byte[] allNodes = new byte[length];
+ byte[] eod = new byte[2];
+
+ socket.Read(allNodes, 0, length);
+ socket.Read(eod, 0, 2); // data is terminated by \r\n
+
+ this.result = new CacheItem(flags, new ArraySegment(allNodes, 0, length));
+ this.ConfigResult = this.result;
+
+ string response = TextSocketHelper.ReadResponse(socket);
+
+ if (String.Compare(response, "END", StringComparison.Ordinal) != 0)
+ throw new MemcachedClientException("No END was received.");
+
+ var result = new TextOperationResult();
+ return result.Pass();
+ }
+
+ protected override bool ReadResponseAsync(PooledSocket socket, Action next)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ ///
+ /// The CacheItem result of a "config get *key*" request
+ ///
+ public CacheItem Result
+ {
+ get { return result; }
+ }
+
+ public CacheItem ConfigResult { get; set; }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Operations/GetHelper.cs b/Amazon.ElastiCacheCluster/Operations/GetHelper.cs
new file mode 100644
index 0000000..0f2dfe7
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Operations/GetHelper.cs
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using Enyim.Caching.Memcached;
+using Amazon.ElastiCacheCluster.Helpers;
+using System;
+using System.Globalization;
+
+namespace Amazon.ElastiCacheCluster.Operations
+{
+ internal static class GetHelper
+ {
+ private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(GetHelper));
+
+ public static void FinishCurrent(PooledSocket socket)
+ {
+ string response = TextSocketHelper.ReadResponse(socket);
+
+ if (String.Compare(response, "END", StringComparison.Ordinal) != 0)
+ throw new MemcachedClientException("No END was received.");
+ }
+
+ public static GetResponse ReadItem(PooledSocket socket)
+ {
+ string description = TextSocketHelper.ReadResponse(socket);
+
+ if (String.Compare(description, "END", StringComparison.Ordinal) == 0)
+ return null;
+
+ if (description.Length < 6 || String.Compare(description, 0, "VALUE ", 0, 6, StringComparison.Ordinal) != 0)
+ throw new MemcachedClientException("No VALUE response received.\r\n" + description);
+
+ ulong cas = 0;
+ string[] parts = description.Split(' ');
+
+ // response is:
+ // VALUE []
+ // 0 1 2 3 4
+ //
+ // cas only exists in 1.2.4+
+ //
+ if (parts.Length == 5)
+ {
+ if (!UInt64.TryParse(parts[4], out cas))
+ throw new MemcachedClientException("Invalid CAS VALUE received.");
+
+ }
+ else if (parts.Length < 4)
+ {
+ throw new MemcachedClientException("Invalid VALUE response received: " + description);
+ }
+
+ ushort flags = UInt16.Parse(parts[2], CultureInfo.InvariantCulture);
+ int length = Int32.Parse(parts[3], CultureInfo.InvariantCulture);
+
+ byte[] allData = new byte[length];
+ byte[] eod = new byte[2];
+
+ socket.Read(allData, 0, length);
+ socket.Read(eod, 0, 2); // data is terminated by \r\n
+
+ GetResponse retval = new GetResponse(parts[1], flags, cas, allData);
+
+ if (log.IsDebugEnabled)
+ log.DebugFormat("Received value. Data type: {0}, size: {1}.", retval.Item.Flags, retval.Item.Data.Count);
+
+ return retval;
+ }
+ }
+
+ #region [ T:GetResponse ]
+ internal class GetResponse
+ {
+ private GetResponse() { }
+ public GetResponse(string key, ushort flags, ulong casValue, byte[] data) : this(key, flags, casValue, data, 0, data.Length) { }
+
+ public GetResponse(string key, ushort flags, ulong casValue, byte[] data, int offset, int count)
+ {
+ this.Key = key;
+ this.CasValue = casValue;
+
+ this.Item = new CacheItem(flags, new ArraySegment(data, offset, count));
+ }
+
+ public readonly string Key;
+ public readonly ulong CasValue;
+ public readonly CacheItem Item;
+ }
+ #endregion
+
+}
+
+#region [ License information ]
+/* ************************************************************
+ *
+ * Copyright (c) 2010 Attila Kiskó, enyim.com
+ *
+ * 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.
+ *
+ * ************************************************************/
+#endregion
diff --git a/Amazon.ElastiCacheCluster/Operations/GetOperation.cs b/Amazon.ElastiCacheCluster/Operations/GetOperation.cs
new file mode 100644
index 0000000..442dd31
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Operations/GetOperation.cs
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using Enyim.Caching.Memcached;
+using Enyim.Caching.Memcached.Protocol;
+using Enyim.Caching.Memcached.Results;
+using Enyim.Caching.Memcached.Results.Extensions;
+using Amazon.ElastiCacheCluster.Helpers;
+
+namespace Amazon.ElastiCacheCluster.Operations
+{
+ internal class GetOperation : SingleItemOperation, IGetOperation, IConfigOperation
+ {
+ private CacheItem result;
+
+ internal GetOperation(string key) : base(key) { }
+
+ protected override System.Collections.Generic.IList> GetBuffer()
+ {
+ var command = "gets " + this.Key + TextSocketHelper.CommandTerminator;
+
+ return TextSocketHelper.GetCommandBuffer(command);
+ }
+
+ protected override IOperationResult ReadResponse(PooledSocket socket)
+ {
+ GetResponse r = GetHelper.ReadItem(socket);
+ var result = new TextOperationResult();
+
+ if (r == null) return result.Fail("Failed to read response");
+
+ this.result = r.Item;
+ this.ConfigResult = r.Item;
+
+ this.Cas = r.CasValue;
+
+ GetHelper.FinishCurrent(socket);
+
+ return result.Pass();
+ }
+
+ CacheItem IGetOperation.Result
+ {
+ get { return this.result; }
+ }
+
+ protected override bool ReadResponseAsync(PooledSocket socket, System.Action next)
+ {
+ throw new System.NotSupportedException();
+ }
+
+ public CacheItem ConfigResult { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Amazon.ElastiCacheCluster/Operations/IConfigOperation.cs b/Amazon.ElastiCacheCluster/Operations/IConfigOperation.cs
new file mode 100644
index 0000000..171bf75
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Operations/IConfigOperation.cs
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Enyim.Caching.Memcached;
+
+namespace Amazon.ElastiCacheCluster.Operations
+{
+ public interface IConfigOperation
+ {
+ CacheItem ConfigResult { get; set; }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Pools/AutoBinaryPool.cs b/Amazon.ElastiCacheCluster/Pools/AutoBinaryPool.cs
new file mode 100644
index 0000000..e4e427c
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Pools/AutoBinaryPool.cs
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Threading;
+using Enyim.Caching.Configuration;
+using Enyim.Collections;
+using System.Security;
+using Enyim.Caching.Memcached;
+using Enyim.Caching.Memcached.Protocol.Binary;
+
+namespace Amazon.ElastiCacheCluster.Pools
+{
+ ///
+ /// Server pool implementing the binary protocol.
+ ///
+ internal class AutoBinaryPool : AutoServerPool
+ {
+ ISaslAuthenticationProvider authenticationProvider;
+ IMemcachedClientConfiguration configuration;
+
+ public AutoBinaryPool(IMemcachedClientConfiguration configuration)
+ : base(configuration, new BinaryOperationFactory())
+ {
+ this.authenticationProvider = GetProvider(configuration);
+ this.configuration = configuration;
+ }
+
+ protected override IMemcachedNode CreateNode(IPEndPoint endpoint)
+ {
+ if (endpoint == null)
+ throw new ArgumentNullException("endpoint");
+ return new BinaryNode(endpoint, this.configuration.SocketPool, this.authenticationProvider);
+ }
+
+ private static ISaslAuthenticationProvider GetProvider(IMemcachedClientConfiguration configuration)
+ {
+ // create&initialize the authenticator, if any
+ // we'll use this single instance everywhere, so it must be thread safe
+ IAuthenticationConfiguration auth = configuration.Authentication;
+ if (auth != null)
+ {
+ Type t = auth.Type;
+ var provider = (t == null) ? null : Enyim.Reflection.FastActivator.Create(t) as ISaslAuthenticationProvider;
+
+ if (provider != null)
+ {
+ provider.Initialize(auth.Parameters);
+ return provider;
+ }
+ }
+
+ return null;
+ }
+
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Pools/AutoServerPool.cs b/Amazon.ElastiCacheCluster/Pools/AutoServerPool.cs
new file mode 100644
index 0000000..dcb0d6b
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Pools/AutoServerPool.cs
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Portions copyright 2010 Attila Kiskó, enyim.com. Please see LICENSE.txt
+ * for applicable license terms and NOTICE.txt for applicable notices.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Net;
+using System.Threading;
+using Enyim.Caching.Configuration;
+using Enyim.Caching.Memcached;
+
+namespace Amazon.ElastiCacheCluster.Pools
+{
+ ///
+ /// A server pool just like the default that enables safely changing the servers of the locator
+ ///
+ internal class AutoServerPool : IServerPool, IDisposable
+ {
+ private static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(DefaultServerPool));
+
+ private IMemcachedNode[] allNodes;
+
+ private IMemcachedClientConfiguration configuration;
+ private IOperationFactory factory;
+ internal IMemcachedNodeLocator nodeLocator;
+
+ private object DeadSync = new Object();
+ private System.Threading.Timer resurrectTimer;
+ private bool isTimerActive;
+ private long deadTimeoutMsec;
+ private bool isDisposed;
+ private event Action nodeFailed;
+
+ ///
+ /// Creates a server pool for auto discovery
+ ///
+ /// The client configuration using the pool
+ /// The factory used to create operations on demand
+ public AutoServerPool(IMemcachedClientConfiguration configuration, IOperationFactory opFactory)
+ {
+ if (configuration == null) throw new ArgumentNullException("socketConfig");
+ if (opFactory == null) throw new ArgumentNullException("opFactory");
+
+ this.configuration = configuration;
+ this.factory = opFactory;
+
+ this.deadTimeoutMsec = (long)this.configuration.SocketPool.DeadTimeout.TotalMilliseconds;
+ }
+
+ ~AutoServerPool()
+ {
+ try { ((IDisposable)this).Dispose(); }
+ catch { }
+ }
+
+ protected virtual IMemcachedNode CreateNode(IPEndPoint endpoint)
+ {
+ return new MemcachedNode(endpoint, this.configuration.SocketPool);
+ }
+
+ private void rezCallback(object state)
+ {
+ var isDebug = log.IsDebugEnabled;
+
+ if (isDebug) log.Debug("Checking the dead servers.");
+
+ // how this works:
+ // 1. timer is created but suspended
+ // 2. Locate encounters a dead server, so it starts the timer which will trigger after deadTimeout has elapsed
+ // 3. if another server goes down before the timer is triggered, nothing happens in Locate (isRunning == true).
+ // however that server will be inspected sooner than Dead Timeout.
+ // S1 died S2 died dead timeout
+ // |----*--------*------------*-
+ // | |
+ // timer start both servers are checked here
+ // 4. we iterate all the servers and record it in another list
+ // 5. if we found a dead server whihc responds to Ping(), the locator will be reinitialized
+ // 6. if at least one server is still down (Ping() == false), we restart the timer
+ // 7. if all servers are up, we set isRunning to false, so the timer is suspended
+ // 8. GOTO 2
+ lock (this.DeadSync)
+ {
+ if (this.isDisposed)
+ {
+ if (log.IsWarnEnabled) log.Warn("IsAlive timer was triggered but the pool is already disposed. Ignoring.");
+
+ return;
+ }
+
+ var nodes = this.allNodes;
+ var aliveList = new List(nodes.Length);
+ var changed = false;
+ var deadCount = 0;
+
+ for (var i = 0; i < nodes.Length; i++)
+ {
+ var n = nodes[i];
+ if (n.IsAlive)
+ {
+ if (isDebug) log.DebugFormat("Alive: {0}", n.EndPoint);
+
+ aliveList.Add(n);
+ }
+ else
+ {
+ if (isDebug) log.DebugFormat("Dead: {0}", n.EndPoint);
+
+ if (n.Ping())
+ {
+ changed = true;
+ aliveList.Add(n);
+
+ if (isDebug) log.Debug("Ping ok.");
+ }
+ else
+ {
+ if (isDebug) log.Debug("Still dead.");
+
+ deadCount++;
+ }
+ }
+ }
+
+ // reinit the locator
+ if (changed)
+ {
+ if (isDebug) log.Debug("Reinitializing the locator.");
+
+ this.nodeLocator.Initialize(aliveList);
+ }
+
+ // stop or restart the timer
+ if (deadCount == 0)
+ {
+ if (isDebug) log.Debug("deadCount == 0, stopping the timer.");
+
+ this.isTimerActive = false;
+ }
+ else
+ {
+ if (isDebug) log.DebugFormat("deadCount == {0}, starting the timer.", deadCount);
+
+ this.resurrectTimer.Change(this.deadTimeoutMsec, Timeout.Infinite);
+ }
+ }
+ }
+
+ private void NodeFail(IMemcachedNode node)
+ {
+ var isDebug = log.IsDebugEnabled;
+ if (isDebug) log.DebugFormat("Node {0} is dead.", node.EndPoint);
+
+ // the timer is stopped until we encounter the first dead server
+ // when we have one, we trigger it and it will run after DeadTimeout has elapsed
+ lock (this.DeadSync)
+ {
+ if (this.isDisposed)
+ {
+ if (log.IsWarnEnabled) log.Warn("Got a node fail but the pool is already disposed. Ignoring.");
+
+ return;
+ }
+
+ // bubble up the fail event to the client
+ var fail = this.nodeFailed;
+ if (fail != null)
+ fail(node);
+
+ // re-initialize the locator
+ var newLocator = this.configuration.CreateNodeLocator();
+ newLocator.Initialize(allNodes.Where(n => n.IsAlive).ToArray());
+ Interlocked.Exchange(ref this.nodeLocator, newLocator);
+
+ // the timer is stopped until we encounter the first dead server
+ // when we have one, we trigger it and it will run after DeadTimeout has elapsed
+ if (!this.isTimerActive)
+ {
+ if (isDebug) log.Debug("Starting the recovery timer.");
+
+ if (this.resurrectTimer == null)
+ this.resurrectTimer = new Timer(this.rezCallback, null, this.deadTimeoutMsec, Timeout.Infinite);
+ else
+ this.resurrectTimer.Change(this.deadTimeoutMsec, Timeout.Infinite);
+
+ this.isTimerActive = true;
+
+ if (isDebug) log.Debug("Timer started.");
+ }
+ }
+ }
+
+ #region [ IServerPool ]
+
+ IMemcachedNode IServerPool.Locate(string key)
+ {
+ var node = this.nodeLocator.Locate(key);
+
+ return node;
+ }
+
+ IOperationFactory IServerPool.OperationFactory
+ {
+ get { return this.factory; }
+ }
+
+ IEnumerable IServerPool.GetWorkingNodes()
+ {
+ return this.nodeLocator.GetWorkingNodes();
+ }
+
+ void IServerPool.Start()
+ {
+ this.allNodes = this.configuration.Servers.
+ Select(ip =>
+ {
+ var node = this.CreateNode(ip);
+ node.Failed += this.NodeFail;
+
+ return node;
+ }).
+ ToArray();
+
+ // initialize the locator
+ var locator = this.configuration.CreateNodeLocator();
+ locator.Initialize(allNodes);
+
+ this.nodeLocator = locator;
+
+ var config = this.configuration as ElastiCacheClusterConfig;
+ if (config.setup.ClusterPoller.IntervalDelay < 0)
+ config.DiscoveryNode.StartPoller();
+ else
+ config.DiscoveryNode.StartPoller(config.setup.ClusterPoller.IntervalDelay);
+ }
+
+ event Action IServerPool.NodeFailed
+ {
+ add { this.nodeFailed += value; }
+ remove { this.nodeFailed -= value; }
+ }
+
+ #endregion
+ #region [ IDisposable ]
+
+ void IDisposable.Dispose()
+ {
+ GC.SuppressFinalize(this);
+
+ lock (this.DeadSync)
+ {
+ if (this.isDisposed) return;
+
+ this.isDisposed = true;
+
+ // dispose the locator first, maybe it wants to access
+ // the nodes one last time
+ var nd = this.nodeLocator as IDisposable;
+ if (nd != null)
+ try { nd.Dispose(); }
+ catch (Exception e) { if (log.IsErrorEnabled) log.Error(e); }
+
+ this.nodeLocator = null;
+
+ for (var i = 0; i < this.allNodes.Length; i++)
+ try { this.allNodes[i].Dispose(); }
+ catch (Exception e) { if (log.IsErrorEnabled) log.Error(e); }
+
+ // stop the timer
+ if (this.resurrectTimer != null)
+ using (this.resurrectTimer)
+ this.resurrectTimer.Change(Timeout.Infinite, Timeout.Infinite);
+
+ this.allNodes = null;
+ this.resurrectTimer = null;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Used to update the servers for Auto discovery
+ ///
+ /// The connections to all the cluster nodes
+ public void UpdateLocator(List endPoints)
+ {
+ var newLocator = this.configuration.CreateNodeLocator();
+ newLocator.Initialize(endPoints.Select(ip =>
+ {
+ var node = this.CreateNode(ip);
+ node.Failed += this.NodeFail;
+
+ return node;
+ }).ToArray());
+
+ Interlocked.Exchange(ref this.nodeLocator, newLocator);
+ }
+ }
+}
diff --git a/Amazon.ElastiCacheCluster/Properties/AssemblyInfo.cs b/Amazon.ElastiCacheCluster/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9f887e0
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright 2008-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file.
+* This file 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.
+ * *****************************************************************************
+ * __ _ _ ___
+ * ( )( \/\/ )/ __)
+ * /__\ \ / \__ \
+ * (_)(_) \/\/ (___/
+ *
+ *
+ */
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AmazonElastiCacheClusterConfig")]
+[assembly: AssemblyDescription("A configuration object to enable auto discovery in Enyim for ElastiCache")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Amazon.com, Inc")]
+[assembly: AssemblyProduct("AmazonElastiCacheClusterConfig")]
+[assembly: AssemblyCopyright("Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("526dcd43-9e27-40fe-aed6-5d72ad69b7cd")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0")]
+[assembly: AssemblyFileVersion("1.0.0")]
diff --git a/Amazon.ElastiCacheCluster/awskey.snk b/Amazon.ElastiCacheCluster/awskey.snk
new file mode 100644
index 0000000..bf0e83e
Binary files /dev/null and b/Amazon.ElastiCacheCluster/awskey.snk differ
diff --git a/Amazon.ElastiCacheCluster/packages.config b/Amazon.ElastiCacheCluster/packages.config
new file mode 100644
index 0000000..cda96ab
--- /dev/null
+++ b/Amazon.ElastiCacheCluster/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/ClusterClientAppTester/App.config b/ClusterClientAppTester/App.config
new file mode 100644
index 0000000..0e1c3c7
--- /dev/null
+++ b/ClusterClientAppTester/App.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ClusterClientAppTester/ClusterClientAppTester.csproj b/ClusterClientAppTester/ClusterClientAppTester.csproj
new file mode 100644
index 0000000..8db2a2b
--- /dev/null
+++ b/ClusterClientAppTester/ClusterClientAppTester.csproj
@@ -0,0 +1,100 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {085886E5-3D56-4E41-941A-611EDD2A31D4}
+ WinExe
+ Properties
+ ClusterClientAppTester
+ ClusterClientAppTester
+ v3.5
+ 512
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\packages\EnyimMemcached.2.12\lib\net35\Enyim.Caching.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+ True
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+ {1255ebd3-0340-4af6-a5f2-3d97d8709546}
+ Amazon.ElastiCacheCluster
+
+
+
+
+
\ No newline at end of file
diff --git a/ClusterClientAppTester/Form1.Designer.cs b/ClusterClientAppTester/Form1.Designer.cs
new file mode 100644
index 0000000..78cb7c8
--- /dev/null
+++ b/ClusterClientAppTester/Form1.Designer.cs
@@ -0,0 +1,395 @@
+namespace ClusterClientAppTester
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.ButtonOlder = new System.Windows.Forms.Button();
+ this.TextOlder = new System.Windows.Forms.TextBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.TextPort = new System.Windows.Forms.TextBox();
+ this.label1 = new System.Windows.Forms.Label();
+ this.ButtonInstantiate = new System.Windows.Forms.Button();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.label4 = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.TextValue = new System.Windows.Forms.TextBox();
+ this.TextKey = new System.Windows.Forms.TextBox();
+ this.ButtonAdd = new System.Windows.Forms.Button();
+ this.groupBox3 = new System.Windows.Forms.GroupBox();
+ this.LabelValue = new System.Windows.Forms.Label();
+ this.label6 = new System.Windows.Forms.Label();
+ this.TextGetKey = new System.Windows.Forms.TextBox();
+ this.label5 = new System.Windows.Forms.Label();
+ this.ButtonGet = new System.Windows.Forms.Button();
+ this.label7 = new System.Windows.Forms.Label();
+ this.LabelStatus = new System.Windows.Forms.Label();
+ this.ProgressBarStatus = new System.Windows.Forms.ProgressBar();
+ this.ButtonExit = new System.Windows.Forms.Button();
+ this.ProgressPoller = new System.Windows.Forms.ProgressBar();
+ this.label8 = new System.Windows.Forms.Label();
+ this.groupBox4 = new System.Windows.Forms.GroupBox();
+ this.LabelVersion = new System.Windows.Forms.Label();
+ this.TimerPoller = new System.Windows.Forms.Timer(this.components);
+ this.groupBox1.SuspendLayout();
+ this.groupBox2.SuspendLayout();
+ this.groupBox3.SuspendLayout();
+ this.groupBox4.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.ButtonOlder);
+ this.groupBox1.Controls.Add(this.TextOlder);
+ this.groupBox1.Controls.Add(this.label2);
+ this.groupBox1.Controls.Add(this.TextPort);
+ this.groupBox1.Controls.Add(this.label1);
+ this.groupBox1.Controls.Add(this.ButtonInstantiate);
+ this.groupBox1.Location = new System.Drawing.Point(12, 12);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(317, 125);
+ this.groupBox1.TabIndex = 0;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Connection Tests";
+ //
+ // ButtonOlder
+ //
+ this.ButtonOlder.Location = new System.Drawing.Point(159, 85);
+ this.ButtonOlder.Name = "ButtonOlder";
+ this.ButtonOlder.Size = new System.Drawing.Size(133, 23);
+ this.ButtonOlder.TabIndex = 7;
+ this.ButtonOlder.Text = "Instantiate From Params";
+ this.ButtonOlder.UseVisualStyleBackColor = true;
+ this.ButtonOlder.Click += new System.EventHandler(this.ButtonOlder_Click);
+ //
+ // TextOlder
+ //
+ this.TextOlder.Location = new System.Drawing.Point(6, 48);
+ this.TextOlder.Name = "TextOlder";
+ this.TextOlder.Size = new System.Drawing.Size(172, 20);
+ this.TextOlder.TabIndex = 6;
+ this.TextOlder.Text = "versiontesting.4mpstz.cfg.use1.cache.amazonaws.com";
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(199, 29);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(74, 13);
+ this.label2.TabIndex = 4;
+ this.label2.Text = "Endpoint Port:";
+ //
+ // TextPort
+ //
+ this.TextPort.Location = new System.Drawing.Point(202, 48);
+ this.TextPort.Name = "TextPort";
+ this.TextPort.Size = new System.Drawing.Size(41, 20);
+ this.TextPort.TabIndex = 3;
+ this.TextPort.Text = "11211";
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(6, 29);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(83, 13);
+ this.label1.TabIndex = 2;
+ this.label1.Text = "Endpoint Name:";
+ //
+ // ButtonInstantiate
+ //
+ this.ButtonInstantiate.Location = new System.Drawing.Point(6, 85);
+ this.ButtonInstantiate.Name = "ButtonInstantiate";
+ this.ButtonInstantiate.Size = new System.Drawing.Size(147, 23);
+ this.ButtonInstantiate.TabIndex = 0;
+ this.ButtonInstantiate.Text = "Instantiate From App.Config";
+ this.ButtonInstantiate.UseVisualStyleBackColor = true;
+ this.ButtonInstantiate.Click += new System.EventHandler(this.ButtonInstantiate_Click);
+ //
+ // groupBox2
+ //
+ this.groupBox2.Controls.Add(this.label4);
+ this.groupBox2.Controls.Add(this.label3);
+ this.groupBox2.Controls.Add(this.TextValue);
+ this.groupBox2.Controls.Add(this.TextKey);
+ this.groupBox2.Controls.Add(this.ButtonAdd);
+ this.groupBox2.Location = new System.Drawing.Point(335, 12);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Size = new System.Drawing.Size(235, 125);
+ this.groupBox2.TabIndex = 1;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "Initial Add";
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(109, 29);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(37, 13);
+ this.label4.TabIndex = 4;
+ this.label4.Text = "Value:";
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(3, 29);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(28, 13);
+ this.label3.TabIndex = 3;
+ this.label3.Text = "Key:";
+ //
+ // TextValue
+ //
+ this.TextValue.Location = new System.Drawing.Point(112, 48);
+ this.TextValue.Name = "TextValue";
+ this.TextValue.Size = new System.Drawing.Size(100, 20);
+ this.TextValue.TabIndex = 2;
+ this.TextValue.Text = "Hello World";
+ //
+ // TextKey
+ //
+ this.TextKey.Location = new System.Drawing.Point(6, 48);
+ this.TextKey.Name = "TextKey";
+ this.TextKey.Size = new System.Drawing.Size(90, 20);
+ this.TextKey.TabIndex = 1;
+ this.TextKey.Text = "Test";
+ //
+ // ButtonAdd
+ //
+ this.ButtonAdd.Enabled = false;
+ this.ButtonAdd.Location = new System.Drawing.Point(6, 96);
+ this.ButtonAdd.Name = "ButtonAdd";
+ this.ButtonAdd.Size = new System.Drawing.Size(89, 23);
+ this.ButtonAdd.TabIndex = 0;
+ this.ButtonAdd.Text = "Add to Cache";
+ this.ButtonAdd.UseVisualStyleBackColor = true;
+ this.ButtonAdd.Click += new System.EventHandler(this.ButtonAdd_Click);
+ //
+ // groupBox3
+ //
+ this.groupBox3.Controls.Add(this.LabelValue);
+ this.groupBox3.Controls.Add(this.label6);
+ this.groupBox3.Controls.Add(this.TextGetKey);
+ this.groupBox3.Controls.Add(this.label5);
+ this.groupBox3.Controls.Add(this.ButtonGet);
+ this.groupBox3.Location = new System.Drawing.Point(12, 144);
+ this.groupBox3.Name = "groupBox3";
+ this.groupBox3.Size = new System.Drawing.Size(317, 109);
+ this.groupBox3.TabIndex = 2;
+ this.groupBox3.TabStop = false;
+ this.groupBox3.Text = "Initial Get";
+ //
+ // LabelValue
+ //
+ this.LabelValue.AutoSize = true;
+ this.LabelValue.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.LabelValue.Location = new System.Drawing.Point(139, 46);
+ this.LabelValue.Name = "LabelValue";
+ this.LabelValue.Size = new System.Drawing.Size(0, 25);
+ this.LabelValue.TabIndex = 4;
+ //
+ // label6
+ //
+ this.label6.AutoSize = true;
+ this.label6.Location = new System.Drawing.Point(141, 20);
+ this.label6.Name = "label6";
+ this.label6.Size = new System.Drawing.Size(37, 13);
+ this.label6.TabIndex = 3;
+ this.label6.Text = "Value:";
+ //
+ // TextGetKey
+ //
+ this.TextGetKey.Location = new System.Drawing.Point(9, 37);
+ this.TextGetKey.Name = "TextGetKey";
+ this.TextGetKey.ReadOnly = true;
+ this.TextGetKey.Size = new System.Drawing.Size(100, 20);
+ this.TextGetKey.TabIndex = 2;
+ this.TextGetKey.Text = "Test";
+ //
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.Location = new System.Drawing.Point(9, 20);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(28, 13);
+ this.label5.TabIndex = 1;
+ this.label5.Text = "Key:";
+ //
+ // ButtonGet
+ //
+ this.ButtonGet.Enabled = false;
+ this.ButtonGet.Location = new System.Drawing.Point(6, 80);
+ this.ButtonGet.Name = "ButtonGet";
+ this.ButtonGet.Size = new System.Drawing.Size(75, 23);
+ this.ButtonGet.TabIndex = 0;
+ this.ButtonGet.Text = "Get Value";
+ this.ButtonGet.UseVisualStyleBackColor = true;
+ this.ButtonGet.Click += new System.EventHandler(this.ButtonGet_Click);
+ //
+ // label7
+ //
+ this.label7.AutoSize = true;
+ this.label7.Location = new System.Drawing.Point(12, 275);
+ this.label7.Name = "label7";
+ this.label7.Size = new System.Drawing.Size(40, 13);
+ this.label7.TabIndex = 3;
+ this.label7.Text = "Status:";
+ //
+ // LabelStatus
+ //
+ this.LabelStatus.AutoSize = true;
+ this.LabelStatus.Location = new System.Drawing.Point(58, 275);
+ this.LabelStatus.Name = "LabelStatus";
+ this.LabelStatus.Size = new System.Drawing.Size(93, 13);
+ this.LabelStatus.TabIndex = 4;
+ this.LabelStatus.Text = "Waiting on action.";
+ //
+ // ProgressBarStatus
+ //
+ this.ProgressBarStatus.Location = new System.Drawing.Point(11, 300);
+ this.ProgressBarStatus.Name = "ProgressBarStatus";
+ this.ProgressBarStatus.Size = new System.Drawing.Size(140, 23);
+ this.ProgressBarStatus.TabIndex = 5;
+ //
+ // ButtonExit
+ //
+ this.ButtonExit.Location = new System.Drawing.Point(495, 300);
+ this.ButtonExit.Name = "ButtonExit";
+ this.ButtonExit.Size = new System.Drawing.Size(75, 23);
+ this.ButtonExit.TabIndex = 6;
+ this.ButtonExit.Text = "Exit";
+ this.ButtonExit.UseVisualStyleBackColor = true;
+ this.ButtonExit.Click += new System.EventHandler(this.ButtonExit_Click);
+ //
+ // ProgressPoller
+ //
+ this.ProgressPoller.Location = new System.Drawing.Point(40, 48);
+ this.ProgressPoller.Maximum = 60;
+ this.ProgressPoller.Name = "ProgressPoller";
+ this.ProgressPoller.Size = new System.Drawing.Size(157, 23);
+ this.ProgressPoller.Step = 1;
+ this.ProgressPoller.TabIndex = 7;
+ //
+ // label8
+ //
+ this.label8.AutoSize = true;
+ this.label8.Location = new System.Drawing.Point(40, 29);
+ this.label8.Name = "label8";
+ this.label8.Size = new System.Drawing.Size(145, 13);
+ this.label8.TabIndex = 8;
+ this.label8.Text = "Wait 1 minute to check poller";
+ //
+ // groupBox4
+ //
+ this.groupBox4.Controls.Add(this.LabelVersion);
+ this.groupBox4.Controls.Add(this.ProgressPoller);
+ this.groupBox4.Controls.Add(this.label8);
+ this.groupBox4.Location = new System.Drawing.Point(336, 144);
+ this.groupBox4.Name = "groupBox4";
+ this.groupBox4.Size = new System.Drawing.Size(234, 109);
+ this.groupBox4.TabIndex = 9;
+ this.groupBox4.TabStop = false;
+ this.groupBox4.Text = "Poller Test";
+ //
+ // LabelVersion
+ //
+ this.LabelVersion.AutoSize = true;
+ this.LabelVersion.Location = new System.Drawing.Point(6, 80);
+ this.LabelVersion.Name = "LabelVersion";
+ this.LabelVersion.Size = new System.Drawing.Size(0, 13);
+ this.LabelVersion.TabIndex = 9;
+ //
+ // TimerPoller
+ //
+ this.TimerPoller.Interval = 1000;
+ this.TimerPoller.Tick += new System.EventHandler(this.TimerPoller_Tick);
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(582, 335);
+ this.Controls.Add(this.groupBox4);
+ this.Controls.Add(this.ButtonExit);
+ this.Controls.Add(this.ProgressBarStatus);
+ this.Controls.Add(this.LabelStatus);
+ this.Controls.Add(this.label7);
+ this.Controls.Add(this.groupBox3);
+ this.Controls.Add(this.groupBox2);
+ this.Controls.Add(this.groupBox1);
+ this.MaximizeBox = false;
+ this.Name = "Form1";
+ this.ShowIcon = false;
+ this.Text = "Cluster Client Tests";
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ this.groupBox2.ResumeLayout(false);
+ this.groupBox2.PerformLayout();
+ this.groupBox3.ResumeLayout(false);
+ this.groupBox3.PerformLayout();
+ this.groupBox4.ResumeLayout(false);
+ this.groupBox4.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.TextBox TextPort;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Button ButtonInstantiate;
+ private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.Button ButtonAdd;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox TextValue;
+ private System.Windows.Forms.TextBox TextKey;
+ private System.Windows.Forms.GroupBox groupBox3;
+ private System.Windows.Forms.Label LabelValue;
+ private System.Windows.Forms.Label label6;
+ private System.Windows.Forms.TextBox TextGetKey;
+ private System.Windows.Forms.Label label5;
+ private System.Windows.Forms.Button ButtonGet;
+ private System.Windows.Forms.Label label7;
+ private System.Windows.Forms.Label LabelStatus;
+ private System.Windows.Forms.ProgressBar ProgressBarStatus;
+ private System.Windows.Forms.TextBox TextOlder;
+ private System.Windows.Forms.Button ButtonOlder;
+ private System.Windows.Forms.Button ButtonExit;
+ private System.Windows.Forms.ProgressBar ProgressPoller;
+ private System.Windows.Forms.Label label8;
+ private System.Windows.Forms.GroupBox groupBox4;
+ private System.Windows.Forms.Timer TimerPoller;
+ private System.Windows.Forms.Label LabelVersion;
+ }
+}
+
diff --git a/ClusterClientAppTester/Form1.cs b/ClusterClientAppTester/Form1.cs
new file mode 100644
index 0000000..904f658
--- /dev/null
+++ b/ClusterClientAppTester/Form1.cs
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+
+using Enyim.Caching;
+using Amazon.ElastiCacheCluster;
+using Enyim.Caching.Memcached;
+
+namespace ClusterClientAppTester
+{
+ public partial class Form1 : Form
+ {
+ private MemcachedClient mem;
+ private ElastiCacheClusterConfig config;
+
+ public Form1()
+ {
+ InitializeComponent();
+ }
+
+ public void ErrorAlert(string error)
+ {
+ MessageBox.Show(error);
+ }
+
+ private void ButtonInstantiate_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ this.LabelStatus.Text = "Instantiating";
+
+ // Instantiates config from app.config in the clusterclient section
+ this.config = new ElastiCacheClusterConfig();
+
+ mem = new MemcachedClient(this.config);
+
+ #region UI Stuff
+
+ this.TimerPoller.Enabled = false;
+ this.ProgressPoller.Value = 0;
+ this.TimerPoller.Enabled = true;
+
+ if (mem == null)
+ {
+ this.LabelStatus.Text = "MemcachedClient returned a null object";
+ }
+ else
+ {
+ this.ButtonAdd.Enabled = true;
+ this.ButtonGet.Enabled = true;
+ this.LabelStatus.Text = "Instantiation Success";
+ this.ProgressBarStatus.Value = 25;
+ }
+
+ #endregion
+ }
+ catch (Exception ex)
+ {
+ this.LabelStatus.Text = ex.Message;
+ }
+ }
+
+ private void ButtonOlder_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ this.LabelStatus.Text = "Instantiating";
+
+ // Instantiates client with default settings and uses the hostname and port provided
+ this.config = new ElastiCacheClusterConfig(this.TextOlder.Text, Convert.ToInt32(this.TextPort.Text));
+
+ this.mem = new MemcachedClient(this.config);
+
+ #region UI Stuff
+
+ this.TimerPoller.Enabled = false;
+ this.ProgressPoller.Value = 0;
+ this.TimerPoller.Enabled = true;
+
+ if (mem == null)
+ {
+ this.LabelStatus.Text = "MemcachedClient returned a null object";
+ }
+ else
+ {
+ this.ButtonAdd.Enabled = true;
+ this.ButtonGet.Enabled = true;
+ this.LabelStatus.Text = "Old Instantiation Success";
+ this.ProgressBarStatus.Value = 25;
+ }
+
+ #endregion
+ }
+ catch (Exception ex)
+ {
+ this.LabelStatus.Text = ex.Message;
+ }
+ }
+
+ private void ButtonAdd_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ this.LabelStatus.Text = "Storing";
+
+ // Stores the same as an Enyim client, just that the nodes are already set through the config object
+ if (mem.Store(StoreMode.Set, this.TextKey.Text, this.TextValue.Text))
+ {
+ #region UI Stuff
+
+ this.TextGetKey.Text = this.TextKey.Text;
+ this.LabelStatus.Text = "Storing Success";
+ if (this.ProgressBarStatus.Value < 50)
+ {
+ this.ProgressBarStatus.Value = 50;
+ }
+
+ #endregion
+ }
+ else
+ {
+ this.LabelStatus.Text = "Failed to store";
+ }
+ }
+ catch (Exception ex)
+ {
+ this.LabelStatus.Text = ex.Message;
+ }
+ }
+
+ private void ButtonGet_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ this.LabelStatus.Text = "Getting";
+
+ // Gets the value the same way as a normal Enyim client from the dynamic nodes provided from the config
+ object val;
+ if (!mem.TryGet(TextGetKey.Text, out val))
+ {
+
+ #region UI Stuff
+
+ this.LabelStatus.Text = "Failed to get";
+ }
+ else
+ {
+ this.LabelValue.Text = val as string;
+ this.LabelStatus.Text = "Getting Success";
+ if (this.ProgressBarStatus.Value < 75)
+ {
+ this.ProgressBarStatus.Value = 75;
+ }
+ }
+
+ #endregion
+ }
+ catch (Exception ex)
+ {
+ this.LabelStatus.Text = ex.Message;
+ }
+ }
+
+ private void ButtonExit_Click(object sender, EventArgs e)
+ {
+ if (mem != null)
+ this.mem.Dispose();
+ Application.Exit();
+ }
+
+ private void TimerPoller_Tick(object sender, EventArgs e)
+ {
+ this.LabelVersion.Text = String.Format("Config version is: {0} ", this.config.DiscoveryNode.ClusterVersion)
+ + String.Format("Number of nodes: {0}", this.config.DiscoveryNode.NodesInCluster);
+ this.ProgressPoller.PerformStep();
+ if (this.ProgressPoller.Value == 60)
+ {
+ this.ProgressPoller.Value = 0;
+ this.ProgressBarStatus.Value = 100;
+ this.LabelStatus.Text = "Poller cycle completed.";
+ }
+
+ }
+ }
+}
diff --git a/ClusterClientAppTester/Form1.resx b/ClusterClientAppTester/Form1.resx
new file mode 100644
index 0000000..e85ec0c
--- /dev/null
+++ b/ClusterClientAppTester/Form1.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ True
+
+
\ No newline at end of file
diff --git a/ClusterClientAppTester/Program.cs b/ClusterClientAppTester/Program.cs
new file mode 100644
index 0000000..06a196d
--- /dev/null
+++ b/ClusterClientAppTester/Program.cs
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace ClusterClientAppTester
+{
+ static class Program
+ {
+ private static Form1 form;
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ try
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+
+ AppDomain currentDomain = AppDomain.CurrentDomain;
+ currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);
+
+ Program.form = new Form1();
+ Application.Run(Program.form);
+ }
+ catch (Exception ex)
+ {
+ System.IO.File.WriteAllText("log.txt", ex.Message);
+ Program.form.ErrorAlert(ex.Message);
+ }
+ }
+
+ static void MyHandler(object sender, UnhandledExceptionEventArgs args)
+ {
+ Exception e = (Exception)args.ExceptionObject;
+ System.IO.File.WriteAllText("log.txt", e.Message);
+ Program.form.Name = e.Message;
+ Program.form.ErrorAlert(e.Message);
+ }
+ }
+}
diff --git a/ClusterClientAppTester/Properties/AssemblyInfo.cs b/ClusterClientAppTester/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cc072ab
--- /dev/null
+++ b/ClusterClientAppTester/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ClusterClientAppTester")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ClusterClientAppTester")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("fdd8eaec-ac53-479d-8703-7f84e890701e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ClusterClientAppTester/Properties/Resources.Designer.cs b/ClusterClientAppTester/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..d859c8d
--- /dev/null
+++ b/ClusterClientAppTester/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ClusterClientAppTester.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ClusterClientAppTester.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/ClusterClientAppTester/Properties/Resources.resx b/ClusterClientAppTester/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/ClusterClientAppTester/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ClusterClientAppTester/Properties/Settings.Designer.cs b/ClusterClientAppTester/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..3c8ea7d
--- /dev/null
+++ b/ClusterClientAppTester/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ClusterClientAppTester.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/ClusterClientAppTester/Properties/Settings.settings b/ClusterClientAppTester/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/ClusterClientAppTester/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/ClusterClientAppTester/packages.config b/ClusterClientAppTester/packages.config
new file mode 100644
index 0000000..cda96ab
--- /dev/null
+++ b/ClusterClientAppTester/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/ElastiCacheClusterConfig.sln b/ElastiCacheClusterConfig.sln
new file mode 100644
index 0000000..3847ed2
--- /dev/null
+++ b/ElastiCacheClusterConfig.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Express 2013 for Windows Desktop
+VisualStudioVersion = 12.0.30501.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.ElastiCacheCluster", "Amazon.ElastiCacheCluster\Amazon.ElastiCacheCluster.csproj", "{1255EBD3-0340-4AF6-A5F2-3D97D8709546}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClusterClientAppTester", "ClusterClientAppTester\ClusterClientAppTester.csproj", "{085886E5-3D56-4E41-941A-611EDD2A31D4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalSimulationTests", "LocalSimulationTests\LocalSimulationTests.csproj", "{4348C13C-0E60-47C2-B86E-8D1FBB4060D5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1255EBD3-0340-4AF6-A5F2-3D97D8709546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1255EBD3-0340-4AF6-A5F2-3D97D8709546}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1255EBD3-0340-4AF6-A5F2-3D97D8709546}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1255EBD3-0340-4AF6-A5F2-3D97D8709546}.Release|Any CPU.Build.0 = Release|Any CPU
+ {085886E5-3D56-4E41-941A-611EDD2A31D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {085886E5-3D56-4E41-941A-611EDD2A31D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {085886E5-3D56-4E41-941A-611EDD2A31D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {085886E5-3D56-4E41-941A-611EDD2A31D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4348C13C-0E60-47C2-B86E-8D1FBB4060D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4348C13C-0E60-47C2-B86E-8D1FBB4060D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4348C13C-0E60-47C2-B86E-8D1FBB4060D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4348C13C-0E60-47C2-B86E-8D1FBB4060D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index ad410e1..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright {yyyy} {name of copyright owner}
-
- 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.
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..98d1f93
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,63 @@
+Apache License
+Version 2.0, January 2004
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
+
+ 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
+ 2. You must cause any modified files to carry prominent notices stating that You changed the files; and
+ 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
+ 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+Note: Other license terms may apply to certain, identified software files contained within or distributed with the accompanying software if such terms are included in the directory containing the accompanying software. Such other license terms will then apply in lieu of the terms of the software license above.
+
+JSON processing code subject to the JSON License from JSON.org:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/LocalSimulationTests/ConfigTests.cs b/LocalSimulationTests/ConfigTests.cs
new file mode 100644
index 0000000..64eb86d
--- /dev/null
+++ b/LocalSimulationTests/ConfigTests.cs
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Configuration;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Amazon.ElastiCacheCluster;
+using Enyim.Caching;
+
+namespace LocalSimulationTests
+{
+ [TestClass]
+ public class ConfigTests
+ {
+ ElastiCacheClusterConfig config;
+ MemcachedClient client;
+
+ [TestMethod]
+ public void ConfigTest()
+ {
+ // The url below is used to bypass the .cfg. contraint of the hostname for testing locally
+ ClusterConfigSettings settings = new ClusterConfigSettings("www.cfg.org", 11211);
+ settings.NodeFactory = new NodeFactory();
+ config = new ElastiCacheClusterConfig(settings);
+ this.config.DiscoveryNode.Dispose();
+ }
+
+ [TestMethod]
+ public void InitialRequestTest()
+ {
+ // The url below is used to bypass the .cfg. contraint of the hostname for testing locally
+ ClusterConfigSettings settings = new ClusterConfigSettings("www.cfg.org", 11211);
+ settings.NodeFactory = new NodeFactory();
+ config = new ElastiCacheClusterConfig(settings);
+
+ client = new MemcachedClient(config);
+
+ Assert.AreEqual(new Version("1.4.14"), config.DiscoveryNode.NodeVersion);
+ Assert.AreEqual(1, config.DiscoveryNode.ClusterVersion);
+ Assert.AreEqual(3, config.DiscoveryNode.NodesInCluster);
+
+ this.config.DiscoveryNode.Dispose();
+ client.Dispose();
+ }
+
+ [TestMethod]
+ public void PollerTesting()
+ {
+ //Poller is set to poll every second to make this test faster
+ ClusterConfigSettings settings = new ClusterConfigSettings("www.cfg.org", 11211);
+ settings.NodeFactory = new NodeFactory();
+ settings.ClusterPoller.IntervalDelay = 1000;
+ config = new ElastiCacheClusterConfig(settings);
+
+ client = new MemcachedClient(config);
+
+ // Buffer time to wait, this can fail occasionally because delays can occur in the poller or timer
+ System.Threading.Thread.Sleep(3000);
+ Assert.AreEqual(3, config.DiscoveryNode.ClusterVersion);
+ Assert.AreEqual(1, config.DiscoveryNode.NodesInCluster);
+
+ this.config.DiscoveryNode.Dispose();
+ client.Dispose();
+ }
+ }
+}
diff --git a/LocalSimulationTests/LocalSimulationTests.csproj b/LocalSimulationTests/LocalSimulationTests.csproj
new file mode 100644
index 0000000..2da9be5
--- /dev/null
+++ b/LocalSimulationTests/LocalSimulationTests.csproj
@@ -0,0 +1,99 @@
+
+
+
+ Debug
+ AnyCPU
+ {4348C13C-0E60-47C2-B86E-8D1FBB4060D5}
+ Library
+ Properties
+ LocalSimulationTests
+ LocalSimulationTests
+ v3.5
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\EnyimMemcached.2.12\lib\net35\Enyim.Caching.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {1255ebd3-0340-4af6-a5f2-3d97d8709546}
+ Amazon.ElastiCacheCluster
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LocalSimulationTests/NodeFactory.cs b/LocalSimulationTests/NodeFactory.cs
new file mode 100644
index 0000000..44b6d4a
--- /dev/null
+++ b/LocalSimulationTests/NodeFactory.cs
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Amazon.ElastiCacheCluster.Factories;
+using Enyim.Caching.Memcached;
+
+namespace LocalSimulationTests
+{
+ public class NodeFactory : IConfigNodeFactory
+ {
+ TestNode node;
+
+ public NodeFactory()
+ {
+ this.node = new TestNode();
+ }
+
+ public IMemcachedNode CreateNode(System.Net.IPEndPoint endpoint, Enyim.Caching.Configuration.ISocketPoolConfiguration config)
+ {
+ return node;
+ }
+ }
+}
diff --git a/LocalSimulationTests/Properties/AssemblyInfo.cs b/LocalSimulationTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4a6d52d
--- /dev/null
+++ b/LocalSimulationTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("LocalSimulationTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("LocalSimulationTests")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("67675e7a-9380-4882-8b1d-f4345703e8d5")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/LocalSimulationTests/TestNode.cs b/LocalSimulationTests/TestNode.cs
new file mode 100644
index 0000000..843959c
--- /dev/null
+++ b/LocalSimulationTests/TestNode.cs
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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.
+ */
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Enyim.Caching.Memcached;
+using Enyim.Caching.Memcached.Protocol;
+using System.Net;
+using Amazon.ElastiCacheCluster.Operations;
+using Enyim.Caching.Memcached.Results;
+
+namespace LocalSimulationTests
+{
+ public class TestNode : IMemcachedNode
+ {
+ private IPEndPoint end;
+ public IPEndPoint EndPoint { get { return end; } }
+
+ private int requestNum = 1;
+
+ public Enyim.Caching.Memcached.Results.IOperationResult Execute(IOperation op)
+ {
+ IConfigOperation getOp = op as IConfigOperation;
+
+ byte[] bytes;
+
+ switch (requestNum)
+ {
+ case 1:
+ bytes = Encoding.UTF8.GetBytes(String.Format("{0}\r\ncluster.0001.use1.cache.amazon.aws.com|10.10.10.1|11211 cluster.0002.use1.cache.amazon.aws.com|10.10.10.2|11211 cluster.0003.use1.cache.amazon.aws.com|10.10.10.3|11211\r\n", this.requestNum));
+ break;
+ case 2:
+ bytes = Encoding.UTF8.GetBytes(String.Format("{0}\r\ncluster.0002.use1.cache.amazon.aws.com|10.10.10.2|11211 cluster.0003.use1.cache.amazon.aws.com|10.10.10.3|11211\r\n", this.requestNum));
+ break;
+ default:
+ bytes = Encoding.UTF8.GetBytes(String.Format("{0}\r\ncluster.0001.use1.cache.amazon.aws.com|10.10.10.1|11211\r\n", this.requestNum));
+ break;
+ }
+ this.requestNum++;
+
+ var arr = new ArraySegment(bytes);
+ getOp.ConfigResult = new CacheItem(0, arr);
+
+ var result = new PooledSocketResult();
+ result.Success = true;
+ return result;
+ }
+
+ public override string ToString()
+ {
+ return "TestingAWSInternal";
+ }
+
+ public bool ExecuteAsync(IOperation op, Action next)
+ {
+ throw new NotImplementedException();
+ }
+
+ public event Action Failed;
+
+ public bool IsAlive
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool Ping()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/LocalSimulationTests/packages.config b/LocalSimulationTests/packages.config
new file mode 100644
index 0000000..cda96ab
--- /dev/null
+++ b/LocalSimulationTests/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..f03ee32
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,14 @@
+Amazon ElastiCache Cluster Configuration for .NET
+Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+This product includes software developed by
+Amazon Technologies, Inc (http://www.amazon.com/).
+
+**********************
+THIRD PARTY COMPONENTS
+**********************
+This software includes third party software subject to the following copyrights:
+- Get operation and socket pools - Copyright 2010 Attila Kiskó, enyim.com.
+
+The licenses for these third party components are included in LICENSE.txt
+
diff --git a/README.md b/README.md
index 09e5173..cf64640 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
# Amazon ElastiCache Cluster Configuration for .NET
-Amazon ElastiCache Cluster Configuration is an enhanced .NET library that supports connecting to an ElastiCache cluster for Auto Discovery. This client library is an extension built upon Enyim and is released under the [Apache 2.0 License](http://aws.amazon.com/apache2.0/).
+Amazon ElastiCache Cluster Configuration is an enhanced .NET library that supports connecting to an Amazon ElastiCache cluster for Auto Discovery. This client library is an extension built upon Enyim and is released under the [Apache 2.0 License](http://aws.amazon.com/apache2.0/).
## Usage
To use Amazon ElastiCache with Auto Discovery, the ElastiCacheClusterConfig object must be passed to the MemcachedClient object. An ElastiCacheClusterConfig can be created from the App.config if there is a clusterclient section or you can pass a different section.
-
+
var client = new MemcachedClient(new ElastiCacheClusterConfig());
ElastiCacheClusterConfig can also be instantiated by providing it an AutoConfigSetup or simply provide the hostname and port like so:
@@ -29,6 +29,9 @@ These settings as well as all the settings included in Enyim can be configured t
## Enyim Client
Because this binary is used as a configuration object for the Enyim MemcachedClient, usage beyond instantiation is all exactly the same so refer to [this wiki](https://github.com/enyim/EnyimMemcached/wiki) or [this google group](https://groups.google.com/forum/#!forum/enyim-memcached) on how to use the actual client.
+## Wiki
+The wiki found [here](https://github.com/awslabs/elasticache-cluster-config-net/wiki) goes into more detail on the usage of the ElastiCacheClusterConfig object as well as how the project takes advantage of Auto Discovery.
+
## Requirements
You'll need .NET Framework 3.5 or later to use the precompiled binaries. To build the client, you'll need Visual Studio 2010 or higher.
diff --git a/packages/repositories.config b/packages/repositories.config
new file mode 100644
index 0000000..59be8ca
--- /dev/null
+++ b/packages/repositories.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file