switch to OO classes modelling Pacemaker objects

This commit is contained in:
Adam Spiers
2014-01-28 17:58:49 +00:00
parent fd3dcd0f77
commit 7a74431e88
23 changed files with 665 additions and 413 deletions

View File

@@ -7,25 +7,24 @@ guard_opts = {
all_after_pass: true,
}
def startup_guards
watch(%r{^Gemfile$}) { yield }
watch(%r{^Gemfile.lock$}) { yield }
watch(%r{^spec/spec_helper\.rb$}) { yield }
end
def all_specs
'spec'
end
group :rspec do
guard 'rspec', guard_opts do
startup_guards { all_specs }
watch(%r{^spec_helper\.rb$}) { all_specs }
watch(%r{^helpers/(.+)\.rb$}) { all_specs }
watch(%r{^Gemfile$}) { all_specs }
watch(%r{^Gemfile.lock$}) { all_specs }
watch(%r{^spec/spec_helper\.rb$}) { all_specs }
watch(%r{^spec/helpers/(.+)\.rb$}) { all_specs }
watch(%r{^libraries/pacemaker\.rb$}) { all_specs }
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^(libraries|providers)/(.+)\.rb$}) do |m|
"spec/#{m[1]}/#{m[2]}_spec.rb"
end
watch(%r{^libraries/pacemaker/(.+)\.rb$}) { |m|
"spec/libraries/#{m[1]}_spec.rb"
}
watch(%r{^(resources|providers)/(.+)\.rb$}) { |m|
"spec/providers/#{m[1]}_spec.rb"
}
end
end

View File

@@ -1,94 +0,0 @@
require 'shellwords'
module Chef::Libraries
module Pacemaker
module CIBObjects
include Chef::Mixin::ShellOut
def get_cib_object_definition(name)
cmd = Mixlib::ShellOut.new("crm configure show #{name}")
cmd.environment['HOME'] = ENV.fetch('HOME', '/root')
cmd.run_command
begin
cmd.error!
cmd.stdout
rescue
nil
end
end
def cib_object_exists?(name)
dfn = get_cib_object_definition(name)
!! (dfn && ! dfn.empty?)
end
def cib_object_type(obj_definition)
unless obj_definition =~ /\A(\w+)\s/
raise "Couldn't extract CIB object type from '#{obj_definition}'"
end
return $1
end
def pacemaker_resource_running?(name)
cmd = shell_out! "crm", "resource", "status", name
Chef::Log.info cmd.stdout
cmd.stdout.include? "resource #{name} is running"
end
def resource_params_string(params)
return "" if ! params or params.empty?
s = " params"
params.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
s
end
def resource_meta_string(meta)
return "" if ! meta or meta.empty?
s = " meta"
meta.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
s
end
def resource_op_string(ops)
return "" if ! ops or ops.empty?
s = " op"
ops.sort.each do |op, attrs|
s << " #{op}"
attrs.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
end
s
end
# CIB object definitions look something like:
#
# primitive keystone ocf:openstack:keystone \
# params os_username="crowbar" os_password="crowbar" os_tenant_name="openstack" \
# meta target-role="Started" is-managed="true" \
# op monitor interval="10" timeout=30s \
# op start interval="10s" timeout="240" \
#
# This method extracts a Hash from one of the params / meta / op lines.
def extract_hash(name, obj_definition, data_type)
unless obj_definition =~ /^\s+#{data_type} (.+?)(\s*\\)?$/
raise "Couldn't retrieve #{data_type} for '#{name}' CIB object from [#{obj_definition}]"
end
h = {}
Shellwords.split($1).each do |kvpair|
unless kvpair =~ /^(.+?)=(.+)$/
raise "Couldn't understand '#{kvpair}' for #{data_type} section of '#{name}' primitive"
end
h[$1] = $2.sub(/^"(.*)"$/, "\1")
end
h
end
end
end
end

2
libraries/pacemaker.rb Normal file
View File

@@ -0,0 +1,2 @@
require_relative 'pacemaker/resource/primitive'
require_relative 'pacemaker/resource/clone'

View File

