From 5bc3db11413c21ed02e67f61c3e8bd059f06d7e9 Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Thu, 30 Jan 2014 16:35:53 +0000 Subject: [PATCH] add Pacemaker::Constraint::Colocation --- libraries/pacemaker.rb | 1 + libraries/pacemaker/constraint.rb | 10 ++ libraries/pacemaker/constraint/colocation.rb | 33 +++++++ providers/colocation.rb | 89 +++++++++--------- resources/colocation.rb | 7 +- spec/fixtures/colocation_constraint.rb | 14 +++ .../pacemaker/constraint/colocation_spec.rb | 27 ++++++ spec/providers/colocation_spec.rb | 93 +++++++++++++++++++ 8 files changed, 227 insertions(+), 47 deletions(-) create mode 100644 libraries/pacemaker/constraint.rb create mode 100644 libraries/pacemaker/constraint/colocation.rb create mode 100644 spec/fixtures/colocation_constraint.rb create mode 100644 spec/libraries/pacemaker/constraint/colocation_spec.rb create mode 100644 spec/providers/colocation_spec.rb diff --git a/libraries/pacemaker.rb b/libraries/pacemaker.rb index b3971f5..aea49aa 100644 --- a/libraries/pacemaker.rb +++ b/libraries/pacemaker.rb @@ -1,2 +1,3 @@ require File.join(File.dirname(__FILE__), %w(pacemaker resource primitive)) require File.join(File.dirname(__FILE__), %w(pacemaker resource clone)) +require File.join(File.dirname(__FILE__), %w(pacemaker constraint colocation)) diff --git a/libraries/pacemaker/constraint.rb b/libraries/pacemaker/constraint.rb new file mode 100644 index 0000000..7ba752b --- /dev/null +++ b/libraries/pacemaker/constraint.rb @@ -0,0 +1,10 @@ +require File.join(File.dirname(__FILE__), 'cib_object') + +module Pacemaker + class Constraint < Pacemaker::CIBObject + def self.description + type = self.to_s.split('::').last + "#{type} constraint" + end + end +end diff --git a/libraries/pacemaker/constraint/colocation.rb b/libraries/pacemaker/constraint/colocation.rb new file mode 100644 index 0000000..8664665 --- /dev/null +++ b/libraries/pacemaker/constraint/colocation.rb @@ -0,0 +1,33 @@ +require File::join(File.dirname(__FILE__), %w(.. constraint)) + +class Pacemaker::Constraint::Colocation < Pacemaker::Constraint + TYPE = 'colocation' + register_type TYPE + + attr_accessor :score, :resources + + def self.from_chef_resource(resource) + attrs = %w(score resources) + new(resource.name).copy_attrs_from_chef_resource(resource, *attrs) + end + + def parse_definition + rsc_re = /(\S+?)(?::(Started|Stopped))?/ + unless definition =~ /^#{TYPE} (\S+) (\d+|[-+]?inf): (.+?)\s*$/ + raise Pacemaker::CIBObject::DefinitionParseError, \ + "Couldn't parse definition '#{definition}'" + end + self.name = $1 + self.score = $2 + self.resources = $3.split + end + + def definition_string + "colocation #{name} #{score}: " + resources.join(' ') + end + + def crm_configure_command + "crm configure " + definition_string + end + +end diff --git a/providers/colocation.rb b/providers/colocation.rb index 1ebfc16..2f8d50b 100644 --- a/providers/colocation.rb +++ b/providers/colocation.rb @@ -17,55 +17,60 @@ # limitations under the License. # -require ::File.join(::File.dirname(__FILE__), - *%w(.. libraries pacemaker cib_object)) +require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker)) +require ::File.join(::File.dirname(__FILE__), 'common') + +include Chef::Mixin::PacemakerCommon action :create do name = new_resource.name - priority = new_resource.priority - multiple = new_resource.multiple - unless resource_exists?(name) - if multiple - multiple_rscs = new_resource.multiple_rscs - - cmd = "crm configure colocation #{name} #{priority}:" - multiple_rscs.each do |rsc| - cmd << " #{rsc}" - end - else - rsc = new_resource.rsc - with_rsc = new_resource.with_rsc - - cmd = "crm configure colocation #{name} #{priority}: #{rsc} #{with_rsc}" - end - - cmd_ = Mixlib::ShellOut.new(cmd) - cmd_.environment['HOME'] = ENV.fetch('HOME', '/root') - cmd_.run_command - begin - cmd_.error! - if resource_exists?(name) - new_resource.updated_by_last_action(true) - Chef::Log.info "Successfully configured colocation '#{name}'." - else - Chef::Log.error "Failed to configure colocation #{name}." - end - rescue - Chef::Log.error "Failed to configure colocation #{name}." - end + if @current_resource_definition.nil? + create_resource(name) + else + maybe_modify_resource(name) end end action :delete do name = new_resource.name - cmd = "crm resource stop #{name}; crm configure delete #{name}" - - e = execute "delete colocation #{name}" do - command cmd - only_if { resource_exists?(name) } - end - - new_resource.updated_by_last_action(true) - Chef::Log.info "Deleted colocation '#{name}'." + next unless @current_resource + rsc = cib_object_class.new(name) + execute rsc.delete_command do + action :nothing + end.run_action(:run) + new_resource.updated_by_last_action(true) + Chef::Log.info "Deleted #{@current_cib_object}'." +end + +def cib_object_class + Pacemaker::Constraint::Colocation +end + +def init_current_resource + name = @new_resource.name + @current_resource = Chef::Resource::PacemakerColocation.new(name) + attrs = [:score, :resources] + @current_cib_object.copy_attrs_to_chef_resource(@current_resource, *attrs) +end + +def create_resource(name) + standard_create_resource +end + +def maybe_modify_resource(name) + Chef::Log.info "Checking existing #{@current_cib_object} for modifications" + + desired_colocation = cib_object_class.from_chef_resource(new_resource) + if desired_colocation.definition_string != @current_cib_object.definition_string + Chef::Log.debug "changed from [#{@current_cib_object.definition_string}] to [#{desired_colocation.definition_string}]" + to_echo = desired_colocation.definition_string.chomp + to_echo.gsub!('\\') { '\\\\' } + to_echo.gsub!("'", "\\'") + cmd = "echo '#{to_echo}' | crm configure load update -" + execute cmd do + action :nothing + end.run_action(:run) + new_resource.updated_by_last_action(true) + end end diff --git a/resources/colocation.rb b/resources/colocation.rb index 0ae5f96..da7e05f 100644 --- a/resources/colocation.rb +++ b/resources/colocation.rb @@ -22,8 +22,5 @@ actions :create, :delete default_action :create attribute :name, :kind_of => String, :name_attribute => true -attribute :priority, :kind_of => String -attribute :multiple, :default => false -attribute :rsc, :kind_of => String -attribute :with_rsc, :kind_of => String -attribute :multiple_rscs, :kind_of => Array +attribute :score, :kind_of => String +attribute :resources, :kind_of => Array diff --git a/spec/fixtures/colocation_constraint.rb b/spec/fixtures/colocation_constraint.rb new file mode 100644 index 0000000..3f5e872 --- /dev/null +++ b/spec/fixtures/colocation_constraint.rb @@ -0,0 +1,14 @@ +require ::File.join(::File.dirname(__FILE__), + *%w(.. .. libraries pacemaker constraint colocation)) + +module Chef::RSpec + module Pacemaker + module Config + COLOCATION_CONSTRAINT = \ + ::Pacemaker::Constraint::Colocation.new('colocation1') + COLOCATION_CONSTRAINT.score = 'inf' + COLOCATION_CONSTRAINT.resources = ['foo'] + COLOCATION_CONSTRAINT_DEFINITION = 'colocation colocation1 inf: foo' + end + end +end diff --git a/spec/libraries/pacemaker/constraint/colocation_spec.rb b/spec/libraries/pacemaker/constraint/colocation_spec.rb new file mode 100644 index 0000000..c46a4fa --- /dev/null +++ b/spec/libraries/pacemaker/constraint/colocation_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require File.join(File.dirname(__FILE__), %w(.. .. .. .. + libraries pacemaker constraint colocation)) +require File.join(File.dirname(__FILE__), %w(.. .. .. fixtures colocation_constraint)) +require File.join(File.dirname(__FILE__), %w(.. .. .. helpers common_object_examples)) + +describe Pacemaker::Constraint::Colocation do + let(:fixture) { Chef::RSpec::Pacemaker::Config::COLOCATION_CONSTRAINT.dup } + + before(:each) do + Mixlib::ShellOut.any_instance.stub(:run_command) + end + + def object_type + 'colocation' + end + + def pacemaker_object_class + Pacemaker::Constraint::Colocation + end + + def fields + %w(name score resources) + end + + it_should_behave_like "a CIB object" +end diff --git a/spec/providers/colocation_spec.rb b/spec/providers/colocation_spec.rb new file mode 100644 index 0000000..aca39d2 --- /dev/null +++ b/spec/providers/colocation_spec.rb @@ -0,0 +1,93 @@ +require 'chef/application' +require File.join(File.dirname(__FILE__), %w(.. spec_helper)) +require File.join(File.dirname(__FILE__), %w(.. helpers common)) +require File.join(File.dirname(__FILE__), %w(.. fixtures colocation_constraint)) + +describe "Chef::Provider::PacemakerColocation" do + # for use inside examples: + let(:colo) { Chef::RSpec::Pacemaker::Config::COLOCATION_CONSTRAINT.dup } + # for use outside examples (e.g. when invoking shared_examples) + colo = Chef::RSpec::Pacemaker::Config::COLOCATION_CONSTRAINT.dup + + before(:each) do + runner_opts = { + :step_into => ['pacemaker_colocation'] + } + @chef_run = ::ChefSpec::Runner.new(runner_opts) + @chef_run.converge "pacemaker::default" + @node = @chef_run.node + @run_context = @chef_run.run_context + + @resource = Chef::Resource::PacemakerColocation.new(colo.name, @run_context) + @resource.score colo.score + @resource.resources colo.resources.dup + end + + let (:provider) { Chef::Provider::PacemakerColocation.new(@resource, @run_context) } + + def cib_object_class + Pacemaker::Constraint::Colocation + end + + include Chef::RSpec::Pacemaker::Common + + describe ":create action" do + def test_modify(expected_cmds) + yield + + expect_definition(colo.definition_string) + + provider.run_action :create + + expected_cmds.each do |cmd| + expect(@chef_run).to run_execute(cmd) + end + expect(@resource).to be_updated + end + + it "should modify the constraint if it has a different score" do + echo_string = colo.definition_string.chomp.gsub('inf', '100') + echo_string.gsub!('\\') { '\\\\' } + expected_configure_cmd_args = [ + "echo '#{echo_string}' | crm configure load update -" + ] + test_modify(expected_configure_cmd_args) do + @resource.score '100' + end + end + + it "should modify the constraint if it has a resource added" do + new_resource = 'bar:Stopped' + expected = colo.dup + expected.resources = expected.resources.dup + [new_resource] + echo_string = expected.definition_string.chomp + echo_string.gsub!('\\') { '\\\\' } + expected_configure_cmd_args = [ + "echo '#{echo_string}' | crm configure load update -" + ] + test_modify(expected_configure_cmd_args) do + @resource.resources expected.resources + end + end + + it "should modify the constraint if it has a different resource" do + new_resources = ['bar:Started'] + colo.resources = new_resources + echo_string = colo.definition_string.chomp + echo_string.gsub!('\\') { '\\\\' } + expected_configure_cmd_args = [ + "echo '#{echo_string}' | crm configure load update -" + ] + test_modify(expected_configure_cmd_args) do + @resource.resources new_resources + end + end + + end + + describe ":delete action" do + it_should_behave_like "action on non-existent resource", \ + :delete, "crm configure delete #{colo.name}", nil + end + +end