diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b77218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.kitchen +.vagrant +Berksfile.lock +Gemfile.lock +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +/cookbooks diff --git a/Berksfile b/Berksfile new file mode 100644 index 0000000..76b777f --- /dev/null +++ b/Berksfile @@ -0,0 +1,4 @@ +site :opscode + +metadata +cookbook 'redis', '>= 0.3.2', chef_api: :config diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..878eb92 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source :rubygems + +gem 'berkshelf' +gem 'vagrant', '~> 1.0.5' diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..29b6c70 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,66 @@ +require 'berkshelf/vagrant' + +Vagrant::Config.run do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + # The path to the Berksfile to use with Vagrant Berkshelf + # config.berkshelf.berksfile_path = "./Berksfile" + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to skip installing and copying to Vagrant's shelf. + # config.berkshelf.only = [] + + # An array of symbols representing groups of cookbook described in the Vagrantfile + # to skip installing and copying to Vagrant's shelf. + # config.berkshelf.except = [] + + config.vm.host_name = "graphiti-berkshelf" + + # config.vm.box = "Berkshelf-CentOS-6.3-x86_64-minimal" + config.vm.box = "Fedora-18-x86_64" + + config.vm.box_url = "https://dl.dropbox.com/u/47541301/vagrantboxes/Fedora-18-x86_64.box" + + # Boot with a GUI so you can see the screen. (Default is headless) + # config.vm.boot_mode = :gui + + # Assign this VM to a host-only network IP, allowing you to access it + # via the IP. Host-only networks can talk to the host machine as well as + # any other machines on the same network, but cannot be accessed (through this + # network interface) by any external networks. + # config.vm.network :hostonly, "192.168.66.1" + + # Assign this VM to a bridged network, allowing you to connect directly to a + # network using the host's network device. This makes the VM appear as another + # physical device on your network. + + # config.vm.network :bridged + + # Forward a port from the guest to the host, which allows for outside + # computers to access the VM, whereas host only networking does not. + config.vm.forward_port 8081, 8081 + + # Share an additional folder to the guest VM. The first argument is + # an identifier, the second is the path on the guest to mount the + # folder, and the third is the path on the host to the actual folder. + # config.vm.share_folder "v-data", "/vagrant_data", "../data" + + config.ssh.max_tries = 40 + config.ssh.timeout = 120 + + config.vm.provision :chef_solo do |chef| + chef.json = { + :base => '/opt/graphiti', + :tmp_dir => '/opt/graphiti/tmp' + } + + chef.binary_path = '/usr/local/bin' + + chef.run_list = [ + "recipe[redis::server]", + "recipe[graphiti::default]" + ] + end +end diff --git a/attributes/graphiti.rb b/attributes/graphiti.rb index 58e36b1..f8921ec 100644 --- a/attributes/graphiti.rb +++ b/attributes/graphiti.rb @@ -1,17 +1,17 @@ -default.graphiti.tarfile = "/usr/src/graphiti.tgz" -default.graphiti.url = "https://github.com/paperlesspost/graphiti/tarball/master" -default.graphiti.base = "/srv/graphiti" -default.graphiti.graphite_host = "127.0.0.1" -default.graphiti.redis_url = "localhost:6379:1/graphiti" -default.graphiti.tmp_dir = "/srv/graphiti/tmp" -default.graphiti.metric_prefix = "collectd" -default.graphiti.default_metrics = %w[carbon.agents.*.metricsReceived] -default.graphiti.unicorn.timeout = 60 -default.graphiti.unicorn.cow_friendly = true -default.graphiti.s3_bucket = "graphiti" -default.graphiti.port = 8081 +default['graphiti']['tarfile'] = "#{Chef::Config[:file_cache_path]}/graphiti.tgz" +default['graphiti']['url'] = "https://github.com/paperlesspost/graphiti/tarball/master" +default['graphiti']['base'] = "/srv/graphiti" +default['graphiti']['graphite_base_url'] = "http://localhost/" +default['graphiti']['redis_url'] = "localhost:6379:1/graphiti" +default['graphiti']['tmp_dir'] = "/srv/graphiti/tmp" +default['graphiti']['metric_prefix'] = "collectd" +default['graphiti']['default_metrics'] = %w[carbon.agents.*.metricsReceived] +default['graphiti']['unicorn']['timeout'] = 60 +default['graphiti']['unicorn']['cow_friendly'] = true +default['graphiti']['s3_bucket'] = "graphiti" +default['graphiti']['port'] = 8081 -default.graphiti.default_options = { +default['graphiti']['default_options'] = { "title" => "New Graph", "from" => "-6h", "font" => "DroidSans", @@ -25,4 +25,10 @@ "areaMode" => "stacked" } -default.graphiti.graph_types = [] +default['graphiti']['auto_refresh'] = { + "enabled" => true, + "interval" => 60 +} + +default['graphiti']['graph_types'] = [] +default['graphiti']['user'] = 'graphiti' diff --git a/chefignore b/chefignore new file mode 100644 index 0000000..856929d --- /dev/null +++ b/chefignore @@ -0,0 +1,52 @@ +# Put files/directories that should be ignored in this file. +# Lines that start with '# ' are comments. + +## OS +.DS_Store +Icon? +nohup.out + +## EDITORS +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED +a.out +*.o +*.pyc +*.so + +## OTHER SCM +*/.bzr/* +*/.hg/* +*/.svn/* + +## Don't send rspecs up in cookbook +.watchr +.rspec +spec/* +spec/fixtures/* +features/* + +## SCM +.gitignore + +# Berkshelf +Berksfile +Berksfile.lock +cookbooks/* + +# Vagrant +.vagrant diff --git a/metadata.rb b/metadata.rb index e871cbf..5567428 100644 --- a/metadata.rb +++ b/metadata.rb @@ -1,3 +1,4 @@ +name "graphiti" maintainer "Heavy Water Software Inc." maintainer_email "ops@hw-ops.com" license "Apache 2.0" @@ -5,7 +6,10 @@ long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) version "0.0.1" -depends "build-essential" -depends "runit" +%w{build-essential iptables runit}.each do |d| + depends d +end -suggests "iptables" +%w{ubuntu debian fedora amazon}.each do |os| + supports os +end diff --git a/recipes/default.rb b/recipes/default.rb index 1a7dc37..08badaa 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -18,95 +18,135 @@ # include_recipe "build-essential" -%w[libcurl4-gnutls-dev ruby1.9.1-full].each do |pkg| - apt_package pkg +user node['graphiti']['user'] do + comment "Graphiti Graphite Dashboard" + system true + action :create end -gem_package "bundler" +case node['platform_family'] +when "debian" + %w[libcurl4-gnutls-dev ruby1.9.1-full].each do |pkg| + package pkg do + action :install + end + end + gem_package "bundler" -remote_file node.graphiti.tarfile do +when "fedora" + %w{ruby ruby-devel rubygem-bundler rubygem-daemons rubygem-linecache19 rubygem-rack rubygem-rake rubygem-sinatra rubygem-haml libcurl-devel}.each do |pkg| + package pkg do + action :install + end + end + +end + +remote_file node['graphiti']['tarfile'] do mode "00666" - owner "www-data" - group "www-data" - source node.graphiti.url + owner node['graphiti']['user'] + group node['graphiti']['user'] + source node['graphiti']['url'] action :create_if_missing end -directory node.graphiti.base do - owner "www-data" - group "www-data" +directory node['graphiti']['base'] do + owner node['graphiti']['user'] + group node['graphiti']['user'] end -directory File.join(node.graphiti.base, "log") do - owner "www-data" - group "www-data" +directory File.join(node['graphiti']['base'], "log") do + owner node['graphiti']['user'] + group node['graphiti']['user'] end -execute "bundle" do - command "bundle install --deployment --binstubs; " + - "bundle exec rake graphiti:metrics" +execute "bundle-install" do + command "bundle install --deployment --binstubs" + cwd node['graphiti']['base'] +# user node['graphiti']['user'] + action :nothing +end - user "www-data" - group "www-data" - environment "PATH" => "/var/lib/gems/1.9.1/bin" - cwd node.graphiti.base +execute "bundle-rake-generate" do + command "bundle exec rake graphiti:metrics" + cwd node['graphiti']['base'] + user node['graphiti']['user'] action :nothing end cron "graphiti:metrics" do minute "*/15" - command "cd #{node.graphiti.base} && /var/lib/gems/1.9.1/bin/bundle exec rake graphiti:metrics" - user "www-data" + command "cd #{node['graphiti']['base']} && bundle exec rake graphiti:metrics" + user node['graphiti']['user'] end execute "graphiti: untar" do - command "tar zxf #{node.graphiti.tarfile} -C #{node.graphiti.base} --strip-components=1" - creates File.join(node.graphiti.base, "Rakefile") - user "www-data" - group "www-data" - notifies :run, resources(:execute => "bundle"), :immediately + command "tar zxf #{node['graphiti']['tarfile']} -C #{node['graphiti']['base']} --strip-components=1" + creates File.join(node['graphiti']['base'], "Rakefile") + user node['graphiti']['user'] + group node['graphiti']['user'] + notifies :run, "execute[bundle-install]", :immediately end -aws = data_bag_item "aws", node.chef_environment -template File.join(node.graphiti.base, "config", "amazon_s3.yml") do - variables :hash => { node.chef_environment => { - "bucket" => node.graphiti.s3_bucket, - "access_key_id" => aws["aws_access_key_id"], - "secret_access_key" => aws["aws_secret_access_key"] - } } - owner "www-data" - group "www-data" - notifies :restart, "service[graphiti]" -end +# XXX domain-specific stuff. Not everyone has their databags set up like this +# (nor does everyone want graph storage in S3 as a feature) +#aws = data_bag_item "aws", node.chef_environment +#template File.join(node.graphiti.base, "config", "amazon_s3.yml") do +# variables :hash => { node.chef_environment => { +# "bucket" => node.graphiti.s3_bucket, +# "access_key_id" => aws["aws_access_key_id"], +# "secret_access_key" => aws["aws_secret_access_key"] +# } } +# owner node['graphiti']['user'] +# group node['graphiti']['user'] +# notifies :restart, "service[graphiti]" +#end -template File.join(node.graphiti.base, "config", "settings.yml") do - owner "www-data" - group "www-data" +template File.join(node['graphiti']['base'], "config", "settings.yml") do + owner node['graphiti']['user'] + group node['graphiti']['user'] variables :hash => { - "graphite_host" => node.graphiti.graphite_host, - "redis_url" => node.graphiti.redis_url, - "tmp_dir" => node.graphiti.tmp_dir, + "graphite_base_url" => node['graphiti']['graphite_base_url'], + "redis_url" => node['graphiti']['redis_url'], + "tmp_dir" => node['graphiti']['tmp_dir'], "fonts" => %w[DroidSans DejaVuSans], - "metric_prefix" => node.graphiti.metric_prefix, - "default_options" => node.graphiti.default_options.to_hash, - "default_metrics" => node.graphiti.default_metrics.to_a, + "metric_prefix" => node['graphiti']['metric_prefix'], + "default_options" => node['graphiti']['default_options'].to_hash, + "auto_refresh" => node['graphiti']['auto_refresh'].to_hash, + "default_metrics" => node['graphiti']['default_metrics'].to_a, } notifies :restart, "service[graphiti]" + notifies :run, "execute[bundle-rake-generate]", :immediately end directory "/var/run/unicorn" do - owner "www-data" - group "www-data" + owner node['graphiti']['user'] + group node['graphiti']['user'] end -template File.join(node.graphiti.base, "config", "unicorn.rb") do - owner "www-data" - group "www-data" - variables( :worker_processes => node.cpu.total, - :timeout => node.graphiti.unicorn.timeout, - :cow_friendly => node.graphiti.unicorn.cow_friendly ) +template File.join(node['graphiti']['base'], "config", "unicorn.rb") do + owner node['graphiti']['user'] + group node['graphiti']['user'] + variables( :worker_processes => node['cpu']['total'], + :timeout => node['graphiti']['unicorn']['timeout'], + :cow_friendly => node['graphiti']['unicorn']['cow_friendly'] ) notifies :restart, "service[graphiti]" end -runit_service "graphiti" +case node['platform_family'] +when "debian" + runit_service "graphiti" +when "fedora" + template "/etc/init.d/graphiti" do + source "graphiti-init.erb" + owner "root" + group "root" + mode 00755 + action :create + end + + service "graphiti" do + action [ :enable, :start ] + end +end diff --git a/recipes/graph_generator.rb b/recipes/graph_generator.rb index 008fc0b..da8569a 100644 --- a/recipes/graph_generator.rb +++ b/recipes/graph_generator.rb @@ -17,6 +17,10 @@ # limitations under the License. # +if Chef::Config[:solo] + Chef::Log.warn("This recipe does not work properly in Solo mode; it depends on search.") + return +end nodes = search(:node, "graphiti_graph_types:*") nodes.map do |remote_node| @@ -46,7 +50,7 @@ "title" => "#{remote_node.fqdn} #{graph_type}" } - options = node.graphiti.default_options.to_hash.deep_merge(options) + options = node['graphiti']['default_options'].to_hash.deep_merge(options) graphiti_graph options["title"] do type graph_type @@ -84,7 +88,7 @@ end } - options = node.graphiti.default_options.to_hash.deep_merge(options) + options = node['graphiti']['default_options'].to_hash.deep_merge(options) graphiti_graph "#{title}_#{from}" do type title diff --git a/templates/default/graphiti-init.erb b/templates/default/graphiti-init.erb new file mode 100644 index 0000000..459faa6 --- /dev/null +++ b/templates/default/graphiti-init.erb @@ -0,0 +1,112 @@ +#!/bin/bash +# +# graphiti - Startup script for the Graphiti dashboard +# +# chkconfig: - 98 02 +# description: Graphiti dashboard (frontend) for Graphite + +### BEGIN INIT INFO +# Provides: graphiti +# Required-Start: $local_fs $network $remote_fs +# Required-Stop: $local_fs $network $remote_fs +# Should-Start: $named $time +# Should-Stop: $named $time +# Short-Description: Startup script for the Graphiti dashboard +# Description: Graphiti dashboard (frontend) for Graphite +### END INIT INFO + +# Source function library +. /etc/init.d/functions + +exec="<%= node['graphiti']['base'] %>/bin/unicorn_rails" +prog="graphiti" + +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog + +config=${CONFIG-<%= node['graphiti']['base'] %>/config/unicorn.rb} +pidfile=${PIDFILE-/var/run/unicorn/unicorn.pid} +railsroot=${RAILS_ROOT-<%= node['graphiti']['base'] %>} +railsenv=${RAILS_ENV-production} +lockfile=${LOCKFILE-/var/lock/subsys/$prog} +unicornport=${UNICORN_PORT-<%= node['graphiti']['port'] %>} + +start() { + [ -x $exec ] || exit 5 + [ -f $config ] || exit 6 + echo -n $"Starting $prog: " + daemon --user graphiti $exec -c $config -E $railsenv -p $unicornport -D + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval + return 0 +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $pidfile $exec + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval + return 0 +} + +restart () { + stop + start +} + +reload() { + echo -n $"Reloading $prog: " + killproc -p $pidfile $exec -HUP + retval=$? + echo + return $retval + return 0 +} + +force_reload() { + restart +} + +rh_status() { + # run checks to determine if the service is running or use generic status + status -p $pidfile $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/templates/default/unicorn.rb.erb b/templates/default/unicorn.rb.erb index 901e745..3e30503 100644 --- a/templates/default/unicorn.rb.erb +++ b/templates/default/unicorn.rb.erb @@ -1,5 +1,5 @@ worker_processes <%= @worker_processes %> -working_directory "/srv/graphiti" +working_directory "<%= node['graphiti']['base'] %>" listen "/var/run/unicorn/unicorn.socket", :backlog => 64 pid "/var/run/unicorn/unicorn.pid" @@ -17,7 +17,7 @@ before_fork do |server, worker| begin uid, gid = Process.euid, Process.egid - user, group = "www-data", "www-data" + user, group = "<%= node['graphiti']['user'] %>", "<%= node['graphiti']['user'] %>" target_uid = Etc.getpwnam(user).uid target_gid = Etc.getgrnam(group).gid worker.tmp.chown(target_uid, target_gid) diff --git a/test/kitchen/Kitchenfile b/test/kitchen/Kitchenfile new file mode 100644 index 0000000..6a314d8 --- /dev/null +++ b/test/kitchen/Kitchenfile @@ -0,0 +1,3 @@ +cookbook "graphiti" do + +end