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,40 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ # Suspend domain.
7
+ class SuspendDomain
8
+ def initialize(app, _env)
9
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::suspend_domain')
10
+ @app = app
11
+ end
12
+
13
+ # make pause
14
+ def call(env)
15
+ env[:ui].info(I18n.t('vagrant_libvirt.suspending_domain'))
16
+
17
+ domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
18
+ raise Errors::NoDomainError if domain.nil?
19
+
20
+ config = env[:machine].provider_config
21
+ if config.suspend_mode == 'managedsave'
22
+ libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
23
+ begin
24
+ libvirt_domain.managed_save
25
+ rescue => e
26
+ env[:ui].error("Error doing a managed save for domain. It may have entered a paused state.
27
+ Check the output of `virsh managedsave DOMAIN_NAME --verbose` on the VM host, error: #{e.message}")
28
+ end
29
+ else
30
+ domain.suspend
31
+ end
32
+
33
+ @logger.info("Machine #{env[:machine].id} is suspended ")
34
+
35
+ @app.call(env)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,109 @@
1
+ require 'log4r'
2
+ require 'vagrant-libvirt/errors'
3
+ require 'vagrant-libvirt/util/timer'
4
+ require 'vagrant/util/retryable'
5
+
6
+ module VagrantPlugins
7
+ module ProviderLibvirt
8
+ module Action
9
+ # Wait till domain is started, till it obtains an IP address and is
10
+ # accessible via ssh.
11
+ class WaitTillUp
12
+ include Vagrant::Util::Retryable
13
+
14
+ def initialize(app, _env)
15
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::wait_till_up')
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ # Initialize metrics if they haven't been
21
+ env[:metrics] ||= {}
22
+
23
+ # Get domain object
24
+ domain = env[:machine].provider.driver.get_domain(env[:machine].id.to_s)
25
+ if domain.nil?
26
+ raise Errors::NoDomainError,
27
+ error_message: "Domain #{env[:machine].id} not found"
28
+ end
29
+
30
+ # Wait for domain to obtain an ip address. Ip address is searched
31
+ # from arp table, either localy or remotely via ssh, if libvirt
32
+ # connection was done via ssh.
33
+ env[:ip_address] = nil
34
+ env[:metrics]['instance_ip_time'] = Util::Timer.time do
35
+ @logger.debug("Searching for IP for MAC address: #{domain.mac}")
36
+ env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ip'))
37
+ retryable(on: Fog::Errors::TimeoutError, tries: 300) do
38
+ # If we're interrupted don't worry about waiting
39
+ return terminate(env) if env[:interrupted]
40
+
41
+ # Wait for domain to obtain an ip address
42
+ domain.wait_for(2) do
43
+ addresses.each_pair do |_type, ip|
44
+ env[:ip_address] = ip[0] unless ip[0].nil?
45
+ end
46
+ !env[:ip_address].nil?
47
+ end
48
+ end
49
+ end
50
+ @logger.info("Got IP address #{env[:ip_address]}")
51
+ @logger.info("Time for getting IP: #{env[:metrics]['instance_ip_time']}")
52
+
53
+ # Machine has ip address assigned, now wait till we are able to
54
+ # connect via ssh.
55
+ env[:metrics]['instance_ssh_time'] = Util::Timer.time do
56
+ env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ssh'))
57
+ retryable(on: Fog::Errors::TimeoutError, tries: 60) do
58
+ # If we're interrupted don't worry about waiting
59
+ next if env[:interrupted]
60
+
61
+ # Wait till we are able to connect via ssh.
62
+ loop do
63
+ # If we're interrupted then just back out
64
+ break if env[:interrupted]
65
+ break if env[:machine].communicate.ready?
66
+ sleep 2
67
+ end
68
+ end
69
+ end
70
+ # if interrupted above, just terminate immediately
71
+ return terminate(env) if env[:interrupted]
72
+ @logger.info("Time for SSH ready: #{env[:metrics]['instance_ssh_time']}")
73
+
74
+ # Booted and ready for use.
75
+ # env[:ui].info(I18n.t("vagrant_libvirt.ready"))
76
+
77
+ @app.call(env)
78
+ end
79
+
80
+ def recover(env)
81
+ return if env['vagrant.error'].is_a?(Vagrant::Errors::VagrantError)
82
+
83
+ # Undo the import
84
+ terminate(env)
85
+ end
86
+
87
+ def terminate(env)
88
+ if env[:machine].provider.state.id != :not_created
89
+ # If we're not supposed to destroy on error then just return
90
+ return unless env[:destroy_on_error]
91
+
92
+ if env[:halt_on_error]
93
+ halt_env = env.dup
94
+ halt_env.delete(:interrupted)
95
+ halt_env[:config_validate] = false
96
+ env[:action_runner].run(Action.action_halt, halt_env)
97
+ else
98
+ destroy_env = env.dup
99
+ destroy_env.delete(:interrupted)
100
+ destroy_env[:config_validate] = false
101
+ destroy_env[:force_confirm_destroy] = true
102
+ env[:action_runner].run(Action.action_destroy, destroy_env)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,42 @@
1
+ require 'digest/md5'
2
+ require 'vagrant/util/retryable'
3
+
4
+ module VagrantPlugins
5
+ module ProviderLibvirt
6
+ module Cap
7
+ class MountP9
8
+ extend Vagrant::Util::Retryable
9
+
10
+ def self.mount_p9_shared_folder(machine, folders)
11
+ folders.each do |_name, opts|
12
+ # Expand the guest path so we can handle things like "~/vagrant"
13
+ expanded_guest_path = machine.guest.capability(
14
+ :shell_expand_guest_path, opts[:guestpath]
15
+ )
16
+
17
+ # Do the actual creating and mounting
18
+ machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
19
+
20
+ # Mount
21
+ mount_tag = Digest::MD5.new.update(opts[:hostpath]).to_s[0, 31]
22
+
23
+ mount_opts = '-o trans=virtio'
24
+ mount_opts += ",access=#{opts[:owner]}" if opts[:owner]
25
+ mount_opts += ",version=#{opts[:version]}" if opts[:version]
26
+ mount_opts += ",#{opts[:mount_opts]}" if opts[:mount_opts]
27
+
28
+ mount_command = "mount -t 9p #{mount_opts} '#{mount_tag}' #{expanded_guest_path}"
29
+ retryable(on: Vagrant::Errors::LinuxMountFailed,
30
+ tries: 5,
31
+ sleep: 3) do
32
+ machine.communicate.sudo('modprobe 9p')
33
+ machine.communicate.sudo('modprobe 9pnet_virtio')
34
+ machine.communicate.sudo(mount_command,
35
+ error_class: Vagrant::Errors::LinuxMountFailed)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module VagrantPlugins
2
+ module ProviderLibvirt
3
+ module Cap
4
+ class NicMacAddresses
5
+ def self.nic_mac_addresses(machine)
6
+ # Vagrant expects a Hash with an index starting at 1 as key
7
+ # and the mac as uppercase string without colons as value
8
+ nic_macs = {}
9
+ machine.provider.mac_addresses.each do |index, mac|
10
+ nic_macs[index + 1] = mac.upcase.delete(':')
11
+ end
12
+ nic_macs
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,113 @@
1
+ require 'log4r'
2
+ require 'ostruct'
3
+ require 'nokogiri'
4
+ require 'digest/md5'
5
+
6
+ require 'vagrant/util/subprocess'
7
+ require 'vagrant/errors'
8
+ require 'vagrant-libvirt/errors'
9
+ # require_relative "helper"
10
+
11
+ module VagrantPlugins
12
+ module SyncedFolder9p
13
+ class SyncedFolder < Vagrant.plugin('2', :synced_folder)
14
+ include Vagrant::Util
15
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
16
+
17
+ def initialize(*args)
18
+ super
19
+ @logger = Log4r::Logger.new('vagrant_libvirt::synced_folders::9p')
20
+ end
21
+
22
+ def usable?(machine, _raise_error = false)
23
+ # bail now if not using libvirt since checking version would throw error
24
+ return false unless machine.provider_name == :libvirt
25
+
26
+ # <filesystem/> support in device attach/detach introduced in 1.2.2
27
+ # version number format is major * 1,000,000 + minor * 1,000 + release
28
+ libvirt_version = machine.provider.driver.connection.client.libversion
29
+ libvirt_version >= 1_002_002
30
+ end
31
+
32
+ def prepare(machine, folders, _opts)
33
+ raise Vagrant::Errors::Error('No libvirt connection') if machine.provider.driver.connection.nil?
34
+ @conn = machine.provider.driver.connection.client
35
+
36
+ begin
37
+ # loop through folders
38
+ folders.each do |id, folder_opts|
39
+ folder_opts.merge!(target: id,
40
+ accessmode: 'passthrough',
41
+ mount: true,
42
+ readonly: nil) { |_k, ov, _nv| ov }
43
+
44
+ mount_tag = Digest::MD5.new.update(folder_opts[:hostpath]).to_s[0, 31]
45
+ folder_opts[:mount_tag] = mount_tag
46
+
47
+ machine.ui.info "================\nMachine id: #{machine.id}\nShould be mounting folders\n #{id}, opts: #{folder_opts}"
48
+
49
+ #xml = to_xml('filesystem', folder_opts)
50
+ xml = Nokogiri::XML::Builder.new do |xml|
51
+ xml.filesystem(type: 'mount', accessmode: folder_opts[:accessmode]) do
52
+ xml.driver(type: 'path', wrpolicy: 'immediate')
53
+ xml.source(dir: folder_opts[:hostpath])
54
+ xml.target(dir: mount_tag)
55
+ xml.readonly unless folder_opts[:readonly].nil?
56
+ end
57
+ end.to_xml(
58
+ save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
59
+ Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
60
+ Nokogiri::XML::Node::SaveOptions::FORMAT
61
+ )
62
+ # puts "<<<<< XML:\n #{xml}\n >>>>>"
63
+ @conn.lookup_domain_by_uuid(machine.id).attach_device(xml, 0)
64
+ end
65
+ rescue => e
66
+ machine.ui.error("could not attach device because: #{e}")
67
+ raise VagrantPlugins::ProviderLibvirt::Errors::AttachDeviceError,
68
+ error_message: e.message
69
+ end
70
+ end
71
+
72
+ # TODO: once up, mount folders
73
+ def enable(machine, folders, _opts)
74
+ # Go through each folder and mount
75
+ machine.ui.info('mounting p9 share in guest')
76
+ # Only mount folders that have a guest path specified.
77
+ mount_folders = {}
78
+ folders.each do |id, opts|
79
+ next unless opts[:mount] && opts[:guestpath] && !opts[:guestpath].empty?
80
+ mount_folders[id] = opts.dup
81
+ # merge common options if not given
82
+ mount_folders[id].merge!(version: '9p2000.L') { |_k, ov, _nv| ov }
83
+ end
84
+ # Mount the actual folder
85
+ machine.guest.capability(
86
+ :mount_p9_shared_folder, mount_folders
87
+ )
88
+ end
89
+
90
+ def cleanup(machine, _opts)
91
+ if machine.provider.driver.connection.nil?
92
+ raise Vagrant::Errors::Error('No libvirt connection')
93
+ end
94
+ @conn = machine.provider.driver.connection.client
95
+ begin
96
+ if machine.id && machine.id != ''
97
+ dom = @conn.lookup_domain_by_uuid(machine.id)
98
+ Nokogiri::XML(dom.xml_desc).xpath(
99
+ '/domain/devices/filesystem'
100
+ ).each do |xml|
101
+ dom.detach_device(xml.to_s)
102
+ machine.ui.info 'Cleaned up shared folders'
103
+ end
104
+ end
105
+ rescue => e
106
+ machine.ui.error("could not detach device because: #{e}")
107
+ raise VagrantPlugins::ProviderLibvirt::Errors::DetachDeviceError,
108
+ error_message: e.message
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,746 @@
1
+ require 'vagrant'
2
+
3
+ class Numeric
4
+ Alphabet = ('a'..'z').to_a
5
+ def vdev
6
+ s = ''
7
+ q = self
8
+ (q, r = (q - 1).divmod(26)) && s.prepend(Alphabet[r]) until q.zero?
9
+ 'vd' + s
10
+ end
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module ProviderLibvirt
15
+ class Config < Vagrant.plugin('2', :config)
16
+ # manually specify URI
17
+ # will supercede most other options if provided
18
+ attr_accessor :uri
19
+
20
+ # A hypervisor name to access via Libvirt.
21
+ attr_accessor :driver
22
+
23
+ # The name of the server, where libvirtd is running.
24
+ attr_accessor :host
25
+
26
+ # If use ssh tunnel to connect to Libvirt.
27
+ attr_accessor :connect_via_ssh
28
+ # Path towards the libvirt socket
29
+ attr_accessor :socket
30
+
31
+ # The username to access Libvirt.
32
+ attr_accessor :username
33
+
34
+ # Password for Libvirt connection.
35
+ attr_accessor :password
36
+
37
+ # ID SSH key file
38
+ attr_accessor :id_ssh_key_file
39
+
40
+ # Libvirt storage pool name, where box image and instance snapshots will
41
+ # be stored.
42
+ attr_accessor :storage_pool_name
43
+
44
+ # Turn on to prevent hostname conflicts
45
+ attr_accessor :random_hostname
46
+
47
+ # Libvirt default network
48
+ attr_accessor :management_network_name
49
+ attr_accessor :management_network_address
50
+ attr_accessor :management_network_mode
51
+ attr_accessor :management_network_mac
52
+ attr_accessor :management_network_guest_ipv6
53
+ attr_accessor :management_network_autostart
54
+ attr_accessor :management_network_pci_bus
55
+ attr_accessor :management_network_pci_slot
56
+
57
+ # Default host prefix (alternative to use project folder name)
58
+ attr_accessor :default_prefix
59
+
60
+ # Domain specific settings used while creating new domain.
61
+ attr_accessor :uuid
62
+ attr_accessor :memory
63
+ attr_accessor :memory_backing
64
+ attr_accessor :channel
65
+ attr_accessor :cpus
66
+ attr_accessor :cpu_mode
67
+ attr_accessor :cpu_model
68
+ attr_accessor :cpu_fallback
69
+ attr_accessor :cpu_features
70
+ attr_accessor :cpu_topology
71
+ attr_accessor :features
72
+ attr_accessor :numa_nodes
73
+ attr_accessor :loader
74
+ attr_accessor :boot_order
75
+ attr_accessor :machine_type
76
+ attr_accessor :machine_arch
77
+ attr_accessor :machine_virtual_size
78
+ attr_accessor :disk_bus
79
+ attr_accessor :disk_device
80
+ attr_accessor :nic_model_type
81
+ attr_accessor :nested
82
+ attr_accessor :volume_cache
83
+ attr_accessor :kernel
84
+ attr_accessor :cmd_line
85
+ attr_accessor :initrd
86
+ attr_accessor :dtb
87
+ attr_accessor :emulator_path
88
+ attr_accessor :graphics_type
89
+ attr_accessor :graphics_autoport
90
+ attr_accessor :graphics_port
91
+ attr_accessor :graphics_passwd
92
+ attr_accessor :graphics_ip
93
+ attr_accessor :video_type
94
+ attr_accessor :video_vram
95
+ attr_accessor :keymap
96
+ attr_accessor :kvm_hidden
97
+ attr_accessor :sound_type
98
+
99
+ # Sets the information for connecting to a host TPM device
100
+ # Only supports socket-based TPMs
101
+ attr_accessor :tpm_model
102
+ attr_accessor :tpm_type
103
+ attr_accessor :tpm_path
104
+
105
+ # Sets the max number of NICs that can be created
106
+ # Default set to 8. Don't change the default unless you know
107
+ # what are doing
108
+ attr_accessor :nic_adapter_count
109
+
110
+ # Storage
111
+ attr_accessor :disks
112
+ attr_accessor :cdroms
113
+
114
+ # Inputs
115
+ attr_accessor :inputs
116
+
117
+ # Channels
118
+ attr_accessor :channels
119
+
120
+ # PCI device passthrough
121
+ attr_accessor :pcis
122
+
123
+ # Random number device passthrough
124
+ attr_accessor :rng
125
+
126
+ # Watchdog device
127
+ attr_accessor :watchdog_dev
128
+
129
+ # USB device passthrough
130
+ attr_accessor :usbs
131
+
132
+ # Redirected devices
133
+ attr_accessor :redirdevs
134
+ attr_accessor :redirfilters
135
+
136
+ # smartcard device
137
+ attr_accessor :smartcard_dev
138
+
139
+ # Suspend mode
140
+ attr_accessor :suspend_mode
141
+
142
+ # Autostart
143
+ attr_accessor :autostart
144
+
145
+ # Attach mgmt network
146
+ attr_accessor :mgmt_attach
147
+
148
+ # Additional qemuargs arguments
149
+ attr_accessor :qemu_args
150
+
151
+ def initialize
152
+ @uri = UNSET_VALUE
153
+ @driver = UNSET_VALUE
154
+ @host = UNSET_VALUE
155
+ @connect_via_ssh = UNSET_VALUE
156
+ @username = UNSET_VALUE
157
+ @password = UNSET_VALUE
158
+ @id_ssh_key_file = UNSET_VALUE
159
+ @storage_pool_name = UNSET_VALUE
160
+ @random_hostname = UNSET_VALUE
161
+ @management_network_name = UNSET_VALUE
162
+ @management_network_address = UNSET_VALUE
163
+ @management_network_mode = UNSET_VALUE
164
+ @management_network_mac = UNSET_VALUE
165
+ @management_network_guest_ipv6 = UNSET_VALUE
166
+ @management_network_autostart = UNSET_VALUE
167
+ @management_network_pci_slot = UNSET_VALUE
168
+ @management_network_pci_bus = UNSET_VALUE
169
+
170
+ # Domain specific settings.
171
+ @uuid = UNSET_VALUE
172
+ @memory = UNSET_VALUE
173
+ @memory_backing = UNSET_VALUE
174
+ @cpus = UNSET_VALUE
175
+ @cpu_mode = UNSET_VALUE
176
+ @cpu_model = UNSET_VALUE
177
+ @cpu_fallback = UNSET_VALUE
178
+ @cpu_features = UNSET_VALUE
179
+ @cpu_topology = UNSET_VALUE
180
+ @features = UNSET_VALUE
181
+ @numa_nodes = UNSET_VALUE
182
+ @loader = UNSET_VALUE
183
+ @machine_type = UNSET_VALUE
184
+ @machine_arch = UNSET_VALUE
185
+ @machine_virtual_size = UNSET_VALUE
186
+ @disk_bus = UNSET_VALUE
187
+ @disk_device = UNSET_VALUE
188
+ @nic_model_type = UNSET_VALUE
189
+ @nested = UNSET_VALUE
190
+ @volume_cache = UNSET_VALUE
191
+ @kernel = UNSET_VALUE
192
+ @initrd = UNSET_VALUE
193
+ @dtb = UNSET_VALUE
194
+ @cmd_line = UNSET_VALUE
195
+ @emulator_path = UNSET_VALUE
196
+ @graphics_type = UNSET_VALUE
197
+ @graphics_autoport = UNSET_VALUE
198
+ @graphics_port = UNSET_VALUE
199
+ @graphics_ip = UNSET_VALUE
200
+ @graphics_passwd = UNSET_VALUE
201
+ @video_type = UNSET_VALUE
202
+ @video_vram = UNSET_VALUE
203
+ @sound_type = UNSET_VALUE
204
+ @keymap = UNSET_VALUE
205
+ @kvm_hidden = UNSET_VALUE
206
+
207
+ @tpm_model = UNSET_VALUE
208
+ @tpm_type = UNSET_VALUE
209
+ @tpm_path = UNSET_VALUE
210
+
211
+ @nic_adapter_count = UNSET_VALUE
212
+
213
+ # Boot order
214
+ @boot_order = []
215
+ # Storage
216
+ @disks = []
217
+ @cdroms = []
218
+
219
+ # Inputs
220
+ @inputs = UNSET_VALUE
221
+
222
+ # Channels
223
+ @channels = UNSET_VALUE
224
+
225
+ # PCI device passthrough
226
+ @pcis = UNSET_VALUE
227
+
228
+ # Random number device passthrough
229
+ @rng = UNSET_VALUE
230
+
231
+ # Watchdog device
232
+ @watchdog_dev = UNSET_VALUE
233
+
234
+ # USB device passthrough
235
+ @usbs = UNSET_VALUE
236
+
237
+ # Redirected devices
238
+ @redirdevs = UNSET_VALUE
239
+ @redirfilters = UNSET_VALUE
240
+
241
+ # smartcard device
242
+ @smartcard_dev = UNSET_VALUE
243
+
244
+ # Suspend mode
245
+ @suspend_mode = UNSET_VALUE
246
+
247
+ # Autostart
248
+ @autostart = UNSET_VALUE
249
+
250
+ # Attach mgmt network
251
+ @mgmt_attach = UNSET_VALUE
252
+
253
+ @qemu_args = []
254
+ end
255
+
256
+ def boot(device)
257
+ @boot_order << device # append
258
+ end
259
+
260
+ def _get_device(disks)
261
+ # skip existing devices and also the first one (vda)
262
+ exist = disks.collect { |x| x[:device] } + [1.vdev.to_s]
263
+ skip = 1 # we're 1 based, not 0 based...
264
+ loop do
265
+ dev = skip.vdev # get lettered device
266
+ return dev unless exist.include?(dev)
267
+ skip += 1
268
+ end
269
+ end
270
+
271
+ def _get_cdrom_dev(cdroms)
272
+ exist = Hash[cdroms.collect { |x| [x[:dev], true] }]
273
+ # hda - hdc
274
+ curr = 'a'.ord
275
+ while curr <= 'd'.ord
276
+ dev = 'hd' + curr.chr
277
+ if exist[dev]
278
+ curr += 1
279
+ next
280
+ else
281
+ return dev
282
+ end
283
+ end
284
+
285
+ # is it better to raise our own error, or let libvirt cause the exception?
286
+ raise 'Only four cdroms may be attached at a time'
287
+ end
288
+
289
+ def _generate_numa
290
+ @numa_nodes.collect { |x|
291
+ # Perform some validation of cpu values
292
+ unless x[:cpus] =~ /^\d+-\d+$/
293
+ raise 'numa_nodes[:cpus] must be in format "integer-integer"'
294
+ end
295
+
296
+ # Convert to KiB
297
+ x[:memory] = x[:memory].to_i * 1024
298
+ }
299
+
300
+ # Grab the value of the last @numa_nodes[:cpus] and verify @cpus matches
301
+ # Note: [:cpus] is zero based and @cpus is not, so we need to +1
302
+ last_cpu = @numa_nodes.last[:cpus]
303
+ last_cpu = last_cpu.scan(/\d+$/)[0]
304
+ last_cpu = last_cpu.to_i + 1
305
+
306
+ if @cpus != last_cpu.to_i
307
+ raise 'The total number of numa_nodes[:cpus] must equal config.cpus'
308
+ end
309
+
310
+ @numa_nodes
311
+ end
312
+
313
+ def cpu_feature(options = {})
314
+ if options[:name].nil? || options[:policy].nil?
315
+ raise 'CPU Feature name AND policy must be specified'
316
+ end
317
+
318
+ @cpu_features = [] if @cpu_features == UNSET_VALUE
319
+
320
+ @cpu_features.push(name: options[:name],
321
+ policy: options[:policy])
322
+ end
323
+
324
+ def cputopology(options = {})
325
+ if options[:sockets].nil? || options[:cores].nil? || options[:threads].nil?
326
+ raise 'CPU topology must have all of sockets, cores and threads specified'
327
+ end
328
+
329
+ if @cpu_topology == UNSET_VALUE
330
+ @cpu_topology = {}
331
+ end
332
+
333
+ @cpu_topology[:sockets] = options[:sockets]
334
+ @cpu_topology[:cores] = options[:cores]
335
+ @cpu_topology[:threads] = options[:threads]
336
+ end
337
+
338
+ def memorybacking(option, config = {})
339
+ case option
340
+ when :source
341
+ raise 'Source type must be specified' if config[:type].nil?
342
+ when :access
343
+ raise 'Access mode must be specified' if config[:mode].nil?
344
+ when :allocation
345
+ raise 'Allocation mode must be specified' if config[:mode].nil?
346
+ end
347
+
348
+ @memory_backing = [] if @memory_backing == UNSET_VALUE
349
+ @memory_backing.push(name: option,
350
+ config: config)
351
+ end
352
+
353
+ def input(options = {})
354
+ if options[:type].nil? || options[:bus].nil?
355
+ raise 'Input type AND bus must be specified'
356
+ end
357
+
358
+ @inputs = [] if @inputs == UNSET_VALUE
359
+
360
+ @inputs.push(type: options[:type],
361
+ bus: options[:bus])
362
+ end
363
+
364
+ def channel(options = {})
365
+ if options[:type].nil?
366
+ raise 'Channel type must be specified.'
367
+ elsif options[:type] == 'unix' && options[:target_type] == 'guestfwd'
368
+ # Guest forwarding requires a target (ip address) and a port
369
+ if options[:target_address].nil? || options[:target_port].nil? ||
370
+ options[:source_path].nil?
371
+ raise 'guestfwd requires target_address, target_port and source_path'
372
+ end
373
+ end
374
+
375
+ @channels = [] if @channels == UNSET_VALUE
376
+
377
+ @channels.push(type: options[:type],
378
+ source_mode: options[:source_mode],
379
+ source_path: options[:source_path],
380
+ target_address: options[:target_address],
381
+ target_name: options[:target_name],
382
+ target_port: options[:target_port],
383
+ target_type: options[:target_type])
384
+ end
385
+
386
+ def random(options = {})
387
+ if !options[:model].nil? && options[:model] != 'random'
388
+ raise 'The only supported rng backend is "random".'
389
+ end
390
+
391
+ @rng = {} if @rng == UNSET_VALUE
392
+
393
+ @rng[:model] = options[:model]
394
+ end
395
+
396
+ def pci(options = {})
397
+ if options[:bus].nil? || options[:slot].nil? || options[:function].nil?
398
+ raise 'Bus AND slot AND function must be specified. Check `lspci` for that numbers.'
399
+ end
400
+
401
+ @pcis = [] if @pcis == UNSET_VALUE
402
+
403
+ @pcis.push(bus: options[:bus],
404
+ slot: options[:slot],
405
+ function: options[:function])
406
+ end
407
+
408
+ def watchdog(options = {})
409
+ if options[:model].nil?
410
+ raise 'Model must be specified.'
411
+ end
412
+
413
+ if @watchdog_dev == UNSET_VALUE
414
+ @watchdog_dev = {}
415
+ end
416
+
417
+ @watchdog_dev[:model] = options[:model]
418
+ @watchdog_dev[:action] = options[:action] || 'reset'
419
+ end
420
+
421
+
422
+ def usb(options = {})
423
+ if (options[:bus].nil? || options[:device].nil?) && options[:vendor].nil? && options[:product].nil?
424
+ raise 'Bus and device and/or vendor and/or product must be specified. Check `lsusb` for these.'
425
+ end
426
+
427
+ @usbs = [] if @usbs == UNSET_VALUE
428
+
429
+ @usbs.push(bus: options[:bus],
430
+ device: options[:device],
431
+ vendor: options[:vendor],
432
+ product: options[:product],
433
+ startupPolicy: options[:startupPolicy])
434
+ end
435
+
436
+ def redirdev(options = {})
437
+ raise 'Type must be specified.' if options[:type].nil?
438
+
439
+ @redirdevs = [] if @redirdevs == UNSET_VALUE
440
+
441
+ @redirdevs.push(type: options[:type])
442
+ end
443
+
444
+ def redirfilter(options = {})
445
+ raise 'Option allow must be specified.' if options[:allow].nil?
446
+
447
+ @redirfilters = [] if @redirfilters == UNSET_VALUE
448
+
449
+ @redirfilters.push(class: options[:class] || -1,
450
+ vendor: options[:class] || -1,
451
+ product: options[:class] || -1,
452
+ version: options[:class] || -1,
453
+ allow: options[:allow])
454
+ end
455
+
456
+ def smartcard(options = {})
457
+ if options[:mode].nil?
458
+ raise 'Option mode must be specified.'
459
+ elsif options[:mode] != 'passthrough'
460
+ raise 'Currently only passthrough mode is supported!'
461
+ elsif options[:type] == 'tcp' && (options[:source_mode].nil? || options[:source_host].nil? || options[:source_service].nil?)
462
+ raise 'If using type "tcp", option "source_mode", "source_host" and "source_service" must be specified.'
463
+ end
464
+
465
+ if @smartcard_dev == UNSET_VALUE
466
+ @smartcard_dev = {}
467
+ end
468
+
469
+ @smartcard_dev[:mode] = options[:mode]
470
+ @smartcard_dev[:type] = options[:type] || 'spicevmc'
471
+ @smartcard_dev[:source_mode] = options[:source_mode] if @smartcard_dev[:type] == 'tcp'
472
+ @smartcard_dev[:source_host] = options[:source_host] if @smartcard_dev[:type] == 'tcp'
473
+ @smartcard_dev[:source_service] = options[:source_service] if @smartcard_dev[:type] == 'tcp'
474
+ end
475
+
476
+ # NOTE: this will run twice for each time it's needed- keep it idempotent
477
+ def storage(storage_type, options = {})
478
+ if storage_type == :file
479
+ if options[:device] == :cdrom
480
+ _handle_cdrom_storage(options)
481
+ else
482
+ _handle_disk_storage(options)
483
+ end
484
+ end
485
+ end
486
+
487
+ def _handle_cdrom_storage(options = {})
488
+ # <disk type="file" device="cdrom">
489
+ # <source file="/home/user/virtio-win-0.1-100.iso"/>
490
+ # <target dev="hdc"/>
491
+ # <readonly/>
492
+ # <address type='drive' controller='0' bus='1' target='0' unit='0'/>
493
+ # </disk>
494
+ #
495
+ # note the target dev will need to be changed with each cdrom drive (hdc, hdd, etc),
496
+ # as will the address unit number (unit=0, unit=1, etc)
497
+
498
+ options = {
499
+ bus: 'ide',
500
+ path: nil
501
+ }.merge(options)
502
+
503
+ cdrom = {
504
+ dev: options[:dev],
505
+ bus: options[:bus],
506
+ path: options[:path]
507
+ }
508
+
509
+ @cdroms << cdrom
510
+ end
511
+
512
+ def _handle_disk_storage(options = {})
513
+ options = {
514
+ type: 'qcow2',
515
+ size: '10G', # matches the fog default
516
+ path: nil,
517
+ bus: 'virtio'
518
+ }.merge(options)
519
+
520
+ disk = {
521
+ device: options[:device],
522
+ type: options[:type],
523
+ size: options[:size],
524
+ path: options[:path],
525
+ bus: options[:bus],
526
+ cache: options[:cache] || 'default',
527
+ allow_existing: options[:allow_existing],
528
+ shareable: options[:shareable],
529
+ serial: options[:serial]
530
+ }
531
+
532
+ @disks << disk # append
533
+ end
534
+
535
+ def qemuargs(options = {})
536
+ @qemu_args << options if options[:value]
537
+ end
538
+
539
+ # code to generate URI from a config moved out of the connect action
540
+ def _generate_uri
541
+ # builds the libvirt connection URI from the given driver config
542
+ # Setup connection uri.
543
+ uri = @driver.dup
544
+ virt_path = case uri
545
+ when 'qemu', 'openvz', 'uml', 'phyp', 'parallels', 'kvm'
546
+ '/system'
547
+ when '@en', 'esx'
548
+ '/'
549
+ when 'vbox', 'vmwarews', 'hyperv'
550
+ '/session'
551
+ else
552
+ raise "Require specify driver #{uri}"
553
+ end
554
+ if uri == 'kvm'
555
+ uri = 'qemu' # use qemu uri for kvm domain type
556
+ end
557
+
558
+ if @connect_via_ssh
559
+ uri << '+ssh://'
560
+ uri << @username + '@' if @username
561
+
562
+ uri << if @host
563
+ @host
564
+ else
565
+ 'localhost'
566
+ end
567
+ else
568
+ uri << '://'
569
+ uri << @host if @host
570
+ end
571
+
572
+ uri << virt_path
573
+ uri << '?no_verify=1'
574
+
575
+ if @id_ssh_key_file
576
+ # set ssh key for access to libvirt host
577
+ uri << "\&keyfile="
578
+ # if no slash, prepend $HOME/.ssh/
579
+ @id_ssh_key_file.prepend("#{`echo ${HOME}`.chomp}/.ssh/") if @id_ssh_key_file !~ /\A\//
580
+ uri << @id_ssh_key_file
581
+ end
582
+ # set path to libvirt socket
583
+ uri << "\&socket=" + @socket if @socket
584
+ uri
585
+ end
586
+
587
+ def finalize!
588
+ @driver = 'kvm' if @driver == UNSET_VALUE
589
+ @host = nil if @host == UNSET_VALUE
590
+ @connect_via_ssh = false if @connect_via_ssh == UNSET_VALUE
591
+ @username = nil if @username == UNSET_VALUE
592
+ @password = nil if @password == UNSET_VALUE
593
+ @id_ssh_key_file = 'id_rsa' if @id_ssh_key_file == UNSET_VALUE
594
+ @storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
595
+ @random_hostname = false if @random_hostname == UNSET_VALUE
596
+ @management_network_name = 'vagrant-libvirt' if @management_network_name == UNSET_VALUE
597
+ @management_network_address = '192.168.121.0/24' if @management_network_address == UNSET_VALUE
598
+ @management_network_mode = 'nat' if @management_network_mode == UNSET_VALUE
599
+ @management_network_mac = nil if @management_network_mac == UNSET_VALUE
600
+ @management_network_guest_ipv6 = 'yes' if @management_network_guest_ipv6 == UNSET_VALUE
601
+ @management_network_autostart = false if @management_network_autostart == UNSET_VALUE
602
+ @management_network_pci_bus = nil if @management_network_pci_bus == UNSET_VALUE
603
+ @management_network_pci_slot = nil if @management_network_pci_slot == UNSET_VALUE
604
+
605
+ # generate a URI if none is supplied
606
+ @uri = _generate_uri if @uri == UNSET_VALUE
607
+
608
+ # Domain specific settings.
609
+ @uuid = '' if @uuid == UNSET_VALUE
610
+ @memory = 512 if @memory == UNSET_VALUE
611
+ @memory_backing = [] if @memory_backing == UNSET_VALUE
612
+ @cpus = 1 if @cpus == UNSET_VALUE
613
+ @cpu_mode = 'host-model' if @cpu_mode == UNSET_VALUE
614
+ @cpu_model = if (@cpu_model == UNSET_VALUE) && (@cpu_mode == 'custom')
615
+ 'qemu64'
616
+ elsif @cpu_mode != 'custom'
617
+ ''
618
+ end
619
+ @cpu_topology = {} if @cpu_topology == UNSET_VALUE
620
+ @cpu_fallback = 'allow' if @cpu_fallback == UNSET_VALUE
621
+ @cpu_features = [] if @cpu_features == UNSET_VALUE
622
+ @features = ['acpi','apic','pae'] if @features == UNSET_VALUE
623
+ @numa_nodes = @numa_nodes == UNSET_VALUE ? nil : _generate_numa
624
+ @loader = nil if @loader == UNSET_VALUE
625
+ @machine_type = nil if @machine_type == UNSET_VALUE
626
+ @machine_arch = nil if @machine_arch == UNSET_VALUE
627
+ @machine_virtual_size = nil if @machine_virtual_size == UNSET_VALUE
628
+ @disk_bus = 'virtio' if @disk_bus == UNSET_VALUE
629
+ @disk_device = 'vda' if @disk_device == UNSET_VALUE
630
+ @nic_model_type = nil if @nic_model_type == UNSET_VALUE
631
+ @nested = false if @nested == UNSET_VALUE
632
+ @volume_cache = 'default' if @volume_cache == UNSET_VALUE
633
+ @kernel = nil if @kernel == UNSET_VALUE
634
+ @cmd_line = '' if @cmd_line == UNSET_VALUE
635
+ @initrd = '' if @initrd == UNSET_VALUE
636
+ @dtb = nil if @dtb == UNSET_VALUE
637
+ @graphics_type = 'vnc' if @graphics_type == UNSET_VALUE
638
+ @graphics_autoport = 'yes' if @graphics_port == UNSET_VALUE
639
+ @graphics_autoport = 'no' if @graphics_port != UNSET_VALUE
640
+ if (@graphics_type != 'vnc' && @graphics_type != 'spice') ||
641
+ @graphics_passwd == UNSET_VALUE
642
+ @graphics_passwd = nil
643
+ end
644
+ @graphics_port = -1 if @graphics_port == UNSET_VALUE
645
+ @graphics_ip = '127.0.0.1' if @graphics_ip == UNSET_VALUE
646
+ @video_type = 'cirrus' if @video_type == UNSET_VALUE
647
+ @video_vram = 9216 if @video_vram == UNSET_VALUE
648
+ @sound_type = nil if @sound_type == UNSET_VALUE
649
+ @keymap = 'en-us' if @keymap == UNSET_VALUE
650
+ @kvm_hidden = false if @kvm_hidden == UNSET_VALUE
651
+ @tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE
652
+ @tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE
653
+ @tpm_path = nil if @tpm_path == UNSET_VALUE
654
+ @nic_adapter_count = 8 if @nic_adapter_count == UNSET_VALUE
655
+ @emulator_path = nil if @emulator_path == UNSET_VALUE
656
+
657
+ # Boot order
658
+ @boot_order = [] if @boot_order == UNSET_VALUE
659
+
660
+ # Storage
661
+ @disks = [] if @disks == UNSET_VALUE
662
+ @disks.map! do |disk|
663
+ disk[:device] = _get_device(@disks) if disk[:device].nil?
664
+ disk
665
+ end
666
+ @cdroms = [] if @cdroms == UNSET_VALUE
667
+ @cdroms.map! do |cdrom|
668
+ cdrom[:dev] = _get_cdrom_dev(@cdroms) if cdrom[:dev].nil?
669
+ cdrom
670
+ end
671
+
672
+ # Inputs
673
+ @inputs = [{ type: 'mouse', bus: 'ps2' }] if @inputs == UNSET_VALUE
674
+
675
+ # Channels
676
+ @channels = [] if @channels == UNSET_VALUE
677
+
678
+ # PCI device passthrough
679
+ @pcis = [] if @pcis == UNSET_VALUE
680
+
681
+ # Random number generator passthrough
682
+ @rng = {} if @rng == UNSET_VALUE
683
+
684
+ # Watchdog device
685
+ @watchdog_dev = {} if @watchdog_dev == UNSET_VALUE
686
+
687
+ # USB device passthrough
688
+ @usbs = [] if @usbs == UNSET_VALUE
689
+
690
+ # Redirected devices
691
+ @redirdevs = [] if @redirdevs == UNSET_VALUE
692
+ @redirfilters = [] if @redirfilters == UNSET_VALUE
693
+
694
+ # smartcard device
695
+ @smartcard_dev = {} if @smartcard_dev == UNSET_VALUE
696
+
697
+ # Suspend mode
698
+ @suspend_mode = 'pause' if @suspend_mode == UNSET_VALUE
699
+
700
+ # Autostart
701
+ @autostart = false if @autostart == UNSET_VALUE
702
+
703
+ # Attach mgmt network
704
+ @mgmt_attach = true if @mgmt_attach == UNSET_VALUE
705
+
706
+ @qemu_args = [] if @qemu_args == UNSET_VALUE
707
+ end
708
+
709
+ def validate(machine)
710
+ errors = _detected_errors
711
+
712
+ machine.provider_config.disks.each do |disk|
713
+ if disk[:path] && (disk[:path][0] == '/')
714
+ errors << "absolute volume paths like '#{disk[:path]}' not yet supported"
715
+ end
716
+ end
717
+
718
+ machine.config.vm.networks.each do |_type, opts|
719
+ if opts[:mac]
720
+ opts[:mac].downcase!
721
+ if opts[:mac] =~ /\A([0-9a-f]{12})\z/
722
+ opts[:mac] = opts[:mac].scan(/../).join(':')
723
+ end
724
+ unless opts[:mac] =~ /\A([0-9a-f]{2}:){5}([0-9a-f]{2})\z/
725
+ errors << "Configured NIC MAC '#{opts[:mac]}' is not in 'xx:xx:xx:xx:xx:xx' or 'xxxxxxxxxxxx' format"
726
+ end
727
+ end
728
+ end
729
+
730
+ { 'Libvirt Provider' => errors }
731
+ end
732
+
733
+ def merge(other)
734
+ super.tap do |result|
735
+ c = disks.dup
736
+ c += other.disks
737
+ result.disks = c
738
+
739
+ c = cdroms.dup
740
+ c += other.cdroms
741
+ result.cdroms = c
742
+ end
743
+ end
744
+ end
745
+ end
746
+ end