zookeeper 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ v0.3.0. Wickman's rewrite.
3
+
2
4
  v0.2.2. Fix compatibility with stock Leopard fat-binary Ruby.
3
5
 
4
6
  v0.2.1. No more camelcase classname.
@@ -6,3 +8,4 @@ v0.2.1. No more camelcase classname.
6
8
  v0.2. Bundle C dependencies, like memcached.gem.
7
9
 
8
10
  v0.1. First release.
11
+
data/Manifest CHANGED
@@ -3,8 +3,20 @@ LICENSE
3
3
  Manifest
4
4
  README
5
5
  Rakefile
6
+ examples/cloud_config.rb
6
7
  ext/extconf.rb
7
- ext/zkc-3.2.2.tar.gz
8
+ ext/zkc-3.3.1.tar.gz
8
9
  ext/zookeeper_c.c
10
+ ext/zookeeper_lib.c
11
+ ext/zookeeper_lib.h
9
12
  lib/zookeeper.rb
13
+ lib/zookeeper/acls.rb
14
+ lib/zookeeper/callbacks.rb
15
+ lib/zookeeper/constants.rb
16
+ lib/zookeeper/exceptions.rb
17
+ lib/zookeeper/stat.rb
10
18
  test/test_basic.rb
19
+ test/test_callback1.rb
20
+ test/test_esoteric.rb
21
+ test/test_watcher1.rb
22
+ test/test_watcher2.rb
data/README CHANGED
@@ -4,7 +4,14 @@ An interface to the Zookeeper distributed configuration server.
4
4
 
5
5
  == License
6
6
 
7
+ <<<<<<< HEAD
7
8
  Copyright 2008 Phillip Pearson, and 2010 Twitter, Inc. Licensed under the MIT License. See the included LICENSE file. Portions copyright 2008-2010 the Apache Software Foundation, licensed under the Apache 2 license, and used with permission.
9
+ =======
10
+ Copyright 2008 Phillip Pearson, and 2010 Twitter, Inc. Licensed under the
11
+ MIT License. See the included LICENSE file. Portions copyright 2008-2010
12
+ the Apache Software Foundation, licensed under the Apache 2 license, and
13
+ used with permission.
14
+ >>>>>>> wickman
8
15
 
9
16
  == Install
10
17
 
@@ -17,6 +24,7 @@ Connect to a server:
17
24
  require 'rubygems'
18
25
  require 'zookeeper'
19
26
  z = Zookeeper.new("localhost:2181")
27
+ <<<<<<< HEAD
20
28
 
21
29
  Create, set and read nodes:
22
30
 
@@ -51,3 +59,25 @@ Acquire locks:
51
59
  z.try_acquire "/parent/lock-", "content for the lock file" do |have_lock|
52
60
  puts have_lock ? "we have the lock" : "we don't have the lock"
53
61
  end
62
+ =======
63
+ z.get_children(:path => "/")
64
+
65
+ == Idioms
66
+
67
+ The following methods are initially supported:
68
+ get
69
+ set
70
+ get_children
71
+ stat
72
+ create
73
+ delete
74
+ get_acl
75
+ set_acl
76
+
77
+ All support async callbacks. get, get_children and stat support both
78
+ watchers and callbacks.
79
+
80
+ Calls take a dictionary of parameters. With the exception of set_acl, the
81
+ only required parameter is :path. Each call returns a dictionary with at
82
+ minimum two keys :req_id and :rc.
83
+ >>>>>>> wickman
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'echoe'
2
2
 
3
3
  Echoe.new("zookeeper") do |p|
4
- p.author = "Phillip Pearson, Eric Maland, Evan Weaver"
4
+ p.author = "Phillip Pearson, Eric Maland, Evan Weaver, Brian Wickman"
5
5
  p.project = "fauna"
6
6
  p.summary = "An interface to the Zookeeper distributed configuration server."
7
7
  p.url = "http://blog.evanweaver.com/files/doc/fauna/zookeeper/"
