Class: ZK::Client::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/zk/client/base.rb

Overview

Note:

There is a lot of functionality mixed into the subclasses of this class! You should take a look at Unixisms, Conveniences, and StateMixin for a lot of the higher-level functionality!

This class forms the base API for interacting with ZooKeeper. Most people will want to create instances of the class ZK::Client::Threaded, and the most convenient way of doing that is through the top-level method ZK.new

Examples:

Create a new default connection


# if no host:port is given, we connect to localhost:2181 by default
# (convenient for use in tests and in irb/pry)

zk = ZK.new

Create a new connection, specifying host


zk = ZK.new('localhost:2181')

For quick tasks, you can use the visitor pattern, (like the File class)


ZK.open('localhost:2181') do |zk|
  # do stuff with connection
end

# connection is automatically closed

How to handle a fork()


zk = ZK.new

fork do
  zk.reopen() # <-- reopen is the important thing

  zk.create('/child/pid', $$.to_s, :ephemeral => true)  # for example.

  # etc.
end

Direct Known Subclasses

Threaded

Constant Summary

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, opts = {}) ⇒ Base

This method is abstract.

Overridden in subclasses

Create a new client and connect to the zookeeper server.

Examples:

Threaded client with two hosts and a chroot path


ZK::Client.new("zk01:2181,zk02:2181/chroot/path")

See Also:



100
101
102
103
104
# File 'lib/zk/client/base.rb', line 100

def initialize(host, opts={})
  # keep track of the process we were in when we started
  @host = host
  @pid  = Process.pid
end

Instance Attribute Details

#event_handlerObject (readonly)

The Eventhandler is used by client code to register callbacks to handle events triggered for given paths.

See Also:



49
50
51
# File 'lib/zk/client/base.rb', line 49

def event_handler
  @event_handler
end

Instance Method Details

#children(path, opts = {}) ⇒ Object

Note:

It is important to note that the list of children is not sorted. If you need them to be ordered, you must call .sort on the returned array

Return the list of the children of the node of the given path.

If the watch is true and the call is successful (no exception is thrown), registered watchers of the children of the node will be enabled. The watch will be triggered by a successful operation that deletes the node of the given path or creates/delete a child under the node. See watcher for documentation on how to register blocks to be called when a watch event is fired.

Examples:

get children for path


zk.create("/path", :data => "foo")
zk.create("/path/child_0", :data => "child0")
zk.create("/path/child_1", :data => "child1")
zk.children("/path")
# => ["child_0", "child_1"]

get children and set watch


# same setup as above

zk.children("/path", :watch => true)
# => ["child_0", "child_1"]

Options Hash (opts):

  • :watch (bool) — default: false

    set to true if you want your registered callbacks for this node to be called on change

  • :ignore (:no_node) — default: nil

    Do not raise an error if one of the given statuses is returned from ZooKeeper. This option may be given as either a symbol (for a single option) or as an Array of symbols for multiple ignores.

    • :no_node: silences the error case where you try to set /foo/bar/baz but it doesn't exist.

Raises:



712
713
714
715
716
717
718
719
720
721
# File 'lib/zk/client/base.rb', line 712

def children(path, opts={})

  h = { :path => path }.merge(opts)

  rv = setup_watcher!(:child, h) do
    call_and_check_rc(:get_children, h)
  end

  opts[:callback] ? rv : rv[:children]
end

#closeObject

close the underlying connection, but do not reset callbacks registered via the register method. This is to be used when preparing to fork.



142
143
144
# File 'lib/zk/client/base.rb', line 142

def close
  wrap_state_closed_error { cnx.close if cnx && !cnx.closed? }
end

#close!Object

close the underlying connection and clear all pending events.



135
136
137
138
# File 'lib/zk/client/base.rb', line 135

def close!
  event_handler.close
  close
end

#closed?Boolean

returns true if the connection has been closed



74
75
76
77
78
79
# File 'lib/zk/client/base.rb', line 74

def closed?
  return true if cnx.nil?

  # XXX: should this be *our* idea of closed or ZOO_CLOSED_STATE ?
  defined?(::JRUBY_VERSION) ? jruby_closed? : mri_closed?
end

#connect(opts = {}) ⇒ Object

Connect to the server/cluster. This is called automatically by the constructor by default.



148
149
# File 'lib/zk/client/base.rb', line 148

def connect(opts={})
end

#create(path, opts = {}) ⇒ String? #create(path, data, opts = {}) ⇒ String?

TODO:

Document the asynchronous methods

Create a node with the given path. The node data will be the given data. The path is returned.

