vagrant-libvirt 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -6
- data/README.md +24 -10
- data/Rakefile +0 -1
- data/example_box/Vagrantfile +3 -0
- data/lib/vagrant-libvirt/action.rb +28 -5
- data/lib/vagrant-libvirt/action/connect_libvirt.rb +3 -7
- data/lib/vagrant-libvirt/action/create_domain.rb +8 -0
- data/lib/vagrant-libvirt/action/create_network_interfaces.rb +20 -5
- data/lib/vagrant-libvirt/action/create_networks.rb +112 -45
- data/lib/vagrant-libvirt/action/destroy_networks.rb +21 -12
- data/lib/vagrant-libvirt/action/set_name_of_domain.rb +13 -3
- data/lib/vagrant-libvirt/action/share_folders.rb +1 -1
- data/lib/vagrant-libvirt/action/sync_folders.rb +10 -4
- data/lib/vagrant-libvirt/config.rb +24 -3
- data/lib/vagrant-libvirt/errors.rb +13 -5
- data/lib/vagrant-libvirt/templates/domain.xml.erb +5 -2
- data/lib/vagrant-libvirt/util/libvirt_util.rb +7 -0
- data/lib/vagrant-libvirt/version.rb +1 -1
- data/locales/en.yml +6 -1
- metadata +12 -23
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6a7de8bcfdcab60b3237c7d64fd047fa9b43bbf8
|
4
|
+
data.tar.gz: f98b1b521b2d6665be337341791ae260244d29eb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6d33e5ae966f1758ec327b73a9d3527cfa0b6c3ab461630beb4e13bc391c5f291ab04b48884bc0d568e2376ee3fda1361bce6efab0d3d5513b987f08f7b75513
|
7
|
+
data.tar.gz: 83408a1dcb5c55fd745e204b7d23f78cf1ec9d6e6eb5beb8a27f31cbff4154a916bd9068b7e80dade9318f97b504a77671171486ce954e65b9af73474487e151
|
data/Gemfile
CHANGED
@@ -4,11 +4,9 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :development do
|
7
|
-
|
8
|
-
gem
|
9
|
-
|
10
|
-
gem
|
11
|
-
gem 'coveralls', require: false
|
12
|
-
gem 'vagrant', :git => 'git://github.com/mitchellh/vagrant.git'
|
7
|
+
# We depend on Vagrant for development, but we don't add it as a
|
8
|
+
# gem dependency because we expect to be installed within the
|
9
|
+
# Vagrant environment itself using `vagrant plugin`.
|
10
|
+
gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git"
|
13
11
|
end
|
14
12
|
|
data/README.md
CHANGED
@@ -99,14 +99,19 @@ This provider exposes quite a few provider-specific configuration options:
|
|
99
99
|
* `password` - Password to access Libvirt.
|
100
100
|
* `id_ssh_key_file` - The id ssh key file name to access Libvirt (eg: id_dsa or id_rsa or ... in the user .ssh directory)
|
101
101
|
* `storage_pool_name` - Libvirt storage pool name, where box image and instance snapshots will be stored.
|
102
|
-
* `default_network` - Libvirt default network name. If not specified default network name is 'default'.
|
103
102
|
|
104
103
|
### Domain Specific Options
|
105
104
|
|
105
|
+
* `disk_bus` - The type of disk device to emulate. Defaults to virtio if not set. Possible values are documented in libvirt's [description for _target_](http://libvirt.org/formatdomain.html#elementsDisks).
|
106
106
|
* `memory` - Amount of memory in MBytes. Defaults to 512 if not set.
|
107
107
|
* `cpus` - Number of virtual cpus. Defaults to 1 if not set.
|
108
108
|
* `nested` - [Enable nested virtualization](https://github.com/torvalds/linux/blob/master/Documentation/virtual/kvm/nested-vmx.txt). Default is false.
|
109
|
+
* `cpu_mode` - What cpu mode to use for nested virtualization. Defaults to 'host-model' if not set.
|
109
110
|
* `volume_cache` - Controls the cache mechanism. Possible values are "default", "none", "writethrough", "writeback", "directsync" and "unsafe". [See driver->cache in libvirt documentation](http://libvirt.org/formatdomain.html#elementsDisks).
|
111
|
+
* `kernel` - To launch the guest with a kernel residing on host filesystems (Equivalent to qemu `-kernel`)
|
112
|
+
* `initrd` - To specify the initramfs/initrd to use for the guest (Equivalent to qemu `-initrd`)
|
113
|
+
* `cmd_line` - Arguments passed on to the guest kernel initramfs or initrd to use (Equivalent to qemu `-append`)
|
114
|
+
|
110
115
|
|
111
116
|
Specific domain settings can be set for each domain separately in multi-VM
|
112
117
|
environment. Example below shows a part of Vagrantfile, where specific options
|
@@ -120,7 +125,7 @@ Vagrant.configure("2") do |config|
|
|
120
125
|
domain.memory = 2048
|
121
126
|
domain.cpus = 2
|
122
127
|
domain.nested = true
|
123
|
-
|
128
|
+
domain.volume_cache = 'none'
|
124
129
|
end
|
125
130
|
end
|
126
131
|
|
@@ -229,15 +234,24 @@ starts with 'libvirt__' string. Here is a list of those options:
|
|
229
234
|
Default mode is 'bridge'.
|
230
235
|
* `:mac` - MAC address for the interface.
|
231
236
|
|
232
|
-
|
237
|
+
### Management Network
|
238
|
+
|
239
|
+
Vagrant-libvirt uses a private network to perform some management operations
|
240
|
+
on VMs. All VMs will have an interface connected to this network and
|
241
|
+
an IP address dynamically assigned by libvirt. This is in addition to any
|
242
|
+
networks you configure. The name and address used by this network are
|
243
|
+
configurable at the provider level.
|
244
|
+
|
245
|
+
* `management_network_name` - Name of libvirt network to which all VMs will be connected. If not specified the default is 'vagrant-libvirt'.
|
246
|
+
* `management_network_address` - Address of network to which all VMs will be connected. Must include the address and subnet mask. If not specified the default is '192.168.121.0/24'.
|
233
247
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
248
|
+
You may wonder how vagrant-libvirt knows the IP address a VM received.
|
249
|
+
Libvirt doesn't provide a standard way to find out the IP address of a running
|
250
|
+
domain. But we do know the MAC address of the virtual machine's interface on
|
251
|
+
the management network. Libvirt is closely connected with dnsmasq, which acts as
|
252
|
+
a DHCP server. dnsmasq writes lease information in the `/var/lib/libvirt/dnsmasq`
|
253
|
+
directory. Vagrant-libvirt looks for the MAC address in this file and extracts
|
254
|
+
the corresponding IP address.
|
241
255
|
|
242
256
|
## SSH Access To VM
|
243
257
|
|
data/Rakefile
CHANGED
data/example_box/Vagrantfile
CHANGED
@@ -52,6 +52,9 @@ Vagrant.configure("2") do |config|
|
|
52
52
|
# Libvirt storage pool name, where box image and instance snapshots will
|
53
53
|
# be stored.
|
54
54
|
libvirt.storage_pool_name = "default"
|
55
|
+
|
56
|
+
# Set a prefix for the machines that's different than the project dir name.
|
57
|
+
#libvirt.default_prefix = ''
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
@@ -25,7 +25,13 @@ module VagrantPlugins
|
|
25
25
|
b2.use CreateNetworks
|
26
26
|
b2.use CreateNetworkInterfaces
|
27
27
|
|
28
|
-
|
28
|
+
if Vagrant::VERSION < "1.4.0"
|
29
|
+
b2.use NFS
|
30
|
+
else
|
31
|
+
b2.use SyncedFolderCleanup
|
32
|
+
b2.use SyncedFolders
|
33
|
+
end
|
34
|
+
|
29
35
|
b2.use PrepareNFSSettings
|
30
36
|
b2.use ShareFolders
|
31
37
|
b2.use SetHostname
|
@@ -51,19 +57,32 @@ module VagrantPlugins
|
|
51
57
|
next if env[:result]
|
52
58
|
|
53
59
|
b2.use Call, IsSuspended do |env2, b3|
|
60
|
+
# if vm is suspended resume it then exit
|
54
61
|
if env2[:result]
|
55
62
|
b3.use ResumeDomain
|
56
63
|
next
|
57
64
|
end
|
58
65
|
|
59
|
-
# VM is not running or suspended.
|
60
|
-
|
61
|
-
#
|
62
|
-
b3.use
|
66
|
+
# VM is not running or suspended.
|
67
|
+
|
68
|
+
# Ensure networks are created and active
|
69
|
+
b3.use CreateNetworks
|
70
|
+
|
71
|
+
# Handle shared folders
|
72
|
+
if Vagrant::VERSION < "1.4.0"
|
73
|
+
b3.use NFS
|
74
|
+
else
|
75
|
+
b3.use SyncedFolderCleanup
|
76
|
+
b3.use SyncedFolders
|
77
|
+
end
|
63
78
|
b3.use PrepareNFSSettings
|
64
79
|
b3.use ShareFolders
|
65
80
|
|
81
|
+
# Start it..
|
66
82
|
b3.use StartDomain
|
83
|
+
|
84
|
+
# Machine should gain IP address when comming up,
|
85
|
+
# so wait for dhcp lease and store IP into machines data_dir.
|
67
86
|
b3.use WaitTillUp
|
68
87
|
end
|
69
88
|
end
|
@@ -302,6 +321,10 @@ module VagrantPlugins
|
|
302
321
|
autoload :WaitTillUp, action_root.join('wait_till_up')
|
303
322
|
autoload :SSHRun, 'vagrant/action/builtin/ssh_run'
|
304
323
|
autoload :HandleBoxUrl, 'vagrant/action/builtin/handle_box_url'
|
324
|
+
unless Vagrant::VERSION < "1.4.0"
|
325
|
+
autoload :SyncedFolders, 'vagrant/action/builtin/synced_folders'
|
326
|
+
autoload :SyncedFolderCleanup, 'vagrant/action/builtin/synced_folder_cleanup'
|
327
|
+
end
|
305
328
|
end
|
306
329
|
end
|
307
330
|
end
|
@@ -67,13 +67,9 @@ module VagrantPlugins
|
|
67
67
|
conn_attr[:libvirt_password] = config.password if config.password
|
68
68
|
|
69
69
|
# Setup command for retrieving IP address for newly created machine
|
70
|
-
# with some MAC address. Get it from dnsmasq leases table
|
71
|
-
|
72
|
-
|
73
|
-
ip_command = "LEASES='/var/lib/libvirt/dnsmasq/*.leases'; "
|
74
|
-
ip_command << "[ -f /var/lib/misc/dnsmasq.leases ] && "
|
75
|
-
ip_command << "LEASES='/var/lib/misc/dnsmasq.leases'; "
|
76
|
-
ip_command << "grep $mac $LEASES | awk '{ print $3 }'"
|
70
|
+
# with some MAC address. Get it from dnsmasq leases table
|
71
|
+
ip_command = %q[ LEASES=$(find /var/lib/libvirt/dnsmasq/ /var/lib/misc/ -name '*leases'); ]
|
72
|
+
ip_command << %q[ [ -n "$LEASES" ] && grep $mac $LEASES | awk '{ print $3 }' ]
|
77
73
|
conn_attr[:libvirt_ip_command] = ip_command
|
78
74
|
|
79
75
|
@logger.info("Connecting to Libvirt (#{uri}) ...")
|
@@ -19,9 +19,14 @@ module VagrantPlugins
|
|
19
19
|
# Gather some info about domain
|
20
20
|
@name = env[:domain_name]
|
21
21
|
@cpus = config.cpus
|
22
|
+
@cpu_mode = config.cpu_mode
|
23
|
+
@disk_bus = config.disk_bus
|
22
24
|
@nested = config.nested
|
23
25
|
@memory_size = config.memory*1024
|
24
26
|
@domain_volume_cache = config.volume_cache
|
27
|
+
@kernel = config.kernel
|
28
|
+
@cmd_line = config.cmd_line
|
29
|
+
@initrd = config.initrd
|
25
30
|
|
26
31
|
# TODO get type from driver config option
|
27
32
|
@domain_type = 'kvm'
|
@@ -44,6 +49,9 @@ module VagrantPlugins
|
|
44
49
|
env[:ui].info(" -- Storage pool: #{env[:machine].provider_config.storage_pool_name}")
|
45
50
|
env[:ui].info(" -- Image: #{@domain_volume_path}")
|
46
51
|
env[:ui].info(" -- Volume Cache: #{@domain_volume_cache}")
|
52
|
+
env[:ui].info(" -- Kernel: #{@kernel}")
|
53
|
+
env[:ui].info(" -- Initrd: #{@initrd}")
|
54
|
+
env[:ui].info(" -- Command line : #{@cmd_line}")
|
47
55
|
|
48
56
|
# Create libvirt domain.
|
49
57
|
# Is there a way to tell fog to create new domain with already
|
@@ -16,7 +16,7 @@ module VagrantPlugins
|
|
16
16
|
|
17
17
|
def initialize(app, env)
|
18
18
|
@logger = Log4r::Logger.new('vagrant_libvirt::action::create_network_interfaces')
|
19
|
-
@
|
19
|
+
@management_network_name = env[:machine].provider_config.management_network_name
|
20
20
|
@app = app
|
21
21
|
end
|
22
22
|
|
@@ -37,6 +37,7 @@ module VagrantPlugins
|
|
37
37
|
|
38
38
|
# Assign interfaces to slots.
|
39
39
|
env[:machine].config.vm.networks.each do |type, options|
|
40
|
+
@logger.debug "In config found network type #{type} options #{options}"
|
40
41
|
|
41
42
|
# Get options for this interface. Options can be specified in
|
42
43
|
# Vagrantfile in short format (:ip => ...), or provider format
|
@@ -51,8 +52,10 @@ module VagrantPlugins
|
|
51
52
|
end
|
52
53
|
|
53
54
|
free_slot = options[:adapter].to_i
|
55
|
+
@logger.debug "Using specified adapter slot #{free_slot}"
|
54
56
|
else
|
55
57
|
free_slot = find_empty(adapters)
|
58
|
+
@logger.debug "Adapter not specified so found slot #{free_slot}"
|
56
59
|
raise Errors::InterfaceSlotNotAvailable if free_slot == nil
|
57
60
|
end
|
58
61
|
|
@@ -104,6 +107,7 @@ module VagrantPlugins
|
|
104
107
|
# it has to be available during provisioning - ifdown command is
|
105
108
|
# not acceptable here.
|
106
109
|
next if slot_number == 0
|
110
|
+
@logger.debug "Configuring interface slot_number #{slot_number} options #{options}"
|
107
111
|
|
108
112
|
network = {
|
109
113
|
:interface => slot_number,
|
@@ -139,7 +143,10 @@ module VagrantPlugins
|
|
139
143
|
|
140
144
|
# Return network name according to interface options.
|
141
145
|
def interface_network(libvirt_client, options)
|
142
|
-
|
146
|
+
if options[:network_name]
|
147
|
+
@logger.debug "Found network by name"
|
148
|
+
return options[:network_name]
|
149
|
+
end
|
143
150
|
|
144
151
|
# Get list of all (active and inactive) libvirt networks.
|
145
152
|
available_networks = libvirt_networks(libvirt_client)
|
@@ -147,12 +154,20 @@ module VagrantPlugins
|
|
147
154
|
if options[:ip]
|
148
155
|
address = network_address(options[:ip], options[:netmask])
|
149
156
|
available_networks.each do |network|
|
150
|
-
|
157
|
+
if address == network[:network_address]
|
158
|
+
@logger.debug "Found network by ip"
|
159
|
+
return network[:name]
|
160
|
+
end
|
151
161
|
end
|
152
162
|
end
|
153
163
|
|
154
|
-
#
|
155
|
-
|
164
|
+
# the management network always gets attached to slot 0
|
165
|
+
# because the first network is of type forwarded_port.
|
166
|
+
# this is confusing.
|
167
|
+
# TODO only iterate over networks of type private_network
|
168
|
+
# and prepend the management network to that list
|
169
|
+
@logger.debug "Did not find network so using default of #{@management_network_name}"
|
170
|
+
@management_network_name
|
156
171
|
end
|
157
172
|
end
|
158
173
|
end
|
@@ -2,6 +2,7 @@ require 'log4r'
|
|
2
2
|
require 'vagrant/util/network_ip'
|
3
3
|
require 'vagrant/util/scoped_hash_override'
|
4
4
|
require 'ipaddr'
|
5
|
+
require 'thread'
|
5
6
|
|
6
7
|
module VagrantPlugins
|
7
8
|
module ProviderLibvirt
|
@@ -13,6 +14,8 @@ module VagrantPlugins
|
|
13
14
|
include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
|
14
15
|
include VagrantPlugins::ProviderLibvirt::Util::LibvirtUtil
|
15
16
|
|
17
|
+
@@lock = Mutex.new
|
18
|
+
|
16
19
|
def initialize(app, env)
|
17
20
|
mess = 'vagrant_libvirt::action::create_networks'
|
18
21
|
@logger = Log4r::Logger.new(mess)
|
@@ -25,60 +28,98 @@ module VagrantPlugins
|
|
25
28
|
|
26
29
|
def call(env)
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
management_network_name = env[:machine].provider_config.management_network_name
|
32
|
+
management_network_address = env[:machine].provider_config.management_network_address
|
33
|
+
@logger.info "Using #{management_network_name} at #{management_network_address} as the management network"
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
begin
|
36
|
+
management_network_ip = IPAddr.new(management_network_address)
|
37
|
+
rescue ArgumentError
|
38
|
+
raise Errors::ManagementNetworkError,
|
39
|
+
error_message: "#{management_network_address} is not a valid IP address"
|
40
|
+
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
# creating them via libvirt API, so this provider doesn't implement
|
40
|
-
# them.
|
41
|
-
next if type != :private_network
|
42
|
+
# capture address into $1 and mask into $2
|
43
|
+
management_network_ip.inspect =~ /IPv4:(.*)\/(.*)>/
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
if $2 == '255.255.255.255'
|
46
|
+
raise Errors::ManagementNetworkError,
|
47
|
+
error_message: "#{management_network_address} does not include both an address and subnet mask"
|
48
|
+
end
|
49
|
+
|
50
|
+
management_network_options = {
|
51
|
+
network_name: management_network_name,
|
52
|
+
ip: $1,
|
53
|
+
netmask: $2,
|
54
|
+
dhcp_enabled: true,
|
55
|
+
forward_mode: 'nat',
|
56
|
+
}
|
57
|
+
|
58
|
+
# add management network to list of networks to check
|
59
|
+
networks = [ management_network_options ]
|
60
|
+
|
61
|
+
env[:machine].config.vm.networks.each do |type, original_options|
|
62
|
+
# There are two other types public network and port forwarding,
|
63
|
+
# but there are problems with creating them via libvirt API,
|
64
|
+
# so this provider doesn't implement them.
|
65
|
+
next if type != :private_network
|
66
|
+
# Options can be specified in Vagrantfile in short format (:ip => ...),
|
67
|
+
# or provider format # (:libvirt__network_name => ...).
|
68
|
+
# https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/scoped_hash_override.rb
|
69
|
+
options = scoped_hash_override(original_options, :libvirt)
|
70
|
+
# use default values if not already set
|
71
|
+
options = {
|
48
72
|
netmask: '255.255.255.0',
|
49
73
|
dhcp_enabled: true,
|
50
74
|
forward_mode: 'nat',
|
51
|
-
}.merge(
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
}.merge(options)
|
76
|
+
# add to list of networks to check
|
77
|
+
networks.push(options)
|
78
|
+
end
|
79
|
+
|
80
|
+
# only one vm at a time should try to set up networks
|
81
|
+
# otherwise they'll have inconsitent views of current state
|
82
|
+
# and conduct redundant operations that cause errors
|
83
|
+
@@lock.synchronize do
|
84
|
+
# Iterate over networks If some network is not
|
85
|
+
# available, create it if possible. Otherwise raise an error.
|
86
|
+
networks.each do |options|
|
87
|
+
@logger.debug "Searching for network with options #{options}"
|
88
|
+
|
89
|
+
# should fix other methods so this doesn't have to be instance var
|
90
|
+
@options = options
|
91
|
+
|
92
|
+
# Get a list of all (active and inactive) libvirt networks. This
|
93
|
+
# list is used throughout this class and should be easier to
|
94
|
+
# process than libvirt API calls.
|
95
|
+
@available_networks = libvirt_networks(env[:libvirt_compute].client)
|
96
|
+
|
97
|
+
# Prepare a hash describing network for this specific interface.
|
98
|
+
@interface_network = {
|
99
|
+
name: nil,
|
100
|
+
ip_address: nil,
|
101
|
+
netmask: @options[:netmask],
|
102
|
+
network_address: nil,
|
103
|
+
bridge_name: nil,
|
104
|
+
created: false,
|
105
|
+
active: false,
|
106
|
+
autostart: false,
|
107
|
+
libvirt_network: nil,
|
108
|
+
}
|
109
|
+
|
110
|
+
if @options[:ip]
|
111
|
+
handle_ip_option(env)
|
112
|
+
# in vagrant 1.2.3 and later it is not possible to take this branch
|
113
|
+
# because cannot have name without ip
|
114
|
+
# https://github.com/mitchellh/vagrant/commit/cf2f6da4dbcb4f57c9cdb3b94dcd0bba62c5f5fd
|
115
|
+
elsif @options[:network_name]
|
116
|
+
handle_network_name_option
|
76
117
|
end
|
77
|
-
end
|
78
118
|
|
79
|
-
|
80
|
-
|
119
|
+
autostart_network if !@interface_network[:autostart]
|
120
|
+
activate_network if !@interface_network[:active]
|
81
121
|
end
|
122
|
+
end
|
82
123
|
|
83
124
|
@app.call(env)
|
84
125
|
end
|
@@ -87,6 +128,7 @@ module VagrantPlugins
|
|
87
128
|
|
88
129
|
# Return hash of network for specified name, or nil if not found.
|
89
130
|
def lookup_network_by_name(network_name)
|
131
|
+
@logger.debug "looking up network named #{network_name}"
|
90
132
|
@available_networks.each do |network|
|
91
133
|
return network if network[:name] == network_name
|
92
134
|
end
|
@@ -95,12 +137,25 @@ module VagrantPlugins
|
|
95
137
|
|
96
138
|
# Return hash of network for specified bridge, or nil if not found.
|
97
139
|
def lookup_bridge_by_name(bridge_name)
|
140
|
+
@logger.debug "looking up bridge named #{bridge_name}"
|
98
141
|
@available_networks.each do |network|
|
99
142
|
return network if network[:bridge_name] == bridge_name
|
100
143
|
end
|
101
144
|
nil
|
102
145
|
end
|
103
146
|
|
147
|
+
# Throw an error if dhcp setting for an existing network does not
|
148
|
+
# match what was configured in the vagrantfile
|
149
|
+
# since we always enable dhcp for the management network
|
150
|
+
# this ensures we wont start a vm vagrant cant reach
|
151
|
+
def verify_dhcp
|
152
|
+
unless @options[:dhcp_enabled] == @interface_network[:dhcp_enabled]
|
153
|
+
raise Errors::DHCPMismatch,
|
154
|
+
network_name: @interface_network[:name],
|
155
|
+
requested: @options[:dhcp_enabled] ? 'enabled' : 'disabled'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
104
159
|
# Handle only situations, when ip is specified. Variables @options and
|
105
160
|
# @available_networks should be filled before calling this function.
|
106
161
|
def handle_ip_option(env)
|
@@ -120,11 +175,18 @@ module VagrantPlugins
|
|
120
175
|
if available_network[:network_address] == \
|
121
176
|
@interface_network[:network_address]
|
122
177
|
@interface_network = available_network
|
178
|
+
@logger.debug "found existing network by ip, values are"
|
179
|
+
@logger.debug @interface_network
|
123
180
|
break
|
124
181
|
end
|
125
182
|
end
|
126
183
|
|
184
|
+
if @interface_network[:created]
|
185
|
+
verify_dhcp
|
186
|
+
end
|
187
|
+
|
127
188
|
if @options[:network_name]
|
189
|
+
@logger.debug "Checking that network name does not clash with ip"
|
128
190
|
if @interface_network[:created]
|
129
191
|
# Just check for mismatch error here - if name and ip from
|
130
192
|
# config match together.
|
@@ -156,6 +218,7 @@ module VagrantPlugins
|
|
156
218
|
# Is name for new network set? If not, generate a unique one.
|
157
219
|
count = 0
|
158
220
|
while @interface_network[:name].nil?
|
221
|
+
@logger.debug "generating name for network"
|
159
222
|
|
160
223
|
# Generate a network name.
|
161
224
|
network_name = env[:root_path].basename.to_s.dup
|
@@ -171,6 +234,7 @@ module VagrantPlugins
|
|
171
234
|
# Generate a unique name for network bridge.
|
172
235
|
count = 0
|
173
236
|
while @interface_network[:bridge_name].nil?
|
237
|
+
@logger.debug "generating name for bridge"
|
174
238
|
bridge_name = 'virbr'
|
175
239
|
bridge_name << count.to_s
|
176
240
|
count += 1
|
@@ -195,6 +259,8 @@ module VagrantPlugins
|
|
195
259
|
if !@interface_network
|
196
260
|
raise Errors::NetworkNotAvailableError,
|
197
261
|
network_name: @options[:network_name]
|
262
|
+
else
|
263
|
+
verify_dhcp
|
198
264
|
end
|
199
265
|
end
|
200
266
|
|
@@ -234,6 +300,7 @@ module VagrantPlugins
|
|
234
300
|
begin
|
235
301
|
@interface_network[:libvirt_network] = \
|
236
302
|
@libvirt_client.define_network_xml(to_xml('private_network'))
|
303
|
+
@logger.debug "created network"
|
237
304
|
rescue => e
|
238
305
|
raise Errors::CreateNetworkError, error_message: e.message
|
239
306
|
end
|
@@ -18,7 +18,7 @@ module VagrantPlugins
|
|
18
18
|
# data directory, created_networks file holds UUIDs of each network.
|
19
19
|
created_networks_file = env[:machine].data_dir + 'created_networks'
|
20
20
|
|
21
|
-
@logger.info '
|
21
|
+
@logger.info 'Checking if any networks were created'
|
22
22
|
# If created_networks file doesn't exist, there are no networks we
|
23
23
|
# need to remove.
|
24
24
|
unless File.exist?(created_networks_file)
|
@@ -26,39 +26,45 @@ module VagrantPlugins
|
|
26
26
|
return @app.call(env)
|
27
27
|
end
|
28
28
|
|
29
|
-
@logger.info '
|
29
|
+
@logger.info 'File with created networks exists'
|
30
30
|
|
31
31
|
# Iterate over each created network UUID and try to remove it.
|
32
32
|
created_networks = []
|
33
33
|
file = File.open(created_networks_file, 'r')
|
34
34
|
file.readlines.each do |network_uuid|
|
35
|
-
@logger.info network_uuid
|
35
|
+
@logger.info "Checking for #{network_uuid}"
|
36
|
+
# lookup_network_by_uuid throws same exception
|
37
|
+
# if there is an error or if the network just doesn't exist
|
36
38
|
begin
|
37
39
|
libvirt_network = env[:libvirt_compute].client.lookup_network_by_uuid(
|
38
40
|
network_uuid)
|
39
|
-
rescue
|
40
|
-
|
41
|
-
|
41
|
+
rescue Libvirt::RetrieveError => e
|
42
|
+
# this network is already destroyed, so move on
|
43
|
+
if e.message =~ /Network not found/
|
44
|
+
@logger.info "It is already undefined"
|
45
|
+
next
|
46
|
+
# some other error occured, so raise it again
|
47
|
+
else
|
48
|
+
raise e
|
49
|
+
end
|
42
50
|
end
|
43
51
|
|
44
|
-
# Maybe network doesn't exist anymore.
|
45
|
-
next unless libvirt_network
|
46
|
-
|
47
52
|
# Skip removing if network has still active connections.
|
48
53
|
xml = Nokogiri::XML(libvirt_network.xml_desc)
|
49
54
|
connections = xml.xpath('/network/@connections').first
|
50
|
-
@logger.info connections
|
51
55
|
if connections != nil
|
56
|
+
@logger.info "Still has connections so will not undefine"
|
52
57
|
created_networks << network_uuid
|
53
58
|
next
|
54
59
|
end
|
55
60
|
|
56
|
-
# Shutdown network first.
|
57
|
-
libvirt_network.destroy
|
58
61
|
|
62
|
+
# Shutdown network first.
|
59
63
|
# Undefine network.
|
60
64
|
begin
|
65
|
+
libvirt_network.destroy
|
61
66
|
libvirt_network.undefine
|
67
|
+
@logger.info "Undefined it"
|
62
68
|
rescue => e
|
63
69
|
raise Error::DestroyNetworkError,
|
64
70
|
network_name: libvirt_network.name,
|
@@ -68,13 +74,16 @@ module VagrantPlugins
|
|
68
74
|
file.close
|
69
75
|
|
70
76
|
# Update status of created networks after removing some/all of them.
|
77
|
+
# Not sure why we are doing this, something else seems to always delete the file
|
71
78
|
if created_networks.length > 0
|
72
79
|
File.open(created_networks_file, 'w') do |file|
|
80
|
+
@logger.info "Writing new created_networks file"
|
73
81
|
created_networks.each do |network_uuid|
|
74
82
|
file.puts network_uuid
|
75
83
|
end
|
76
84
|
end
|
77
85
|
else
|
86
|
+
@logger.info "Deleting created_networks file"
|
78
87
|
File.delete(created_networks_file)
|
79
88
|
end
|
80
89
|
|
@@ -5,21 +5,31 @@ module VagrantPlugins
|
|
5
5
|
# Setup name for domain and domain volumes.
|
6
6
|
class SetNameOfDomain
|
7
7
|
def initialize(app, env)
|
8
|
+
@logger = Log4r::Logger.new("vagrant_libvirt::action::set_name_of_domain")
|
8
9
|
@app = app
|
9
10
|
end
|
10
11
|
|
11
12
|
def call(env)
|
12
13
|
require 'securerandom'
|
13
|
-
|
14
|
+
config = env[:machine].provider_config
|
15
|
+
if config.default_prefix.nil?
|
16
|
+
env[:domain_name] = env[:root_path].basename.to_s.dup
|
17
|
+
else
|
18
|
+
env[:domain_name] = config.default_prefix.to_s
|
19
|
+
end
|
14
20
|
env[:domain_name].gsub!(/[^-a-z0-9_]/i, '')
|
15
21
|
env[:domain_name] << '_'
|
16
22
|
env[:domain_name] << env[:machine].name.to_s
|
17
|
-
|
23
|
+
|
24
|
+
@logger.info("Looking for domain #{env[:domain_name]} through list #{env[:libvirt_compute].servers.all}")
|
18
25
|
# Check if the domain name is not already taken
|
19
26
|
domain = ProviderLibvirt::Util::Collection.find_matching(
|
20
27
|
env[:libvirt_compute].servers.all, env[:domain_name])
|
28
|
+
|
29
|
+
@logger.info("Looking for domain #{env[:domain_name]}")
|
30
|
+
|
21
31
|
if domain != nil
|
22
|
-
raise
|
32
|
+
raise ProviderLibvirt::Errors::DomainNameExists,
|
23
33
|
:domain_name => env[:domain_name]
|
24
34
|
end
|
25
35
|
|
@@ -27,7 +27,7 @@ module VagrantPlugins
|
|
27
27
|
# avoid creating an additional directory with rsync
|
28
28
|
hostpath = "#{hostpath}/" if hostpath !~ /\/$/
|
29
29
|
|
30
|
-
env[:ui].info(I18n.t(
|
30
|
+
env[:ui].info(I18n.t('vagrant_libvirt.rsync_folder',
|
31
31
|
:hostpath => hostpath,
|
32
32
|
:guestpath => guestpath))
|
33
33
|
|
@@ -38,9 +38,9 @@ module VagrantPlugins
|
|
38
38
|
|
39
39
|
# Rsync over to the guest path using the SSH info
|
40
40
|
command = [
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
'rsync', '--del', '--verbose', '--archive', '-z',
|
42
|
+
'--exclude', '.vagrant/',
|
43
|
+
'-e', "ssh -p #{ssh_info[:port]} #{proxycommand} -o StrictHostKeyChecking=no #{ssh_key_options(ssh_info)}",
|
44
44
|
hostpath,
|
45
45
|
"#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
|
46
46
|
|
@@ -53,6 +53,12 @@ module VagrantPlugins
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
56
|
+
private
|
57
|
+
|
58
|
+
def ssh_key_options(ssh_info)
|
59
|
+
# Ensure that `private_key_path` is an Array (for Vagrant < 1.4)
|
60
|
+
Array(ssh_info[:private_key_path]).map { |path| "-i '#{path}' " }.join
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|
@@ -26,13 +26,22 @@ module VagrantPlugins
|
|
26
26
|
attr_accessor :storage_pool_name
|
27
27
|
|
28
28
|
# Libvirt default network
|
29
|
-
attr_accessor :
|
29
|
+
attr_accessor :management_network_name
|
30
|
+
attr_accessor :management_network_address
|
31
|
+
|
32
|
+
# Default host prefix (alternative to use project folder name)
|
33
|
+
attr_accessor :default_prefix
|
30
34
|
|
31
35
|
# Domain specific settings used while creating new domain.
|
32
36
|
attr_accessor :memory
|
33
37
|
attr_accessor :cpus
|
38
|
+
attr_accessor :cpu_mode
|
39
|
+
attr_accessor :disk_bus
|
34
40
|
attr_accessor :nested
|
35
41
|
attr_accessor :volume_cache
|
42
|
+
attr_accessor :kernel
|
43
|
+
attr_accessor :cmd_line
|
44
|
+
attr_accessor :initrd
|
36
45
|
|
37
46
|
def initialize
|
38
47
|
@driver = UNSET_VALUE
|
@@ -42,13 +51,19 @@ module VagrantPlugins
|
|
42
51
|
@password = UNSET_VALUE
|
43
52
|
@id_ssh_key_file = UNSET_VALUE
|
44
53
|
@storage_pool_name = UNSET_VALUE
|
45
|
-
@
|
54
|
+
@management_network_name = UNSET_VALUE
|
55
|
+
@management_network_address = UNSET_VALUE
|
46
56
|
|
47
57
|
# Domain specific settings.
|
48
58
|
@memory = UNSET_VALUE
|
49
59
|
@cpus = UNSET_VALUE
|
60
|
+
@cpu_mode = UNSET_VALUE
|
61
|
+
@disk_bus = UNSET_VALUE
|
50
62
|
@nested = UNSET_VALUE
|
51
63
|
@volume_cache = UNSET_VALUE
|
64
|
+
@kernel = UNSET_VALUE
|
65
|
+
@initrd = UNSET_VALUE
|
66
|
+
@cmd_line = UNSET_VALUE
|
52
67
|
end
|
53
68
|
|
54
69
|
def finalize!
|
@@ -59,13 +74,19 @@ module VagrantPlugins
|
|
59
74
|
@password = nil if @password == UNSET_VALUE
|
60
75
|
@id_ssh_key_file = 'id_rsa' if @id_ssh_key_file == UNSET_VALUE
|
61
76
|
@storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
|
62
|
-
@
|
77
|
+
@management_network_name = 'vagrant-libvirt' if @management_network_name == UNSET_VALUE
|
78
|
+
@management_network_address = '192.168.121.0/24' if @management_network_address == UNSET_VALUE
|
63
79
|
|
64
80
|
# Domain specific settings.
|
65
81
|
@memory = 512 if @memory == UNSET_VALUE
|
66
82
|
@cpus = 1 if @cpus == UNSET_VALUE
|
83
|
+
@cpu_mode = 'host-model' if @cpu_mode == UNSET_VALUE
|
84
|
+
@disk_bus = 'virtio' if @disk_bus == UNSET_VALUE
|
67
85
|
@nested = false if @nested == UNSET_VALUE
|
68
86
|
@volume_cache = 'default' if @volume_cache == UNSET_VALUE
|
87
|
+
@kernel = nil if @kernel == UNSET_VALUE
|
88
|
+
@cmd_line = '' if @cmd_line == UNSET_VALUE
|
89
|
+
@initrd = '' if @initrd == UNSET_VALUE
|
69
90
|
end
|
70
91
|
|
71
92
|
def validate(machine)
|
@@ -68,16 +68,19 @@ module VagrantPlugins
|
|
68
68
|
error_key(:fog_create_server_error)
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
error_key(:interface_slot_not_available)
|
71
|
+
# Network exceptions
|
72
|
+
class ManagementNetworkError < VagrantLibvirtError
|
73
|
+
error_key(:management_network_error)
|
75
74
|
end
|
76
75
|
|
77
76
|
class NetworkNameAndAddressMismatch < VagrantLibvirtError
|
78
77
|
error_key(:network_name_and_address_mismatch)
|
79
78
|
end
|
80
79
|
|
80
|
+
class DHCPMismatch < VagrantLibvirtError
|
81
|
+
error_key(:dhcp_mismatch)
|
82
|
+
end
|
83
|
+
|
81
84
|
class CreateNetworkError < VagrantLibvirtError
|
82
85
|
error_key(:create_network_error)
|
83
86
|
end
|
@@ -98,12 +101,17 @@ module VagrantPlugins
|
|
98
101
|
error_key(:activate_network_error)
|
99
102
|
end
|
100
103
|
|
104
|
+
# Other exceptions
|
105
|
+
class InterfaceSlotNotAvailable < VagrantLibvirtError
|
106
|
+
error_key(:interface_slot_not_available)
|
107
|
+
end
|
108
|
+
|
101
109
|
class RsyncError < VagrantLibvirtError
|
102
110
|
error_key(:rsync_error)
|
103
111
|
end
|
104
112
|
|
105
113
|
class DomainNameExists < VagrantLibvirtError
|
106
|
-
error_key(:
|
114
|
+
error_key(:domain_name_exists)
|
107
115
|
end
|
108
116
|
|
109
117
|
class NoDomainError < VagrantLibvirtError
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<vcpu><%= @cpus %></vcpu>
|
5
5
|
|
6
6
|
<% if @nested %>
|
7
|
-
<cpu mode='
|
7
|
+
<cpu mode='<%= @cpu_mode %>'>
|
8
8
|
<model fallback='allow'>qemu64</model>
|
9
9
|
<feature policy='optional' name='vmx'/>
|
10
10
|
<feature policy='optional' name='svm'/>
|
@@ -14,6 +14,9 @@
|
|
14
14
|
<os>
|
15
15
|
<type arch='x86_64'>hvm</type>
|
16
16
|
<boot dev='hd'/>
|
17
|
+
<kernel><%= @kernel %></kernel>
|
18
|
+
<initrd><%= @initrd %></initrd>
|
19
|
+
<cmdline><%= @cmd_line %></cmdline>
|
17
20
|
</os>
|
18
21
|
<features>
|
19
22
|
<acpi/>
|
@@ -26,7 +29,7 @@
|
|
26
29
|
<driver name='qemu' type='qcow2' cache='<%= @domain_volume_cache %>'/>
|
27
30
|
<source file='<%= @domain_volume_path %>'/>
|
28
31
|
<%# we need to ensure a unique target dev -%>
|
29
|
-
<target dev='vda' bus='
|
32
|
+
<target dev='vda' bus='<%= @disk_bus %>'/>
|
30
33
|
</disk>
|
31
34
|
<serial type='pty'>
|
32
35
|
<target port='0'/>
|
@@ -27,6 +27,12 @@ module VagrantPlugins
|
|
27
27
|
netmask = xml.xpath('/network/ip/@netmask').first
|
28
28
|
netmask = netmask.value if netmask
|
29
29
|
|
30
|
+
if xml.at_xpath('//network/ip/dhcp')
|
31
|
+
dhcp_enabled = true
|
32
|
+
else
|
33
|
+
dhcp_enabled = false
|
34
|
+
end
|
35
|
+
|
30
36
|
# Calculate network address of network from ip address and
|
31
37
|
# netmask.
|
32
38
|
if ip && netmask
|
@@ -40,6 +46,7 @@ module VagrantPlugins
|
|
40
46
|
ip_address: ip,
|
41
47
|
netmask: netmask,
|
42
48
|
network_address: network_address,
|
49
|
+
dhcp_enabled: dhcp_enabled,
|
43
50
|
bridge_name: libvirt_network.bridge_name,
|
44
51
|
created: true,
|
45
52
|
active: libvirt_network.active?,
|
data/locales/en.yml
CHANGED
@@ -89,7 +89,7 @@ en:
|
|
89
89
|
fog_create_server_error: |-
|
90
90
|
Error while creating domain: %{error_message}
|
91
91
|
domain_name_exists: |-
|
92
|
-
Name of domain about to create is already taken. Please try to run
|
92
|
+
Name `%{domain_name}` of domain about to create is already taken. Please try to run
|
93
93
|
`vagrant up` command again.
|
94
94
|
creating_storage_pool_error: |-
|
95
95
|
There was error while creating libvirt storage pool: %{error_message}
|
@@ -101,9 +101,14 @@ en:
|
|
101
101
|
Error while attaching new device to domain. %{error_message}
|
102
102
|
no_ip_address_error: |-
|
103
103
|
No IP address found.
|
104
|
+
management_network_error: |-
|
105
|
+
Error in specification of management network: %{error_message}.
|
104
106
|
network_name_and_address_mismatch: |-
|
105
107
|
Address %{ip_address} does not match with network name %{network_name}.
|
106
108
|
Please fix your configuration and run vagrant again.
|
109
|
+
dhcp_mismatch: |-
|
110
|
+
Network %{network_name} exists but does not have dhcp %{requested}.
|
111
|
+
Please fix your configuration and run vagrant again.
|
107
112
|
create_network_error: |-
|
108
113
|
Error occured while creating new network: %{error_message}.
|
109
114
|
network_not_available_error: |-
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vagrant-libvirt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.14
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Lukas Stanek
|
@@ -10,12 +9,11 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2014-01-21 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: fog
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - '='
|
21
19
|
- !ruby/object:Gem::Version
|
@@ -23,7 +21,6 @@ dependencies:
|
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
25
|
- - '='
|
29
26
|
- !ruby/object:Gem::Version
|
@@ -31,23 +28,20 @@ dependencies:
|
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: ruby-libvirt
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- - ~>
|
32
|
+
- - "~>"
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: 0.4.0
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- - ~>
|
39
|
+
- - "~>"
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: 0.4.0
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: nokogiri
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
46
|
- - '='
|
53
47
|
- !ruby/object:Gem::Version
|
@@ -55,7 +49,6 @@ dependencies:
|
|
55
49
|
type: :runtime
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
53
|
- - '='
|
61
54
|
- !ruby/object:Gem::Version
|
@@ -63,17 +56,15 @@ dependencies:
|
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: rake
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
|
-
- -
|
60
|
+
- - ">="
|
69
61
|
- !ruby/object:Gem::Version
|
70
62
|
version: '0'
|
71
63
|
type: :development
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
|
-
- -
|
67
|
+
- - ">="
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: '0'
|
79
70
|
description: Vagrant provider for libvirt.
|
@@ -84,7 +75,7 @@ executables: []
|
|
84
75
|
extensions: []
|
85
76
|
extra_rdoc_files: []
|
86
77
|
files:
|
87
|
-
- .gitignore
|
78
|
+
- ".gitignore"
|
88
79
|
- CHANGELOG.md
|
89
80
|
- Gemfile
|
90
81
|
- LICENSE
|
@@ -146,27 +137,25 @@ files:
|
|
146
137
|
homepage: https://github.com/pradels/vagrant-libvirt
|
147
138
|
licenses:
|
148
139
|
- MIT
|
140
|
+
metadata: {}
|
149
141
|
post_install_message:
|
150
142
|
rdoc_options: []
|
151
143
|
require_paths:
|
152
144
|
- lib
|
153
145
|
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
-
none: false
|
155
146
|
requirements:
|
156
|
-
- -
|
147
|
+
- - ">="
|
157
148
|
- !ruby/object:Gem::Version
|
158
149
|
version: '0'
|
159
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
|
-
none: false
|
161
151
|
requirements:
|
162
|
-
- -
|
152
|
+
- - ">="
|
163
153
|
- !ruby/object:Gem::Version
|
164
154
|
version: '0'
|
165
155
|
requirements: []
|
166
156
|
rubyforge_project:
|
167
|
-
rubygems_version:
|
157
|
+
rubygems_version: 2.0.14
|
168
158
|
signing_key:
|
169
|
-
specification_version:
|
159
|
+
specification_version: 4
|
170
160
|
summary: Vagrant provider for libvirt.
|
171
161
|
test_files: []
|
172
|
-
has_rdoc:
|