@@ -0,0 +1,91 @@
require 'mixlib/shellout'
module Pacemaker
class ObjectTypeMismatch < StandardError
end
class CIBObject
attr_accessor :name, :definition
@@subclasses = { }
class << self
attr_reader :object_type
def register_type(type_name)
@object_type = type_name
@@subclasses[type_name] = self
end
def get_definition(name)
cmd = Mixlib::ShellOut.new("crm configure show #{name}")
cmd.environment['HOME'] = ENV.fetch('HOME', '/root')
cmd.run_command
begin
cmd.error!
cmd.stdout
rescue
nil
end
end
def type(definition)
unless definition =~ /\A(\w+)\s/
raise "Couldn't extract CIB object type from '#{definition}'"
end
return $1
end
def from_name(name)
definition = get_definition(name)
return nil unless definition
obj_type = type(definition)
subclass = @@subclasses[obj_type]
unless subclass
raise "No subclass of #{self.name} was registered with type '#{obj_type}'"
end
obj = subclass.from_definition(definition)
unless name == obj.name
raise "Name '#{obj.name}' in definition didn't match name '#{name}' used for retrieval"
end
obj
end
def from_definition(definition)
obj = new(name)
obj.definition = definition
obj.parse_definition
obj
end
end
def initialize(name)
@name = name
@definition = nil
end
def load_definition
@definition = self.class.get_definition(name)
if @definition and ! @definition.empty? and type != self.class.object_type
raise ObjectTypeMismatch, "Expected #{self.class.object_type} type but loaded definition was type #{type}"
end
end
def parse_definition
raise NotImplementedError, "#{self.class} must implement #parse_definition"
end
def exists?
!! (definition && ! definition.empty?)
end
def type
self.class.type(definition)
end
def delete_command
"crm configure delete '#{name}'"
end
end
end

View File

@@ -0,0 +1,23 @@
require 'chef/mixin/shell_out'
require_relative 'cib_object'
module Pacemaker
class Resource < Pacemaker::CIBObject
include Chef::Mixin::ShellOut
def running?
cmd = shell_out! "crm", "resource", "status", name
Chef::Log.info cmd.stdout
!! cmd.stdout.include?("resource #{name} is running")
end
def start_command
"crm resource start '#{name}'"
end
def stop_command
"crm resource stop '#{name}'"
end
end
end

View File

@@ -0,0 +1,8 @@
require_relative File::join(%w(.. resource))
class Pacemaker::Resource::Clone < Pacemaker::Resource
register_type 'clone'
attr_accessor :primitive
end

View File

