├── .codeclimate.yml ├── .gitignore ├── .kitchen.yml ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── .yardopts ├── Berksfile ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── attributes └── default.rb ├── chefignore ├── libraries ├── zookeeper_config.rb └── zookeeper_service.rb ├── metadata.rb ├── recipes └── default.rb └── test ├── fixtures └── test-zookeeper-cluster │ ├── metadata.rb │ └── recipes │ └── default.rb ├── integration ├── default │ └── serverspec │ │ └── default_spec.rb └── helpers │ └── serverspec │ └── spec_helper.rb └── spec ├── libraries ├── zookeeper_config_spec.rb └── zookeeper_service_spec.rb ├── recipes └── default_spec.rb └── spec_helper.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | foodcritic: 4 | enabled: true 5 | rubocop: 6 | enabled: true 7 | ratings: 8 | paths: 9 | - "**.rb" 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore docs files 2 | _gh_pages 3 | _site 4 | .ruby-version 5 | .node-version 6 | Gemfile.lock 7 | 8 | # Numerous always-ignore extensions 9 | *.diff 10 | *.err 11 | *.orig 12 | *.log 13 | *.rej 14 | *.swo 15 | *.swp 16 | *.zip 17 | *.vi 18 | *~ 19 | 20 | # OS or Editor folders 21 | .DS_Store 22 | ._* 23 | Thumbs.db 24 | .cache 25 | .project 26 | .settings 27 | .tmproj 28 | *.esproj 29 | nbproject 30 | *.sublime-project 31 | *.sublime-workspace 32 | .idea 33 | 34 | # Komodo 35 | *.komodoproject 36 | .komodotools 37 | 38 | # grunt-html-validation 39 | validation-status.json 40 | validation-report.json 41 | 42 | # Folders to ignore 43 | bin 44 | node_modules 45 | tmp 46 | vendor 47 | .bundle 48 | 49 | # Chef specifics to ignore 50 | .chef 51 | .chefdk 52 | .kitchen 53 | .vagrant 54 | Berksfile.lock 55 | coverage/ 56 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | 5 | provisioner: 6 | name: chef_zero 7 | 8 | platforms: 9 | - name: ubuntu-14.04 10 | - name: ubuntu-12.04 11 | - name: centos-7.1 12 | - name: centos-6.7 13 | 14 | suites: 15 | - name: default 16 | run_list: 17 | - "recipe[test-zookeeper-cluster::default]" 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | --default-path test/spec 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AllCops: 3 | Exclude: 4 | - 'Guardfile' 5 | - 'Rakefile' 6 | - 'Vagrantfile' 7 | - 'Policyfile.rb' 8 | - 'Berksfile' 9 | - 'Thorfile' 10 | - 'Gemfile' 11 | - 'metadata.rb' 12 | - 'test/**/*' 13 | - 'bin/**' 14 | - 'vendor/**/*' 15 | AlignParameters: 16 | Enabled: false 17 | ClassLength: 18 | Enabled: false 19 | CyclomaticComplexity: 20 | Enabled: false 21 | Documentation: 22 | Enabled: false 23 | Encoding: 24 | Enabled: false 25 | Style/FileName: 26 | Enabled: false 27 | LineLength: 28 | Enabled: false 29 | MethodLength: 30 | Enabled: false 31 | Metrics/AbcSize: 32 | Enabled: false 33 | PerceivedComplexity: 34 | Enabled: false 35 | Style/SpaceBeforeFirstArg: 36 | Enabled: false 37 | Style/ClassAndModuleChildren: 38 | Enabled: false 39 | Style/FileName: 40 | Enabled: false 41 | Style/GuardClause: 42 | Enabled: false 43 | Style/PercentLiteralDelimiters: 44 | Enabled: false 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | cache: bundler 4 | sudo: false 5 | notifications: 6 | slack: bloomberg-rnd:BvYmxrV9xj902XWTRNrkLNkR 7 | script: bundle exec rake travis 8 | rvm: 9 | - 2.2 10 | branches: 11 | only: 12 | - master 13 | matrix: 14 | fast_finish: true 15 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --plugin classmethods 2 | --embed-mixin ClassMethods 3 | --hide-api private 4 | --markup markdown 5 | --hide-void-return 6 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | metadata 3 | 4 | group :test, :development do 5 | cookbook 'test-zookeeper-cluster', path: File.expand_path('../test/fixtures/test-zookeeper-cluster', __FILE__) 6 | end 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | 7 | ## 1.3.1 8 | - [PR#18] Fixes problem where java-properties gem isn't loaded. 9 | 10 | ## 1.3.0 11 | - [PR#16] Fixes service resource not restarting when upgraded. 12 | - [PR#17] Adds support for log4j configuration. 13 | 14 | ## 1.2.0 15 | - Fixes several issues with usage of the Poise Service cookbook. 16 | - Fixes a few bits for testing and linting harness. 17 | 18 | ## 1.0.0 19 | - Custom resources for managing Apache Zookeeper configuration and service lifecycle. 20 | - Default recipe which installs Apache Zookeeper and starts service from attributes. 21 | 22 | [PR#16]: https://github.com/bloomberg/zookeeper-cookbook/pull/16 23 | [PR#17]: https://github.com/bloomberg/zookeeper-cookbook/pull/17 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'berkshelf' 3 | gem 'poise', '~> 2.2' 4 | gem 'poise-service', '~> 1.0' 5 | gem 'poise-boiler' 6 | 7 | group :lint do 8 | gem 'rubocop' 9 | gem 'foodcritic' 10 | end 11 | 12 | group :unit do 13 | gem 'chefspec' 14 | end 15 | 16 | group :integration do 17 | gem 'serverspec' 18 | end 19 | 20 | group :development do 21 | gem 'awesome_print' 22 | gem 'guard' 23 | gem 'guard-kitchen' 24 | gem 'guard-rspec' 25 | gem 'guard-rubocop' 26 | gem 'rake' 27 | gem 'stove' 28 | end 29 | 30 | group :doc do 31 | gem 'yard' 32 | end 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2016, Bloomberg Finance L.P. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zookeeper-cluster-cookbook 2 | [![Build Status](https://img.shields.io/travis/bloomberg/zookeeper-cookbook.svg)](https://travis-ci.org/bloomberg/zookeeper-cookbook) 3 | [![Code Quality](https://img.shields.io/codeclimate/github/bloomberg/zookeeper-cookbook.svg)](https://codeclimate.com/github/bloomberg/zookeeper-cookbook) 4 | [![Cookbook Version](https://img.shields.io/cookbook/v/zookeeper-cluster.svg)](https://supermarket.chef.io/cookbooks/zookeeper-cluster) 5 | [![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 6 | 7 | [Application cookbook][0] which installs and configures 8 | [Apache Zookeeper][1]. 9 | 10 | Apache Zookeeper is a highly-available, centralized service which is 11 | commonly used for maintaining configuration information, distributed 12 | service discovery and providing coordination services. This cookbook 13 | takes a simplified approach towards configuring Apache Zookeeper. 14 | 15 | ## Basic Usage 16 | This cookbook was designed from the ground up to make it dead simple 17 | to install and configure a Zookeeper cluster using Chef. It also highlights 18 | several of our best practices for developing reusable Chef cookbooks 19 | at Bloomberg. 20 | 21 | This cookbook provides [node attributes](attributes/default.rb) which 22 | can be used to fine tune the default recipe which installs and 23 | configures Zookeeper. The values from the node attributes are passed 24 | directly into the configuration and service resources. 25 | 26 | Out of the box the following platforms are certified to work and 27 | are tested using our [Test Kitchen][8] configuration. Additional platforms 28 | _may_ work, but your mileage may vary. 29 | - CentOS (RHEL) 6.6, 7.1 30 | - Ubuntu 12.04, 14.04 31 | 32 | The correct way to use this cookbook is to create a 33 | [wrapper cookbook][2] which configures all of the members of the 34 | Zookeeper ensemble (cluster). We do this by using a data bag for each 35 | Chef environment. The default recipe in your wrapper cookbook may 36 | look something like the following block: 37 | ```ruby 38 | bag = data_bag_item('config', 'zookeeper')[node.chef_environment] 39 | node.default['zookeeper-cluster']['config']['instance_name'] = node['fqdn'] 40 | node.default['zookeeper-cluster']['config']['ensemble'] = bag 41 | include_recipe 'zookeeper-cluster::default' 42 | ``` 43 | 44 | The data bag for the above block should have an array of 45 | fully-qualified hostnames, the _exact_ ones that appear in 46 | `node['fqdn']`, which represent the members of the Zookeeper 47 | ensemble. These hostnames are used when configuring the Zookeeper 48 | service on each node. 49 | ```json 50 | { 51 | "id": "zookeeper", 52 | "development": [ 53 | "zk1.dev.inf.example.com", 54 | "zk2.dev.inf.example.com", 55 | "zk3.dev.inf.example.com" 56 | ], 57 | "production": [ 58 | "zk1.prod.inf.example.com", 59 | "zk2.prod.inf.example.com", 60 | "zk3.prod.inf.example.com", 61 | "zk4.prod.inf.example.com", 62 | "zk5.prod.inf.example.com", 63 | ] 64 | } 65 | ``` 66 | 67 | [0]: http://blog.vialstudios.com/the-environment-cookbook-pattern/#theapplicationcookbook 68 | [1]: https://zookeeper.apache.org 69 | [2]: http://blog.vialstudios.com/the-environment-cookbook-pattern#thewrappercookbook 70 | [3]: http://blog.vialstudios.com/the-environment-cookbook-pattern#thelibrarycookbook 71 | [4]: https://github.com/johnbellone/libartifact-cookbook 72 | [5]: https://github.com/poise/poise 73 | [6]: https://github.com/poise/poise-service 74 | [7]: https://github.com/skottler/selinux 75 | [8]: https://github.com/test-kitchen/test-kitchen 76 | [9]: https://zookeeper.apache.org/doc/trunk/zookeeperAdmin.html 77 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'bundler/setup' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop/rake_task' 6 | require 'foodcritic' 7 | require 'kitchen' 8 | 9 | namespace :style do 10 | desc 'Run Ruby style checks' 11 | RuboCop::RakeTask.new(:ruby) 12 | 13 | desc 'Run Chef style checks' 14 | FoodCritic::Rake::LintTask.new(:chef) 15 | end 16 | 17 | desc 'Run all style checks' 18 | task style: ['style:chef', 'style:ruby'] 19 | 20 | desc 'Run ChefSpec unit tests' 21 | RSpec::Core::RakeTask.new(:unit) do |t| 22 | t.pattern = 'test/spec/**{,/*/**}/*_spec.rb' 23 | end 24 | 25 | # Integration tests. Kitchen.ci 26 | desc 'Run Test Kitchen with Vagrant' 27 | task :vagrant do 28 | Kitchen.logger = Kitchen.default_file_logger 29 | Kitchen::Config.new.instances.each do |instance| 30 | instance.test(:always) 31 | end 32 | end 33 | 34 | desc 'Run style & unit tests on Travis' 35 | task travis: %w(style unit) 36 | 37 | # Default 38 | desc 'Run style, unit, and Vagrant-based integration tests' 39 | task default: %w(style unit vagrant) 40 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: zookeeper-cluster 3 | # License: Apache 2.0 4 | # 5 | # Copyright 2015-2016, Bloomberg Finance L.P. 6 | # 7 | default['zookeeper-cluster']['config']['path'] = '/etc/zookeeper/zoo.properties' 8 | default['zookeeper-cluster']['config']['ensemble'] = [] 9 | default['zookeeper-cluster']['config']['properties']['tickTime'] = 2_000 10 | default['zookeeper-cluster']['config']['properties']['initLimit'] = 5 11 | default['zookeeper-cluster']['config']['properties']['syncLimit'] = 2 12 | default['zookeeper-cluster']['config']['properties']['leaderServes'] = 'yes' 13 | default['zookeeper-cluster']['config']['properties']['forceSync'] = 'no' 14 | 15 | default['zookeeper-cluster']['service_name'] = 'zookeeper' 16 | default['zookeeper-cluster']['service_user'] = 'zookeeper' 17 | default['zookeeper-cluster']['service_group'] = 'zookeeper' 18 | 19 | default['zookeeper-cluster']['service']['environment']['ZOOCFGDIR'] = '/etc/zookeeper' 20 | default['zookeeper-cluster']['service']['environment']['JMXPORT'] = 9_010 21 | default['zookeeper-cluster']['service']['version'] = '3.5.0-alpha' 22 | default['zookeeper-cluster']['service']['binary_checksum'] = '87814f3afa9cf846db8d7e695e82e11480f7b19d79d8f146e58c4aefb4289bf4' 23 | default['zookeeper-cluster']['service']['binary_url'] = "http://mirror.cc.columbia.edu/pub/software/apache/zookeeper/zookeeper-%{version}/zookeeper-%{version}.tar.gz" # rubocop:disable Style/StringLiterals 24 | 25 | default['zookeeper-cluster']['log4j']['path'] = '/etc/zookeeper/log4j.properties' 26 | 27 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.root.logger'] = 'INFO, ROLLINGFILE' 28 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.console.threshold'] = 'INFO' 29 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.log.dir'] = '.' 30 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.log.file'] = 'zookeeper.log' 31 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.log.threshold'] = 'INFO' 32 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.tracelog.dir'] = '.' 33 | default['zookeeper-cluster']['log4j']['properties']['zookeeper.tracelog.file'] = 'zookeeper_trace.log' 34 | 35 | default['zookeeper-cluster']['log4j']['properties']['log4j.rootLogger'] = '${zookeeper.root.logger}' 36 | 37 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.CONSOLE'] = 'org.apache.log4j.ConsoleAppender' 38 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.CONSOLE.Threshold'] = '${zookeeper.console.threshold}' 39 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.CONSOLE.layout'] = 'org.apache.log4j.PatternLayout' 40 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.CONSOLE.layout.ConversionPattern'] = '%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n' 41 | 42 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE'] = 'org.apache.log4j.RollingFileAppender' 43 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.Threshold'] = '${zookeeper.log.threshold}' 44 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.File'] = '${zookeeper.log.dir}/${zookeeper.log.file}' 45 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.MaxFileSize'] = '10MB' 46 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.MaxBackupIndex'] = '25' 47 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.layout'] = 'org.apache.log4j.PatternLayout' 48 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.ROLLINGFILE.layout.ConversionPattern'] = '%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n' 49 | 50 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.TRACEFILE'] = 'org.apache.log4j.FileAppender' 51 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.TRACEFILE.Threshold'] = 'TRACE' 52 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.TRACEFILE.File'] = '${zookeeper.tracelog.dir}/${zookeeper.tracelog.file}' 53 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.TRACEFILE.layout'] = 'org.apache.log4j.PatternLayout' 54 | default['zookeeper-cluster']['log4j']['properties']['log4j.appender.TRACEFILE.layout.ConversionPattern'] = '%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n' 55 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # or sharing to the community site. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | Guardfile 55 | Procfile 56 | 57 | # SCM # 58 | ####### 59 | .git 60 | */.git 61 | .gitignore 62 | .gitmodules 63 | .gitconfig 64 | .gitattributes 65 | .svn 66 | */.bzr/* 67 | */.hg/* 68 | */.svn/* 69 | 70 | # Berkshelf # 71 | ############# 72 | cookbooks/* 73 | tmp 74 | 75 | # Cookbooks # 76 | ############# 77 | CONTRIBUTING 78 | CHANGELOG* 79 | 80 | # Strainer # 81 | ############ 82 | Colanderfile 83 | Strainerfile 84 | .colander 85 | .strainer 86 | 87 | # Vagrant # 88 | ########### 89 | .vagrant 90 | Vagrantfile 91 | 92 | # Travis # 93 | ########## 94 | .travis.yml 95 | -------------------------------------------------------------------------------- /libraries/zookeeper_config.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: zookeeper-cluster 3 | # License: Apache 2.0 4 | # 5 | # Copyright 2015-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ZookeeperClusterCookbook 10 | module Resource 11 | # A `zookeeper_config` fused resource which manages a 12 | # configuration for Zookeeper. 13 | # @action enable 14 | # @action disable 15 | # @action start 16 | # @action stop 17 | # @action restart 18 | # @action reload 19 | # @since 1.0 20 | class ZookeeperConfig < Chef::Resource 21 | include Poise(fused: true) 22 | provides(:zookeeper_config) 23 | 24 | attribute(:path, kind_of: String, name_attribute: true) 25 | attribute(:owner, kind_of: String, default: 'zookeeper') 26 | attribute(:group, kind_of: String, default: 'zookeeper') 27 | 28 | attribute(:instance_name, kind_of: String, required: true) 29 | attribute(:data_dir, kind_of: String, default: '/var/lib/zookeeper') 30 | attribute(:client_port, kind_of: Integer, default: 2181) 31 | attribute(:leader_port, kind_of: Integer, default: 2888) 32 | attribute(:election_port, kind_of: Integer, default: 3888) 33 | attribute(:ensemble, kind_of: Array, default: [], required: true) 34 | attribute(:properties, option_collector: true, default: {}) 35 | 36 | def myid 37 | ensemble.index(instance_name).next.to_s 38 | end 39 | 40 | # Outputs the +properties+ in the Java Properties file format. This is 41 | # what Zookeeper daemon consumes to tweak its internal configuration. 42 | def to_s 43 | servers = ensemble.map { |n| "server.#{ensemble.index(n).next}:#{n}:#{leader_port}:#{election_port}" } 44 | properties.merge( 45 | 'dataDir' => data_dir, 46 | 'leaderPort' => leader_port, 47 | 'clientPort' => client_port, 48 | 'electionPort' => election_port).map { |kv| kv.join('=') }.concat(servers).join("\n") 49 | end 50 | 51 | action(:create) do 52 | notifying_block do 53 | directory ::File.dirname(new_resource.path) do 54 | recursive true 55 | mode '0755' 56 | end 57 | 58 | directory new_resource.data_dir do 59 | owner new_resource.owner 60 | group new_resource.group 61 | recursive true 62 | mode '0755' 63 | end 64 | 65 | file ::File.join(new_resource.data_dir, 'myid') do 66 | content new_resource.myid 67 | mode '0644' 68 | end 69 | 70 | file new_resource.path do 71 | content new_resource.to_s 72 | mode '0644' 73 | end 74 | end 75 | end 76 | 77 | action(:delete) do 78 | notifying_block do 79 | directory new_resource.data_dir do 80 | action :delete 81 | end 82 | 83 | directory ::File.dirname(new_resource.path) do 84 | action :delete 85 | end 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /libraries/zookeeper_service.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: zookeeper-cluster 3 | # License: Apache 2.0 4 | # 5 | # Copyright 2015-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise_service/service_mixin' 8 | 9 | module ZookeeperClusterCookbook 10 | module Resource 11 | # A `zookeeper_service` resource which uses `poise_service`. 12 | # @action enable 13 | # @action disable 14 | # @action start 15 | # @action stop 16 | # @action restart 17 | # @action reload 18 | # @since 1.0 19 | class ZookeeperService < Chef::Resource 20 | include Poise 21 | provides(:zookeeper_service) 22 | include PoiseService::ServiceMixin 23 | 24 | # @!attribute version 25 | # @return [String] 26 | attribute(:version, kind_of: String, required: true) 27 | 28 | # @!attribute install_method 29 | # @return [Symbol] 30 | attribute(:install_method, default: 'binary', equal_to: %w{binary package}) 31 | 32 | # @!attribute install_path 33 | # @return [String] 34 | attribute(:install_path, kind_of: String, default: '/srv') 35 | 36 | # @!attribute user 37 | # @return [String] 38 | attribute(:user, kind_of: String, default: 'zookeeper') 39 | 40 | # @!attribute group 41 | # @return [String] 42 | attribute(:group, kind_of: String, default: 'zookeeper') 43 | 44 | # @!attribute environment 45 | # @return [String] 46 | attribute(:environment, kind_of: Hash, default: lazy { default_environment }) 47 | 48 | # @!attribute package_name 49 | # @return [String] 50 | attribute(:package_name, kind_of: String, default: 'zookeeper') 51 | 52 | # @!attribute binary_url 53 | # @return [String] 54 | attribute(:binary_url, kind_of: String) 55 | 56 | # @!attribute binary_url 57 | # @return [String] 58 | attribute(:binary_checksum, kind_of: String) 59 | 60 | # @!attribute data_dir 61 | # @return [String] 62 | attribute(:data_dir, kind_of: String, default: '/var/lib/zookeeper') 63 | 64 | # @!attribute data_log_dir 65 | # @return [String] 66 | attribute(:log_dir, kind_of: String, default: '/var/log/zookeeper') 67 | 68 | # @!attribute config_filename 69 | # @return [String] 70 | attribute(:config_path, kind_of: String, default: '/etc/zookeeper/zoo.properties') 71 | 72 | def default_environment 73 | { PATH: '/usr/local/bin:/usr/bin:/bin' } 74 | end 75 | 76 | def current_path 77 | ::File.join(install_path, 'zookeeper', 'current', "zookeeper-#{version}") 78 | end 79 | 80 | def command 81 | "#{current_path}/bin/zkServer.sh start-foreground #{config_path}" 82 | end 83 | end 84 | end 85 | 86 | module Provider 87 | # A `zookeeper_service` provider which uses `poise_service`. 88 | # @see ZookeeperCookbook::Resources::ZookeeperService 89 | # @provides zookeeper_service 90 | # @since 1.0 91 | class ZookeeperService < Chef::Provider 92 | include Poise 93 | provides(:zookeeper_service) 94 | include PoiseService::ServiceMixin 95 | 96 | def action_enable 97 | notifying_block do 98 | package new_resource.package_name do 99 | version new_resource.version unless new_resource.version.nil? 100 | only_if { new_resource.install_method == 'package' } 101 | end 102 | 103 | libartifact_file "zookeeper-#{new_resource.version}" do 104 | artifact_name 'zookeeper' 105 | artifact_version new_resource.version 106 | install_path new_resource.install_path 107 | remote_url new_resource.binary_url % { version: new_resource.version } 108 | remote_checksum new_resource.binary_checksum 109 | only_if { new_resource.install_method == 'binary' } 110 | end 111 | 112 | directory new_resource.data_dir do 113 | recursive true 114 | mode '0755' 115 | owner new_resource.user 116 | group new_resource.group 117 | end 118 | 119 | directory new_resource.log_dir do 120 | recursive true 121 | mode '0755' 122 | owner new_resource.user 123 | group new_resource.group 124 | end 125 | end 126 | super 127 | end 128 | 129 | def action_disable 130 | notifying_block do 131 | directory new_resource.data_dir do 132 | action :delete 133 | end 134 | end 135 | super 136 | end 137 | 138 | def service_options(service) 139 | service.command(new_resource.command) 140 | service.directory(new_resource.current_path) 141 | service.user(new_resource.user) 142 | service.environment(new_resource.environment.merge(ZOO_LOG_DIR: new_resource.log_dir)) 143 | service.restart_on_update(true) 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'zookeeper-cluster' 2 | maintainer 'John Bellone' 3 | maintainer_email 'jbellone@bloomberg.net' 4 | license 'Apache 2.0' 5 | description 'Application cookbook which installs and configures a Zookeeper cluster.' 6 | long_description 'Application cookbook which installs and configures a Zookeeper cluster.' 7 | version '1.3.2' 8 | 9 | supports 'ubuntu', '>= 12.04' 10 | supports 'centos', '>= 6.6' 11 | supports 'redhat', '>= 6.6' 12 | 13 | depends 'java' 14 | depends 'libartifact', '~> 1.3' 15 | depends 'poise', '~> 2.2' 16 | depends 'poise-service', '~> 1.0' 17 | depends 'selinux' 18 | depends 'rc', '~> 1.5' 19 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: zookeeper-cluster 3 | # License: Apache 2.0 4 | # 5 | # Copyright 2015-2016, Bloomberg Finance L.P. 6 | # 7 | include_recipe 'selinux::disabled', 'rc::default' 8 | 9 | node.default['java']['jdk_version'] = '8' 10 | node.default['java']['accept_license_agreement'] = true 11 | 12 | # FATAL: You must set the attribute node['java']['oracle']['accept_oracle_download_terms'] to true, 13 | # if you want to download directly from the oracle site! 14 | node.default['java']['oracle']['accept_oracle_download_terms'] = true 15 | 16 | include_recipe 'java::default' 17 | 18 | poise_service_user node['zookeeper-cluster']['service_user'] do 19 | group node['zookeeper-cluster']['service_group'] 20 | end 21 | 22 | config = zookeeper_config node['zookeeper-cluster']['service_name'] do |r| 23 | instance_name node['fqdn'] 24 | owner node['zookeeper-cluster']['service_user'] 25 | group node['zookeeper-cluster']['service_group'] 26 | 27 | node['zookeeper-cluster']['config'].each_pair { |k, v| r.send(k, v) } 28 | notifies :restart, "zookeeper_service[#{name}]", :delayed 29 | end 30 | 31 | zookeeper_service node['zookeeper-cluster']['service_name'] do |r| 32 | user node['zookeeper-cluster']['service_user'] 33 | group node['zookeeper-cluster']['service_group'] 34 | config_path config.path 35 | 36 | node['zookeeper-cluster']['service'].each_pair { |k, v| r.send(k, v) } 37 | end 38 | 39 | rc_file node['zookeeper-cluster']['log4j']['path'] do 40 | owner node['zookeeper-cluster']['service_user'] 41 | group node['zookeeper-cluster']['service_group'] 42 | type 'java' 43 | options node['zookeeper-cluster']['log4j']['properties'] 44 | 45 | notifies :restart, "zookeeper_service[#{node['zookeeper-cluster']['service_name']}]", :delayed 46 | end 47 | -------------------------------------------------------------------------------- /test/fixtures/test-zookeeper-cluster/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'test-zookeeper-cluster' 2 | version '0.1.0' 3 | depends 'zookeeper-cluster' 4 | -------------------------------------------------------------------------------- /test/fixtures/test-zookeeper-cluster/recipes/default.rb: -------------------------------------------------------------------------------- 1 | node.default['zookeeper-cluster']['config']['ensemble'] = [ node['hostname'] ] 2 | include_recipe 'zookeeper-cluster::default' 3 | -------------------------------------------------------------------------------- /test/integration/default/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe service('zookeeper') do 4 | it { should be_enabled } 5 | it { should be_running } 6 | end 7 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/spec/libraries/zookeeper_config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | require 'poise_boiler/spec_helper' 4 | require_relative '../../../libraries/zookeeper_config' 5 | 6 | describe ZookeeperClusterCookbook::Resource::ZookeeperConfig do 7 | step_into(:zookeeper_config) 8 | context '#action_create' do 9 | recipe do 10 | zookeeper_config '/etc/zookeeper/zoo.properties' do 11 | instance_name 'a' 12 | ensemble %w{a b c} 13 | end 14 | end 15 | 16 | it { is_expected.to create_directory('/etc/zookeeper').with(recursive: true) } 17 | it { is_expected.to create_file('/var/lib/zookeeper/myid') } 18 | it do 19 | is_expected.to create_file('/etc/zookeeper/zoo.properties') 20 | end 21 | it do 22 | is_expected.to create_directory('/var/lib/zookeeper') 23 | .with(owner: 'zookeeper', group: 'zookeeper') 24 | .with(recursive: true) 25 | end 26 | end 27 | 28 | context '#action_delete' do 29 | recipe do 30 | zookeeper_config '/etc/zookeeper/zoo.properties' do 31 | instance_name 'a' 32 | ensemble %w{a b c} 33 | action :delete 34 | end 35 | end 36 | 37 | it { is_expected.to delete_directory('/var/lib/zookeeper') } 38 | it { is_expected.to delete_directory('/etc/zookeeper') } 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/spec/libraries/zookeeper_service_spec.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | require 'poise_boiler/spec_helper' 4 | require_relative '../../../libraries/zookeeper_service' 5 | 6 | describe ZookeeperClusterCookbook::Resource::ZookeeperService do 7 | step_into(:zookeeper_service) 8 | context '#action_create' do 9 | recipe do 10 | zookeeper_service 'zookeeper' do 11 | version '3.5.0-alpha' 12 | end 13 | end 14 | 15 | end 16 | 17 | context '#action_disable' do 18 | recipe do 19 | zookeeper_service 'zookeeper' do 20 | version '3.5.0-alpha' 21 | action :disable 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/spec/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe_recipe 'zookeeper-cluster::default' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.converge(described_recipe) } 5 | 6 | it { expect(chef_run).to create_poise_service_user('zookeeper').with(group: 'zookeeper') } 7 | it { expect(chef_run).to include_recipe('selinux::disabled') } 8 | it { expect(chef_run).to include_recipe('java::default') } 9 | it { expect(chef_run).to include_recipe('rc::default') } 10 | it { expect(chef_run).to create_zookeeper_config('zookeeper') } 11 | it { expect(chef_run).to enable_zookeeper_service('zookeeper') } 12 | 13 | context 'with default attributes' do 14 | it 'converges successfully' do 15 | chef_run 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | require 'chefspec/cacher' 4 | 5 | RSpec.configure do |config| 6 | config.platform = 'redhat' 7 | config.version = '6.6' 8 | 9 | config.color = true 10 | config.alias_example_group_to :describe_recipe, type: :recipe 11 | config.alias_example_group_to :describe_resource, type: :resource 12 | 13 | config.filter_run :focus 14 | config.run_all_when_everything_filtered = true 15 | 16 | Kernel.srand config.seed 17 | config.order = :random 18 | 19 | if config.files_to_run.one? 20 | config.default_formatter = 'doc' 21 | end 22 | 23 | config.expect_with :rspec do |expectations| 24 | expectations.syntax = :expect 25 | end 26 | 27 | config.mock_with :rspec do |mocks| 28 | mocks.syntax = :expect 29 | mocks.verify_partial_doubles = true 30 | end 31 | end 32 | --------------------------------------------------------------------------------