vagrant-libvirt 0.0.41 → 0.0.42

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.github/issue_template.md +37 -0
  4. data/.gitignore +21 -0
  5. data/.travis.yml +24 -0
  6. data/Gemfile +26 -0
  7. data/LICENSE +22 -0
  8. data/README.md +1380 -0
  9. data/Rakefile +8 -0
  10. data/example_box/README.md +29 -0
  11. data/example_box/Vagrantfile +60 -0
  12. data/example_box/metadata.json +5 -0
  13. data/lib/vagrant-libvirt.rb +29 -0
  14. data/lib/vagrant-libvirt/action.rb +370 -0
  15. data/lib/vagrant-libvirt/action/create_domain.rb +322 -0
  16. data/lib/vagrant-libvirt/action/create_domain_volume.rb +87 -0
  17. data/lib/vagrant-libvirt/action/create_network_interfaces.rb +302 -0
  18. data/lib/vagrant-libvirt/action/create_networks.rb +361 -0
  19. data/lib/vagrant-libvirt/action/destroy_domain.rb +83 -0
  20. data/lib/vagrant-libvirt/action/destroy_networks.rb +95 -0
  21. data/lib/vagrant-libvirt/action/forward_ports.rb +227 -0
  22. data/lib/vagrant-libvirt/action/halt_domain.rb +41 -0
  23. data/lib/vagrant-libvirt/action/handle_box_image.rb +156 -0
  24. data/lib/vagrant-libvirt/action/handle_storage_pool.rb +57 -0
  25. data/lib/vagrant-libvirt/action/is_created.rb +18 -0
  26. data/lib/vagrant-libvirt/action/is_running.rb +21 -0
  27. data/lib/vagrant-libvirt/action/is_suspended.rb +42 -0
  28. data/lib/vagrant-libvirt/action/message_already_created.rb +16 -0
  29. data/lib/vagrant-libvirt/action/message_not_created.rb +16 -0
  30. data/lib/vagrant-libvirt/action/message_not_running.rb +16 -0
  31. data/lib/vagrant-libvirt/action/message_not_suspended.rb +16 -0
  32. data/lib/vagrant-libvirt/action/message_will_not_destroy.rb +17 -0
  33. data/lib/vagrant-libvirt/action/package_domain.rb +105 -0
  34. data/lib/vagrant-libvirt/action/prepare_nfs_settings.rb +94 -0
  35. data/lib/vagrant-libvirt/action/prepare_nfs_valid_ids.rb +17 -0
  36. data/lib/vagrant-libvirt/action/prune_nfs_exports.rb +27 -0
  37. data/lib/vagrant-libvirt/action/read_mac_addresses.rb +40 -0
  38. data/lib/vagrant-libvirt/action/remove_libvirt_image.rb +20 -0
  39. data/lib/vagrant-libvirt/action/remove_stale_volume.rb +50 -0
  40. data/lib/vagrant-libvirt/action/resume_domain.rb +34 -0
  41. data/lib/vagrant-libvirt/action/set_boot_order.rb +109 -0
  42. data/lib/vagrant-libvirt/action/set_name_of_domain.rb +64 -0
  43. data/lib/vagrant-libvirt/action/share_folders.rb +71 -0
  44. data/lib/vagrant-libvirt/action/start_domain.rb +307 -0
  45. data/lib/vagrant-libvirt/action/suspend_domain.rb +40 -0
  46. data/lib/vagrant-libvirt/action/wait_till_up.rb +109 -0
  47. data/lib/vagrant-libvirt/cap/mount_p9.rb +42 -0
  48. data/lib/vagrant-libvirt/cap/nic_mac_addresses.rb +17 -0
  49. data/lib/vagrant-libvirt/cap/synced_folder.rb +113 -0
  50. data/lib/vagrant-libvirt/config.rb +746 -0
  51. data/lib/vagrant-libvirt/driver.rb +118 -0
  52. data/lib/vagrant-libvirt/errors.rb +153 -0
  53. data/lib/vagrant-libvirt/plugin.rb +92 -0
  54. data/lib/vagrant-libvirt/provider.rb +130 -0
  55. data/lib/vagrant-libvirt/templates/default_storage_pool.xml.erb +13 -0
  56. data/lib/vagrant-libvirt/templates/domain.xml.erb +244 -0
  57. data/lib/vagrant-libvirt/templates/private_network.xml.erb +42 -0
  58. data/lib/vagrant-libvirt/templates/public_interface.xml.erb +26 -0
  59. data/lib/vagrant-libvirt/util.rb +11 -0
  60. data/lib/vagrant-libvirt/util/collection.rb +19 -0
  61. data/lib/vagrant-libvirt/util/erb_template.rb +22 -0
  62. data/lib/vagrant-libvirt/util/error_codes.rb +100 -0
  63. data/lib/vagrant-libvirt/util/network_util.rb +151 -0
  64. data/lib/vagrant-libvirt/util/timer.rb +17 -0
  65. data/lib/vagrant-libvirt/version.rb +5 -0
  66. data/locales/en.yml +162 -0
  67. data/spec/spec_helper.rb +9 -0
  68. data/spec/support/environment_helper.rb +46 -0
  69. data/spec/support/libvirt_context.rb +30 -0
  70. data/spec/support/sharedcontext.rb +34 -0
  71. data/spec/unit/action/destroy_domain_spec.rb +97 -0
  72. data/spec/unit/action/set_name_of_domain_spec.rb +21 -0
  73. data/spec/unit/action/wait_till_up_spec.rb +127 -0
  74. data/spec/unit/config_spec.rb +113 -0
  75. data/spec/unit/templates/domain_all_settings.xml +137 -0
  76. data/spec/unit/templates/domain_defaults.xml +46 -0
  77. data/spec/unit/templates/domain_spec.rb +84 -0
  78. data/tools/create_box.sh +130 -0
  79. data/tools/prepare_redhat_for_box.sh +119 -0
  80. data/vagrant-libvirt.gemspec +54 -0
  81. metadata +93 -3