@@ -0,0 +1,124 @@
require 'shellwords'
require_relative File::join(%w(.. resource))
class Pacemaker::Resource::Primitive < Pacemaker::Resource
TYPE = 'primitive'
register_type TYPE
attr_accessor :agent, :params, :meta, :op
def initialize(*args)
super(*args)
@agent = nil
end
def self.from_chef_resource(resource)
primitive = new(resource.name)
%w(agent params meta op).each do |data|
value = resource.send(data.to_sym)
writer = (data + '=').to_sym
primitive.send(writer, value)
end
return primitive
end
def parse_definition
unless definition =~ /\Aprimitive (\S+) (\S+)/
raise RuntimeError, "Couldn't parse definition '#{definition}'"
end
self.name = $1
self.agent = $2
%w(params meta).each do |data_type|
hash = self.class.extract_hash(definition, data_type)
writer = (data_type + '=').to_sym
send(writer, hash)
end
# FIXME: deal with op
end
def params_string
self.class.params_string(params)
end
def meta_string
self.class.meta_string(meta)
end
def op_string
self.class.op_string(op)
end
def definition_string
return <<EOF
primitive #{name} #{agent} \\
#{params_string} \\
#{meta_string} \\
#{op_string}
EOF
end
def crm_configure_command
args = %w(crm configure primitive)
args << [name, agent, params_string, meta_string, op_string]
args.join " "
end
def self.params_string(params)
return "" if ! params or params.empty?
s = " params"
params.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
s
end
def self.meta_string(meta)
return "" if ! meta or meta.empty?
s = " meta"
meta.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
s
end
def self.op_string(ops)
return "" if ! ops or ops.empty?
s = " op"
ops.sort.each do |op, attrs|
s << " #{op}"
attrs.sort.each do |key, value|
s << %' #{key}="#{value}"'
end
end
s
end
# CIB object definitions look something like:
#
# primitive keystone ocf:openstack:keystone \
# params os_username="crowbar" os_password="crowbar" os_tenant_name="openstack" \
# meta target-role="Started" is-managed="true" \
# op monitor interval="10" timeout=30s \
# op start interval="10s" timeout="240" \
#
# This method extracts a Hash from one of the params / meta / op lines.
def self.extract_hash(obj_definition, data_type)
unless obj_definition =~ /^\s+#{data_type} (.+?)(\s*\\)?$/
raise "Couldn't retrieve #{data_type} for '#{name}' CIB object from [#{obj_definition}]"
end
h = {}
Shellwords.split($1).each do |kvpair|
unless kvpair =~ /^(.+?)=(.+)$/
raise "Couldn't understand '#{kvpair}' for #{data_type} section of '#{name}' primitive"
end
h[$1] = $2.sub(/^"(.*)"$/, "\1")
end
h
end
end

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -17,9 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
include Chef::Libraries::Pacemaker::CIBObjects
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
# For vagrant env, switch to the following 'require' command.
#require "/srv/chef/file_store/cookbooks/pacemaker/providers/helper"
@@ -43,10 +41,11 @@ end
action :delete do
name = new_resource.name
next unless @current_resource
if pacemaker_resource_running?(name)
rsc = Pacemaker::Resource::Primitive.new(name)
if rsc.running?
raise "Cannot delete running resource primitive #{name}"
end
execute "crm configure delete #{name}" do
execute rsc.delete_command do
action :nothing
end.run_action(:run)
new_resource.updated_by_last_action(true)
@@ -58,8 +57,9 @@ action :start do
unless @current_resource
raise "Cannot start non-existent resource primitive '#{name}'"
end
next if pacemaker_resource_running?(name)
execute "crm resource start #{name}" do
rsc = Pacemaker::Resource::Primitive.new(name)
next if rsc.running?
execute rsc.start_command do
action :nothing
end.run_action(:run)
new_resource.updated_by_last_action(true)
@@ -71,8 +71,9 @@ action :stop do
unless @current_resource
raise "Cannot stop non-existent resource primitive '#{name}'"
end
next unless pacemaker_resource_running?(name)
execute "crm resource stop #{name}" do
rsc = Pacemaker::Resource::Primitive.new(name)
next unless rsc.running?
execute rsc.stop_command do
action :nothing
end.run_action(:run)
new_resource.updated_by_last_action(true)
@@ -88,32 +89,36 @@ end
def load_current_resource
name = @new_resource.name
obj_definition = get_cib_object_definition(name)
return if ! obj_definition or obj_definition.empty?
Chef::Log.debug "CIB object definition #{obj_definition}"
unless obj_definition =~ /\Aprimitive #{name} (\S+)/
Chef::Log.warn "Resource '#{name}' was not a primitive"
primitive = Pacemaker::Resource::Primitive.new(name)
begin
primitive.load_definition
rescue Pacemaker::ObjectTypeMismatch => e
Chef::Log.warn e.message
return
end
agent = $1
@current_resource_definition = obj_definition
if ! primitive.definition or primitive.definition.empty?
Chef::Log.debug "CIB object definition nil or empty"
return
end
Chef::Log.debug "CIB object definition #{primitive.definition}"
@current_resource_definition = primitive.definition
primitive.parse_definition
@current_primitive = primitive
@current_resource = Chef::Resource::PacemakerPrimitive.new(name)
@current_resource.agent(agent)
@current_resource.agent(primitive.agent)
%w(params meta).each do |data_type|
h = extract_hash(name, obj_definition, data_type)
@current_resource.send(data_type.to_sym, h)
Chef::Log.debug "detected #{name} has #{data_type} #{h}"
method = data_type.to_sym
value = primitive.send(method)
@current_resource.send(method, value)
Chef::Log.debug "detected #{name} has #{data_type} #{value}"
end
end
def create_resource(name)
cmd = "crm configure primitive #{name} #{new_resource.agent}"
cmd << resource_params_string(new_resource.params)
cmd << resource_meta_string(new_resource.meta)
cmd << resource_op_string(new_resource.op)
primitive = Pacemaker::Resource::Primitive.from_chef_resource(new_resource)
cmd = primitive.crm_configure_command
Chef::Log.info "Creating new resource primitive #{name}"
@@ -121,7 +126,7 @@ def create_resource(name)
action :nothing
end.run_action(:run)
if cib_object_exists?(name)
if primitive.exists?
new_resource.updated_by_last_action(true)
Chef::Log.info "Successfully configured primitive '#{name}'."
else

View File

@@ -17,7 +17,7 @@
# limitations under the License.
#
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries cib_objects))
require ::File.join(::File.dirname(__FILE__), *%w(.. libraries pacemaker cib_object))
action :create do
name = new_resource.name

View File

