diff --git a/acceptance/tests/resource/zpool/basic_tests.rb b/acceptance/tests/resource/zpool/basic_tests.rb index 07fde2f42..5467395bf 100644 --- a/acceptance/tests/resource/zpool/basic_tests.rb +++ b/acceptance/tests/resource/zpool/basic_tests.rb @@ -1,92 +1,161 @@ test_name "ZPool: configuration" confine :to, :platform => 'solaris' require 'puppet/acceptance/solaris_util' extend Puppet::Acceptance::ZPoolUtils teardown do step "ZPool: cleanup" agents.each do |agent| clean agent end end agents.each do |agent| step "ZPool: setup" setup agent #----------------------------------- - step "ZPool: ensure create" + step "ZPool: create zpool disk" apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, disk=>'/ztstpool/dsk1' }") do assert_match( /ensure: created/, result.stdout, "err: #{agent}") end - step "ZPool: idempotency - create" + step "ZPool: zpool should be idempotent" apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, disk=>'/ztstpool/dsk1' }") do assert_no_match( /ensure: created/, result.stdout, "err: #{agent}") end - step "ZPool: remove" + step "ZPool: remove zpool" apply_manifest_on(agent, "zpool{ tstpool: ensure=>absent }") do assert_match( /ensure: removed/ , result.stdout, "err: #{agent}") end - step "ZPool: disk array" + step "ZPool: create zpool with a disk array" apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, disk=>['/ztstpool/dsk1','/ztstpool/dsk2'] }") do assert_match( /ensure: created/ , result.stdout, "err: #{agent}") end - step "ZPool: disk array: verify" + step "ZPool: verify disk array was created" on agent, "zpool list -H" do assert_match( /tstpool/ , result.stdout, "err: #{agent}") end - step "ZPool: disk array: verify with puppet" + step "ZPool: verify puppet resource reports on the disk array" on agent, "puppet resource zpool tstpool" do assert_match(/ensure => 'present'/, result.stdout, "err: #{agent}") assert_match(/disk +=> .'.+dsk1 .+dsk2'./, result.stdout, "err: #{agent}") end - step "ZPool: remove again for mirror tests" + step "ZPool: remove zpool in preparation for mirror tests" apply_manifest_on(agent, "zpool{ tstpool: ensure=>absent }") do assert_match( /ensure: removed/ , result.stdout, "err: #{agent}") end - step "ZPool: mirror: ensure can create" - apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, mirror=>['/ztstpool/dsk1','/ztstpool/dsk2', '/ztstpool/dsk3'] }") do + step "ZPool: create mirrored zpool with 3 virtual devices" + apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, mirror=>['/ztstpool/dsk1 /ztstpool/dsk2 /ztstpool/dsk3'] }") do assert_match( /ensure: created/ , result.stdout, "err: #{agent}") end - step "ZPool: mirror: ensure can create: verify" + step "ZPool: verify mirrors were created" on agent, "zpool status -v tstpool" do - assert_match( /tstpool/ , result.stdout, "err: #{agent}") - assert_match( /mirror/ , result.stdout, "err: #{agent}") + # NAME STATE READ WRITE CKSUM + # tstpool ONLINE 0 0 0 + # mirror-0 ONLINE 0 0 0 + # /ztstpool/dsk1 ONLINE 0 0 0 + # /ztstpool/dsk2 ONLINE 0 0 0 + # /ztstpool/dsk3 ONLINE 0 0 0 + assert_match( /tstpool.*\n\s+mirror.*\n\s*\/ztstpool\/dsk1.*\n\s*\/ztstpool\/dsk2.*\n\s*\/ztstpool\/dsk3/m, result.stdout, "err: #{agent}") end - step "ZPool: mirror: ensure can create: vefify (puppet)" + step "ZPool: verify puppet resource reports on the mirror" on agent, "puppet resource zpool tstpool" do assert_match(/ensure => 'present'/, result.stdout, "err: #{agent}") - assert_match(/mirror => .'.+dsk1 .+dsk2 .+dsk3'./, result.stdout, "err: #{agent}") + assert_match(/mirror => \['\/ztstpool\/dsk1 \/ztstpool\/dsk2 \/ztstpool\/dsk3'\]/, result.stdout, "err: #{agent}") end - step "ZPool: remove for raidz test)" + + step "ZPool: remove zpool in preparation for multiple mirrors" apply_manifest_on(agent,"zpool{ tstpool: ensure=>absent }") do assert_match(/ensure: removed/, result.stdout, "err: #{agent}") end - step "ZPool: raidz: ensure can create" - apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, raidz=>['/ztstpool/dsk1','/ztstpool/dsk2', '/ztstpool/dsk3'] }") do + step "ZPool: create 2 mirrored zpools each with 2 virtual devices" + apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, mirror=>['/ztstpool/dsk1 /ztstpool/dsk2', '/ztstpool/dsk3 /ztstpool/dsk5'] }") do assert_match( /ensure: created/ , result.stdout, "err: #{agent}") end - step "ZPool: raidz: ensure can create: verify" + step "ZPool: verify both mirrors were created" on agent, "zpool status -v tstpool" do - assert_match( /tstpool/ , result.stdout, "err: #{agent}") - assert_match( /raidz/ , result.stdout, "err: #{agent}") + # NAME STATE READ WRITE CKSUM + # tstpool ONLINE 0 0 0 + # mirror-0 ONLINE 0 0 0 + # /ztstpool/dsk1 ONLINE 0 0 0 + # /ztstpool/dsk2 ONLINE 0 0 0 + # mirror-1 ONLINE 0 0 0 + # /ztstpool/dsk3 ONLINE 0 0 0 + # /ztstpool/dsk5 ONLINE 0 0 0 + assert_match( /tstpool.*\n\s+mirror.*\n\s*\/ztstpool\/dsk1.*\n\s*\/ztstpool\/dsk2.*\n\s+mirror.*\n\s*\/ztstpool\/dsk3.*\n\s*\/ztstpool\/dsk5/m, result.stdout, "err: #{agent}") + end + + step "ZPool: verify puppet resource reports on both mirrors" + on agent, "puppet resource zpool tstpool" do + assert_match(/ensure => 'present'/, result.stdout, "err: #{agent}") + assert_match(/mirror => \['\/ztstpool\/dsk1 \/ztstpool\/dsk2', '\/ztstpool\/dsk3 \/ztstpool\/dsk5'\]/, result.stdout, "err: #{agent}") + end + + step "ZPool: remove zpool in preparation for raidz test" + apply_manifest_on(agent,"zpool{ tstpool: ensure=>absent }") do + assert_match(/ensure: removed/, result.stdout, "err: #{agent}") + end + + step "ZPool: create raidz pool consisting of 3 virtual devices" + apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, raidz=>['/ztstpool/dsk1 /ztstpool/dsk2 /ztstpool/dsk3'] }") do + assert_match( /ensure: created/ , result.stdout, "err: #{agent}") + end + + step "ZPool: verify raidz pool was created" + on agent, "zpool status -v tstpool" do + # NAME STATE READ WRITE CKSUM + # tstpool ONLINE 0 0 0 + # raidz1-0 ONLINE 0 0 0 + # /ztstpool/dsk1 ONLINE 0 0 0 + # /ztstpool/dsk2 ONLINE 0 0 0 + # /ztstpool/dsk3 ONLINE 0 0 0 + assert_match( /tstpool.*\n\s+raidz.*\n\s*\/ztstpool\/dsk1.*\n\s*\/ztstpool\/dsk2.*\n\s*\/ztstpool\/dsk3/m, result.stdout, "err: #{agent}") + end + + step "ZPool: verify puppet reports on the raidz pool" + on agent, "puppet resource zpool tstpool" do + assert_match(/ensure => 'present'/, result.stdout, "err: #{agent}") + assert_match(/raidz => \['\/ztstpool\/dsk1 \/ztstpool\/dsk2 \/ztstpool\/dsk3'\]/, result.stdout, "err: #{agent}") + end + + step "ZPool: remove zpool in preparation for multiple raidz pools" + apply_manifest_on(agent,"zpool{ tstpool: ensure=>absent }") do + assert_match(/ensure: removed/, result.stdout, "err: #{agent}") + end + + step "ZPool: create 2 mirrored zpools each with 2 virtual devices" + apply_manifest_on(agent, "zpool{ tstpool: ensure=>present, raidz=>['/ztstpool/dsk1 /ztstpool/dsk2', '/ztstpool/dsk3 /ztstpool/dsk5'] }") do + assert_match( /ensure: created/ , result.stdout, "err: #{agent}") + end + + step "ZPool: verify both raidz were created" + on agent, "zpool status -v tstpool" do + # NAME STATE READ WRITE CKSUM + # tstpool ONLINE 0 0 0 + # raidz1-0 ONLINE 0 0 0 + # /ztstpool/dsk1 ONLINE 0 0 0 + # /ztstpool/dsk2 ONLINE 0 0 0 + # raidz1-1 ONLINE 0 0 0 + # /ztstpool/dsk3 ONLINE 0 0 0 + # /ztstpool/dsk5 ONLINE 0 0 0 + assert_match( /tstpool.*\n\s+raidz.*\n\s*\/ztstpool\/dsk1.*\n\s*\/ztstpool\/dsk2.*\n\s+raidz.*\n\s*\/ztstpool\/dsk3.*\n\s*\/ztstpool\/dsk5/m, result.stdout, "err: #{agent}") end - step "ZPool: raidz: ensure can create: verify (puppet)" + step "ZPool: verify puppet resource reports on both raidz" on agent, "puppet resource zpool tstpool" do assert_match(/ensure => 'present'/, result.stdout, "err: #{agent}") - assert_match(/raidz +=> .'.+dsk1 .+dsk2 .+dsk3'./, result.stdout, "err: #{agent}") + assert_match(/raidz => \['\/ztstpool\/dsk1 \/ztstpool\/dsk2', '\/ztstpool\/dsk3 \/ztstpool\/dsk5'\]/, result.stdout, "err: #{agent}") end end diff --git a/lib/puppet/provider/zpool/zpool.rb b/lib/puppet/provider/zpool/zpool.rb index f0ed41c3d..d6f7b3f2c 100644 --- a/lib/puppet/provider/zpool/zpool.rb +++ b/lib/puppet/provider/zpool/zpool.rb @@ -1,120 +1,125 @@ Puppet::Type.type(:zpool).provide(:zpool) do desc "Provider for zpool." commands :zpool => 'zpool' #NAME SIZE ALLOC FREE CAP HEALTH ALTROOT def self.instances zpool(:list, '-H').split("\n").collect do |line| name, size, alloc, free, cap, health, altroot = line.split(/\s+/) new({:name => name, :ensure => :present}) end end def process_zpool_data(pool_array) if pool_array == [] return Hash.new(:absent) end #get the name and get rid of it pool = Hash.new pool[:pool] = pool_array[0] pool_array.shift tmp = [] #order matters here :( pool_array.reverse.each do |value| sym = nil case value when "spares"; sym = :spare when "logs"; sym = :log when /^mirror|^raidz1|^raidz2/; sym = value =~ /^mirror/ ? :mirror : :raidz pool[:raid_parity] = "raidz2" if value =~ /^raidz2/ else tmp << value sym = :disk if value == pool_array.first end if sym pool[sym] = pool[sym] ? pool[sym].unshift(tmp.reverse.join(' ')) : [tmp.reverse.join(' ')] tmp.clear end end pool end def get_pool_data # http://docs.oracle.com/cd/E19082-01/817-2271/gbcve/index.html # we could also use zpool iostat -v mypool for a (little bit) cleaner output out = execute("zpool status #{@resource[:pool]}", :failonfail => false, :combine => false) zpool_data = out.lines.select { |line| line.index("\t") == 0 }.collect { |l| l.strip.split("\s")[0] } zpool_data.shift zpool_data end def current_pool @current_pool = process_zpool_data(get_pool_data) unless (defined?(@current_pool) and @current_pool) @current_pool end def flush @current_pool= nil end #Adds log and spare def build_named(name) if prop = @resource[name.intern] [name] + prop.collect { |p| p.split(' ') }.flatten else [] end end #query for parity and set the right string def raidzarity @resource[:raid_parity] ? @resource[:raid_parity] : "raidz1" end + #handle mirror or raid + def handle_multi_arrays(prefix, array) + array.collect{ |a| [prefix] + a.split(' ') }.flatten + end + #builds up the vdevs for create command def build_vdevs if disk = @resource[:disk] disk.collect { |d| d.split(' ') }.flatten elsif mirror = @resource[:mirror] - ["mirror"] + mirror + handle_multi_arrays("mirror", mirror) elsif raidz = @resource[:raidz] - [raidzarity] + raidz + handle_multi_arrays(raidzarity, raidz) end end def create zpool(*([:create, @resource[:pool]] + build_vdevs + build_named("spare") + build_named("log"))) end def destroy zpool :destroy, @resource[:pool] end def exists? if current_pool[:pool] == :absent false else true end end [:disk, :mirror, :raidz, :log, :spare].each do |field| define_method(field) do current_pool[field] end define_method(field.to_s + "=") do |should| self.fail "zpool #{field} can't be changed. should be #{should}, currently is #{current_pool[field]}" end end end diff --git a/spec/unit/provider/zpool/zpool_spec.rb b/spec/unit/provider/zpool/zpool_spec.rb index dd94ae6d5..6fce286c1 100755 --- a/spec/unit/provider/zpool/zpool_spec.rb +++ b/spec/unit/provider/zpool/zpool_spec.rb @@ -1,214 +1,251 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zpool).provider(:zpool) do let(:name) { 'mypool' } let(:zpool) { '/usr/sbin/zpool' } let(:resource) do Puppet::Type.type(:zpool).new(:name => name, :provider => :zpool) end let(:provider) { resource.provider } before do provider.class.stubs(:which).with('zpool').returns(zpool) end context '#current_pool' do it "should call process_zpool_data with the result of get_pool_data only once" do provider.stubs(:get_pool_data).returns(["foo", "disk"]) provider.expects(:process_zpool_data).with(["foo", "disk"]).returns("stuff").once provider.current_pool provider.current_pool end end describe "self.instances" do it "should have an instances method" do provider.class.should respond_to(:instances) end it "should list instances" do provider.class.expects(:zpool).with(:list,'-H').returns File.read(my_fixture('zpool-list.out')) instances = provider.class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } instances.size.should == 2 instances[0].should == {:name => 'rpool', :ensure => :present} instances[1].should == {:name => 'mypool', :ensure => :present} end end context '#flush' do it "should reload the pool" do provider.stubs(:get_pool_data) provider.expects(:process_zpool_data).returns("stuff").times(2) provider.current_pool provider.flush provider.current_pool end end context '#process_zpool_data' do let(:zpool_data) { ["foo", "disk"] } describe "when there is no data" do it "should return a hash with ensure=>:absent" do provider.process_zpool_data([])[:ensure].should == :absent end end describe "when there is a spare" do it "should add the spare disk to the hash" do zpool_data.concat ["spares", "spare_disk"] provider.process_zpool_data(zpool_data)[:spare].should == ["spare_disk"] end end describe "when there are two spares" do it "should add the spare disk to the hash as a single string" do zpool_data.concat ["spares", "spare_disk", "spare_disk2"] provider.process_zpool_data(zpool_data)[:spare].should == ["spare_disk spare_disk2"] end end describe "when there is a log" do it "should add the log disk to the hash" do zpool_data.concat ["logs", "log_disk"] provider.process_zpool_data(zpool_data)[:log].should == ["log_disk"] end end describe "when there are two logs" do it "should add the log disks to the hash as a single string" do zpool_data.concat ["spares", "spare_disk", "spare_disk2"] provider.process_zpool_data(zpool_data)[:spare].should == ["spare_disk spare_disk2"] end end describe "when the vdev is a single mirror" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror", "disk1", "disk2"] provider.process_zpool_data(zpool_data)[:mirror].should == ["disk1 disk2"] end end describe "when the vdev is a single mirror on solaris 10u9 or later" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror-0", "disk1", "disk2"] provider.process_zpool_data(zpool_data)[:mirror].should == ["disk1 disk2"] end end describe "when the vdev is a double mirror" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror", "disk1", "disk2", "mirror", "disk3", "disk4"] provider.process_zpool_data(zpool_data)[:mirror].should == ["disk1 disk2", "disk3 disk4"] end end describe "when the vdev is a double mirror on solaris 10u9 or later" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror-0", "disk1", "disk2", "mirror-1", "disk3", "disk4"] provider.process_zpool_data(zpool_data)[:mirror].should == ["disk1 disk2", "disk3 disk4"] end end describe "when the vdev is a raidz1" do it "should call create_multi_array with raidz1" do zpool_data = ["mirrorpool", "raidz1", "disk1", "disk2"] provider.process_zpool_data(zpool_data)[:raidz].should == ["disk1 disk2"] end end describe "when the vdev is a raidz1 on solaris 10u9 or later" do it "should call create_multi_array with raidz1" do zpool_data = ["mirrorpool", "raidz1-0", "disk1", "disk2"] provider.process_zpool_data(zpool_data)[:raidz].should == ["disk1 disk2"] end end describe "when the vdev is a raidz2" do it "should call create_multi_array with raidz2 and set the raid_parity" do zpool_data = ["mirrorpool", "raidz2", "disk1", "disk2"] pool = provider.process_zpool_data(zpool_data) pool[:raidz].should == ["disk1 disk2"] pool[:raid_parity].should == "raidz2" end end describe "when the vdev is a raidz2 on solaris 10u9 or later" do it "should call create_multi_array with raidz2 and set the raid_parity" do zpool_data = ["mirrorpool", "raidz2-0", "disk1", "disk2"] pool = provider.process_zpool_data(zpool_data) pool[:raidz].should == ["disk1 disk2"] pool[:raid_parity].should == "raidz2" end end end describe "when calling the getters and setters" do [:disk, :mirror, :raidz, :log, :spare].each do |field| describe "when calling #{field}" do it "should get the #{field} value from the current_pool hash" do pool_hash = {} pool_hash[field] = 'value' provider.stubs(:current_pool).returns(pool_hash) provider.send(field).should == 'value' end end describe "when setting the #{field}" do it "should fail if readonly #{field} values change" do provider.stubs(:current_pool).returns(Hash.new("currentvalue")) expect { provider.send((field.to_s + "=").intern, "shouldvalue") }.to raise_error(Puppet::Error, /can\'t be changed/) end end end end context '#create' do - before do - resource[:disk] = "disk1" + context "when creating disks for a zpool" do + before do + resource[:disk] = "disk1" + end + + it "should call create with the build_vdevs value" do + provider.expects(:zpool).with(:create, name, 'disk1') + provider.create + end + + it "should call create with the 'spares' and 'log' values" do + resource[:spare] = ['value1'] + resource[:log] = ['value2'] + provider.expects(:zpool).with(:create, name, 'disk1', 'spare', 'value1', 'log', 'value2') + provider.create + end end - it "should call create with the build_vdevs value" do - provider.expects(:zpool).with(:create, name, 'disk1') - provider.create + context "when creating mirrors for a zpool" do + it "executes 'create' for a single group of mirrored devices" do + resource[:mirror] = ["disk1 disk2"] + provider.expects(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2') + provider.create + end + + it "repeats the 'mirror' keyword between groups of mirrored devices" do + resource[:mirror] = ["disk1 disk2", "disk3 disk4"] + provider.expects(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2', 'mirror', 'disk3', 'disk4') + provider.create + end end - it "should call create with the 'spares' and 'log' values" do - resource[:spare] = ['value1'] - resource[:log] = ['value2'] - provider.expects(:zpool).with(:create, name, 'disk1', 'spare', 'value1', 'log', 'value2') - provider.create + describe "when creating raidz for a zpool" do + it "executes 'create' for a single raidz group" do + resource[:raidz] = ["disk1 disk2"] + provider.expects(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2') + provider.create + end + + it "execute 'create' for a single raidz2 group" do + resource[:raidz] = ["disk1 disk2"] + resource[:raid_parity] = 'raidz2' + provider.expects(:zpool).with(:create, name, 'raidz2', 'disk1', 'disk2') + provider.create + end + + it "repeats the 'raidz1' keyword between each group of raidz devices" do + resource[:raidz] = ["disk1 disk2", "disk3 disk4"] + provider.expects(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2', 'raidz1', 'disk3', 'disk4') + provider.create + end end end context '#delete' do it "should call zpool with destroy and the pool name" do provider.expects(:zpool).with(:destroy, name) provider.destroy end end context '#exists?' do it "should get the current pool" do provider.expects(:current_pool).returns({:pool => 'somepool'}) provider.exists? end it "should return false if the current_pool is absent" do provider.expects(:current_pool).returns({:pool => :absent}) provider.should_not be_exists end it "should return true if the current_pool has values" do provider.expects(:current_pool).returns({:pool => name}) provider.should be_exists end end end