diff --git a/Gemfile b/Gemfile index 6f45e72..c5d780a 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ gem 'celluloid', github: 'celluloid/celluloid', branch: 'master' gem 'celluloid-io', github: 'celluloid/celluloid-io', branch: 'master' gem 'celluloid-zmq', github: 'celluloid/celluloid-zmq', branch: 'master' gem 'celluloid-redis', github: 'celluloid/celluloid-redis', branch: 'master' +gem 'http', github: 'tarcieri/http', branch: 'master' gem 'reel', github: 'celluloid/reel', branch: 'master' #gem 'ffi-rzmq', github: 'chuckremes/ffi-rzmq' @@ -15,3 +16,5 @@ gem 'coveralls', require: false gemspec gem 'zk' +gem 'etcd', '~> 0.2.0.beta.1' + diff --git a/lib/dcell/celluloid_ext.rb b/lib/dcell/celluloid_ext.rb index 55909c3..3b37090 100644 --- a/lib/dcell/celluloid_ext.rb +++ b/lib/dcell/celluloid_ext.rb @@ -13,11 +13,10 @@ class ActorProxy # Marshal uses respond_to? to determine if this object supports _dump so # unfortunately we have to monkeypatch in _dump support as the proxy # itself normally jacks respond_to? and proxies to the actor - alias_method :__respond_to?, :respond_to? def respond_to?(meth, check_private = false) return false if meth == :marshal_dump return true if meth == :_dump - __respond_to?(meth, check_private) + method_missing(:respond_to?, meth, include_private) end # Dump an actor proxy via its mailbox diff --git a/lib/dcell/registries/etcd_adapter.rb b/lib/dcell/registries/etcd_adapter.rb new file mode 100644 index 0000000..ce33bb9 --- /dev/null +++ b/lib/dcell/registries/etcd_adapter.rb @@ -0,0 +1,104 @@ +require 'uri' +require 'etcd' + +module DCell + module Registry + class EtcdAdapter + PREFIX = '/dcell' + DEFAULT_SCHEME = 'http' + DEFAULT_PORT = 4001 + + # Create a new connect to Etc daemon. + # + # servers: List of Etcd servers to connect to. Each server + # has a host/port configuration. + def initialize(options={}) + options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h } + + @env = options['env'] || 'production' + @base_path = "#{PREFIX}/#{@env}" + + if options['server'] + servers = [ options['server'] ] + else + servers = options['servers'] + end + + # Sanity check. + raise 'no Etcd servers given' unless servers + + # Add default Etcd port unless specified. + servers.map! do |server| + if server[/:\d+$/] + server + else + "#{server}:#{DEFAULT_PORT}" + end + end + + server = server.first + @etcd = Etcd.client(host: server.host, port: server.port) + + server = server.is_a?(URI) ? server : URI(server) + if server.scheme && server.host && server.port + server + else + server = URI("#{DEFAULT_SCHEME}://#{server.to_s}:#{DEFAULT_PORT}") + end + end + + server = servers[rand(servers.count)] + @etcd = Etcd.client(host: server.host, + port: server.port, + use_ssl: server.scheme == 'https' ? true : false, + user_name: server.user, + password: server.password) + + @node_registry = Registry.new(@etcd, @base_path, :nodes) + @global_registry = Registry.new(@etcd, @base_path, :globals) + end + + def get_node(node_id); @node_registry.get(node_id) end + def set_node(node_id, addr); @node_registry.set(node_id, addr) end + def nodes; @node_registry.all end + def clear_nodes; @node_registry.clear end + + def get_global(key); @global_registry.get(key) end + def set_global(key, value); @global_registry.set(key, value) end + def global_keys; @global_registry.all end + def clear_globals; @global_registry.clear end + + class Registry + def initialize(etcd, base_path, name) + @etcd = etcd + @base_path = File.join(base_path, name.to_s) + + unless @etcd.exists?(@base_path) + @etcd.create(@base_path, dir: true) + end + end + + def get(key) + result = @etcd.get(File.join(base_path, key)) + Marshal.load(result.value) + end + + def set(key, value) + path = File.join base_path, key + string = Marshal.dump value + @etcd.set(path, value: string) + rescue Etcd::NodeExist + @etcd.update(path, value: string) + end + + def all + @etcd.get(@base_path).children + end + + def clear + @etcd.delete(@base_path) + @etcd.create(@base_path, dir: true) + end + end + end +end diff --git a/spec/dcell/registries/etcd_adapter_spec.rb b/spec/dcell/registries/etcd_adapter_spec.rb new file mode 100644 index 0000000..745a0d3 --- /dev/null +++ b/spec/dcell/registries/etcd_adapter_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' +require 'dcell/registries/etcd_adapter' + +describe DCell::Registry::EtcdAdapter, :pending => ENV["CI"] && "no etcd" do + subject { DCell::Registry::EtcdAdapter.new :server => 'localhost', :env => 'test' } + it_behaves_like "a DCell registry" +end