@@ -0,0 +1,13 @@
#require ::File.join(::File.dirname(__FILE__), *%w(.. .. libraries pacemaker clone))
require_relative 'keystone_primitive'
module Chef::RSpec
module Pacemaker
module Config
include Chef::RSpec::Pacemaker::Config
CLONE = ::Pacemaker::Clone.new('clone1')
CLONE.primitive = KEYSTONE
end
end
end

View File

@@ -1,38 +0,0 @@
require ::File.join(::File.dirname(__FILE__), *%w(.. .. libraries cib_objects))
module Chef::RSpec
module Pacemaker
module Config
extend Chef::Libraries::Pacemaker::CIBObjects
RA = {
:name => "keystone",
:agent => "ocf:openstack:keystone",
:params => [
[ "os_password", "adminpw" ],
[ "os_auth_url", "http://node1:5000/v2.0" ],
[ "os_username", "admin" ],
[ "os_tenant_name", "openstack" ],
[ "user", "openstack-keystone" ],
],
:meta => [
[ "target-role", "Started" ],
[ "is-managed", "true" ]
],
:op => [
[ "monitor", { "timeout" => "60", "interval" => "10s" } ],
[ "start", { "timeout" => "240", "interval" => "10s" } ]
],
}
RA[:params_string] = resource_params_string(RA[:params])
RA[:meta_string] = resource_meta_string(RA[:meta])
RA[:op_string] = resource_op_string(RA[:op])
RA[:config] = <<EOF
primitive keystone ocf:openstack:keystone \\
#{RA[:params_string]} \\
#{RA[:meta_string]} \\
#{RA[:op_string]}
EOF
end
end
end

View File

@@ -0,0 +1,31 @@
require ::File.join(::File.dirname(__FILE__), *%w(.. .. libraries pacemaker resource primitive))
module Chef::RSpec
module Pacemaker
module Config
KEYSTONE_PRIMITIVE = ::Pacemaker::Resource::Primitive.new('keystone')
KEYSTONE_PRIMITIVE.agent = "ocf:openstack:keystone"
KEYSTONE_PRIMITIVE.params = [
[ "os_password", "adminpw" ],
[ "os_auth_url", "http://node1:5000/v2.0" ],
[ "os_username", "admin" ],
[ "os_tenant_name", "openstack" ],
[ "user", "openstack-keystone" ],
]
KEYSTONE_PRIMITIVE.meta = [
[ "target-role", "Started" ],
[ "is-managed", "true" ]
]
KEYSTONE_PRIMITIVE.op = [
[ "monitor", { "timeout" => "60", "interval" => "10s" } ],
[ "start", { "timeout" => "240", "interval" => "10s" } ]
]
KEYSTONE_PRIMITIVE_DEFINITION = <<'EOF'
primitive keystone ocf:openstack:keystone \
params os_auth_url="http://node1:5000/v2.0" os_password="adminpw" os_tenant_name="openstack" os_username="admin" user="openstack-keystone" \
meta is-managed="true" target-role="Started" \
op monitor interval="10s" timeout="60" start interval="10s" timeout="240"
EOF
end
end
end

View File

@@ -0,0 +1,112 @@
require 'spec_helper'
require 'mixlib/shellout'
require_relative File.join(%w(.. .. libraries pacemaker))
require_relative File.join(%w(.. helpers keystone_primitive))
describe Pacemaker::CIBObject do
before(:each) do
Mixlib::ShellOut.any_instance.stub(:run_command)
end
let(:cib_object) { Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE.dup }
#####################################################################
# examples start here
context "no CIB object" do
before(:each) do
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:error!) \
.and_raise(RuntimeError)
end
describe "#load_definition" do
it "should return nil cluster config" do
cib_object.load_definition
expect(cib_object.definition).to eq(nil)
end
end
describe "#exists?" do
it "should return false" do
cib_object.load_definition
expect(cib_object.exists?).to be(false)
end
end
end
context "keystone primitive resource CIB object" do
before(:each) do
Mixlib::ShellOut.any_instance.stub(:error!)
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return(cib_object.definition_string)
end
context "with definition loaded" do
before(:each) do
cib_object.load_definition
end
describe "#exists?" do
it "should return true" do
expect(cib_object.exists?).to be(true)
end
end
describe "#load_definition" do
it "should retrieve cluster config" do
expect(cib_object.definition).to eq(cib_object.definition_string)
end
end
describe "#type" do
it "should return primitive" do
expect(cib_object.type).to eq("primitive")
end
end
end
describe "::from_name" do
it "should instantiate a Pacemaker::Resource::Primitive" do
obj = Pacemaker::CIBObject.from_name(cib_object.name)
expect(obj.is_a? Pacemaker::Resource::Primitive).to be_true
end
end
end
context "CIB object with unregistered type" do
before(:each) do
Mixlib::ShellOut.any_instance.stub(:error!)
end
describe "::from_name" do
it "should refuse to instantiate from any subclass" do
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return("unregistered #{cib_object.name} <definition>")
expect {
Pacemaker::CIBObject.from_name(cib_object.name)
}.to raise_error "No subclass of Pacemaker::CIBObject was registered with type 'unregistered'"
end
end
end
context "invalid CIB object definition" do
before(:each) do
Mixlib::ShellOut.any_instance.stub(:error!)
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return("nonsense")
end
describe "#type" do
it "should raise an error without a valid definition" do
expect { cib_object.load_definition }.to \
raise_error(RuntimeError, "Couldn't extract CIB object type from 'nonsense'")
end
end
end
end