If the ephemeral option is given, the znode created will be removed by the server automatically when the session associated with the creation of the node expires. Note that ephemeral nodes cannot have children.

The sequence option, if true, will cause the server to create a sequential node. The actual path name of a sequential node will be the given path plus a suffix "_i" where i is the current sequential number of the node. Once such a node is created, the sequential number for the path will be incremented by one (i.e. the generated path will be unique across all clients).

Note that since a different actual path is used for each invocation of creating sequential node with the same path argument, the call will never throw a NodeExists exception.

If a node is created successfully, the ZooKeeper server will trigger the watches on the path left by exists calls, and the watches on the parent of the node by children calls.

Examples:

create node, no data, persistent


zk.create("/path")
# => "/path"

create node, ACL will default to ACL::OPEN_ACL_UNSAFE


zk.create("/path", "foo")
# => "/path"

create ephemeral node


zk.create("/path", '', :mode => :ephemeral)
# => "/path"

create sequential node


zk.create("/path", '', :sequential => true)
# => "/path0"

# or you can also do:

zk.create("/path", '', :mode => :persistent_sequential)
# => "/path0"

create ephemeral and sequential node


zk.create("/path", '', :sequence => true, :ephemeral => true)
# => "/path0"

# or you can also do:

zk.create("/path", "foo", :mode => :ephemeral_sequential)
# => "/path0"

create a child path


zk.create("/path/child", "bar")
# => "/path/child"

create a sequential child path


zk.create("/path/child", "bar", :sequence => true, :ephemeral => true)
# => "/path/child0"

# or you can also do:

zk.create("/path/child", "bar", :mode => :ephemeral_sequential)
# => "/path/child0"

Overloads:

  • #create(path, opts = {}) ⇒ String?

    creates a znode at the absolute path with blank data and given options

    Options Hash (opts):

    • :callback (Zookeeper::Callbacks::StringCallback) — default: nil

      provide a callback object that will be called when the znode has been created

    • :context (Object) — default: nil

      an object passed to the :callback given as the context param

    • :or (:set, nil) — default: nil

      syntactic sugar to say 'if this path already exists, then set its contents.' Note that this will also create all intermediate paths as it delegates to Unixisms#mkdir_p. Note that this option can only be used to create or set persistent, non-sequential paths. If an option is used to specify either, an ArgumentError will be raised. (note: not available for zk-eventmachine)

    • :mode (:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral) — default: nil

      may be specified instead of :ephemeral and :sequence options. If :mode and either of the :ephermeral or :sequential options are given, the :mode option will win

    • :ignore (:no_node, :node_exists) — default: nil

      Do not raise an error if one of the given statuses is returned from ZooKeeper. This option may be given as either a symbol (for a single option) or as an Array of symbols for multiple ignores. This is useful when you want to create a node but don't care if it's already been created, and don't want to have to wrap it in a begin/rescue/end block.

      • :no_node: silences the error case where you try to create /foo/bar/baz but any of the parent paths (/foo or /foo/bar) don't exist.

      • :node_exists: silences the error case where you try to create /foo/bar but it already exists.

  • #create(path, data, opts = {}) ⇒ String?

    creates a znode at the absolute path with given data and options

    Options Hash (opts):

    • :callback (Zookeeper::Callbacks::StringCallback) — default: nil

      provide a callback object that will be called when the znode has been created

    • :context (Object) — default: nil

      an object passed to the :callback given as the context param

    • :or (:set, nil) — default: nil

      syntactic sugar to say 'if this path already exists, then set its contents.' Note that this will also create all intermediate paths as it delegates to Unixisms#mkdir_p. Note that this option can only be used to create or set persistent, non-sequential paths. If an option is used to specify either, an ArgumentError will be raised. (note: not available for zk-eventmachine)

    • :mode (:ephemeral_sequential, :persistent_sequential, :persistent, :ephemeral) — default: nil

      may be specified instead of :ephemeral and :sequence options. If :mode and either of the :ephermeral or :sequential options are given, the :mode option will win

    • :ignore (:no_node, :node_exists) — default: nil

      Do not raise an error if one of the given statuses is returned from ZooKeeper. This option may be given as either a symbol (for a single option) or as an Array of symbols for multiple ignores. This is useful when you want to create a node but don't care if it's already been created, and don't want to have to wrap it in a begin/rescue/end block.

      • :no_node: silences the error case where you try to create /foo/bar/baz but any of the parent paths (/foo or /foo/bar) don't exist.

      • :node_exists: silences the error case where you try to create /foo/bar but it already exists.

Raises:

Since:

  • 1.4.0: :ignore option



350
351
352
353
354
# File 'lib/zk/client/base.rb', line 350

