diff --git a/libraries/default.rb b/libraries/default.rb new file mode 100644 index 0000000..20c4e8a --- /dev/null +++ b/libraries/default.rb @@ -0,0 +1,76 @@ +# +# Cookbook Name:: heartbeat +# Library:: default +# +# Copyright 2009-2012, Opscode, Inc. +# +# 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. +# + +class Chef + class Resource + class HeartbeatResourceGroup + include Chef::Mixin::ParamsValidate + include Chef::Mixin::RecipeDefinitionDSLCore + + attr_reader :run_context, :cookbook_name, :recipe_name, :sub_resources + + def initialize(run_context, cookbook_name, recipe_name) + @run_context = run_context + @cookbook_name = cookbook_name + @recipe_name = recipe_name + @sub_resources = [] + end + + def default_node(default_node=nil) + set_or_return(:default_node, default_node, :kind_of => String) + end + + def method_missing(name, *args, &block) + # Build the set of names to check for a valid resource + lookup_path = ["heartbeat_#{name}"] + run_context.cookbook_collection.each do |cookbook_name, cookbook_ver| + lookup_path << "#{cookbook_name}_heartbeat_#{name}" + end + resource = nil + # Try to find our resource + lookup_path.each do |resource_name| + begin + Chef::Log.debug "Trying to load heartbeat resource #{resource_name} for #{name}" + resource = super(resource_name.to_sym, args.first || name, &block) + break + rescue NameError => e + # Works on any MRI ruby + if e.name == resource_name.to_sym || e.inspect =~ /\b#{resource_name}\b/ + next + else + raise e + end + end + end + raise NameError, "No resource found for #{name}. Tried #{lookup_path.join(', ')}" unless resource + raise ArgumentError, "Resource instance #{resource} is not a valid heartbeat resource" unless resource.respond_to?(:to_resource) + provider = resource.provider || begin + Chef::Platform.provider_for_resource(resource) + rescue ArgumentError => e + nil + end + # As a default this has #action_nothing on it + resource.provider Chef::Provider::HeartbeatNull unless provider + @sub_resources << resource + resource + end + + end + end +end diff --git a/providers/default.rb b/providers/default.rb new file mode 100644 index 0000000..1b4b47b --- /dev/null +++ b/providers/default.rb @@ -0,0 +1,57 @@ +# +# Cookbook Name:: heartbeat +# Provider:: default +# +# Copyright 2009-2012, Opscode, Inc. +# +# 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. +# + +action :create do + query = new_resource.search || "recipes:#{new_resource.cookbook_name}\\:\\:#{new_resource.recipe_name}" + nodes = search(:node, query) + nodes << node if nodes.select{|n| n['macaddress'] == node['macaddress']}.empty? + interface = new_resource.interface.is_a?(Array) ? new_resource.interface : [new_resource.interface] + authkeys = new_resource.authkeys.is_a?(Array) ? new_resource.authkeys : [new_resource.authkeys] + + template "/etc/ha.d/ha.cf" do + cookbook "heartbeat" + source "ha.cf.erb" + mode "644" + owner "root" + group "root" + notifies :restart, "service[heartbeat]" + variables :heartbeat => new_resource, :nodes => nodes, :interface => interface + end + + template "/etc/ha.d/authkeys" do + cookbook "heartbeat" + source "authkeys.erb" + owner "root" + group "root" + mode "600" + notifies :restart, "service[heartbeat]" + variables :active => new_resource.active_key || authkeys.length, :keys => authkeys + end + + template "/etc/ha.d/haresources" do + cookbook "heartbeat" + source "haresources.erb" + owner "root" + group "root" + mode "644" + notifies :restart, "service[heartbeat]" + variables :heartbeat => new_resource, :default => nodes.sort_by{|n| n['macaddress']}.first['hostname'] + end + +end diff --git a/providers/null.rb b/providers/null.rb new file mode 100644 index 0000000..b30efa4 --- /dev/null +++ b/providers/null.rb @@ -0,0 +1,20 @@ +# +# Cookbook Name:: heartbeat +# Provider:: null +# +# Copyright 2009-2012, Opscode, Inc. +# +# 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. +# + +# This space left intentionally blank diff --git a/resources/default.rb b/resources/default.rb new file mode 100644 index 0000000..1de79b8 --- /dev/null +++ b/resources/default.rb @@ -0,0 +1,55 @@ +# +# Cookbook Name:: heartbeat +# Resource:: default +# +# Copyright 2009-2012, Opscode, Inc. +# +# 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. +# + +default_action :create + +def after_created + run_context.resource_collection.each do |res| + raise "You may only define a single heartbeat resource per node. Found #{res}: #{res.defined_at}" if res.is_a?(self.class) && res.name != name + end +end + +# These map directly to ha.cf directives +attribute :auto_failback, :kind_of => [TrueClass, FalseClass], :default => false +attribute :autojoin, :equal_to => [:none, 'none', :other, 'other', :any, 'any'], :default => :none +attribute :compression, :equal_to => [nil, :zlib, 'zlib', :bz2, 'bz2'], :default => nil +attribute :compression_threshold, :kind_of => Integer, :default => 2 +attribute :deadtime, :kind_of => Integer, :default => 2 +attribute :initdead, :kind_of => Integer, :default => 15 +attribute :keepalive, :kind_of => Integer, :default => 250 +attribute :logfacility, :kind_of => String, :default => 'local0' +attribute :udpport, :kind_of => Integer, :default => 694 +attribute :warntime, :kind_of => Integer, :default => 1 + +attribute :search, :kind_of => String +attribute :authkeys, :kind_of => [Array, String], :required => true +attribute :active_key, :kind_of => Integer +attribute :mode, :equal_to => [:bcast, 'bcast', :mcast, 'mcast', :ucast, 'ucast'], :default => :ucast +attribute :interface, :kind_of => [String, Array], :default => node['network']['default_interface'] +attribute :mcast_group, :kind_of => String +attribute :mcast_ttl, :kind_of => Integer, :default => 1 +attribute :resource_groups, :default => [] + +def resources(ip=nil, &block) + group = ::Chef::Resource::HeartbeatResourceGroup.new(run_context, cookbook_name, recipe_name) + group.ipaddr ip if ip + group.instance_eval(&block) if block + resource_groups << group + group +end diff --git a/resources/ipaddr.rb b/resources/ipaddr.rb new file mode 100644 index 0000000..ae16371 --- /dev/null +++ b/resources/ipaddr.rb @@ -0,0 +1,24 @@ +# +# Cookbook Name:: heartbeat +# Resource:: ipaddr +# +# Copyright 2009-2012, Opscode, Inc. +# +# 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. +# + +attribute :ip, :kind_of => String, :name_attribute => true + +def to_resource + ip +end diff --git a/templates/default/authkeys.erb b/templates/default/authkeys.erb new file mode 100644 index 0000000..3f709fe --- /dev/null +++ b/templates/default/authkeys.erb @@ -0,0 +1,4 @@ +auth <%= @active %> +<% @keys.each_with_index do |secret, i| -%> +<%= i+1 %> sha1 <%= secret %> +<% end -%> diff --git a/templates/default/ha.cf.erb b/templates/default/ha.cf.erb new file mode 100644 index 0000000..3709547 --- /dev/null +++ b/templates/default/ha.cf.erb @@ -0,0 +1,28 @@ +udpport <%= @heartbeat.udpport %> +autojoin <%= @heartbeat.autojoin %> +<% if @heartbeat.compression -%> +compression <%= @heartbeat.compression %> +<% end -%> +compression_threshold <%= @heartbeat.compression_threshold %> +deadtime <%= @heartbeat.deadtime %> +initdead <%= @heartbeat.initdead %> +keepalive <%= @heartbeat.keepalive %>ms +logfacility <%= @heartbeat.logfacility %> +warntime <%= @heartbeat.warntime %> + +<% case @heartbeat.mode.to_sym -%> +<% when :ucast -%> +<% @nodes.select{|n| n['macaddress'] != node['macaddress']}.each do |n| -%> +ucast <%=@interface.first %> <%= n['ipaddress'] %> +<% end -%> +<% when :bcast -%> +bcast <%= @interface.join(' ') %> +<% when :mcast -%> +mcast <%= @interface.first %> <%= @heartbeat.mcast_group %> <%= @heartbeat.udpport %> <%= @heartbeat.mcast_ttl %> +<% end -%> + +auto_failback <%= @heartbeat.auto_failback ? "on" : "off" %> + +<% @nodes.each do |n| -%> +node <%= n['hostname'] %> +<% end -%> diff --git a/templates/default/haresources.erb b/templates/default/haresources.erb new file mode 100644 index 0000000..0a9c85f --- /dev/null +++ b/templates/default/haresources.erb @@ -0,0 +1,3 @@ +<% @heartbeat.resource_groups.each do |group| -%> +<%= group.default_node || @default %> <%= group.sub_resources.map{|r| r.to_resource}.join(' ') %> +<% end -%>