Class: Snapsync::Snapshot

Inherits:
Object
  • Object
show all
Defined in:
lib/snapsync/snapshot.rb

Overview

Representation of a single Snapper snapshot

Defined Under Namespace

Classes: SnapshotCompareError

Constant Summary collapse

PARTIAL_MARKER =
"snapsync-partial"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(snapshot_dir) ⇒ Snapshot

Returns a new instance of Snapshot.

Parameters:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/snapsync/snapshot.rb', line 77

def initialize(snapshot_dir)
    @snapshot_dir = snapshot_dir
    @btrfs = Btrfs.get(snapshot_dir)

    if !snapshot_dir.directory?
        raise InvalidSnapshot, "#{snapshot_dir} does not exist"
    elsif !subvolume_dir.directory?
        raise InvalidSnapshot, "#{snapshot_dir}'s subvolume directory does not exist (#{subvolume_dir})"
    end

    # This loads the information and also validates that snapshot_dir is
    # indeed a snapper snapshot
    load_info
end

Instance Attribute Details

#btrfsBtrfs (readonly)

Returns:



13
14
15
# File 'lib/snapsync/snapshot.rb', line 13

def btrfs
  @btrfs
end

#dateDateTime (readonly)

The snapshot’s date

Returns:

  • (DateTime)


30
31
32
# File 'lib/snapsync/snapshot.rb', line 30

def date
  @date
end

#numObject (readonly)

The snapshot number



33
34
35
# File 'lib/snapsync/snapshot.rb', line 33

def num
  @num
end

#snapshot_dirPathname (readonly)

The path to the snapshot directory

Returns:



10
11
12
# File 'lib/snapsync/snapshot.rb', line 10

def snapshot_dir
  @snapshot_dir
end

#user_dataHash<String,String> (readonly)

The snapshot’s user data

Returns:

  • (Hash<String,String>)


38
39
40
# File 'lib/snapsync/snapshot.rb', line 38

def user_data
  @user_data
end

Class Method Details

.each(snapshot_dir, with_partial: false) {|snapshot| ... } ⇒ Object

Enumerate the snapshots from the given directory

The directory is supposed to be maintained in a snapper-compatible foramt, meaning that the snapshot directory name must be the snapshot’s number

Yield Parameters:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/snapsync/snapshot.rb', line 162

def self.each(snapshot_dir, with_partial: false)
    return enum_for(__method__, snapshot_dir, with_partial: with_partial) if !block_given?
    each_snapshot_raw(snapshot_dir) do |path, snapshot, error|
        if error
            Snapsync.warn "ignored #{path} in #{self}: #{error}"
        elsif snapshot.num != Integer(path.basename.to_s)
            Snapsync.warn "ignored #{path} in #{self}: the snapshot reports num=#{snapshot.num} but its directory is called #{path.basename}"
        elsif !with_partial && snapshot.partial?
            Snapsync.warn "ignored #{path} in #{self}: this is a partial snapshot"
        else
            yield snapshot
        end
    end
end

.each_snapshot_raw(snapshot_dir) {|path, snapshot, err| ... } ⇒ Object

Yield Parameters:



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/snapsync/snapshot.rb', line 142

def self.each_snapshot_raw(snapshot_dir)
    return enum_for(__method__, snapshot_dir) if !block_given?
    snapshot_dir.each_child do |path|
        if path.directory? && path.basename.to_s =~ /^\d+$/
            begin
                snapshot = Snapshot.new(path)
                yield(path, snapshot, nil)
            rescue InvalidSnapshot => e
                yield(path, nil, e)
            end
        end
    end
end

.partial_marker_path(snapshot_dir) ⇒ Object



42
43
44
# File 'lib/snapsync/snapshot.rb', line 42

def self.partial_marker_path(snapshot_dir)
    snapshot_dir + PARTIAL_MARKER
end

Instance Method Details

#infoSubvolumeInfo

Returns:



16
17
18
19
20
# File 'lib/snapsync/snapshot.rb', line 16

def info
    # Load btrfs subvolume info
    @info = SubvolumeInfo.new(subvolume_dir) unless @info
    @info
end

#load_infoObject

Loads snapper’s info.xml, validates it and assigns the information to the relevant attributes



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/snapsync/snapshot.rb', line 179

