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,227 @@
1
+ module VagrantPlugins
2
+ module ProviderLibvirt
3
+ module Action
4
+ # Adds support for vagrant's `forward_ports` configuration directive.
5
+ class ForwardPorts
6
+ @@lock = Mutex.new
7
+
8
+ def initialize(app, _env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::forward_ports')
11
+ end
12
+
13
+ def call(env)
14
+ @env = env
15
+
16
+ # Get the ports we're forwarding
17
+ env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config)
18
+
19
+ # Warn if we're port forwarding to any privileged ports
20
+ env[:forwarded_ports].each do |fp|
21
+ next unless fp[:host] <= 1024
22
+ env[:ui].warn I18n.t(
23
+ 'vagrant.actions.vm.forward_ports.privileged_ports'
24
+ )
25
+ break
26
+ end
27
+
28
+ # Continue, we need the VM to be booted in order to grab its IP
29
+ @app.call env
30
+
31
+ if @env[:forwarded_ports].any?
32
+ env[:ui].info I18n.t('vagrant.actions.vm.forward_ports.forwarding')
33
+ forward_ports
34
+ end
35
+ end
36
+
37
+ def forward_ports
38
+ @env[:forwarded_ports].each do |fp|
39
+ message_attributes = {
40
+ adapter: fp[:adapter] || 'eth0',
41
+ guest_port: fp[:guest],
42
+ host_port: fp[:host]
43
+ }
44
+
45
+ @env[:ui].info(I18n.t(
46
+ 'vagrant.actions.vm.forward_ports.forwarding_entry',
47
+ message_attributes
48
+ ))
49
+
50
+ if fp[:protocol] == 'udp'
51
+ env[:ui].warn I18n.t('vagrant_libvirt.warnings.forwarding_udp')
52
+ next
53
+ end
54
+
55
+ ssh_pid = redirect_port(
56
+ @env[:machine],
57
+ fp[:host_ip] || 'localhost',
58
+ fp[:host],
59
+ fp[:guest_ip] || @env[:machine].provider.ssh_info[:host],
60
+ fp[:guest],
61
+ fp[:gateway_ports] || false
62
+ )
63
+ store_ssh_pid(fp[:host], ssh_pid)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def compile_forwarded_ports(config)
70
+ mappings = {}
71
+
72
+ config.vm.networks.each do |type, options|
73
+ next if options[:disabled]
74
+
75
+ next unless type == :forwarded_port && options[:id] != 'ssh'
76
+ if options.fetch(:host_ip, '').to_s.strip.empty?
77
+ options.delete(:host_ip)
78
+ end
79
+ mappings[options[:host]] = options
80
+ end
81
+
82
+ mappings.values
83
+ end
84
+
85
+ def redirect_port(machine, host_ip, host_port, guest_ip, guest_port,
86
+ gateway_ports)
87
+ ssh_info = machine.ssh_info
88
+ params = %W(
89
+ -L
90
+ #{host_ip}:#{host_port}:#{guest_ip}:#{guest_port}
91
+ -N
92
+ #{ssh_info[:host]}
93
+ ).join(' ')
94
+ params += ' -g' if gateway_ports
95
+
96
+ options = (%W(
97
+ User=#{ssh_info[:username]}
98
+ Port=#{ssh_info[:port]}
99
+ UserKnownHostsFile=/dev/null
100
+ StrictHostKeyChecking=no
101
+ PasswordAuthentication=no
102
+ ForwardX11=#{ssh_info[:forward_x11] ? 'yes' : 'no'}
103
+ IdentitiesOnly=#{ssh_info[:keys_only] ? 'yes' : 'no'}
104
+ ) + ssh_info[:private_key_path].map do |pk|
105
+ "IdentityFile='\"#{pk}\"'"
106
+ end).map { |s| s.prepend('-o ') }.join(' ')
107
+
108
+ options += " -o ProxyCommand=\"#{ssh_info[:proxy_command]}\"" if machine.provider_config.connect_via_ssh
109
+
110
+ # TODO: instead of this, try and lock and get the stdin from spawn...
111
+ ssh_cmd = 'exec '
112
+ if host_port <= 1024
113
+ @@lock.synchronize do
114
+ # TODO: add i18n
115
+ @env[:ui].info 'Requesting sudo for host port(s) <= 1024'
116
+ r = system('sudo -v')
117
+ if r
118
+ ssh_cmd << 'sudo ' # add sudo prefix
119
+ end
120
+ end
121
+ end
122
+
123
+ ssh_cmd << "ssh #{options} #{params}"
124
+
125
+ @logger.debug "Forwarding port with `#{ssh_cmd}`"
126
+ log_file = ssh_forward_log_file(host_ip, host_port,
127
+ guest_ip, guest_port)
128
+ @logger.info "Logging to #{log_file}"
129
+ spawn(ssh_cmd, [:out, :err] => [log_file, 'w'])
130
+ end
131
+
132
+ def ssh_forward_log_file(host_ip, host_port, guest_ip, guest_port)
133
+ log_dir = @env[:machine].data_dir.join('logs')
134
+ log_dir.mkdir unless log_dir.directory?
135
+ File.join(
136
+ log_dir,
137
+ 'ssh-forwarding-%s_%s-%s_%s.log' %
138
+ [host_ip, host_port, guest_ip, guest_port]
139
+ )
140
+ end
141
+
142
+ def store_ssh_pid(host_port, ssh_pid)
143
+ data_dir = @env[:machine].data_dir.join('pids')
144
+ data_dir.mkdir unless data_dir.directory?
145
+
146
+ data_dir.join("ssh_#{host_port}.pid").open('w') do |pid_file|
147
+ pid_file.write(ssh_pid)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ module VagrantPlugins
156
+ module ProviderLibvirt
157
+ module Action
158
+ # Cleans up ssh-forwarded ports on VM halt/destroy.
159
+ class ClearForwardedPorts
160
+ @@lock = Mutex.new
161
+
162
+ def initialize(app, _env)
163
+ @app = app
164
+ @logger = Log4r::Logger.new(
165
+ 'vagrant_libvirt::action::clear_forward_ports'
166
+ )
167
+ end
168
+
169
+ def call(env)
170
+ @env = env
171
+
172
+ if ssh_pids.any?
173
+ env[:ui].info I18n.t(
174
+ 'vagrant.actions.vm.clear_forward_ports.deleting'
175
+ )
176
+ ssh_pids.each do |tag|
177
+ next unless ssh_pid?(tag[:pid])
178
+ @logger.debug "Killing pid #{tag[:pid]}"
179
+ kill_cmd = ''
180
+
181
+ if tag[:port] <= 1024
182
+ kill_cmd << 'sudo ' # add sudo prefix
183
+ end
184
+
185
+ kill_cmd << "kill #{tag[:pid]}"
186
+ @@lock.synchronize do
187
+ system(kill_cmd)
188
+ end
189
+ end
190
+
191
+ @logger.info 'Removing ssh pid files'
192
+ remove_ssh_pids
193
+ else
194
+ @logger.info 'No ssh pids found'
195
+ end
196
+
197
+ @app.call env
198
+ end
199
+
200
+ protected
201
+
202
+ def ssh_pids
203
+ glob = @env[:machine].data_dir.join('pids').to_s + '/ssh_*.pid'
204
+ @ssh_pids = Dir[glob].map do |file|
205
+ {
206
+ pid: File.read(file).strip.chomp,
207
+ port: File.basename(file)['ssh_'.length..-1 * ('.pid'.length + 1)].to_i
208
+ }
209
+ end
210
+ end
211
+
212
+ def ssh_pid?(pid)
213
+ @logger.debug 'Checking if #{pid} is an ssh process '\
214
+ 'with `ps -o cmd= #{pid}`'
215
+ `ps -o cmd= #{pid}`.strip.chomp =~ /ssh/
216
+ end
217
+
218
+ def remove_ssh_pids
219
+ glob = @env[:machine].data_dir.join('pids').to_s + '/ssh_*.pid'
220
+ Dir[glob].each do |file|
221
+ File.delete file
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,41 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ # Halt the domain.
7
+ class HaltDomain
8
+ def initialize(app, _env)
9
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::halt_domain')
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ env[:ui].info(I18n.t('vagrant_libvirt.halt_domain'))
15
+
16
+ domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
17
+ raise Errors::NoDomainError if domain.nil?
18
+
19
+ begin
20
+ env[:machine].guest.capability(:halt)
21
+ rescue
22
+ @logger.info('Trying libvirt graceful shutdown.')
23
+ domain.shutdown
24
+ end
25
+
26
+
27
+ begin
28
+ domain.wait_for(30) do
29
+ !ready?
30
+ end
31
+ rescue Fog::Errors::TimeoutError
32
+ @logger.info('VM is still running. Calling force poweroff.')
33
+ domain.poweroff
34
+ end
35
+
36
+ @app.call(env)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,156 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ class HandleBoxImage
7
+ @@lock = Mutex.new
8
+
9
+ def initialize(app, _env)
10
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::handle_box_image')
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ # Verify box metadata for mandatory values.
16
+ #
17
+ # Virtual size has to be set for allocating space in storage pool.
18
+ box_virtual_size = env[:machine].box.metadata['virtual_size']
19
+ raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
20
+
21
+ # Support qcow2 format only for now, but other formats with backing
22
+ # store capability should be usable.
23
+ box_format = env[:machine].box.metadata['format']
24
+ if box_format.nil?
25
+ raise Errors::NoBoxFormatSet
26
+ elsif box_format != 'qcow2'
27
+ raise Errors::WrongBoxFormatSet
28
+ end
29
+
30
+ # Get config options
31
+ config = env[:machine].provider_config
32
+ box_image_file = env[:machine].box.directory.join('box.img').to_s
33
+ env[:box_volume_name] = env[:machine].box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
34
+ env[:box_volume_name] << "_vagrant_box_image_#{begin
35
+ env[:machine].box.version.to_s
36
+ rescue
37
+ ''
38
+ end}.img"
39
+
40
+ # Override box_virtual_size
41
+ if config.machine_virtual_size
42
+ if config.machine_virtual_size < box_virtual_size
43
+ # Warn that a virtual size less than the box metadata size
44
+ # is not supported and will be ignored
45
+ env[:ui].warn I18n.t(
46
+ 'vagrant_libvirt.warnings.ignoring_virtual_size_too_small',
47
+ requested: config.machine_virtual_size, minimum: box_virtual_size
48
+ )
49
+ else
50
+ env[:ui].info I18n.t('vagrant_libvirt.manual_resize_required')
51
+ box_virtual_size = config.machine_virtual_size
52
+ end
53
+ end
54
+ # save for use by later actions
55
+ env[:box_virtual_size] = box_virtual_size
56
+
57
+ # while inside the synchronize block take care not to call the next
58
+ # action in the chain, as must exit this block first to prevent
59
+ # locking all subsequent actions as well.
60
+ @@lock.synchronize do
61
+ # Don't continue if image already exists in storage pool.
62
+ break if ProviderLibvirt::Util::Collection.find_matching(
63
+ env[:machine].provider.driver.connection.volumes.all, env[:box_volume_name]
64
+ )
65
+
66
+ # Box is not available as a storage pool volume. Create and upload
67
+ # it as a copy of local box image.
68
+ env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
69
+
70
+ # Create new volume in storage pool
71
+ unless File.exist?(box_image_file)
72
+ raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
73
+ end
74
+ box_image_size = File.size(box_image_file) # B
75
+ message = "Creating volume #{env[:box_volume_name]}"
76
+ message << " in storage pool #{config.storage_pool_name}."
77
+ @logger.info(message)
78
+ begin
79
+ fog_volume = env[:machine].provider.driver.connection.volumes.create(
80
+ name: env[:box_volume_name],
81
+ allocation: "#{box_image_size / 1024 / 1024}M",
82
+ capacity: "#{box_virtual_size}G",
83
+ format_type: box_format,
84
+ pool_name: config.storage_pool_name
85
+ )
86
+ rescue Fog::Errors::Error => e
87
+ raise Errors::FogCreateVolumeError,
88
+ error_message: e.message
89
+ end
90
+
91
+ # Upload box image to storage pool
92
+ ret = upload_image(box_image_file, config.storage_pool_name,
93
+ env[:box_volume_name], env) do |progress|
94
+ env[:ui].clear_line
95
+ env[:ui].report_progress(progress, box_image_size, false)
96
+ end
97
+
98
+ # Clear the line one last time since the progress meter doesn't
99
+ # disappear immediately.
100
+ env[:ui].clear_line
101
+
102
+ # If upload failed or was interrupted, remove created volume from
103
+ # storage pool.
104
+ if env[:interrupted] || !ret
105
+ begin
106
+ fog_volume.destroy
107
+ rescue
108
+ nil
109
+ end
110
+ end
111
+ end
112
+
113
+ @app.call(env)
114
+ end
115
+
116
+ protected
117
+
118
+ # Fog libvirt currently doesn't support uploading images to storage
119
+ # pool volumes. Use ruby-libvirt client instead.
120
+ def upload_image(image_file, pool_name, volume_name, env)
121
+ image_size = File.size(image_file) # B
122
+
123
+ begin
124
+ pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(
125
+ pool_name
126
+ )
127
+ volume = pool.lookup_volume_by_name(volume_name)
128
+ stream = env[:machine].provider.driver.connection.client.stream
129
+ volume.upload(stream, offset = 0, length = image_size)
130
+
131
+ # Exception ProviderLibvirt::RetrieveError can be raised if buffer is
132
+ # longer than length accepted by API send function.
133
+ #
134
+ # TODO: How to find out if buffer is too large and what is the
135
+ # length that send function will accept?
136
+
137
+ buf_size = 1024 * 250 # 250K
138
+ progress = 0
139
+ open(image_file, 'rb') do |io|
140
+ while (buff = io.read(buf_size))
141
+ sent = stream.send buff
142
+ progress += sent
143
+ yield progress
144
+ end
145
+ end
146
+ rescue => e
147
+ raise Errors::ImageUploadError,
148
+ error_message: e.message
149
+ end
150
+
151
+ progress == image_size
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,57 @@
1
+ require 'log4r'
2
+
3
+ module VagrantPlugins
4
+ module ProviderLibvirt
5
+ module Action
6
+ class HandleStoragePool
7
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
8
+
9
+ @@lock = Mutex.new
10
+
11
+ def initialize(app, _env)
12
+ @logger = Log4r::Logger.new('vagrant_libvirt::action::handle_storage_pool')
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ # Get config options.
18
+ config = env[:machine].provider_config
19
+
20
+ # while inside the synchronize block take care not to call the next
21
+ # action in the chain, as must exit this block first to prevent
22
+ # locking all subsequent actions as well.
23
+ @@lock.synchronize do
24
+ # Check for storage pool, where box image should be created
25
+ break if ProviderLibvirt::Util::Collection.find_matching(
26
+ env[:machine].provider.driver.connection.pools.all, config.storage_pool_name
27
+ )
28
+
29
+ @logger.info("No storage pool '#{config.storage_pool_name}' is available.")
30
+
31
+ # If user specified other pool than default, don't create default
32
+ # storage pool, just write error message.
33
+ raise Errors::NoStoragePool if config.storage_pool_name != 'default'
34
+
35
+ @logger.info("Creating storage pool 'default'")
36
+
37
+ # Fog libvirt currently doesn't support creating pools. Use
38
+ # ruby-libvirt client directly.
39
+ begin
40
+ libvirt_pool = env[:machine].provider.driver.connection.client.define_storage_pool_xml(
41
+ to_xml('default_storage_pool')
42
+ )
43
+ libvirt_pool.build
44
+ libvirt_pool.create
45
+ rescue => e
46
+ raise Errors::CreatingStoragePoolError,
47
+ error_message: e.message
48
+ end
49
+ raise Errors::NoStoragePool unless libvirt_pool
50
+ end
51
+
52
+ @app.call(env)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end