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,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