View File

@@ -1,167 +0,0 @@
require 'spec_helper'
require_relative File.join(%w(.. .. libraries cib_objects))
require_relative File.join(%w(.. helpers keystone_config))
describe Chef::Libraries::Pacemaker::CIBObjects do
include Chef::Libraries::Pacemaker::CIBObjects
shared_context "shellout stubs" do
before(:each) do
Mixlib::ShellOut.any_instance.stub(:run_command)
end
end
shared_context "keystone config" do
let(:ra) { Chef::RSpec::Pacemaker::Config::RA }
end
shared_context "keystone primitive" do
include_context "shellout stubs"
include_context "keystone config"
before(:each) do
Mixlib::ShellOut.any_instance.stub(:error!)
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return(ra[:config])
end
end
shared_context "no keystone primitive" do
include_context "shellout stubs"
before(:each) do
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:error!) \
.and_raise(RuntimeError)
end
end
describe "#get_cib_object_definition" do
include_context "keystone primitive"
it "should retrieve cluster config" do
expect(get_cib_object_definition("keystone")).to eq(ra[:config])
end
end
describe "#get_cib_object_definition" do
include_context "no keystone primitive"
it "should return nil cluster config" do
expect(get_cib_object_definition("keystone")).to eq(nil)
end
end
describe "#cib_object_exists?" do
include_context "keystone primitive"
it "should return true" do
expect(cib_object_exists?("keystone")).to be(true)
end
end
describe "#cib_object_exists?" do
include_context "no keystone primitive"
it "should return false" do
expect(cib_object_exists?("keystone")).to be(false)
end
end
describe "#cib_object_type" do
include_context "keystone config"
it "should return primitive" do
expect(cib_object_type(ra[:config])).to eq("primitive")
end
it "should raise an error without valid config" do
expect { cib_object_type("nonsense") }.to raise_error
end
end
describe "#pacemaker_resource_running?" do
before(:each) do
@cmd = double(Mixlib::ShellOut)
expect(self).to receive(:shell_out!) \
.with(*%w(crm resource status keystone)).ordered \
.and_return(@cmd)
end
it "should return true" do
expect(@cmd).to receive(:stdout).at_least(:once).and_return("resource keystone is running")
expect(pacemaker_resource_running?("keystone")).to be(true)
end
it "should return false" do
expect(@cmd).to receive(:stdout).at_least(:once).and_return("resource keystone is not running")
expect(pacemaker_resource_running?("keystone")).to be(false)
end
end
describe "#resource_params_string" do
it "should return empty string with nil params" do
expect(resource_params_string(nil)).to eq("")
end
it "should return empty string with empty params" do
expect(resource_params_string({})).to eq("")
end
it "should return a resource params string" do
params = {
"foo" => "bar",
"baz" => "qux",
}
expect(resource_params_string(params)).to eq(%' params baz="qux" foo="bar"')
end
end
describe "#resource_meta_string" do
it "should return empty string with nil meta" do
expect(resource_meta_string(nil)).to eq("")
end
it "should return empty string with empty meta" do
expect(resource_meta_string({})).to eq("")
end
it "should return a resource meta string" do
meta = {
"foo" => "bar",
"baz" => "qux",
}
expect(resource_meta_string(meta)).to eq(%' meta baz="qux" foo="bar"')
end
end
describe "#resource_op_string" do
it "should return empty string with nil op" do
expect(resource_op_string(nil)).to eq("")
end
it "should return empty string with empty op" do
expect(resource_op_string({})).to eq("")
end
it "should return a resource op string" do
op = {
"monitor" => {
"foo" => "bar",
"baz" => "qux",
}
}
expect(resource_op_string(op)).to eq(%' op monitor baz="qux" foo="bar"')
end
end
describe "#extract_hash" do
include_context "keystone config"
it "should extract a hash from config" do
expect(extract_hash("keystone", ra[:config], "params")).to eq(Hash[ra[:params]])
end
end
end

