Chef zookeeper_bridge
cookbook, used to help integrating the Chef Run with ZooKeeper.
It can help in the following:
- Installing and running Chef ZooKeeper Handler.
- Reading or writing Chef Node attributes to and from ZooKeeper.
- Running ZooKeeper Client commands.
- Interacting with ZooKeeper read/write locks during the Chef Run.
- Interacting with ZooKeeper semaphores during the Chef Run.
- Wait until a ZooKeeper znode has the desired state or a certain event happens.
Some of the resources included in this cookbook have not been widely tested, so you should consider this cookbook as something experimental.
This cookbook is mainly used by calling the resources it provides. See their documentation below. The zookeeper_bridge::default
recipe needs to be included before their use.
This cookbook has been tested on the following platforms:
- Amazon
- CentOS
- Debian
- Ubuntu
Please, let us know if you use it successfully on any other platform.
- build-essential
~> 2.0
- chef_handler
- Ruby
1.9.3
or higher. zk
ruby gem.
Attribute | Default | Description |
---|---|---|
node['zookeeper_bridge']['server'] |
'127.0.0.1:2181' |
ZooKeeper server address. |
node['zookeeper_bridge']['chef_handler']['version'] |
nil (latest) |
chef-handler-zookeeper gem version to install. |
node['zookeeper_bridge']['chef_handler']['znode'] |
"/chef/#{node.name}/status" |
chef-handler-zookeeper znode path. The path must be absolute. |
Recipe required before using the resources.
Installs and enables chef-handler-zookeeper
gem.
The node['zookeeper_bridge']['chef_handler']['znode']
path must exist before calling this recipe:
$ ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] create /chef {}
[zk: localhost:2181(CONNECTED) 1] create /chef/server1.example.com {}
[zk: localhost:2181(CONNECTED) 2] create /chef/server1.example.com/status {}
Or using the recipe itself:
# We set the ZooKeeper server address
node.default['zookeeper_bridge']['server'] = 'zk.example.com'
# zookeeper_bridge_cli resource should ignore cli errors if they already exist
zookeeper_bridge_cli 'create /chef {}'
zookeeper_bridge_cli "create /chef/#{node.name} {}"
zookeeper_bridge_cli "create /chef/#{node.name}/status {}"
This is because the chef-handler-zookeeper
requires that the znode exists.
Now we can install and enable the handler:
node.default['zookeeper_bridge']['chef_handler']['znode'] = "/chef/#{node.name}/status"
include_recipe 'zookeeper_bridge::chef_handler'
Install some dependencies required by this cookbook. Used by the other recipes.
Runs a Read or Shared Lock inside ZooKeeper. This resource is intended to be used together with the zookeeper_bridge_wrlock
resource.
run
Parameter | Default | Description |
---|---|---|
path | name | Znode path. The path can be relative to '/_zklocking' . |
server | node['zookeeper_bridge']['server'] |
ZooKeeper server address. |
wait | true |
This can be an integer to wait a maximum of seconds and raise a timeout exception if this time is exceeded. By default is set to true , which will wait infinitely. |
block | nil |
The recipe code that will be run within the lock. |
zookeeper_bridge_rdlock 'lock1' do
server 'zk.example.com'
block do
# recipe code can be used here
execute '...'
end
end
Then we can use an exclusive lock from another node:
zookeeper_bridge_wrlock 'lock1' do
server 'zk.example.com'
block do
# recipe code can be used here
execute '...'
end
end
Runs a Write or Exclusive Lock inside ZooKeeper. This resource is intended to be used together with the zookeeper_bridge_rdlock
resource.
run
Parameter | Default | Description |
---|---|---|
path | name | Znode path. The path can be relative to '/_zklocking' . |
server | node['zookeeper_bridge']['server'] |
ZooKeeper server address. |
wait | true |
This can be an integer to wait a maximum of seconds and raise a timeout exception if this time is exceeded. By default is set to true , which will wait infinitely. |
block | nil |
The recipe code that will be run within the lock. |
The following block will only be running by a maximum of one node at a particular instant:
zookeeper_bridge_wrlock 'lock1' do
server 'zk.example.com'
block do
# recipe code can be used here
execute '...'
end
end
Runs a Semaphore inside ZooKeeper.
run
Parameter | Default | Description |
---|---|---|
path | name | Znode path. The path can be relative to '/_zksemaphore' . |
server | node['zookeeper_bridge']['server'] |
ZooKeeper server address. |
size | nil |
Semaphore size: the maximum number of nodes that will be able to run the block at the same time. |
block | nil |
The recipe code that will be run within the semaphore. |
wait | true |
This can be an integer to wait a maximum of seconds and raise a timeout exception if this time is exceeded. By default is set to true , which will wait infinitely. |
You can call this from multiple nodes. The code within the following block will be running by a maximum of three nodes at the same time:
zookeeper_bridge_sem 'sem1' do
server 'zk.example.com'
size 3
block do
# recipe code can be used here
execute '...'
end
end
Used to read or write Chef Node attributes from or to ZooKeeper znode paths. The attributes are saved into the znode using JSON format.
read
: Read Node attributes from a znode.write
: Write Node attributes to a znode.
Parameter | Default | Description |
---|---|---|
path | name | Znode path. The path must be absolute. |
server | node['zookeeper_bridge']['server'] |
ZooKeeper server address. |
attribute | nil |
Node attribute object or a Ruby Hash. This should be something like node['foo'] for reading and node.normal['foo'] for writing. |
merge | calculated | Whether to merge hashes. This is true by default for :read action, which will merge the current node attributes with the attributes read from ZooKeeper. For :write is false by default, which will not merge the current attributes saved in ZooKeeper with the node attributes to write, the data in ZooKeeper will be completely overwritten. |
force_encoding | nil |
Force character encoding. For example: 'UTF-8' . |
The znode to read attributes from must exist before reading it. For writing, at least the parent znode must exist:
$ ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] create /chef {}
[zk: localhost:2181(CONNECTED) 1] create /chef/server1.example.com {}
[zk: localhost:2181(CONNECTED) 2] create /chef/server1.example.com/read_attributes {"attr1":"value1"}
We can also create them from a recipe using zookeeper_bridge_cli
:
# We set the ZooKeeper server address
node.default['zookeeper_bridge']['server'] = 'zk.example.com'
# zookeeper_bridge_cli resource should ignore cli errors if they already exist
zookeeper_bridge_cli('create /chef {}').run_action(:run)
zookeeper_bridge_cli("create /chef/#{node.name} {}").run_action(:run)
# zkCli.sh does not support spaces in the data:
zookeeper_bridge_cli("create /chef/#{node.name}/read_attributes {\"attr1\":\"value1\"}")
.run_action(:run)
Now we can read and write all node attributes from and to ZooKeeper:
zookeeper_bridge_attrs "/chef/#{node.name}/read_attributes" do
attribute node.normal
action :nothing
end.run_action(:read)
# [...]
zookeeper_bridge_attrs "/chef/#{node.name}/write_attributes" do
attribute node.attributes
action :write
end
Note: You need to understand how compile and converge phases work on Chef Run to know when to use #run_action
.
As in the previous example, we create the necessary znodes:
$ ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] create /chef {}
[zk: localhost:2181(CONNECTED) 1] create /chef/server1.example.com {}
[zk: localhost:2181(CONNECTED) 2] create /chef/server1.example.com/apache_attributes {}
We can also create them from a recipe using zookeeper_bridge_cli
:
# We set the ZooKeeper server address
node.default['zookeeper_bridge']['server'] = 'zk.example.com'
# zookeeper_bridge_cli resource should ignore cli errors if they already exist
zookeeper_bridge_cli 'create /chef {}'
zookeeper_bridge_cli "create /chef/#{node.name} {}"
zookeeper_bridge_cli "create /chef/#{node.name}/apache_attributes {}"
Now we can read and write apache attributes:
# We use override in this case to overwrite default and normal values, why not?
zookeeper_bridge_attrs "/chef/#{node.name}/apache_attributes" do
attribute node.override['apache']
action :nothing
end.run_action(:read)
# [...]
zookeeper_bridge_attrs "/chef/#{node.name}/apache_attributes" do
attribute node['apache']
action :write
end
Waits until a given ZooKeeper znode path exists, does not exist or changes its state.
run
Parameter | Default | Description |
---|---|---|
path | name | Znode path. The path must be absolute. |
server | node['zookeeper_bridge']['server'] |
ZooKeeper server address. |
status | :any |
Wait until znode has this status. Possible values: :any , :created or :deleted. . :any means to ignore the status, normally used when the event parameter below is set. |
event | :none |
Wait until specific znode event occurs. Possible values: :none , :created , :deleted. , :changed , :child or an array of multiple values. :none means to ignore the events, normally used when the status parameter is set. :child is for znode child events. |
Wait until host znode is created (at compile time, to avoid compiling the next resources):
zookeeper_bridge_wait "/chef/#{node.name}" do
status :created
event :none
action :nothing
end.run_action(:run)
Wait until the attributes exists before reading them:
zookeeper_bridge_wait "/chef/#{node.name}/attributes" do
status :created
event :none
action :nothing
end.run_action(:run)
zookeeper_bridge_attrs "/chef/#{node.name}/attributes" do
attribute node.normal
action :nothing
end.run_action(:read)
Continue the Chef Run convergence only when the stop znode does not exist:
zookeeper_bridge_wait "/chef/#{node.name}/chef_stop" do
status :deleted
event :none
end
Continue the Chef Run convergence only when the continue znode is updated:
zookeeper_bridge_wait "/chef/#{node.name}/chef_continue" do
status :any
event :changed
end
Runs a ZooKeeper command using the zkCli.sh
script. This resource should be run from the ZooKeeper server node, because zkCli.sh
connects to localhost (connecting to remote server is not supported yet).
Remember that this script has some limitations, so use it with caution.
run
: Runs a command.
Parameter | Default | Description |
---|---|---|
command | name | ZooKeeper zkCli.sh command. |
base_path | calculated | ZooKeeper installation path. |
sleep | nil |
Time to sleep in seconds before the command is run (type Float ). |
background | false |
Whether to run the command in background. |
zookeeper_bridge_cli 'create /test some_random_data'
This resource is currently used in the integration tests. See the zookeeper_bridge_test cookbook recipes for more usage examples.
See TESTING.md.
Helper method for locating a zookeeper_bridge_attrs
resource in the collection.
resource = chef_run.zookeeper_bridge_attrs("/chef/#{node['fqdn']}/attributes")
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run reads zookeeper_bridge_attrs
at compile time.
expect(chef_run).to read_zookeeper_bridge_attrs("/chef/#{node['fqdn']}/attributes").at_compile_time
Assert that the Chef Run writes zookeeper_bridge_attrs
.
expect(chef_run).to write_zookeeper_bridge_attrs("/chef/#{node['fqdn']}/attributes")
Helper method for locating a zookeeper_bridge_cli
resource in the collection.
resource = chef_run.zookeeper_bridge_cli('ls /chef')
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run runs zookeeper_bridge_cli
.
expect(chef_run).to run_zookeeper_bridge_cli('create /test some_random_data')
Helper method for locating a zookeeper_bridge_rdlock
resource in the collection.
resource = chef_run.zookeeper_bridge_rdlock('my_lock')
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run runs zookeeper_bridge_rdlock
.
expect(chef_run).to run_zookeeper_bridge_rdlock('my_lock')
Helper method for locating a zookeeper_bridge_sem
resource in the collection.
resource = chef_run.zookeeper_bridge_sem('my_semaphore')
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run runs zookeeper_bridge_sem
.
expect(chef_run).to run_zookeeper_bridge_sem('my_semaphore')
.with_size(1)
Helper method for locating a zookeeper_bridge_wait
resource in the collection.
resource = chef_run.zookeeper_bridge_wait("/chef/#{node['fqdn']}/attributes")
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run runs zookeeper_bridge_wait
.
# ensure waits until the attributes file exists
expect(chef_run).to run_zookeeper_bridge_wait("/chef/#{node['fqdn']}/attributes")
.with_status(:created)
.with_event(:none)
Helper method for locating a zookeeper_bridge_wrlock
resource in the collection.
resource = chef_run.zookeeper_bridge_wrlock('my_lock')
expect(resource).to notify('service[apache2]').to(:reload)
Assert that the Chef Run runs zookeeper_bridge_wrlock
.
expect(chef_run).to run_zookeeper_bridge_wrlock('my_lock')
Please do not hesitate to open an issue with any questions or problems.
See CONTRIBUTING.md.
See TODO.md.
Author: | Xabier de Zuazo ([email protected]) |
Copyright: | Copyright (c) 2015, Xabier de Zuazo |
Copyright: | Copyright (c) 2013-2014, Onddo Labs, SL. |
License: | Apache License, Version 2.0 |
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.