def load_info
    info_xml = snapshot_dir + "info.xml"
    if !info_xml.file?
        raise InvalidSnapshot, "#{snapshot_dir}/info.xml does not exist, is this really a snapper snapshot ?"
    end

    xml = REXML::Document.new(info_xml.read)
    if !xml.root
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not look like a snapper info file (not an XML file ?)"
    elsif xml.root.name != 'snapshot'
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not look like a snapper info file (root is not 'snapshot')"
    end

    date = xml.root.elements.to_a('date')
    if date.empty?
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not have a date element"
    else
        @date = DateTime.parse(date.first.text)
    end

    num = xml.root.elements.to_a('num')
    if num.empty?
        raise InvalidInfoFile, "#{snapshot_dir}/info.xml does not have a num element"
    else
        @num = Integer(num.first.text)
    end

    @user_data = Hash.new
    xml.root.elements.to_a('userdata').each do |node|
        k = node.elements['key'].text
        v = node.elements['value'].text
        user_data[k] = v
    end
end

#partial?Boolean

Whether this snapshot has only been partially synchronized

Returns:

  • (Boolean)


55
56
57
# File 'lib/snapsync/snapshot.rb', line 55

def partial?
    partial_marker_path.exist?
end

#partial_marker_pathPathname

A file that is used to mark the snapshot has having only been partially synchronized

Returns:



50
51
52
# File 'lib/snapsync/snapshot.rb', line 50

def partial_marker_path
    self.class.partial_marker_path(snapshot_dir)
end

#sizeInteger

Compute an estimate of the size of sending the whole subvolume

Returns:

  • (Integer)

    size in bytes



118
119
120
# File 'lib/snapsync/snapshot.rb', line 118

def size
    size_diff_from_gen(0)
end

#size_diff_from(snapshot) ⇒ Integer

Compute the size difference between the given snapshot and self

This is an estimate of the size required to send this snapshot using the given snapshot as parent

Parameters:

  • snapshot (Snapshot)

    a reference snapshot, which would be used as parent in the ‘send’ operation

Returns:

  • (Integer)

    the size in bytes of the difference between the given snapshot and the current subvolume’s state



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/snapsync/snapshot.rb', line 101

def size_diff_from(snapshot)
    if btrfs.mountpoint != snapshot.btrfs.mountpoint
        recv_uuid = snapshot.info.received_uuid
        local_snapshot = btrfs.subvolume_table.find do |s|
            s.uuid == recv_uuid
        end
        raise "Cannot find snapshot with uuid #{recv_uuid} locally." if local_snapshot.nil?
        snapshot_gen = local_snapshot.cgen
    else
        snapshot_gen = snapshot.info.gen_at_creation
    end
    size_diff_from_gen(snapshot_gen)
end

#size_diff_from_gen(gen) ⇒ Integer

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Compute the size of the ‘diff’ between a generation and the subvolume’s current state

Parameters:

  • gen (Integer)

    the reference generation (the ‘from’ in the diff)

Returns:

  • (Integer)

    size in bytes

See Also:



130
131
132
133
134
135
136
137
# File 'lib/snapsync/snapshot.rb', line 130

def size_diff_from_gen(gen)
    btrfs.find_new(subvolume_dir, gen).inject(0) do |size, line|
        if line =~ /len (\d+)/
            size + Integer($1)
        else size
        end
    end
end

#subvolume_dirPathname

The path to the snapshot’s subvolume

Returns:



25
# File 'lib/snapsync/snapshot.rb', line 25

def subvolume_dir; snapshot_dir + "snapshot" end

#synchronization_point?Boolean

Whether this snapshot is one of snapsync’s synchronization points

Returns:

  • (Boolean)


65
66
67
# File 'lib/snapsync/snapshot.rb', line 65

def synchronization_point?
    user_data['snapsync']
end

#synchronization_point_for?(target) ⇒ Boolean

Whether this snapshot is one of snapsync’s synchronization points for the given target

Returns:

  • (Boolean)


71
72
73
# File 'lib/snapsync/snapshot.rb', line 71

def synchronization_point_for?(target)
    user_data['snapsync'] == target.uuid
end

#to_timeObject

This snapshot’s reference time



60
61
62
# File 'lib/snapsync/snapshot.rb', line 60

def to_time
    date.to_time
end