View File

@@ -0,0 +1,108 @@
require 'spec_helper'
require_relative File.join(%w(.. .. libraries pacemaker resource primitive))
require_relative File.join(%w(.. helpers keystone_primitive))
describe Pacemaker::Resource::Primitive do
before(:each) do
@primitive = Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE.dup
Mixlib::ShellOut.any_instance.stub(:run_command)
end
it "should barf if the loaded definition's type is not primitive" do
Mixlib::ShellOut.any_instance.stub(:error!)
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return("clone foo blah blah")
expect { @primitive.load_definition }.to \
raise_error(Pacemaker::ObjectTypeMismatch,
"Expected primitive type but loaded definition was type clone")
end
describe "#params_string" do
it "should return empty string with nil params" do
@primitive.params = nil
expect(@primitive.params_string).to eq("")
end
it "should return empty string with empty params" do
@primitive.params = {}
expect(@primitive.params_string).to eq("")
end
it "should return a resource params string" do
@primitive.params = {
"foo" => "bar",
"baz" => "qux",
}
expect(@primitive.params_string).to eq(%' params baz="qux" foo="bar"')
end
end
describe "#meta_string" do
it "should return empty string with nil meta" do
@primitive.meta = nil
expect(@primitive.meta_string).to eq("")
end
it "should return empty string with empty meta" do
@primitive.meta = {}
expect(@primitive.meta_string).to eq("")
end
it "should return a resource meta string" do
@primitive.meta = {
"foo" => "bar",
"baz" => "qux",
}
expect(@primitive.meta_string).to eq(%' meta baz="qux" foo="bar"')
end
end
describe "#op_string" do
it "should return empty string with nil op" do
@primitive.op = nil
expect(@primitive.op_string).to eq("")
end
it "should return empty string with empty op" do
@primitive.op = {}
expect(@primitive.op_string).to eq("")
end
it "should return a resource op string" do
@primitive.op = {
"monitor" => {
"foo" => "bar",
"baz" => "qux",
}
}
expect(@primitive.op_string).to eq(%' op monitor baz="qux" foo="bar"')
end
end
describe "::extract_hash" do
it "should extract a hash from config" do
expect(@primitive.class.extract_hash(@primitive.definition_string, "params")).to \
eq(Hash[@primitive.params])
end
end
describe "#definition_string" do
it "should return the definition string" do
expect(@primitive.definition_string).to \
eq(Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE_DEFINITION)
end
end
describe "#parse_definition" do
before(:each) do
@parsed = Pacemaker::Resource::Primitive.new(@primitive.name)
@parsed.definition = Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE_DEFINITION
@parsed.parse_definition
end
it "should parse the agent" do
expect(@parsed.agent).to eq(@primitive.agent)
end
end
end

View File

@@ -0,0 +1,27 @@
require 'spec_helper'
require_relative File.join(%w(.. .. libraries pacemaker resource))
describe Pacemaker::Resource do
describe "#running?" do
let(:rsc) { Pacemaker::Resource.new('keystone') }
before(:each) do
@cmd = double(Mixlib::ShellOut)
expect(rsc).to receive(:shell_out!) \
.with(*%w(crm resource status keystone)) \
.and_return(@cmd)
end
it "should return true" do
expect(@cmd).to receive(:stdout).at_least(:once) \
.and_return("resource #{rsc.name} is running on: d52-54-00-e5-6b-a0")
expect(rsc.running?).to be(true)
end
it "should return false" do
expect(@cmd).to receive(:stdout).at_least(:once) \
.and_return("resource #{rsc.name} is NOT running")
expect(rsc.running?).to be(false)
end
end
end

View File