@@ -0,0 +1,125 @@
1
+ require "rubygems"
2
+ require "zookeeper"
3
+
4
+ # A basic cloud-based YAML config library. Ruby Zookeeper client example.
5
+ #
6
+ # If you pass in a file as 'zk:/foo.yml/blah' it will go out to zookeeper.
7
+ # Otherwise the file is assumed to be local. The yml file will get parsed
8
+ # and cached locally, and keys after the .yml get interpreted as keys into
9
+ # the YAML.
10
+ #
11
+ # e.g. get(zk:/config/service.yml/key1/key2/key3..) =>
12
+ # zk.get(:path => /config/service.yml)
13
+ # yaml <= YAML.parse(data)
14
+ # yaml[key1][key2][key3]...
15
+ #
16
+ # If keys are unspecified, it returns the parsed YAML as one big object
17
+ #
18
+ # TODO if staleness is set to 0, read in YAML immediately before next
19
+ # get(...)
20
+
21
+ class CloudConfig
22
+ class NodeNotFound < StandardError; end
23
+ class BadPathError < StandardError; end
24
+
25
+ DEFAULT_SERVERS = "localhost:2181"
26
+
27
+ def initialize(zkservers = DEFAULT_SERVERS, staleness = 15) # maximum allowed staleness in seconds
28
+ @staleness = staleness
29
+ @lock = Mutex.new
30
+ @zkservers = DEFAULT_SERVERS
31
+
32
+ # cache
33
+ @data = {}
34
+ @zkcb = Zookeeper::WatcherCallback.new { dirty_callback(@zkcb.context) }
35
+ @zk = nil
36
+ end
37
+
38
+ def get(node)
39
+ filename, keys = extract_filename(node)
40
+
41
+ # read(filename) is potentially a zk call, so do not hold the lock during the read
42
+ if @lock.synchronize { !@data.has_key?(filename) }
43
+ d = YAML.load(read(filename))
44
+ @lock.synchronize { @data[filename] = d }
45
+ end
46
+
47
+ # synchronized b/c we potentially have a background thread updating data nodes from zk
48
+ # if keys is empty, return the whole file, otherwise roll up the keys
49
+ @lock.synchronize {
50
+ keys.empty? ? @data[filename] : keys.inject(@data[filename]) { |hash, key| hash[key] }
51
+ }
52
+ end
53
+
54
+ # todo:
55
+ # factor get-and-watch into a different subsystem (so you can have
56
+ # polling stat() ops on local filesystem.)
57
+ def read(yaml)
58
+ # read yaml file and register watcher. if watcher fires, set up
59
+ # background thread to do read and update data.
60
+ if yaml.match(/^zk:/)
61
+ @zk ||= init_zk
62
+ yaml = yaml['zk:'.length..-1] # strip off zk: from zk:/config/path.yml
63
+ resp = get_and_register(yaml)
64
+
65
+ if resp[:rc] != Zookeeper::ZOK
66
+ @zk.unregister_watcher(resp[:req_id])
67
+ raise NodeNotFound
68
+ end
69
+
70
+ resp[:data]
71
+ else
72
+ raise NodeNotFound unless File.exists?(yaml)
73
+ File.read(yaml)
74
+ end
75
+ end
76
+
77
+ def extract_filename(node)
78
+ path_elements = node.split("/")
79
+
80
+ yamlindex = path_elements.map{ |x| x.match("\.yml$") != nil }.index(true)
81
+ raise BadPathError unless yamlindex
82
+
83
+ yamlname = path_elements[0..yamlindex].join '/'
84
+ yamlkeys = path_elements[(yamlindex+1)..-1]
85
+
86
+ return yamlname, yamlkeys
87
+ end
88
+
89
+ private
90
+ def init_zk
91
+ Zookeeper.new(@zkservers)
92
+ end
93
+
94
+ def get_and_register(znode)
95
+ @zk.get(:path => znode, :watcher => @zkcb,
96
+ :watcher_context => { :path => znode,
97
+ :wait => rand(@staleness) })
98
+ end
99
+
100
+ def dirty_callback(context)
101
+ path = context[:path]
102
+ wait = context[:wait]
103
+
104
+ # Fire off a background update that waits a randomized period of time up
105
+ # to @staleness seconds.
106
+ Thread.new do
107
+ sleep wait
108
+ background_update(path)
109
+ end
110
+ end
111
+
112
+ def background_update(zkpath)
113
+ # do a synchronous get/register a new watcher
114
+ resp = get_and_register(zkpath)
115
+ if resp[:rc] != Zookeeper::ZOK
116
+ # puts "Unable to read #{zkpath} from Zookeeper!" @logger.error
117
+ zk.unregister_watcher(resp[:req_id])
118
+ else
119
+ # puts "Updating data."
120
+ d = YAML.load(resp[:data])
121
+ @lock.synchronize { @data["zk:#{zkpath}"] = d }
122
+ end
123
+ end
124
+ end
125
+
Binary file