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
|
# Helper class for manipulating VM storage *volumes*, i.e. it deals
# only with creation of images and keeps a name => volume path lookup
# table (plugging drives or getting info of plugged devices is done in
# the VM class). We'd like better coupling, but given the ridiculous
# disconnect between Libvirt::StoragePool and Libvirt::Domain (hint:
# they have nothing with each other to do whatsoever) it's what makes
# sense.
require 'libvirt'
require 'rexml/document'
require 'etc'
class VMStorage
@@virt = nil
def initialize(virt, xml_path)
@@virt ||= virt
@xml_path = xml_path
pool_xml = REXML::Document.new(File.read("#{@xml_path}/storage_pool.xml"))
pool_name = pool_xml.elements['pool/name'].text
begin
@pool = @@virt.lookup_storage_pool_by_name(pool_name)
rescue Libvirt::RetrieveError
# There's no pool with that name, so we don't have to clear it
else
VMStorage.clear_storage_pool(@pool)
end
@pool_path = "#{$tmp_dir}/#{pool_name}"
pool_xml.elements['pool/target/path'].text = @pool_path
@pool = @@virt.define_storage_pool_xml(pool_xml.to_s)
@pool.build
@pool.create
@pool.refresh
end
def VMStorage.clear_storage_pool_volumes(pool)
was_not_active = !pool.active?
if was_not_active
pool.create
end
pool.list_volumes.each do |vol_name|
vol = pool.lookup_volume_by_name(vol_name)
vol.delete
end
if was_not_active
pool.destroy
end
rescue
# Some of the above operations can fail if the pool's path was
# deleted by external means; let's ignore that.
end
def VMStorage.clear_storage_pool(pool)
VMStorage.clear_storage_pool_volumes(pool)
pool.destroy if pool.active?
pool.undefine
end
def clear_pool
VMStorage.clear_storage_pool(@pool)
end
def clear_volumes
VMStorage.clear_storage_pool_volumes(@pool)
end
def create_new_disk(name, options = {})
options[:size] ||= 2
options[:unit] ||= "GiB"
options[:type] ||= "qcow2"
begin
old_vol = @pool.lookup_volume_by_name(name)
rescue Libvirt::RetrieveError
# noop
else
old_vol.delete
end
uid = Etc::getpwnam("libvirt-qemu").uid
gid = Etc::getgrnam("libvirt-qemu").gid
vol_xml = REXML::Document.new(File.read("#{@xml_path}/volume.xml"))
vol_xml.elements['volume/name'].text = name
size_b = convert_to_bytes(options[:size].to_f, options[:unit])
vol_xml.elements['volume/capacity'].text = size_b.to_s
vol_xml.elements['volume/target/format'].attributes["type"] = options[:type]
vol_xml.elements['volume/target/path'].text = "#{@pool_path}/#{name}"
vol_xml.elements['volume/target/permissions/owner'].text = uid.to_s
vol_xml.elements['volume/target/permissions/group'].text = gid.to_s
vol = @pool.create_volume_xml(vol_xml.to_s)
@pool.refresh
end
def clone_to_new_disk(from, to)
begin
old_to_vol = @pool.lookup_volume_by_name(to)
rescue Libvirt::RetrieveError
# noop
else
old_to_vol.delete
end
from_vol = @pool.lookup_volume_by_name(from)
xml = REXML::Document.new(from_vol.xml_desc)
pool_path = REXML::Document.new(@pool.xml_desc).elements['pool/target/path'].text
xml.elements['volume/name'].text = to
xml.elements['volume/target/path'].text = "#{pool_path}/#{to}"
@pool.create_volume_xml_from(xml.to_s, from_vol)
end
def disk_format(name)
vol = @pool.lookup_volume_by_name(name)
vol_xml = REXML::Document.new(vol.xml_desc)
return vol_xml.elements['volume/target/format'].attributes["type"]
end
def disk_path(name)
@pool.lookup_volume_by_name(name).path
end
# We use parted for the disk_mk* functions since it can format
# partitions "inside" the super block device; mkfs.* need a
# partition device (think /dev/sdaX), so we'd have to use something
# like losetup or kpartx, which would require administrative
# privileges. These functions only work for raw disk images.
# TODO: We should switch to guestfish/libguestfs (which has
# ruby-bindings) so we could use qcow2 instead of raw, and more
# easily use LVM volumes.
# For type, see label-type for mklabel in parted(8)
def disk_mklabel(name, type)
assert_equal("raw", disk_format(name))
path = disk_path(name)
cmd_helper("/sbin/parted -s '#{path}' mklabel #{type}")
end
# For fstype, see fs-type for mkfs in parted(8)
def disk_mkpartfs(name, fstype)
assert(disk_format(name), "raw")
path = disk_path(name)
cmd_helper("/sbin/parted -s '#{path}' mkpartfs primary '#{fstype}' 0% 100%")
end
end
|