@@ -0,0 +1,322 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ class CreateDomain
7
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
8
+
9
+ def initialize(app, _env)
10
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::create_domain')
11
+ @app = app
12
+ end
13
+
14
+ def _disk_name(name, disk)
15
+ "#{name}-#{disk[:device]}.#{disk[:type]}" # disk name
16
+ end
17
+
18
+ def _disks_print(disks)
19
+ disks.collect do |x|
20
+ "#{x[:device]}(#{x[:type]},#{x[:size]})"
21
+ end.join(', ')
22
+ end
23
+
24
+ def _cdroms_print(cdroms)
25
+ cdroms.collect { |x| x[:dev] }.join(', ')
26
+ end
27
+
28
+ def call(env)
29
+ # Get config.
30
+ config = env[:machine].provider_config
31
+
32
+ # Gather some info about domain
33
+ @name = env[:domain_name]
34
+ @uuid = config.uuid
35
+ @cpus = config.cpus.to_i
36
+ @cpu_features = config.cpu_features
37
+ @cpu_topology = config.cpu_topology
38
+ @features = config.features
39
+ @cpu_mode = config.cpu_mode
40
+ @cpu_model = config.cpu_model
41
+ @cpu_fallback = config.cpu_fallback
42
+ @numa_nodes = config.numa_nodes
43
+ @loader = config.loader
44
+ @machine_type = config.machine_type
45
+ @machine_arch = config.machine_arch
46
+ @disk_bus = config.disk_bus
47
+ @disk_device = config.disk_device
48
+ @nested = config.nested
49
+ @memory_size = config.memory.to_i * 1024
50
+ @memory_backing = config.memory_backing
51
+ @management_network_mac = config.management_network_mac
52
+ @domain_volume_cache = config.volume_cache
53
+ @kernel = config.kernel
54
+ @cmd_line = config.cmd_line
55
+ @emulator_path = config.emulator_path
56
+ @initrd = config.initrd
57
+ @dtb = config.dtb
58
+ @graphics_type = config.graphics_type
59
+ @graphics_autoport = config.graphics_autoport
60
+ @graphics_port = config.graphics_port
61
+ @graphics_ip = config.graphics_ip
62
+ @graphics_passwd = if config.graphics_passwd.to_s.empty?
63
+ ''
64
+ else
65
+ "passwd='#{config.graphics_passwd}'"
66
+ end
67
+ @video_type = config.video_type
68
+ @sound_type = config.sound_type
69
+ @video_vram = config.video_vram
70
+ @keymap = config.keymap
71
+ @kvm_hidden = config.kvm_hidden
72
+
73
+ @tpm_model = config.tpm_model
74
+ @tpm_type = config.tpm_type
75
+ @tpm_path = config.tpm_path
76
+
77
+ # Boot order
78
+ @boot_order = config.boot_order
79
+
80
+ # Storage
81
+ @storage_pool_name = config.storage_pool_name
82
+ @disks = config.disks
83
+ @cdroms = config.cdroms
84
+
85
+ # Input
86
+ @inputs = config.inputs
87
+
88
+ # Channels
89
+ @channels = config.channels
90
+
91
+ # PCI device passthrough
92
+ @pcis = config.pcis
93
+
94
+ # Watchdog device
95
+ @watchdog_dev = config.watchdog_dev
96
+
97
+ # USB device passthrough
98
+ @usbs = config.usbs
99
+
100
+ # Redirected devices
101
+ @redirdevs = config.redirdevs
102
+ @redirfilters = config.redirfilters
103
+
104
+ # smartcard device
105
+ @smartcard_dev = config.smartcard_dev
106
+
107
+ # RNG device passthrough
108
+ @rng = config.rng
109
+
110
+ config = env[:machine].provider_config
111
+ @domain_type = config.driver
112
+
113
+ @os_type = 'hvm'
114
+
115
+ # Get path to domain image from the storage pool selected if we have a box.
116
+ if env[:machine].config.vm.box
117
+ actual_volumes =
118
+ env[:machine].provider.driver.connection.volumes.all.select do |x|
119
+ x.pool_name == @storage_pool_name
120
+ end
121
+ domain_volume = ProviderLibvirt::Util::Collection.find_matching(
122
+ actual_volumes, "#{@name}.img"
123
+ )
124
+ raise Errors::DomainVolumeExists if domain_volume.nil?
125
+ @domain_volume_path = domain_volume.path
126
+ end
127
+
128
+ # If we have a box, take the path from the domain volume and set our storage_prefix.
129
+ # If not, we dump the storage pool xml to get its defined path.
130
+ # the default storage prefix is typically: /var/lib/libvirt/images/
131
+ if env[:machine].config.vm.box
132
+ storage_prefix = File.dirname(@domain_volume_path) + '/' # steal
133
+ else
134
+ storage_pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(@storage_pool_name)
135
+ raise Errors::NoStoragePool if storage_pool.nil?
136
+ xml = Nokogiri::XML(storage_pool.xml_desc)
137
+ storage_prefix = xml.xpath('/pool/target/path').inner_text.to_s + '/'
138
+ end
139
+
140
+ @disks.each do |disk|
141
+ disk[:path] ||= _disk_name(@name, disk)
142
+
143
+ # On volume creation, the <path> element inside <target>
144
+ # is oddly ignored; instead the path is taken from the
145
+ # <name> element:
146
+ # http://www.redhat.com/archives/libvir-list/2008-August/msg00329.html
147
+ disk[:name] = disk[:path]
148
+
149
+ disk[:absolute_path] = storage_prefix + disk[:path]
150
+
151
+ if env[:machine].provider.driver.connection.volumes.select do |x|
152
+ x.name == disk[:name] && x.pool_name == @storage_pool_name
153
+ end.empty?
154
+ # make the disk. equivalent to:
155
+ # qemu-img create -f qcow2 <path> 5g
156
+ begin
157
+ env[:machine].provider.driver.connection.volumes.create(
158
+ name: disk[:name],
159
+ format_type: disk[:type],
160
+ path: disk[:absolute_path],
161
+ capacity: disk[:size],
162
+ #:allocation => ?,
163
+ pool_name: @storage_pool_name
164
+ )
165
+ rescue Fog::Errors::Error => e
166
+ raise Errors::FogDomainVolumeCreateError,
167
+ error_message: e.message
168
+ end
169
+ else
170
+ disk[:preexisting] = true
171
+ end
172
+ end
173
+
174
+ # Output the settings we're going to use to the user
175
+ env[:ui].info(I18n.t('vagrant_libvirt.creating_domain'))
176
+ env[:ui].info(" -- Name: #{@name}")
177
+ env[:ui].info(" -- Forced UUID: #{@uuid}") if @uuid != ''
178
+ env[:ui].info(" -- Domain type: #{@domain_type}")
179
+ env[:ui].info(" -- Cpus: #{@cpus}")
180
+ if not @cpu_topology.empty?
181
+ env[:ui].info(" -- CPU topology: sockets=#{@cpu_topology[:sockets]}, cores=#{@cpu_topology[:cores]}, threads=#{@cpu_topology[:threads]}")
182
+ end
183
+ env[:ui].info("")
184
+ @cpu_features.each do |cpu_feature|
185
+ env[:ui].info(" -- CPU Feature: name=#{cpu_feature[:name]}, policy=#{cpu_feature[:policy]}")
186
+ end
187
+ @features.each do |feature|
188
+ env[:ui].info(" -- Feature: #{feature}")
189
+ end
190
+ env[:ui].info(" -- Memory: #{@memory_size / 1024}M")
191
+ @memory_backing.each do |backing|
192
+ env[:ui].info(" -- Memory Backing: #{backing[:name]}: #{backing[:config].map { |k,v| "#{k}='#{v}'"}.join(' ')}")
193
+ end
194
+ env[:ui].info(" -- Management MAC: #{@management_network_mac}")
195
+ env[:ui].info(" -- Loader: #{@loader}")
196
+ if env[:machine].config.vm.box
197
+ env[:ui].info(" -- Base box: #{env[:machine].box.name}")
198
+ end
199
+ env[:ui].info(" -- Storage pool: #{@storage_pool_name}")
200
+ env[:ui].info(" -- Image: #{@domain_volume_path} (#{env[:box_virtual_size]}G)")
201
+ env[:ui].info(" -- Volume Cache: #{@domain_volume_cache}")
202
+ env[:ui].info(" -- Kernel: #{@kernel}")
203
+ env[:ui].info(" -- Initrd: #{@initrd}")
204
+ env[:ui].info(" -- Graphics Type: #{@graphics_type}")
205
+ env[:ui].info(" -- Graphics Port: #{@graphics_port}")
206
+ env[:ui].info(" -- Graphics IP: #{@graphics_ip}")
207
+ env[:ui].info(" -- Graphics Password: #{@graphics_passwd.empty? ? 'Not defined' : 'Defined'}")
208
+ env[:ui].info(" -- Video Type: #{@video_type}")
209
+ env[:ui].info(" -- Video VRAM: #{@video_vram}")
210
+ env[:ui].info(" -- Sound Type: #{@sound_type}")
211
+ env[:ui].info(" -- Keymap: #{@keymap}")
212
+ env[:ui].info(" -- TPM Path: #{@tpm_path}")
213
+
214
+ @boot_order.each do |device|
215
+ env[:ui].info(" -- Boot device: #{device}")
216
+ end
217
+
218
+ unless @disks.empty?
219
+ env[:ui].info(" -- Disks: #{_disks_print(@disks)}")
220
+ end
221
+
222
+ @disks.each do |disk|
223
+ msg = " -- Disk(#{disk[:device]}): #{disk[:absolute_path]}"
224
+ msg += ' Shared' if disk[:shareable]
225
+ msg += ' (Remove only manually)' if disk[:allow_existing]
226
+ msg += ' Not created - using existed.' if disk[:preexisting]
227
+ env[:ui].info(msg)
228
+ end
229
+
230
+ unless @cdroms.empty?
231
+ env[:ui].info(" -- CDROMS: #{_cdroms_print(@cdroms)}")
232
+ end
233
+
234
+ @cdroms.each do |cdrom|
235
+ env[:ui].info(" -- CDROM(#{cdrom[:dev]}): #{cdrom[:path]}")
236
+ end
237
+
238
+ @inputs.each do |input|
239
+ env[:ui].info(" -- INPUT: type=#{input[:type]}, bus=#{input[:bus]}")
240
+ end
241
+
242
+ @channels.each do |channel|
243
+ env[:ui].info(" -- CHANNEL: type=#{channel[:type]}, mode=#{channel[:source_mode]}")
244
+ env[:ui].info(" -- CHANNEL: target_type=#{channel[:target_type]}, target_name=#{channel[:target_name]}")
245
+ end
246
+
247
+ @pcis.each do |pci|
248
+ env[:ui].info(" -- PCI passthrough: #{pci[:bus]}:#{pci[:slot]}.#{pci[:function]}")
249
+ end
250
+
251
+ unless @rng[:model].nil?
252
+ env[:ui].info(" -- RNG device model: #{@rng[:model]}")
253
+ end
254
+
255
+ if not @watchdog_dev.empty?
256
+ env[:ui].info(" -- Watchdog device: model=#{@watchdog_dev[:model]}, action=#{@watchdog_dev[:action]}")
257
+ end
258
+
259
+ @usbs.each do |usb|
260
+ usb_dev = []
261
+ usb_dev.push("bus=#{usb[:bus]}") if usb[:bus]
262
+ usb_dev.push("device=#{usb[:device]}") if usb[:device]
263
+ usb_dev.push("vendor=#{usb[:vendor]}") if usb[:vendor]
264
+ usb_dev.push("product=#{usb[:product]}") if usb[:product]
265
+ env[:ui].info(" -- USB passthrough: #{usb_dev.join(', ')}")
266
+ end
267
+
268
+ unless @redirdevs.empty?
269
+ env[:ui].info(' -- Redirected Devices: ')
270
+ @redirdevs.each do |redirdev|
271
+ msg = " -> bus=usb, type=#{redirdev[:type]}"
272
+ env[:ui].info(msg)
273
+ end
274
+ end
275
+
276
+ unless @redirfilters.empty?
277
+ env[:ui].info(' -- USB Device filter for Redirected Devices: ')
278
+ @redirfilters.each do |redirfilter|
279
+ msg = " -> class=#{redirfilter[:class]}, "
280
+ msg += "vendor=#{redirfilter[:vendor]}, "
281
+ msg += "product=#{redirfilter[:product]}, "
282
+ msg += "version=#{redirfilter[:version]}, "
283
+ msg += "allow=#{redirfilter[:allow]}"
284
+ env[:ui].info(msg)
285
+ end
286
+ end
287
+
288
+ if not @smartcard_dev.empty?
289
+ env[:ui].info(" -- smartcard device: mode=#{@smartcard_dev[:mode]}, type=#{@smartcard_dev[:type]}")
290
+ end
291
+
292
+ @qargs = config.qemu_args
293
+ if not @qargs.empty?
294
+ env[:ui].info(' -- Command line args: ')
295
+ @qargs.each do |arg|
296
+ msg = " -> value=#{arg[:value]}, "
297
+ env[:ui].info(msg)
298
+ end
299
+ end
300
+
301
+ env[:ui].info(" -- Command line : #{@cmd_line}") unless @cmd_line.empty?
302
+
303
+ # Create libvirt domain.
304
+ # Is there a way to tell fog to create new domain with already
305
+ # existing volume? Use domain creation from template..
306
+ begin
307
+ server = env[:machine].provider.driver.connection.servers.create(
308
+ xml: to_xml('domain')
309
+ )
310
+ rescue Fog::Errors::Error => e
311
+ raise Errors::FogCreateServerError, error_message: e.message
312
+ end
313
+
314
+ # Immediately save the ID since it is created at this point.
315
+ env[:machine].id = server.id
316
+
317
+ @app.call(env)
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,87 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ # Create a snapshot of base box image. This new snapshot is just new
7
+ # cow image with backing storage pointing to base box image. Use this
8
+ # image as new domain volume.
9
+ class CreateDomainVolume
10
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
11
+
12
+ def initialize(app, _env)
13
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::create_domain_volume')
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ env[:ui].info(I18n.t('vagrant_libvirt.creating_domain_volume'))
19
+
20
+ # Get config options.
21
+ config = env[:machine].provider_config
22
+
23
+ # This is name of newly created image for vm.
24
+ @name = "#{env[:domain_name]}.img"
25
+
26
+ # Verify the volume doesn't exist already.
27
+ domain_volume = ProviderLibvirt::Util::Collection.find_matching(
28
+ env[:machine].provider.driver.connection.volumes.all, @name
29
+ )
30
+ raise Errors::DomainVolumeExists if domain_volume
31
+
32
+ # Get path to backing image - box volume.
33
+ box_volume = ProviderLibvirt::Util::Collection.find_matching(
34
+ env[:machine].provider.driver.connection.volumes.all, env[:box_volume_name]
35
+ )
36
+ @backing_file = box_volume.path
37
+
38
+ # Virtual size of image. Take value worked out by HandleBoxImage
39
+ @capacity = env[:box_virtual_size] # G
40
+
41
+ # Create new volume from xml template. Fog currently doesn't support
42
+ # volume snapshots directly.
43
+ begin
44
+ xml = Nokogiri::XML::Builder.new do |xml|
45
+ xml.volume do
46
+ xml.name(@name)
47
+ xml.capacity(@capacity, unit: 'G')
48
+ xml.target do
49
+ xml.format(type: 'qcow2')
50
+ xml.permissions do
51
+ xml.owner 0
52
+ xml.group 0
53
+ xml.mode '0600'
54
+ xml.label 'virt_image_t'
55
+ end
56
+ end
57
+ xml.backingStore do
58
+ xml.path(@backing_file)
59
+ xml.format(type: 'qcow2')
60
+ xml.permissions do
61
+ xml.owner 0
62
+ xml.group 0
63
+ xml.mode '0600'
64
+ xml.label 'virt_image_t'
65
+ end
66
+ end
67
+ end
68
+ end.to_xml(
69
+ save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
70
+ Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
71
+ Nokogiri::XML::Node::SaveOptions::FORMAT
72
+ )
73
+ domain_volume = env[:machine].provider.driver.connection.volumes.create(
74
+ xml: xml,
75
+ pool_name: config.storage_pool_name
76
+ )
77
+ rescue Fog::Errors::Error => e
78
+ raise Errors::FogDomainVolumeCreateError,
79
+ error_message: e.message
80
+ end
81
+
82
+ @app.call(env)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,302 @@
1
+ require 'log4r'
2
+ require 'vagrant/util/network_ip'
3
+ require 'vagrant/util/scoped_hash_override'
4
+
5
+ module VagrantPlugins
6
+ module ProviderLibvirt
7
+ module Action
8
+ # Create network interfaces for domain, before domain is running.
9
+ # Networks for connecting those interfaces should be already prepared.
10
+ class CreateNetworkInterfaces
11
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
12
+ include VagrantPlugins::ProviderLibvirt::Util::NetworkUtil
13
+ include Vagrant::Util::NetworkIP
14
+ include Vagrant::Util::ScopedHashOverride
15
+
16
+ def initialize(app, env)
17
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::create_network_interfaces')
18
+ @management_network_name = env[:machine].provider_config.management_network_name
19
+ config = env[:machine].provider_config
20
+ @nic_model_type = config.nic_model_type || 'virtio'
21
+ @nic_adapter_count = config.nic_adapter_count
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ # Get domain first.
27
+ begin
28
+ domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(
29
+ env[:machine].id.to_s
30
+ )
31
+ rescue => e
32
+ raise Errors::NoDomainError,
33
+ error_message: e.message
34
+ end
35
+
36
+ # Setup list of interfaces before creating them.
37
+ adapters = []
38
+
39
+ # Vagrant gives you adapter 0 by default
40
+ # Assign interfaces to slots.
41
+ configured_networks(env, @logger).each do |options|
42
+ # dont need to create interface for this type
43
+ next if options[:iface_type] == :forwarded_port
44
+
45
+ # TODO: fill first ifaces with adapter option specified.
46
+ if options[:adapter]
47
+ if adapters[options[:adapter]]
48
+ raise Errors::InterfaceSlotNotAvailable
49
+ end
50
+
51
+ free_slot = options[:adapter].to_i
52
+ @logger.debug "Using specified adapter slot #{free_slot}"
53
+ else
54
+ free_slot = find_empty(adapters)
55
+ @logger.debug "Adapter not specified so found slot #{free_slot}"
56
+ raise Errors::InterfaceSlotExhausted if free_slot.nil?
57
+ end
58
+
59
+ # We have slot for interface, fill it with interface configuration.
60
+ adapters[free_slot] = options
61
+ adapters[free_slot][:network_name] = interface_network(
62
+ env[:machine].provider.driver.connection.client, adapters[free_slot]
63
+ )
64
+ end
65
+
66
+ # Create each interface as new domain device.
67
+ @macs_per_network = Hash.new(0)
68
+ adapters.each_with_index do |iface_configuration, slot_number|
69
+ @iface_number = slot_number
70
+ @network_name = iface_configuration[:network_name]
71
+ @source_options = {
72
+ network: @network_name
73
+ }
74
+ @mac = iface_configuration.fetch(:mac, false)
75
+ @model_type = iface_configuration.fetch(:model_type, @nic_model_type)
76
+ @driver_name = iface_configuration.fetch(:driver_name, false)
77
+ @driver_queues = iface_configuration.fetch(:driver_queues, false)
78
+ @device_name = iface_configuration.fetch(:iface_name, false)
79
+ @mtu = iface_configuration.fetch(:mtu, nil)
80
+ @pci_bus = iface_configuration.fetch(:bus, nil)
81
+ @pci_slot = iface_configuration.fetch(:slot, nil)
82
+ template_name = 'interface'
83
+ # Configuration for public interfaces which use the macvtap driver
84
+ if iface_configuration[:iface_type] == :public_network
85
+ @device = iface_configuration.fetch(:dev, 'eth0')
86
+ @mode = iface_configuration.fetch(:mode, 'bridge')
87
+ @type = iface_configuration.fetch(:type, 'direct')
88
+ @model_type = iface_configuration.fetch(:model_type, @nic_model_type)
89
+ @driver_name = iface_configuration.fetch(:driver_name, false)
90
+ @driver_queues = iface_configuration.fetch(:driver_queues, false)
91
+ @portgroup = iface_configuration.fetch(:portgroup, nil)
92
+ @network_name = iface_configuration.fetch(:network_name, @network_name)
93
+ template_name = 'public_interface'
94
+ @logger.info("Setting up public interface using device #{@device} in mode #{@mode}")
95
+ @ovs = iface_configuration.fetch(:ovs, false)
96
+ @trust_guest_rx_filters = iface_configuration.fetch(:trust_guest_rx_filters, false)
97
+ # configuration for udp or tcp tunnel interfaces (p2p conn btwn guest OSes)
98
+ elsif iface_configuration.fetch(:tunnel_type, nil)
99
+ @type = iface_configuration.fetch(:tunnel_type)
100
+ @tunnel_port = iface_configuration.fetch(:tunnel_port, nil)
101
+ raise Errors::TunnelPortNotDefined if @tunnel_port.nil?
102
+ if @type == 'udp'
103
+ # default udp tunnel source to 127.0.0.1
104
+ @udp_tunnel={
105
+ address: iface_configuration.fetch(:tunnel_local_ip,'127.0.0.1'),
106
+ port: iface_configuration.fetch(:tunnel_local_port)
107
+ }
108
+ end
109
+ # default mcast tunnel to 239.255.1.1. Web search says this
110
+ # 239.255.x.x is a safe range to use for general use mcast
111
+ default_ip = if @type == 'mcast'
112
+ '239.255.1.1'
113
+ else
114
+ '127.0.0.1'
115
+ end
116
+ @source_options = {
117
+ address: iface_configuration.fetch(:tunnel_ip, default_ip),
118
+ port: @tunnel_port
119
+ }
120
+ @tunnel_type = iface_configuration.fetch(:model_type, @nic_model_type)
121
+ @driver_name = iface_configuration.fetch(:driver_name, false)
122
+ @driver_queues = iface_configuration.fetch(:driver_queues, false)
123
+ template_name = 'tunnel_interface'
124
+ @logger.info("Setting up #{@type} tunnel interface using #{@tunnel_ip} port #{@tunnel_port}")
125
+ end
126
+
127
+ message = "Creating network interface eth#{@iface_number}"
128
+ message << " connected to network #{@network_name}."
129
+ if @mac
130
+ @mac = @mac.scan(/(\h{2})/).join(':')
131
+ message << " Using MAC address: #{@mac}"
132
+ end
133
+ @logger.info(message)
134
+
135
+ begin
136
+ # FIXME: all options for network driver should be hash from Vagrantfile
137
+ driver_options = {}
138
+ driver_options[:name] = @driver_name if @driver_name
139
+ driver_options[:queues] = @driver_queues if @driver_queues
140
+ @udp_tunnel ||= {}
141
+ xml = if template_name == 'interface' or
142
+ template_name == 'tunnel_interface'
143
+ interface_xml(@type,
144
+ @source_options,
145
+ @mac,
146
+ @device_name,
147
+ @iface_number,
148
+ @model_type,
149
+ @mtu,
150
+ driver_options,
151
+ @udp_tunnel,
152
+ @pci_bus,
153
+ @pci_slot)
154
+ else
155
+ to_xml(template_name)
156
+ end
157
+ domain.attach_device(xml)
158
+ rescue => e
159
+ raise Errors::AttachDeviceError,
160
+ error_message: e.message
161
+ end
162
+
163
+ # Re-read the network configuration and grab the MAC address
164
+ if iface_configuration[:iface_type] == :public_network
165
+ xml = Nokogiri::XML(domain.xml_desc)
166
+ source = "@network='#{@network_name}'"
167
+ if @type == 'direct'
168
+ source = "@dev='#{@device}'"
169
+ elsif @portgroup.nil?
170
+ source = "@bridge='#{@device}'"
171
+ end
172
+ if not @mac
173
+ macs = xml.xpath("/domain/devices/interface[source[#{source}]]/mac/@address")
174
+ @mac = macs[@macs_per_network[source]]
175
+ iface_configuration[:mac] = @mac.to_s
176
+ end
177
+ @macs_per_network[source] += 1
178
+ end
179
+ end
180
+
181
+ # Continue the middleware chain.
182
+ @app.call(env)
183
+
184
+ if env[:machine].config.vm.box
185
+ # Configure interfaces that user requested. Machine should be up and
186
+ # running now.
187
+ networks_to_configure = []
188
+
189
+ adapters.each_with_index do |options, slot_number|
190
+ # Skip configuring the management network, which is on the first interface.
191
+ # It's used for provisioning and it has to be available during provisioning,
192
+ # ifdown command is not acceptable here.
193
+ next if slot_number.zero?
194
+ next if options[:auto_config] === false
195
+ @logger.debug "Configuring interface slot_number #{slot_number} options #{options}"
196
+
197
+ network = {
198
+ interface: slot_number,
199
+ use_dhcp_assigned_default_route: options[:use_dhcp_assigned_default_route],
200
+ mac_address: options[:mac]
201
+ }
202
+
203
+ if options[:ip]
204
+ network = {
205
+ type: :static,
206
+ ip: options[:ip],
207
+ netmask: options[:netmask],
208
+ gateway: options[:gateway]
209
+ }.merge(network)
210
+ else
211
+ network[:type] = :dhcp
212
+ end
213
+
214
+ # do not run configure_networks for tcp tunnel interfaces
215
+ next if options.fetch(:tunnel_type, nil)
216
+
217
+ networks_to_configure << network
218
+ end
219
+
220
+ env[:ui].info I18n.t('vagrant.actions.vm.network.configuring')
221
+ env[:machine].guest.capability(
222
+ :configure_networks, networks_to_configure
223
+ )
224
+
225
+ end
226
+ end
227
+
228
+ private
229
+
230
+ def target_dev_name(device_name, type, iface_number)
231
+ if device_name
232
+ device_name
233
+ elsif type == 'network'
234
+ "vnet#{iface_number}"
235
+ else
236
+ # TODO can we use same name vnet#ifnum?
237
+ #"tnet#{iface_number}" FIXME plugin vagrant-libvirt trying to create second tnet0 interface
238
+ "vnet#{iface_number}"
239
+ end
240
+ end
241
+
242
+ def interface_xml(type, source_options, mac, device_name,
243
+ iface_number, model_type, mtu, driver_options,
244
+ udp_tunnel={}, pci_bus, pci_slot)
245
+ Nokogiri::XML::Builder.new do |xml|
246
+ xml.interface(type: type || 'network') do
247
+ xml.source(source_options) do
248
+ xml.local(udp_tunnel) if type == 'udp'
249
+ end
250
+ xml.mac(address: mac) if mac
251
+ xml.target(dev: target_dev_name(device_name, type, iface_number))
252
+ xml.alias(name: "net#{iface_number}")
253
+ xml.model(type: model_type.to_s)
254
+ xml.mtu(size: Integer(mtu)) if mtu
255
+ xml.driver(driver_options)
256
+ xml.address(type: 'pci', bus: pci_bus, slot: pci_slot) if pci_bus and pci_slot
257
+ end
258
+ end.to_xml(
259
+ save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
260
+ Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
261
+ Nokogiri::XML::Node::SaveOptions::FORMAT
262
+ )
263
+ end
264
+
265
+ def find_empty(array, start = 0, stop = @nic_adapter_count)
266
+ (start..stop).each do |i|
267
+ return i unless array[i]
268
+ end
269
+ nil
270
+ end
271
+
272
+ # Return network name according to interface options.
273
+ def interface_network(libvirt_client, options)
274
+ # no need to get interface network for tcp tunnel config
275
+ return 'tunnel_interface' if options.fetch(:tunnel_type, nil)
276
+
277
+ if options[:network_name]
278
+ @logger.debug 'Found network by name'
279
+ return options[:network_name]
280
+ end
281
+
282
+ # Get list of all (active and inactive) libvirt networks.
283
+ available_networks = libvirt_networks(libvirt_client)
284
+
285
+ return 'public' if options[:iface_type] == :public_network
286
+
287
+ if options[:ip]
288
+ address = network_address(options[:ip], options[:netmask])
289
+ available_networks.each do |network|
290
+ if address == network[:network_address]
291
+ @logger.debug 'Found network by ip'
292
+ return network[:name]
293
+ end
294
+ end
295
+ end
296
+
297
+ raise Errors::NetworkNotAvailableError, network_name: options[:ip]
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end