def create(path, *args)
  h = parse_create_args(path, *args)
  rv = call_and_check_rc(:create, h)
  h[:callback] ? rv : rv[:path]
end

#delete(path, opts = {}) ⇒ Object

Delete the node with the given path. The call will succeed if such a node exists, and the given version matches the node's version (if the given version is -1, it matches any node's versions), and the node has no children.

This operation, if successful, will trigger all the watches on the node of the given path left by exists API calls, and the watches on the parent node left by children API calls.

Can be called with just the path, otherwise a hash with the arguments set. Supports being executed asynchronousy by passing a callback object.

Examples:

delete a node

zk.delete("/path")

delete a node with a specific version

zk.delete("/path", :version => 5)

Options Hash (opts):

  • :version (Integer) — default: -1

    matches all versions of a node if the default is used, otherwise acts as an assertion that the znode has the supplied version.

  • :callback (Zookeeper::Callbacks::VoidCallback)

    will be called asynchronously when the operation is complete

  • :context (Object)

    an object passed to the :callback given as the context param

  • :ignore (:no_node, :not_empty, :bad_version) — default: nil

    Do not raise an error if one of the given statuses is returned from ZooKeeper. This option may be given as either a symbol (for a single option) or as an Array of symbols for multiple ignores. This is useful when you want to delete a node but don't care if it's already been deleted, and don't want to have to wrap it in a begin/rescue/end block.

    • :no_node: silences the error case where you try to delete /foo/bar/baz but it doesn't exist.

    • :not_empty: silences the error case where you try to delete /foo/bar but it has children.

    • :bad_version: silences the error case where you give a :version but it doesn't match the server's version.

Raises:

Since:

  • 1.4.0: :ignore option



790
791
792
793
794
# File 'lib/zk/client/base.rb', line 790

def delete(path, opts={})
  h = { :path => path, :version => -1 }.merge(opts)
  rv = call_and_check_rc(:delete, h)
  opts[:callback] ? rv : nil
end

#event_dispatch_thread?Boolean

returns true if the caller is calling from the event dispatch thread



1023
1024
1025
# File 'lib/zk/client/base.rb', line 1023

def event_dispatch_thread?
  cnx.event_dispatch_thread?
end

#exists?(path, opts = {}) ⇒ Boolean

sugar around stat

only works for the synchronous version of stat. for async version, this method will act exactly like stat

Examples:


# instead of:

zk.stat('/path').exists?
# => true

# you can do:

zk.exists?('/path')
# => true


646
647
648
649
650
# File 'lib/zk/client/base.rb', line 646

def exists?(path, opts={})
  # XXX: this should use the underlying 'exists' call!
  rv = stat(path, opts)
  opts[:callback] ? rv : rv.exists?
end

#get(path, opts = {}) ⇒ Array

TODO:

fix references to Watcher documentation

Return the data and stat of the node of the given path.

If :watch is true and the call is successful (no exception is raised), registered watchers on the node will be 'armed'. The watch will be triggered by a successful operation that sets data on the node, or deletes the node. See watcher for documentation on how to register blocks to be called when a watch event is fired.

Supports being executed asynchronousy by passing a callback object.

Examples:

get data for path


zk.get("/path")
# => ['this is the data', #<Zookeeper::Stat>]

get data and set watch on node


zk.get("/path", :watch => true)
# => ['this is the data', #<Zookeeper::Stat>]

Options Hash (opts):

  • :watch (bool) — default: false

    set to true if you want your registered callbacks for this node to be called on change

  • :callback (Zookeeper::Callbacks::DataCallback)

    to make this call asynchronously

  • :context (Object)

    an object passed to the :callback given as the context param

Raises:



453
454
455
456
457
458
459
460
461
# File 'lib/zk/client/base.rb', line 453

def get(path, opts={})
  h = { :path => path }.merge(opts)

  rv = setup_watcher!(:data, h) do
    call_and_check_rc(:get, h)
  end

  opts[:callback] ? rv : rv.values_at(:data, :stat)
end

#get_acl(path, opts = {}) ⇒ Object

TODO:

this method is pretty much untested, YMMV

Return the ACL and stat of the node of the given path.

Examples:

get acl


zk.get_acl("/path")
# => [ACL]

get acl with stat


stat = ZK::Stat.new
zk.get_acl("/path", :stat => stat)
# => [ACL]

Options Hash (opts):

  • (nil) (Zookeeper::Stat)

    provide a Stat object that will be set with the Stat information of the node path

  • (nil) (ZookeeperCallback::AclCallback)

    :callback for an asynchronous call to occur

  • :context (Object) — default: nil

    an object passed to the :callback given as the context param