@@ -1,12 +1,12 @@
require 'chef/application'
require_relative File.join(%w(.. spec_helper))
require_relative File.join(%w(.. helpers keystone_config))
require_relative File.join(%w(.. helpers keystone_primitive))
describe "Chef::Provider::PacemakerPrimitive" do
# for use inside examples:
let(:ra) { Chef::RSpec::Pacemaker::Config::RA }
let(:rsc) { Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE }
# for use outside examples (e.g. when invoking shared_examples)
ra = Chef::RSpec::Pacemaker::Config::RA
rsc = Chef::RSpec::Pacemaker::Config::KEYSTONE_PRIMITIVE
before(:each) do
runner_opts = {
@@ -17,27 +17,48 @@ describe "Chef::Provider::PacemakerPrimitive" do
@node = @chef_run.node
@run_context = @chef_run.run_context
@resource = Chef::Resource::PacemakerPrimitive.new(ra[:name], @run_context)
@resource.agent ra[:agent]
@resource.params Hash[ra[:params]]
@resource.meta Hash[ra[:meta]]
@resource.op Hash[ra[:op]]
@resource = Chef::Resource::PacemakerPrimitive.new(rsc.name, @run_context)
@resource.agent rsc.agent
@resource.params Hash[rsc.params]
@resource.meta Hash[rsc.meta]
@resource.op Hash[rsc.op]
end
let (:provider) { Chef::Provider::PacemakerPrimitive.new(@resource, @run_context) }
# "crm configure show" is executed by load_current_resource, and
# again later on for the :create action, to see whether to create or
# modify.
def expect_definition(definition)
Mixlib::ShellOut.any_instance.stub(:run_command)
Mixlib::ShellOut.any_instance.stub(:error!)
expect_any_instance_of(Mixlib::ShellOut) \
.to receive(:stdout) \
.and_return(definition)
end
def expect_exists(exists)
expect_any_instance_of(Pacemaker::Resource::Primitive) \
.to receive(:exists?) \
.and_return(exists)
end
def expect_running(running)
expect_any_instance_of(Pacemaker::Resource::Primitive) \
.to receive(:running?) \
.and_return(running)
end
describe ":create action" do
it "should modify the primitive if it already exists" do
new_params = Hash[ra[:params]].merge("os_password" => "newpasswd")
new_params = Hash[rsc.params].merge("os_password" => "newpasswd")
new_params.delete("os_tenant_name")
@resource.params new_params
@resource.meta Hash[ra[:meta]].merge("target-role" => "Stopped")
@resource.meta Hash[rsc.meta].merge("target-role" => "Stopped")
# get_cib_object_definition is invoked by load_current_resource
# and later used to see whether to create or modify.
expect(provider).to receive(:get_cib_object_definition).and_return(ra[:config])
expect_definition(rsc.definition_string)
configure_cmd_prefix = "crm_resource --resource #{ra[:name]}"
configure_cmd_prefix = "crm_resource --resource #{rsc.name}"
expected_configure_cmd_args = [
%'--set-parameter "os_password" --parameter-value "newpasswd"',
%'--delete-parameter "os_tenant_name"',
@@ -45,6 +66,7 @@ describe "Chef::Provider::PacemakerPrimitive" do
]
provider.run_action :create
expected_configure_cmd_args.each do |args|
cmd = configure_cmd_prefix + " " + args
expect(@chef_run).to run_execute(cmd)
@@ -53,33 +75,24 @@ describe "Chef::Provider::PacemakerPrimitive" do
end
it "should create a primitive if it doesn't already exist" do
# get_cib_object_definition is invoked by load_current_resource
# and later used to see whether to create or modify.
expect(provider).to receive(:get_cib_object_definition).and_return("")
# Later, the :create action calls cib_object_exists? to check
expect_definition("")
# Later, the :create action calls Pacemaker::Resource::Primitive#exists? to check
# that creation succeeded.
expect(provider).to receive(:cib_object_exists?).and_return(true)
expect_exists(true)
provider.run_action :create
cmd = "crm configure primitive #{ra[:name]} #{ra[:agent]}" + \
ra[:params_string] + ra[:meta_string] + ra[:op_string]
expect(@chef_run).to run_execute(cmd)
expect(@chef_run).to run_execute(rsc.crm_configure_command)
expect(@resource).to be_updated
end
it "should barf if the primitive has the wrong agent" do
it "should barf if the primitive is already defined with the wrong agent" do
existing_agent = "ocf:openstack:something-else"
config = ra[:config].sub(ra[:agent], existing_agent)
# get_cib_object_definition is invoked by load_current_resource
# and later used to see whether to create or modify.
expect(provider).to receive(:get_cib_object_definition).and_return(config)
definition = rsc.definition_string.sub(rsc.agent, existing_agent)
expect_definition(definition)
expected_error = \
"Existing resource primitive '#{ra[:name]}' has agent '#{existing_agent}' " \
"Existing resource primitive '#{rsc.name}' has agent '#{existing_agent}' " \
"but recipe wanted '#{@resource.agent}'"
expect { provider.run_action :create }.to \
raise_error(RuntimeError, expected_error)
@@ -90,8 +103,7 @@ describe "Chef::Provider::PacemakerPrimitive" do
shared_examples "action on non-existent resource" do |action, cmd, expected_error|
it "should not attempt to #{action.to_s} a non-existent resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).once.and_return("")
expect_definition("")
if expected_error
expect { provider.run_action action }.to \
@@ -107,28 +119,28 @@ describe "Chef::Provider::PacemakerPrimitive" do
describe ":delete action" do
it_should_behave_like "action on non-existent resource", \
:delete, "crm configure delete #{ra[:name]}", nil
:delete, "crm configure delete #{rsc.name}", nil
it "should not delete a running resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).once.and_return(ra[:config])
expect_definition(rsc.definition_string)
expect_running(true)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(true)
cmd = "crm configure delete #{ra[:name]}"
expected_error = "Cannot delete running resource primitive #{ra[:name]}"
expected_error = "Cannot delete running resource primitive #{rsc.name}"
expect { provider.run_action :delete }.to \
raise_error(RuntimeError, expected_error)
cmd = "crm configure delete '#{rsc.name}'"
expect(@chef_run).not_to run_execute(cmd)
expect(@resource).not_to be_updated
end
it "should delete a non-running resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).once.and_return(ra[:config])
expect_definition(rsc.definition_string)
expect_running(false)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(false)
cmd = "crm configure delete #{ra[:name]}"
provider.run_action :delete
cmd = "crm configure delete '#{rsc.name}'"
expect(@chef_run).to run_execute(cmd)
expect(@resource).to be_updated
end
@@ -137,30 +149,28 @@ describe "Chef::Provider::PacemakerPrimitive" do
describe ":start action" do
it_should_behave_like "action on non-existent resource", \
:start,
"crm resource start #{ra[:name]}", \
"Cannot start non-existent resource primitive '#{ra[:name]}'"
"crm resource start #{rsc.name}", \
"Cannot start non-existent resource primitive '#{rsc.name}'"
it "should do nothing to a started resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).and_return(ra[:config])
expect_definition(rsc.definition_string)
expect_running(true)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(true)
cmd = "crm resource start #{ra[:name]}"
provider.run_action :start
cmd = "crm resource start #{rsc.name}"
expect(@chef_run).not_to run_execute(cmd)
expect(@resource).not_to be_updated
end
it "should start a stopped resource" do
config = ra[:config].sub("Started", "Stopped")
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).and_return(config)
config = rsc.definition_string.sub("Started", "Stopped")
expect_definition(config)
expect_running(false)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(false)
cmd = "crm resource start #{ra[:name]}"
provider.run_action :start
cmd = "crm resource start '#{rsc.name}'"
expect(@chef_run).to run_execute(cmd)
expect(@resource).to be_updated
end
@@ -169,29 +179,27 @@ describe "Chef::Provider::PacemakerPrimitive" do
describe ":stop action" do
it_should_behave_like "action on non-existent resource", \
:stop,
"crm resource stop #{ra[:name]}", \
"Cannot stop non-existent resource primitive '#{ra[:name]}'"
"crm resource stop #{rsc.name}", \
"Cannot stop non-existent resource primitive '#{rsc.name}'"
it "should do nothing to a stopped resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).and_return(ra[:config])
expect_definition(rsc.definition_string)
expect_running(false)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(false)
cmd = "crm resource start #{ra[:name]}"
provider.run_action :stop
cmd = "crm resource start #{rsc.name}"
expect(@chef_run).not_to run_execute(cmd)
expect(@resource).not_to be_updated
end
it "should stop a started resource" do
# get_cib_object_definition is invoked by load_current_resource
expect(provider).to receive(:get_cib_object_definition).and_return(ra[:config])
expect_definition(rsc.definition_string)
expect_running(true)
expect(provider).to receive(:pacemaker_resource_running?).once.and_return(true)
cmd = "crm resource stop #{ra[:name]}"
provider.run_action :stop
cmd = "crm resource stop '#{rsc.name}'"
expect(@chef_run).to run_execute(cmd)
expect(@resource).to be_updated
end

View File

@@ -17,7 +17,7 @@ RSpec.configure do |config|
#config.role_path = '/var/roles'
# Specify the Chef log_level (default: :warn)
#config.log_level = :debug
config.log_level = ENV['CHEF_LOG_LEVEL'].to_sym if ENV['CHEF_LOG_LEVEL']
# Specify the path to a local JSON file with Ohai data (default: nil)
#config.path = 'ohai.json'