vagrant-libvirt 0.0.41 → 0.0.42

Sign up to get free protection for your applications and to get access to all the features.
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