dalli-elasticache 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3223c4e9eb1fd636a8841a415905203022f03276
4
- data.tar.gz: b853a9d5674e3d4335cd8b6046f3dfa3eba5ef0f
2
+ SHA256:
3
+ metadata.gz: 79559ed673ac332f7e1187182b4764a5252bb0282d4044768cee640ca98cbe64
4
+ data.tar.gz: 6bfeb80b35680e43d1ad5036ceb7b7e245a258d864e4050096a3f1dd2258b624
5
5
  SHA512:
6
- metadata.gz: aa250e3c5c9aa4e55434c7d05b76deaea939735414dafac2cd5b2a3dbf42b46042c7931420f872e837a79384394f15a1f1e48f32d9d44eca529f4896e6fc9063
7
- data.tar.gz: 627f5c1718932bcbfd7cd661a8c03b55e1ccbb714478598ffffcb28e247f07b503a2e3208f3c90b3778a2a62bbf9496f5ae884f5e470a702f9cd6451d22ddefb
6
+ metadata.gz: 8eb49b57f0cb0db07dfea42a7182773d6ebec8ada72a881771718ee514da2a2b5e2126762bbfe2c4b8cd237c12001b8928a0b49737c5ee164734375e10f09e37
7
+ data.tar.gz: 4c7aa77db106082486010d5e82d1ce8e984bb0265b292f59854f33b7ff9bda48a488866b234ab9c92c2ac2efa07dd497a5b2e41a72f3ee67d79971c5552f1975
data/README.md CHANGED
@@ -1,46 +1,63 @@
1
- Dalli ElastiCache [![Gem Version](https://badge.fury.io/rb/dalli-elasticache.svg)](http://badge.fury.io/rb/dalli-elasticache) [![Build Status](https://travis-ci.org/ktheory/dalli-elasticache.svg)](https://travis-ci.org/ktheory/dalli-elasticache) [![Code Climate](https://codeclimate.com/github/ktheory/dalli-elasticache.png)](https://codeclimate.com/github/ktheory/dalli-elasticache)
1
+ Dalli ElastiCache [![Gem Version](https://badge.fury.io/rb/dalli-elasticache.svg)](http://badge.fury.io/rb/dalli-elasticache) [![Build Status](https://github.com/ktheory/dalli-elasticache/actions/workflows/tests.yml/badge.svg)](https://github.com/ktheory/dalli-elasticache/actions/workflows/tests.yml) [![Code Climate](https://codeclimate.com/github/ktheory/dalli-elasticache.png)](https://codeclimate.com/github/ktheory/dalli-elasticache)
2
2
  =================
3
3
 
4
- Use [AWS ElastiCache AutoDiscovery](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.html) to automatically configure your [Dalli memcached client](https://github.com/mperham/dalli) with all the nodes in your cluster.
4
+ Use [AWS ElastiCache AutoDiscovery](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.html) or [Google Cloud MemoryStore Auto Discovery](https://cloud.google.com/memorystore/docs/memcached/using-auto-discovery) to automatically configure your [Dalli memcached client](https://github.com/petergoldstein/dalli) with all the nodes in your cluster.
5
5
 
6
6
  Installation
7
7
  ------------
8
8
 
9
- Install the [rubygem](https://rubygems.org/gems/dalli-elasticache):
9
+ Install the [gem](https://rubygems.org/gems/dalli-elasticache):
10
10
 
11
11
  ```ruby
12
12
  # in your Gemfile
13
13
  gem 'dalli-elasticache'
14
14
  ```
15
15
 
16
- Setup for Rails 3.x and Newer
17
- -----------------------------
16
+ Using Dalli Elasticache in Rails
17
+ ---------------------------------
18
18
 
19
- Configure your environment-specific application settings:
19
+ Note that the list of memcached servers used by Rails will be refreshed each time an app server process starts. If the list of nodes in your cluster changes, this configuration will not be reflected in the Rails configuraiton without such a server process restart.
20
+
21
+ ### Configuring a Cache Store
22
+
23
+ The most common use of Dalli in Rails is to support a cache store. To set up your cache store with a cluster, you'll need to generate the list of servers with Dalli ElastiCache and pass them to the `cache_store` configuration. This needs to be done in your `config/environments/RAILS_ENV.rb` file for each Rails environment where you want to use a cluster.
20
24
 
21
25
  ```ruby
22
26
  # in config/environments/production.rb
23
27
  endpoint = "my-cluster-name.abc123.cfg.use1.cache.amazonaws.com:11211"
24
28
  elasticache = Dalli::ElastiCache.new(endpoint)
25
29
 
26
- config.cache_store = :dalli_store, elasticache.servers, {:expires_in => 1.day, :compress => true}
30
+ config.cache_store = :mem_cache_store, elasticache.servers, { expires_in: 1.day }
27
31
  ```
32
+ ### Configuring a Session Store
28
33
 
29
- Note that the ElastiCache server list will be refreshed each time an app server process starts.
34
+ Another use of Dalli in Rails is to support a Rails session store. Dalli ElastiCache can also be used in this case. The usage is very similar - first use Dalli ElastiCache to generate the list of servers, and then pass that result to the Rails configuration. In `config/application.rb` you would write:
35
+
36
+ ```ruby
37
+ # in config/environments/production.rb
38
+ endpoint = "my-cluster-name.abc123.cfg.use1.cache.amazonaws.com:11211"
39
+ elasticache = Dalli::ElastiCache.new(endpoint)
30
40
 
31
- Client Usage
41
+ config.session_store = :mem_cache_store, memcache_server: elasticache.servers, pool_size: 10, pool_timeout: 5, expire_after: 1.day
42
+ ```
43
+
44
+ ### Dalli Considerations
45
+
46
+ Please see [here](https://github.com/petergoldstein/dalli/wiki/Using-Dalli-with-Rails) for more information on configuring Dalli and Rails.
47
+
48
+
49
+ Using Dalli ElastiCache with a Dalli Client
32
50
  ------------
33
51
 
34
- Create an ElastiCache instance:
52
+ To initialize a Dalli Client the is configured for all the nodes of a cluster, one simply needs to pass the configuration endpoint and any options for the Dalli Client into the `Dalli::ElastiCache` initializer. Then one can use the methods on the `Dalli::ElastiCache` object to generate an appropriately configured `Dalli::Client`or to get information about the cluster.
35
53
 
36
54
  ```ruby
37
55
  config_endpoint = "aaron-scratch.vfdnac.cfg.use1.cache.amazonaws.com:11211"
38
56
 
39
57
  # Options for configuring the Dalli::Client
40
58
  dalli_options = {
41
- :expires_in => 24 * 60 * 60,
42
- :namespace => "my_app",
43
- :compress => true
59
+ expires_in: 24 * 60 * 60,
60
+ namespace: "my_app"
44
61
  }
45
62
 
46
63
  elasticache = Dalli::ElastiCache.new(config_endpoint, dalli_options)
@@ -75,6 +92,4 @@ elasticache.refresh.client
75
92
  License
76
93
  -------
77
94
 
78
- Copyright 2013 Aaron Suggs
79
-
80
- Released under an [MIT License](http://opensource.org/licenses/MIT)
95
+ Copyright (2017-2022) Aaron Suggs, Peter M. Goldstein. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,8 +1,10 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'bundler/gem_tasks'
6
+ require 'rspec/core/rake_task'
5
7
 
6
8
  RSpec::Core::RakeTask.new(:test)
7
9
 
8
- task :default => :test
10
+ task default: :test
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ module Elasticache
5
+ module AutoDiscovery
6
+ ##
7
+ # Base command class for configuration endpoint
8
+ # command. Contains the network logic.
9
+ ##
10
+ class BaseCommand
11
+ attr_reader :host, :port
12
+
13
+ def initialize(host, port)
14
+ @host = host
15
+ @port = port
16
+ end
17
+
18
+ # Send an ASCII command to the endpoint
19
+ #
20
+ # Returns the raw response as a String
21
+ def send_command
22
+ socket = TCPSocket.new(@host, @port)
23
+ begin
24
+ socket.puts command
25
+ response_from_socket(socket)
26
+ ensure
27
+ socket.close
28
+ end
29
+ end
30
+
31
+ def response_from_socket(socket)
32
+ data = +''
33
+ until (line = socket.readline).include?('END')
34
+ data << line
35
+ end
36
+
37
+ data
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ module Elasticache
5
+ module AutoDiscovery
6
+ ##
7
+ # Encapsulates execution of the 'config' command, which is used to
8
+ # extract the list of nodes and determine if that list of nodes has changed.
9
+ ##
10
+ class ConfigCommand < BaseCommand
11
+ attr_reader :engine_version
12
+
13
+ CONFIG_COMMAND = "config get cluster\r\n"
14
+
15
+ # Legacy command for version < 1.4.14
16
+ LEGACY_CONFIG_COMMAND = "get AmazonElastiCache:cluster\r\n"
17
+
18
+ def initialize(host, port, engine_version)
19
+ super(host, port)
20
+ @engine_version = engine_version
21
+ end
22
+
23
+ def response
24
+ ConfigResponse.new(send_command)
25
+ end
26
+
27
+ def command
28
+ return LEGACY_CONFIG_COMMAND if legacy_config?
29
+
30
+ CONFIG_COMMAND
31
+ end
32
+
33
+ def legacy_config?
34
+ return false unless engine_version
35
+ return false if engine_version.casecmp('unknown').zero?
36
+
37
+ Gem::Version.new(engine_version) < Gem::Version.new('1.4.14')
38
+ rescue ArgumentError
39
+ # Just assume false if we can't parse the engine_version
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,48 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
4
  module Elasticache
3
5
  module AutoDiscovery
4
-
5
6
  # This class wraps the raw ASCII response from an Auto Discovery endpoint
6
7
  # and provides methods for extracting data from that response.
7
8
  #
8
9
  # http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.AddingToYourClientLibrary.html
9
-
10
10
  class ConfigResponse
11
-
12
11
  # The raw response text
13
12
  attr_reader :text
14
-
13
+
15
14
  # Matches the version line of the response
16
- VERSION_REGEX = /^(\d+)$/
17
-
15
+ VERSION_REGEX = /^(\d+)\r?\n/.freeze
16
+
18
17
  # Matches strings like "my-cluster.001.cache.aws.com|10.154.182.29|11211"
19
- NODE_REGEX = /(([-.a-zA-Z0-9]+)\|(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\|(\d+))/
20
- NODE_LIST_REGEX = /^(#{NODE_REGEX}\s*)+$/
21
-
18
+ NODE_REGEX = /(([-.a-zA-Z0-9]+)\|(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)\|(\d+))/.freeze
19
+ NODE_LIST_REGEX = /^(#{NODE_REGEX}\s*)+$/.freeze
20
+
22
21
  def initialize(response_text)
23
22
  @text = response_text.to_s
24
23
  end
25
-
24
+
26
25
  # The number of times the configuration has been changed
27
26
  #
28
27
  # Returns an integer
29
28
  def version
30
- VERSION_REGEX.match(@text)[1].to_i
29
+ m = VERSION_REGEX.match(@text)
30
+ return -1 unless m
31
+
32
+ m[1].to_i
31
33
  end
32
-
34
+
33
35
  # Node hosts, ip addresses, and ports
34
36
  #
35
37
  # Returns an Array of Hashes with values for :host, :ip and :port
36
38
  def nodes
37
39
  NODE_LIST_REGEX.match(@text).to_s.scan(NODE_REGEX).map do |match|
38
- {
39
- :host => match[1],
40
- :ip => match[2],
41
- :port => match[3].to_i
42
- }
40
+ Node.new(match[1], match[2], match[3].to_i)
43
41
  end
44
42
  end
45
-
46
43
  end
47
44
  end
48
45
  end
@@ -1,76 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
4
  module Elasticache
3
5
  module AutoDiscovery
6
+ ##
7
+ # This is a representation of the configuration endpoint for
8
+ # a memcached cluster. It encapsulates information returned from
9
+ # that endpoint.
10
+ ##
4
11
  class Endpoint
5
-
6
12
  # Endpoint configuration
7
- attr_reader :host
8
- attr_reader :port
9
-
13
+ attr_reader :host, :port
14
+
10
15
  # Matches Strings like "my-host.cache.aws.com:11211"
11
- ENDPOINT_REGEX = /([-.a-zA-Z0-9]+):(\d+)/
12
-
13
- STATS_COMMAND = "stats\r\n"
14
- CONFIG_COMMAND = "config get cluster\r\n"
15
- # Legacy command for version < 1.4.14
16
- OLD_CONFIG_COMMAND = "get AmazonElastiCache:cluster\r\n"
17
-
18
- def initialize(endpoint)
19
- ENDPOINT_REGEX.match(endpoint) do |m|
20
- @host = m[1]
21
- @port = m[2].to_i
22
- end
23
- end
24
-
16
+ ENDPOINT_REGEX = /^([-_.a-zA-Z0-9]+)(?::(\d+))?$/.freeze
17
+
18
+ def initialize(addr)
19
+ @host, @port = parse_endpoint_address(addr)
20
+ end
21
+
22
+ DEFAULT_PORT = 11_211
23
+ def parse_endpoint_address(addr)
24
+ m = ENDPOINT_REGEX.match(addr)
25
+ raise ArgumentError, "Unable to parse configuration endpoint address - #{addr}" unless m
26
+
27
+ [m[1], (m[2] || DEFAULT_PORT).to_i]
28
+ end
29
+
25
30
  # A cached ElastiCache::StatsResponse
26
31
  def stats
27
- @stats ||= get_stats_from_remote
32
+ @stats ||= StatsCommand.new(@host, @port).response
28
33
  end
29
-
34
+
30
35
  # A cached ElastiCache::ConfigResponse
31
36
  def config
32
- @config ||= get_config_from_remote
37
+ @config ||= ConfigCommand.new(@host, @port, engine_version).response
33
38
  end
34
-
39
+
35
40
  # The memcached engine version
36
41
  def engine_version
37
- stats.version
38
- end
39
-
40
- protected
41
-
42
- def with_socket(&block)
43
- TCPSocket.new(config_host, config_port)
44
- end
45
-
46
- def get_stats_from_remote
47
- data = remote_command(STATS_COMMAND)
48
- StatsResponse.new(data)
49
- end
50
-
51
- def get_config_from_remote
52
- if engine_version < Gem::Version.new("1.4.14")
53
- data = remote_command(OLD_CONFIG_COMMAND)
54
- else
55
- data = remote_command(CONFIG_COMMAND)
56
- end
57
- ConfigResponse.new(data)
58
- end
59
-
60
- # Send an ASCII command to the endpoint
61
- #
62
- # Returns the raw response as a String
63
- def remote_command(command)
64
- socket = TCPSocket.new(@host, @port)
65
- socket.puts command
66
-
67
- data = ""
68
- until (line = socket.readline) =~ /END/
69
- data << line
70
- end
71
-
72
- socket.close
73
- data
42
+ stats.engine_version
74
43
  end
75
44
  end
76
45
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ module Elasticache
5
+ module AutoDiscovery
6
+ ##
7
+ # Represents a single memcached node in the
8
+ # cluster.
9
+ ##
10
+ class Node
11
+ attr_reader :host, :ip, :port
12
+
13
+ def initialize(host, ip, port)
14
+ @host = host
15
+ @ip = ip
16
+ @port = port
17
+ end
18
+
19
+ def ==(other)
20
+ host == other.host &&
21
+ ip == other.ip &&
22
+ port == other.port
23
+ end
24
+
25
+ def eql?(other)
26
+ self == other
27
+ end
28
+
29
+ def hash
30
+ [host, ip, port].hash
31
+ end
32
+
33
+ def to_s
34
+ "#{@host}:#{@port}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dalli
4
+ module Elasticache
5
+ module AutoDiscovery
6
+ ##
7
+ # Encapsulates execution of the 'stats' command, which is used to
8
+ # extract the engine_version
9
+ ##
10
+ class StatsCommand < BaseCommand
11
+ STATS_COMMAND = "stats\r\n"
12
+
13
+ def response
14
+ StatsResponse.new(send_command)
15
+ end
16
+
17
+ def command
18
+ STATS_COMMAND
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,29 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
4
  module Elasticache
3
5
  module AutoDiscovery
4
-
5
- # This class wraps the raw ASCII response from an Auto Discovery endpoint
6
- # and provides methods for extracting data from that response.
6
+ # This class wraps the raw ASCII response from a stats call to an
7
+ # Auto Discovery endpoint and provides methods for extracting data
8
+ # from that response.
7
9
  #
8
10
  # http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.AddingToYourClientLibrary.html
9
-
10
11
  class StatsResponse
11
-
12
12
  # The raw response text
13
13
  attr_reader :text
14
-
14
+
15
15
  # Matches the version line of the response
16
- VERSION_REGEX = /^STAT version ([0-9.]+)\s*$/
17
-
16
+ VERSION_REGEX = /^STAT version ([0-9.]+|unknown)\s*/i.freeze
17
+
18
18
  def initialize(response_text)
19
19
  @text = response_text.to_s
20
20
  end
21
-
21
+
22
22
  # Extract the engine version stat
23
23
  #
24
- # Returns a Gem::Version
25
- def version
26
- Gem::Version.new(VERSION_REGEX.match(@text)[1])
24
+ # Returns a string
25
+ def engine_version
26
+ m = VERSION_REGEX.match(@text)
27
+ return '' unless m && m[1]
28
+
29
+ m[1]
27
30
  end
28
31
  end
29
32
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dalli
2
4
  class ElastiCache
3
- VERSION = "0.2.0"
5
+ VERSION = '1.0.0'
4
6
  end
5
7
  end
@@ -1,47 +1,73 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dalli'
2
4
  require 'socket'
3
- require 'dalli/elasticache/version'
4
- require 'dalli/elasticache/auto_discovery/endpoint'
5
- require 'dalli/elasticache/auto_discovery/config_response'
6
- require 'dalli/elasticache/auto_discovery/stats_response'
5
+ require_relative 'elasticache/version'
6
+ require_relative 'elasticache/auto_discovery/endpoint'
7
+ require_relative 'elasticache/auto_discovery/base_command'
8
+ require_relative 'elasticache/auto_discovery/node'
9
+ require_relative 'elasticache/auto_discovery/config_response'
10
+ require_relative 'elasticache/auto_discovery/config_command'
11
+ require_relative 'elasticache/auto_discovery/stats_response'
12
+ require_relative 'elasticache/auto_discovery/stats_command'
7
13
 
8
14
  module Dalli
15
+ ##
16
+ # Dalli::Elasticache provides an interface for providing a configuration
17
+ # endpoint for a memcached cluster on ElasticCache and retrieving the
18
+ # list of addresses (hostname and port) for the individual nodes of that cluster.
19
+ #
20
+ # This allows the caller to pass that server list to Dalli, which then
21
+ # distributes cached items consistently over the nodes.
22
+ ##
9
23
  class ElastiCache
10
24
  attr_reader :endpoint, :options
11
-
12
- def initialize(config_endpoint, options={})
25
+
26
+ ##
27
+ # Creates a new Dalli::ElasticCache instance.
28
+ #
29
+ # config_endpoint - a String containing the host and (optionally) port of the
30
+ # configuration endpoint for the cluster. If not specified the port will
31
+ # default to 11211. The host must be either a DNS name or an IPv4 address. IPv6
32
+ # addresses are not handled at this time.
33
+ # dalli_options - a set of options passed to the Dalli::Client that is returned
34
+ # by the client method. Otherwise unused.
35
+ ##
36
+ def initialize(config_endpoint, dalli_options = {})
13
37
  @endpoint = Dalli::Elasticache::AutoDiscovery::Endpoint.new(config_endpoint)
14
- @options = options
38
+ @options = dalli_options
15
39
  end
16
-
40
+
17
41
  # Dalli::Client configured to connect to the cluster's nodes
18
42
  def client
19
43
  Dalli::Client.new(servers, options)
20
44
  end
21
-
45
+
22
46
  # The number of times the cluster configuration has been changed
23
47
  #
24
48
  # Returns an integer
25
49
  def version
26
50
  endpoint.config.version
27
51
  end
28
-
52
+
29
53
  # The cache engine version of the cluster
54
+ #
55
+ # Returns a string
30
56
  def engine_version
31
57
  endpoint.engine_version
32
58
  end
33
-
59
+
34
60
  # List of cluster server nodes with ip addresses and ports
35
61
  # Always use host name instead of private elasticache IPs as internal IPs can change after a node is rebooted
36
62
  def servers
37
- endpoint.config.nodes.map{ |h| "#{h[:host]}:#{h[:port]}" }
63
+ endpoint.config.nodes.map(&:to_s)
38
64
  end
39
-
65
+
40
66
  # Clear all cached data from the cluster endpoint
41
67
  def refresh
42
68
  config_endpoint = "#{endpoint.host}:#{endpoint.port}"
43
69
  @endpoint = Dalli::Elasticache::AutoDiscovery::Endpoint.new(config_endpoint)
44
-
70
+
45
71
  self
46
72
  end
47
73
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Support default bundler require path
2
- require 'dalli/elasticache'
4
+ require_relative 'elasticache'