Raises:



836
837
838
839
840
# File 'lib/zk/client/base.rb', line 836

def get_acl(path, opts={})
  h = { :path => path }.merge(opts)
  rv = call_and_check_rc(:get_acl, h)
  opts[:callback] ? rv : rv.values_at(:children, :stat)
end

#register(path, interests = nil, &block) ⇒ EventHandlerSubscription #register(path, opts = {}, &block) ⇒ EventHandlerSubscription

Note:

All node watchers are one-shot handlers. After an event is delivered to your handler, you must re-watch the node to receive more events. This leads to a pattern you will find throughout ZK code that avoids races, see the example below "avoiding a race"

Register a block that should be delivered events for a given path. After registering a block, you need to call #get, #stat, or #children with the :watch => true option for the block to receive the next event (see note). #get and #stat will cause the block to receive events when the path is created, deleted, or its data is changed. #children will cause the block to receive events about its list of child nodes changing (i.e. being added or deleted, but not their content changing).

This method will return an EventHandlerSubscription instance that can be used to remove the block from further updates by calling its .unsubscribe method.

You can specify a list of event types after the path that you wish to receive in your block using the :only option. This allows you to register different blocks for different types of events. This sounds more convenient, but there is a potential pitfall. The :only option does filtering behind the scenes, so if you need a :created event, but a :changed event is delivered instead, and you don't have a handler registered for the :changed event which re-watches, then you will most likely just miss it and blame the author. You should try to stick to the style where you use a single block to test for the different event types, re-registering as necessary. If you find that block gets too out of hand, then use the :only option and break the logic up between handlers.

Examples:

avoiding a race waiting for a node to be deleted


# we expect that '/path/to/node' exists currently and want to be notified
# when it's deleted

# register a handler that will be called back when an event occurs on
# node
# 
node_subscription = zk.register('/path/to/node') do |event|
  if event.node_deleted?
    do_something_when_node_deleted
  end
end

# check to see if our condition is true *while* setting a watch on the node
# if our condition happens to be true while setting the watch
#
unless exists?('/path/to/node', :watch => true)
  node_subscription.unsubscribe   # cancel the watch
  do_something_when_node_deleted  # call the callback
end

only creation events


sub = zk.register('/path/to/znode', :only => :created) do |event|
  # do something when the node is created
end

only changed or children events


sub = zk.register('/path/to/znode', :only => [:changed, :child]) do |event|
  if event.node_changed?
    # do something on change
  else
    # we know it's a child event
  end
end

deprecated 1.0 style interests


sub = zk.register('/path/to/znode', [:changed, :child]) do |event|
  if event.node_changed?
    # do something on change
  else
    # we know it's a child event
  end
end

Overloads:

  • #register(path, interests = nil, &block) ⇒ EventHandlerSubscription
    Deprecated.

    use the :only => :created form

    Since:

    • 1.0

  • #register(path, opts = {}, &block) ⇒ EventHandlerSubscription

    Options Hash (opts):

    • :only (Array, Symbol, nil) — default: nil

      a symbol or array-of-symbols indicating which events you would like the block to be called for. Valid events are :created, :deleted, :changed, and :child. If nil, the block will receive all events

    Since:

    • 1.1

