vagrant-libvirt 0.4.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +251 -33
- data/lib/vagrant-libvirt/action.rb +7 -1
- data/lib/vagrant-libvirt/action/clean_machine_folder.rb +30 -0
- data/lib/vagrant-libvirt/action/create_domain.rb +28 -11
- data/lib/vagrant-libvirt/action/create_domain_volume.rb +57 -55
- data/lib/vagrant-libvirt/action/create_network_interfaces.rb +0 -3
- data/lib/vagrant-libvirt/action/create_networks.rb +11 -4
- data/lib/vagrant-libvirt/action/destroy_domain.rb +1 -1
- data/lib/vagrant-libvirt/action/forward_ports.rb +36 -37
- data/lib/vagrant-libvirt/action/halt_domain.rb +25 -9
- data/lib/vagrant-libvirt/action/handle_box_image.rb +170 -77
- data/lib/vagrant-libvirt/action/is_running.rb +1 -3
- data/lib/vagrant-libvirt/action/is_suspended.rb +4 -4
- data/lib/vagrant-libvirt/action/set_boot_order.rb +6 -2
- data/lib/vagrant-libvirt/action/wait_till_up.rb +1 -25
- data/lib/vagrant-libvirt/cap/{mount_p9.rb → mount_9p.rb} +2 -2
- data/lib/vagrant-libvirt/cap/mount_virtiofs.rb +37 -0
- data/lib/vagrant-libvirt/cap/{synced_folder.rb → synced_folder_9p.rb} +4 -5
- data/lib/vagrant-libvirt/cap/synced_folder_virtiofs.rb +109 -0
- data/lib/vagrant-libvirt/config.rb +34 -2
- data/lib/vagrant-libvirt/driver.rb +3 -1
- data/lib/vagrant-libvirt/errors.rb +24 -1
- data/lib/vagrant-libvirt/plugin.rb +14 -5
- data/lib/vagrant-libvirt/templates/domain.xml.erb +7 -6
- data/lib/vagrant-libvirt/templates/private_network.xml.erb +1 -1
- data/lib/vagrant-libvirt/util/byte_number.rb +71 -0
- data/lib/vagrant-libvirt/util/network_util.rb +21 -3
- data/lib/vagrant-libvirt/version +1 -1
- data/locales/en.yml +12 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/binding_proc.rb +24 -0
- data/spec/support/matchers/have_file_content.rb +63 -0
- data/spec/unit/action/clean_machine_folder_spec.rb +58 -0
- data/spec/unit/action/create_domain_spec.rb +15 -5
- data/spec/unit/action/create_domain_spec/additional_disks_domain.xml +54 -0
- data/spec/unit/action/create_domain_spec/default_domain.xml +49 -0
- data/spec/unit/action/create_domain_volume_spec.rb +104 -0
- data/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml +21 -0
- data/spec/unit/action/destroy_domain_spec.rb +1 -1
- data/spec/unit/action/forward_ports_spec.rb +202 -0
- data/spec/unit/action/halt_domain_spec.rb +90 -0
- data/spec/unit/action/handle_box_image_spec.rb +441 -0
- data/spec/unit/action/wait_till_up_spec.rb +11 -15
- data/spec/unit/config_spec.rb +12 -9
- data/spec/unit/templates/domain_all_settings.xml +8 -0
- data/spec/unit/templates/domain_spec.rb +20 -1
- data/spec/unit/util/byte_number_spec.rb +26 -0
- metadata +52 -18
@@ -13,24 +13,40 @@ module VagrantPlugins
|
|
13
13
|
def call(env)
|
14
14
|
env[:ui].info(I18n.t('vagrant_libvirt.halt_domain'))
|
15
15
|
|
16
|
+
timeout = env[:machine].config.vm.graceful_halt_timeout
|
16
17
|
domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
|
17
18
|
raise Errors::NoDomainError if domain.nil?
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@logger.info('Trying Libvirt graceful shutdown.')
|
23
|
-
domain.shutdown
|
20
|
+
if env[:force_halt]
|
21
|
+
domain.poweroff
|
22
|
+
return @app.call(env)
|
24
23
|
end
|
25
24
|
|
26
|
-
|
27
25
|
begin
|
28
|
-
|
29
|
-
|
26
|
+
Timeout.timeout(timeout) do
|
27
|
+
begin
|
28
|
+
env[:machine].guest.capability(:halt)
|
29
|
+
rescue Timeout::Error
|
30
|
+
raise
|
31
|
+
rescue
|
32
|
+
@logger.info('Trying Libvirt graceful shutdown.')
|
33
|
+
# Read domain object again
|
34
|
+
dom = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
|
35
|
+
if dom.state.to_s == 'running'
|
36
|
+
dom.shutdown
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
domain.wait_for(timeout) do
|
41
|
+
!ready?
|
42
|
+
end
|
30
43
|
end
|
31
|
-
rescue
|
44
|
+
rescue Timeout::Error
|
32
45
|
@logger.info('VM is still running. Calling force poweroff.')
|
33
46
|
domain.poweroff
|
47
|
+
rescue
|
48
|
+
@logger.error('Failed to shutdown cleanly. Calling force poweroff.')
|
49
|
+
domain.poweroff
|
34
50
|
end
|
35
51
|
|
36
52
|
@app.call(env)
|
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'log4r'
|
2
|
+
require 'open3'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'vagrant-libvirt/util/byte_number'
|
2
6
|
|
3
7
|
module VagrantPlugins
|
4
8
|
module ProviderLibvirt
|
@@ -16,118 +20,207 @@ module VagrantPlugins
|
|
16
20
|
end
|
17
21
|
|
18
22
|
def call(env)
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# Virtual size has to be set for allocating space in storage pool.
|
22
|
-
box_virtual_size = env[:machine].box.metadata['virtual_size']
|
23
|
-
raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
|
23
|
+
# Handle box formats converting between v1 => v2 and ensuring
|
24
|
+
# any obsolete settings are rejected.
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
disks = env[:machine].box.metadata.fetch('disks', [])
|
27
|
+
if disks.empty?
|
28
|
+
# Handle box v1 format
|
29
|
+
|
30
|
+
# Only qcow2 format is supported in v1, but other formats with backing
|
31
|
+
# store capability should be usable.
|
32
|
+
box_format = env[:machine].box.metadata['format']
|
33
|
+
HandleBoxImage.verify_box_format(box_format)
|
34
|
+
|
35
|
+
env[:box_volume_number] = 1
|
36
|
+
env[:box_volumes] = [{
|
37
|
+
:path => HandleBoxImage.get_box_image_path(env[:machine].box, 'box.img'),
|
38
|
+
:name => HandleBoxImage.get_volume_name(env[:machine].box, 'box'),
|
39
|
+
:virtual_size => HandleBoxImage.get_virtual_size(env),
|
40
|
+
:format => box_format,
|
41
|
+
}]
|
42
|
+
else
|
43
|
+
# Handle box v2 format
|
44
|
+
# {
|
45
|
+
# 'path': '<path-of-file-box>',
|
46
|
+
# 'name': '<name-to-use-in-storage>' # optional, will use index
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
env[:box_volume_number] = disks.length()
|
50
|
+
target_volumes = Hash[]
|
51
|
+
env[:box_volumes] = Array.new(env[:box_volume_number]) { |i|
|
52
|
+
raise Errors::BoxFormatMissingAttribute, attribute: "disks[#{i}]['path']" if disks[i]['path'].nil?
|
53
|
+
|
54
|
+
image_path = HandleBoxImage.get_box_image_path(env[:machine].box, disks[i]['path'])
|
55
|
+
format, virtual_size = HandleBoxImage.get_box_disk_settings(image_path)
|
56
|
+
volume_name = HandleBoxImage.get_volume_name(
|
57
|
+
env[:machine].box,
|
58
|
+
disks[i].fetch('name', disks[i]['path'].sub(/#{File.extname(disks[i]['path'])}$/, '')),
|
59
|
+
)
|
60
|
+
|
61
|
+
# allowing name means needing to check that it doesn't cause a clash
|
62
|
+
existing = target_volumes[volume_name]
|
63
|
+
if !existing.nil?
|
64
|
+
raise Errors::BoxFormatDuplicateVolume, volume: volume_name, new_disk: "disks[#{i}]", orig_disk: "disks[#{existing}]"
|
65
|
+
end
|
66
|
+
target_volumes[volume_name] = i
|
67
|
+
|
68
|
+
{
|
69
|
+
:path => image_path,
|
70
|
+
:name => volume_name,
|
71
|
+
:virtual_size => virtual_size,
|
72
|
+
:format => HandleBoxImage.verify_box_format(format)
|
73
|
+
}
|
74
|
+
}
|
32
75
|
end
|
33
76
|
|
34
77
|
# Get config options
|
35
78
|
config = env[:machine].provider_config
|
36
|
-
|
37
|
-
env[:
|
38
|
-
|
39
|
-
|
40
|
-
env[:machine].box.version.to_s
|
41
|
-
rescue
|
42
|
-
''
|
43
|
-
end}.img"
|
79
|
+
box_image_files = []
|
80
|
+
env[:box_volumes].each do |d|
|
81
|
+
box_image_files.push(d[:path])
|
82
|
+
end
|
44
83
|
|
45
84
|
# Override box_virtual_size
|
85
|
+
box_virtual_size = env[:box_volumes][0][:virtual_size]
|
46
86
|
if config.machine_virtual_size
|
47
|
-
|
87
|
+
config_machine_virtual_size = ByteNumber.from_GB(config.machine_virtual_size)
|
88
|
+
puts config_machine_virtual_size < box_virtual_size
|
89
|
+
if config_machine_virtual_size < box_virtual_size
|
48
90
|
# Warn that a virtual size less than the box metadata size
|
49
91
|
# is not supported and will be ignored
|
50
92
|
env[:ui].warn I18n.t(
|
51
93
|
'vagrant_libvirt.warnings.ignoring_virtual_size_too_small',
|
52
|
-
requested:
|
94
|
+
requested: config_machine_virtual_size.to_GB, minimum: box_virtual_size.to_GB
|
53
95
|
)
|
54
96
|
else
|
55
97
|
env[:ui].info I18n.t('vagrant_libvirt.manual_resize_required')
|
56
|
-
box_virtual_size =
|
98
|
+
box_virtual_size = config_machine_virtual_size
|
57
99
|
end
|
58
100
|
end
|
59
101
|
# save for use by later actions
|
60
|
-
env[:
|
102
|
+
env[:box_volumes][0][:virtual_size] = box_virtual_size
|
61
103
|
|
62
104
|
# while inside the synchronize block take care not to call the next
|
63
105
|
# action in the chain, as must exit this block first to prevent
|
64
106
|
# locking all subsequent actions as well.
|
65
107
|
@@lock.synchronize do
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
|
75
|
-
|
76
|
-
# Create new volume in storage pool
|
77
|
-
unless File.exist?(box_image_file)
|
78
|
-
raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
|
108
|
+
env[:box_volumes].each_index do |i|
|
109
|
+
# Don't continue if image already exists in storage pool.
|
110
|
+
box_volume = env[:machine].provider.driver.connection.volumes.all(
|
111
|
+
name: env[:box_volumes][i][:name]
|
112
|
+
).first
|
113
|
+
next if box_volume && box_volume.id
|
114
|
+
|
115
|
+
send_box_image(env, config, box_image_files[i], env[:box_volumes][i])
|
79
116
|
end
|
80
|
-
|
81
|
-
message = "Creating volume #{env[:box_volume_name]}"
|
82
|
-
message << " in storage pool #{config.storage_pool_name}."
|
83
|
-
@logger.info(message)
|
117
|
+
end
|
84
118
|
|
85
|
-
|
86
|
-
|
119
|
+
@app.call(env)
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
87
123
|
|
124
|
+
def self.get_volume_name(box, name)
|
125
|
+
vol_name = box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
|
126
|
+
vol_name << "_vagrant_box_image_#{
|
88
127
|
begin
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
capacity: "#{box_virtual_size}G",
|
93
|
-
format_type: box_format,
|
94
|
-
owner: @storage_volume_uid,
|
95
|
-
group: @storage_volume_gid,
|
96
|
-
pool_name: config.storage_pool_name
|
97
|
-
)
|
98
|
-
rescue Fog::Errors::Error => e
|
99
|
-
raise Errors::FogCreateVolumeError,
|
100
|
-
error_message: e.message
|
128
|
+
box.version.to_s
|
129
|
+
rescue
|
130
|
+
''
|
101
131
|
end
|
132
|
+
}_#{name.dup.gsub('/', '-SLASH-')}.img"
|
133
|
+
end
|
102
134
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
end
|
135
|
+
def self.get_virtual_size(env)
|
136
|
+
# Virtual size has to be set for allocating space in storage pool.
|
137
|
+
box_virtual_size = env[:machine].box.metadata['virtual_size']
|
138
|
+
raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
|
139
|
+
return ByteNumber.from_GB(box_virtual_size)
|
140
|
+
end
|
111
141
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
142
|
+
def self.get_box_image_path(box, box_name)
|
143
|
+
return box.directory.join(box_name).to_s
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.verify_box_format(box_format, disk_index=nil)
|
147
|
+
if box_format.nil?
|
148
|
+
raise Errors::NoBoxFormatSet
|
149
|
+
elsif box_format != 'qcow2'
|
150
|
+
if disk_index.nil?
|
151
|
+
raise Errors::WrongBoxFormatSet
|
152
|
+
else
|
153
|
+
raise Errors::WrongDiskFormatSet,
|
154
|
+
disk_index: disk_index
|
124
155
|
end
|
125
156
|
end
|
157
|
+
return box_format
|
158
|
+
end
|
126
159
|
|
127
|
-
|
160
|
+
def self.get_box_disk_settings(image_path)
|
161
|
+
stdout, stderr, status = Open3.capture3('qemu-img', 'info', '--output=json', image_path)
|
162
|
+
if !status.success?
|
163
|
+
raise Errors::BadBoxImage, image: image_path, out: stdout, err: stderr
|
164
|
+
end
|
165
|
+
|
166
|
+
image_info = JSON.parse(stdout)
|
167
|
+
format = image_info['format']
|
168
|
+
virtual_size = ByteNumber.new(image_info['virtual-size'])
|
169
|
+
|
170
|
+
return format, virtual_size
|
128
171
|
end
|
129
172
|
|
130
|
-
|
173
|
+
def send_box_image(env, config, box_image_file, box_volume)
|
174
|
+
# Box is not available as a storage pool volume. Create and upload
|
175
|
+
# it as a copy of local box image.
|
176
|
+
env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
|
177
|
+
|
178
|
+
# Create new volume in storage pool
|
179
|
+
unless File.exist?(box_image_file)
|
180
|
+
raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
|
181
|
+
end
|
182
|
+
box_image_size = File.size(box_image_file) # B
|
183
|
+
message = "Creating volume #{box_volume[:name]}"
|
184
|
+
message << " in storage pool #{config.storage_pool_name}."
|
185
|
+
@logger.info(message)
|
186
|
+
begin
|
187
|
+
fog_volume = env[:machine].provider.driver.connection.volumes.create(
|
188
|
+
name: box_volume[:name],
|
189
|
+
allocation: "#{box_image_size / 1024 / 1024}M",
|
190
|
+
capacity: "#{box_volume[:virtual_size].to_B}B",
|
191
|
+
format_type: box_volume[:format],
|
192
|
+
owner: storage_uid(env),
|
193
|
+
group: storage_gid(env),
|
194
|
+
pool_name: config.storage_pool_name
|
195
|
+
)
|
196
|
+
rescue Fog::Errors::Error => e
|
197
|
+
raise Errors::FogCreateVolumeError,
|
198
|
+
error_message: e.message
|
199
|
+
end
|
200
|
+
|
201
|
+
# Upload box image to storage pool
|
202
|
+
ret = upload_image(box_image_file, config.storage_pool_name,
|
203
|
+
box_volume[:name], env) do |progress|
|
204
|
+
rewriting(env[:ui]) do |ui|
|
205
|
+
ui.clear_line
|
206
|
+
ui.report_progress(progress, box_image_size, false)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Clear the line one last time since the progress meter doesn't
|
211
|
+
# disappear immediately.
|
212
|
+
rewriting(env[:ui]) {|ui| ui.clear_line}
|
213
|
+
|
214
|
+
# If upload failed or was interrupted, remove created volume from
|
215
|
+
# storage pool.
|
216
|
+
if env[:interrupted] || !ret
|
217
|
+
begin
|
218
|
+
fog_volume.destroy
|
219
|
+
rescue
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
131
224
|
|
132
225
|
# Fog Libvirt currently doesn't support uploading images to storage
|
133
226
|
# pool volumes. Use ruby-libvirt client instead.
|
@@ -9,9 +9,7 @@ module VagrantPlugins
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def call(env)
|
12
|
-
|
13
|
-
raise Errors::NoDomainError if domain.nil?
|
14
|
-
env[:result] = domain.state.to_s == 'running'
|
12
|
+
env[:result] = env[:machine].state.id == :running
|
15
13
|
|
16
14
|
@app.call(env)
|
17
15
|
end
|
@@ -16,9 +16,9 @@ module VagrantPlugins
|
|
16
16
|
libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
|
17
17
|
if config.suspend_mode == 'managedsave'
|
18
18
|
if libvirt_domain.has_managed_save?
|
19
|
-
env[:result] =
|
19
|
+
env[:result] = env[:machine].state.id == :shutoff
|
20
20
|
else
|
21
|
-
env[:result] =
|
21
|
+
env[:result] = env[:machine].state.id == :paused
|
22
22
|
if env[:result]
|
23
23
|
env[:ui].warn('One time switching to pause suspend mode, found a paused VM.')
|
24
24
|
config.suspend_mode = 'pause'
|
@@ -27,10 +27,10 @@ module VagrantPlugins
|
|
27
27
|
else
|
28
28
|
if libvirt_domain.has_managed_save?
|
29
29
|
env[:ui].warn('One time switching to managedsave suspend mode, state found.')
|
30
|
-
env[:result] =
|
30
|
+
env[:result] = [:shutoff, :paused].include?(env[:machine].state.id)
|
31
31
|
config.suspend_mode = 'managedsave'
|
32
32
|
else
|
33
|
-
env[:result] =
|
33
|
+
env[:result] = env[:machine].state.id == :paused
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -86,9 +86,13 @@ module VagrantPlugins
|
|
86
86
|
|
87
87
|
def search_network(nets, xml)
|
88
88
|
str = '/domain/devices/interface'
|
89
|
-
str += "[(@type='network' or @type='udp' or @type='bridge')"
|
89
|
+
str += "[(@type='network' or @type='udp' or @type='bridge' or @type='direct')"
|
90
90
|
unless nets.empty?
|
91
|
-
|
91
|
+
net = nets.first
|
92
|
+
network = net['network']
|
93
|
+
dev = net['dev']
|
94
|
+
str += " and source[@network='#{network}']" if network
|
95
|
+
str += " and source[@dev='#{dev}']" if dev
|
92
96
|
end
|
93
97
|
str += ']'
|
94
98
|
@logger.debug(str)
|
@@ -47,30 +47,6 @@ module VagrantPlugins
|
|
47
47
|
@logger.info("Got IP address #{env[:ip_address]}")
|
48
48
|
@logger.info("Time for getting IP: #{env[:metrics]['instance_ip_time']}")
|
49
49
|
|
50
|
-
# Machine has ip address assigned, now wait till we are able to
|
51
|
-
# connect via ssh.
|
52
|
-
env[:metrics]['instance_ssh_time'] = Util::Timer.time do
|
53
|
-
env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ssh'))
|
54
|
-
retryable(on: Fog::Errors::TimeoutError, tries: 60) do
|
55
|
-
# If we're interrupted don't worry about waiting
|
56
|
-
next if env[:interrupted]
|
57
|
-
|
58
|
-
# Wait till we are able to connect via ssh.
|
59
|
-
loop do
|
60
|
-
# If we're interrupted then just back out
|
61
|
-
break if env[:interrupted]
|
62
|
-
break if env[:machine].communicate.ready?
|
63
|
-
sleep 2
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
# just return if interrupted and let the warden call recover
|
68
|
-
return if env[:interrupted]
|
69
|
-
@logger.info("Time for SSH ready: #{env[:metrics]['instance_ssh_time']}")
|
70
|
-
|
71
|
-
# Booted and ready for use.
|
72
|
-
# env[:ui].info(I18n.t("vagrant_libvirt.ready"))
|
73
|
-
|
74
50
|
@app.call(env)
|
75
51
|
end
|
76
52
|
|
@@ -80,7 +56,7 @@ module VagrantPlugins
|
|
80
56
|
end
|
81
57
|
|
82
58
|
def terminate(env)
|
83
|
-
if env[:machine].
|
59
|
+
if env[:machine].state.id != :not_created
|
84
60
|
# If we're not supposed to destroy on error then just return
|
85
61
|
return unless env[:destroy_on_error]
|
86
62
|
|
@@ -4,10 +4,10 @@ require 'vagrant/util/retryable'
|
|
4
4
|
module VagrantPlugins
|
5
5
|
module ProviderLibvirt
|
6
6
|
module Cap
|
7
|
-
class
|
7
|
+
class Mount9P
|
8
8
|
extend Vagrant::Util::Retryable
|
9
9
|
|
10
|
-
def self.
|
10
|
+
def self.mount_9p_shared_folder(machine, folders)
|
11
11
|
folders.each do |_name, opts|
|
12
12
|
# Expand the guest path so we can handle things like "~/vagrant"
|
13
13
|
expanded_guest_path = machine.guest.capability(
|