Class: OsCtl::Lib::Exporter::Zfs

Inherits:
Base
  • Object
show all
Includes:
Utils::Log, Utils::System
Defined in:
lib/libosctl/exporter/zfs.rb

Overview

Handles dumping containers as ZFS streams into tar archives

Usage:

exporter.dump_rootfs do
  # Create a snapshot a dump it
  exporter.dump_base

  # Create another snapshot and dump it as an incremental stream
  # from the base snapshot
  exporter.dump_incremental
end

Constant Summary

Constants inherited from Base

Base::BLOCK_SIZE, Base::DIR_MODE, Base::FILE_MODE

Instance Attribute Summary collapse

Attributes inherited from Base

#ct, #datasets, #opts, #tar

Instance Method Summary collapse

Methods included from Utils::System

#repeat_on_failure, #syscmd, #zfs

Methods included from Utils::Log

included

Methods inherited from Base

#add_file_from_disk, #close, #dump_configs, #dump_metadata, #dump_user_hook_scripts

Constructor Details

#initialize(*_) ⇒ Zfs

Returns a new instance of Zfs.



20
21
22
23
24
25
# File 'lib/libosctl/exporter/zfs.rb', line 20

def initialize(*_)
  super

  @datasets = ct.datasets[1..] # skip the root dataset
  @snapshots = []
end

Instance Attribute Details

#base_snapObject (readonly, protected)

Returns the value of attribute base_snap.



80
81
82
# File 'lib/libosctl/exporter/zfs.rb', line 80

def base_snap
  @base_snap
end

#snapshotsObject (readonly, protected)

Returns the value of attribute snapshots.



80
81
82
# File 'lib/libosctl/exporter/zfs.rb', line 80

def snapshots
  @snapshots
end

Instance Method Details

#dump_baseObject

Dump initial data stream

Should be called from within the block given to #dump_rootfs.



55
56
57
58
59
60
61
# File 'lib/libosctl/exporter/zfs.rb', line 55

def dump_base
  @base_snap = snapshot(ct.dataset, 'base')

  each_dataset_file('base') do |ds, file|
    dump_stream(file, ds, base_snap)
  end
end

#dump_file_name(compression, name) ⇒ Object (protected)



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/libosctl/exporter/zfs.rb', line 184

def dump_file_name(compression, name)
  base = File.join('rootfs', "#{name}.dat")

  case compression
  when :gzip
    "#{base}.gz"

  else
    base
  end
end

#dump_incremental(from_snap: nil) ⇒ Object

Dump incremental data stream from the base stream

Should be called from within the block given to #dump_rootfs.



66
67
68
69
70
71
72
# File 'lib/libosctl/exporter/zfs.rb', line 66

def dump_incremental(from_snap: nil)
  snap = snapshot(ct.dataset, 'incr')

  each_dataset_file('incremental') do |ds, file|
    dump_stream(file, ds, snap, from_snap || base_snap)
  end
end

#dump_rootfs { ... } ⇒ Object

Method used to wrap dumping of base and incremental data streams of rootfs

Yields:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/libosctl/exporter/zfs.rb', line 30

def dump_rootfs
  tar.mkdir('rootfs', DIR_MODE)

  each_dataset_dir do |_ds, dir|
    tar.mkdir(File.join('rootfs', dir), DIR_MODE)
  end

  yield

  snapshots.reverse!

  each_dataset do |ds|
    snapshots.each do |snap|
      zfs(:destroy, '', "#{ds}@#{snap}")
    end
  end

  tar.add_file('snapshots.yml', FILE_MODE) do |tf|
    tf.write(ConfigFile.dump_yaml(snapshots))
  end
end

#dump_stream(name, dataset, snap, from_snap = nil) ⇒ Object (protected)



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/libosctl/exporter/zfs.rb', line 123

def dump_stream(name, dataset, snap, from_snap = nil)
  compression = get_compression(dataset)

  cmd = if from_snap
          "#{zfs_send} -I @#{from_snap} #{dataset}@#{snap}"
        else
          "#{zfs_send} #{dataset}@#{snap}"
        end

  tar.add_file(dump_file_name(compression, name), FILE_MODE) do |tf|
    IO.popen("exec #{cmd}") do |io|
      process_stream(compression, io, tf)
    end
  end
end

#each_dataset {|ds| ... } ⇒ Object (protected)

Iterate over all datasets

Yield Parameters:



84
85
86
87
# File 'lib/libosctl/exporter/zfs.rb', line 84

def each_dataset(&block)
  block.call(ct.dataset)
  datasets.each(&block)
end

#each_dataset_dir {|ds, dir_name| ... } ⇒ Object (protected)

Iterate over all datasets and yield the dataset along with directory name for the tar archive, where its streams will be stored.

Yield Parameters:

  • ds (Zfs::Dataset)
  • dir_name (String)

    directory name within the archive



94
95
96
97
98
# File 'lib/libosctl/exporter/zfs.rb', line 94

def each_dataset_dir
  each_dataset do |ds|
    yield(ds, ds.relative_name)
  end
end

#each_dataset_file(name) {|ds, fname| ... } ⇒ Object (protected)

Iterate over all datasets and yield the dataset along with file name for the archive.

Parameters:

  • name (String)

    base/incremental

Yield Parameters:

  • ds (Zfs::Dataset)
  • fname (String)

    file name within the archive



106
107
108
109
110
# File 'lib/libosctl/exporter/zfs.rb', line 106

def each_dataset_file(name)
  each_dataset_dir do |ds, dir|
    yield(ds, File.join(dir, name))
  end
end

#formatObject



74
75
76
# File 'lib/libosctl/exporter/zfs.rb', line 74

def format
  :zfs
end

#get_compression(dataset) ⇒ Object (protected)



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/libosctl/exporter/zfs.rb', line 170

def get_compression(dataset)
  case opts[:compression]
  when :auto
    if !opts[:compressed_send] || zfs(:get, '-H -o value compression', dataset).output.strip == 'off'
      :gzip
    else
      :off
    end

  else
    opts[:compression].to_sym
  end
end

#process_stream(compression, stream, tf) ⇒ Object (protected)



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
# File 'lib/libosctl/exporter/zfs.rb', line 139

def process_stream(compression, stream, tf)
  case compression
  when :gzip
    gz = Zlib::GzipWriter.new(tf)
    attempts = 0

    until stream.eof?
      data = stream.read(BLOCK_SIZE)

      begin
        gz.write(data)
        attempts = 0
      rescue Zlib::BufError
        attempts += 1
        raise if attempts > 5

        sleep(0.1)
        retry
      end
    end

    gz.close

  when :off
    tf.write(stream.read(BLOCK_SIZE)) until stream.eof?

  else
    raise "unexpected compression type '#{compression}'"
  end
end

#snapshot(dataset, type) ⇒ Object (protected)



116
117
118
119
120
121
# File 'lib/libosctl/exporter/zfs.rb', line 116

def snapshot(dataset, type)
  snap = snapshot_name(type)
  zfs(:snapshot, '-r', "#{dataset}@#{snap}")
  snapshots << snap
  snap
end

#snapshot_name(type) ⇒ Object (protected)



112
113
114
# File 'lib/libosctl/exporter/zfs.rb', line 112

def snapshot_name(type)
  "osctl-#{type}-#{Time.now.to_i}"
end

#zfs_sendObject (protected)



196
197
198
199
200
201
202
203
# File 'lib/libosctl/exporter/zfs.rb', line 196

def zfs_send
  if opts[:compressed_send]
    'zfs send -c'

  else
    'zfs send'
  end
end