Yields:

  • (event)

    We will call your block with the watch event object (which has the connection the event occurred on as its #zk attribute)

See Also:



1018
1019
1020
# File 'lib/zk/client/base.rb', line 1018

def register(path, opts={}, &block)
  event_handler.register(path, opts, &block)
end

#reopen(timeout = nil) ⇒ Symbol

reopen the underlying connection

The timeout param is here mainly for legacy support.



130
131
# File 'lib/zk/client/base.rb', line 130

def reopen(timeout=nil)
end

#session_idFixnum



899
900
901
# File 'lib/zk/client/base.rb', line 899

def session_id
  cnx.session_id
end

#session_passwdString



904
905
906
# File 'lib/zk/client/base.rb', line 904

def session_passwd
  cnx.session_passwd
end

#set(path, data, opts = {}) ⇒ Stat?

Set the data for the node of the given path if such a node exists and the given version matches the version of the node (if the given version is -1, it matches any node's versions). Passing the version allows you to perform optimistic locking, in that if someone changes the node's data "behind your back", your update will fail. Since #create does not return a Zookeeper::Stat object, you should be aware that nodes are created with version == 0.

This operation, if successful, will trigger all the watches on the node of the given path left by get calls.

Called with a hash of arguments set. Supports being executed asynchronousy by passing a callback object.

Examples:

unconditionally set the data of "/path"


zk.set("/path", "foo")

set the data of "/path" only if the version is 0


zk.set("/path", "foo", :version => 0)

set the data of a non-existent node, check for success


if zk.set("/path/does/not/exist", 'blah', :ignore => :no_node)
  puts 'the node existed and we updated it to say "blah"'
else
  puts "pffft, i didn't wanna update that stupid node anyway"
end

fail to set the data of a node, ignore bad_version


data, stat = zk.get('/path')

if zk.set("/path", 'blah', :version => stat.version, :ignore => :bad_version)
  puts 'the node existed, had the right version, and we updated it to say "blah"'
else
  puts "guess someone beat us to it"
end

Options Hash (opts):

  • :version (Integer) — default: -1

    matches all versions of a node if the default is used, otherwise acts as an assertion that the znode has the supplied version.

  • :callback (Zookeeper::Callbacks::StatCallback)

    will recieve the Zookeeper::Stat object asynchronously

  • :context (Object)

    an object passed to the :callback given as the context param

  • :ignore (:no_node, :bad_version) — default: nil

    Do not raise an error if one of the given statuses is returned from ZooKeeper. This option may be given as either a symbol (for a single option) or as an Array of symbols for multiple ignores. This is useful when you want to set a node if it exists but don't care if it doesn't.

    • :no_node: silences the error case where you try to set /foo/bar/baz but it doesn't exist.

    • :bad_version: silences the error case where you give a :version but it doesn't match the server's version.

Raises:

Since:

  • 1.4.0: :ignore option



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/zk/client/base.rb', line 555

def set(path, data, opts={})
  h = { :path => path, :data => data }.merge(opts)

  rv = call_and_check_rc(:set, h)

  logger.debug { "rv: #{rv.inspect}" }

  # the reason we check the :rc here is: if the user set an :ignore which
  # has successfully squashed an error code from turning into an exception
  # we want to return nil. If the user was successful, we want to return
  # the Stat we got back from the server
  #
  # in the case of an async request, we want to return the result code of
  # the async operation (the submission)
  
  if opts[:callback]
    rv 
  elsif (rv[:rc] == Zookeeper::ZOK)
    rv[:stat]
  else
    nil
  end
end

#set_acl(path, acls, opts = {}) ⇒ Object

Set the ACL for the node of the given path if such a node exists and the given version matches the version of the node. Return the stat of the node.

@todo: TBA - waiting on clarification of method use

Options Hash (opts):

  • :version (Integer) — default: -1

    matches all versions of a node if the default is used, otherwise acts as an assertion that the znode has the supplied version.

  • :callback (Zookeeper::Callbacks::VoidCallback)

    will be called asynchronously when the operation is complete

  • :context (Object)

    an object passed to the :callback given as the context param

Raises:



867
868
869
870
871
# File 'lib/zk/client/base.rb', line 867

def set_acl(path, acls, opts={})
  h = { :path => path, :acl => acls }.merge(opts)
  rv = call_and_check_rc(:set_acl, h)
  opts[:callback] ? rv : rv[:stat]
end

#stat(path, opts = {}) ⇒ Zookeeper::Stat

Return the stat of the node of the given path. Return nil if the node doesn't exist.

If the watch is true and the call is successful (no exception is thrown), a watch will be left on the node with the given path. The watch will be triggered by a successful operation that creates/delete the node or sets the data on the node.

Can be called with just the path, otherwise a hash with the arguments set. Supports being executed asynchronousy by passing a callback object.

Examples:

get stat for for path

>> zk.stat("/path")
# => ZK::Stat

get stat for path and enable watchers

>> zk.stat("/path", :watch => true)
# => ZK::Stat

exists for non existent path


>> stat = zk.stat("/non_existent_path")
# => #<Zookeeper::Stat:0x000001eb54 @exists=false>
>> stat.exists?
# => false

Options Hash (opts):

  • :watch (bool) — default: false

    set to true if you want to enable registered watches on this node

  • :callback (Zookeeper::Callbacks::StatCallback)

    will recieve the Zookeeper::Stat object asynchronously

  • :context (Object)

    an object passed to the :callback given as the context param



619
620
621
622
623
624
625
626
# File 'lib/zk/client/base.rb', line 619

def stat(path, opts={})
  h = { :path => path }.merge(opts)

  setup_watcher!(:data, h) do
    rv = call_and_check_rc(:stat, h.merge(:ignore => :no_node))
    opts[:callback] ? rv : rv.fetch(:stat)
  end
end

#watcherObject

Deprecated.

for backwards compatibility only

use ZK::Client::Base#event_handler instead



69
70
71
# File 'lib/zk/client/base.rb', line 69

def watcher
  event_handler
end