File: zpool.rb

package info (click to toggle)
puppet-agent 8.10.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 27,392 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (189 lines) | stat: -rw-r--r-- 5,084 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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").map do |line|
      name, _size, _alloc, _free, _cap, _health, _altroot = line.split(%r{\s+})
      new(name: name, ensure: :present)
    end
  end

  def get_zpool_property(prop)
    zpool(:get, prop, @resource[:name]).split("\n").reverse.map { |line|
      name, _property, value, _source = line.split("\s")
      value if name == @resource[:name]
    }.shift
  end

  def process_zpool_data(pool_array)
    if pool_array == []
      return Hash.new(:absent)
    end
    # get the name and get rid of it
    pool = {}
    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 'cache'
        sym = :cache
      when %r{^mirror|^raidz1|^raidz2}
        sym = (value =~ %r{^mirror}) ? :mirror : :raidz
        pool[:raid_parity] = 'raidz2' if %r{^raidz2}.match?(value)
      else
        # get full drive name if the value is a partition (Linux only)
        tmp << if Facter.value(:kernel) == 'Linux' && value =~ %r{/dev/(:?[a-z]+([0-9]+n[0-9]+p)?1|disk/by-id/.+-part1)$}
                 execute("lsblk -p -no pkname #{value}").chomp
               else
                 value
               end
        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
    # https://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
    zpool_opts = case Facter.value(:kernel)
                 # use full device names ("-P") on Linux/ZOL to prevent
                 # mismatches between creation and display paths:
                 when 'Linux'
                   '-P'
                 else
                   ''
                 end
    out = execute("zpool status #{zpool_opts} #{@resource[:pool]}", failonfail: false, combine: false)
    zpool_data = out.lines.select { |line| line.index("\t") == 0 }.map { |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) && @current_pool
    @current_pool
  end

  def flush
    @current_pool = nil
  end

  # Adds log and spare
  def build_named(name)
    prop = @resource[name.to_sym]
    if prop
      [name] + prop.map { |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.map { |a| [prefix] + a.split(' ') }.flatten
  end

  # builds up the vdevs for create command
  def build_vdevs
    disk = @resource[:disk]
    mirror = @resource[:mirror]
    raidz = @resource[:raidz]

    if disk
      disk.map { |d| d.split(' ') }.flatten
    elsif mirror
      handle_multi_arrays('mirror', mirror)
    elsif raidz
      handle_multi_arrays(raidzarity, raidz)
    end
  end

  def add_pool_properties
    properties = []
    [:ashift, :autoexpand, :failmode].each do |property|
      if (value = @resource[property]) && value != ''
        properties << '-o' << "#{property}=#{value}"
      end
    end
    properties
  end

  def create
    zpool(*([:create] + add_pool_properties + [@resource[:pool]] + build_vdevs + build_named('spare') + build_named('log') + build_named('cache')))
  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, :cache].each do |field|
    define_method(field) do
      current_pool[field]
    end

    # rubocop:disable Style/SignalException
    define_method(field.to_s + '=') do |should|
      fail "zpool #{field} can't be changed. should be #{should}, currently is #{current_pool[field]}"
    end
  end

  [:autoexpand, :failmode].each do |field|
    define_method(field) do
      get_zpool_property(field)
    end

    define_method(field.to_s + '=') do |should|
      zpool(:set, "#{field}=#{should}", @resource[:name])
    end
  end

  # Borrow the code from the ZFS provider here so that we catch and return '-'
  # as ashift is linux only.
  # see lib/puppet/provider/zfs/zfs.rb

  PARAMETER_UNSET_OR_NOT_AVAILABLE = '-'.freeze unless defined? PARAMETER_UNSET_OR_NOT_AVAILABLE

  define_method(:ashift) do
    get_zpool_property(:ashift)
  rescue
    PARAMETER_UNSET_OR_NOT_AVAILABLE
  end

  define_method('ashift=') do |should|
    zpool(:set, "ashift=#{should}", @resource[:name])
  rescue
    PARAMETER_UNSET_OR_NOT_AVAILABLE
  end
end