vagrant-kvm 0.1.0

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 (52) hide show
  1. data/.gitignore +14 -0
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE +8 -0
  5. data/README.md +90 -0
  6. data/Rakefile +15 -0
  7. data/example_box/README.md +18 -0
  8. data/example_box/box.xml +78 -0
  9. data/example_box/metadata.json +3 -0
  10. data/lib/vagrant-kvm.rb +20 -0
  11. data/lib/vagrant-kvm/action.rb +268 -0
  12. data/lib/vagrant-kvm/action/boot.rb +22 -0
  13. data/lib/vagrant-kvm/action/check_box.rb +36 -0
  14. data/lib/vagrant-kvm/action/check_created.rb +21 -0
  15. data/lib/vagrant-kvm/action/check_kvm.rb +23 -0
  16. data/lib/vagrant-kvm/action/check_running.rb +21 -0
  17. data/lib/vagrant-kvm/action/created.rb +20 -0
  18. data/lib/vagrant-kvm/action/destroy.rb +19 -0
  19. data/lib/vagrant-kvm/action/destroy_confirm.rb +17 -0
  20. data/lib/vagrant-kvm/action/export.rb +57 -0
  21. data/lib/vagrant-kvm/action/forced_halt.rb +21 -0
  22. data/lib/vagrant-kvm/action/import.rb +54 -0
  23. data/lib/vagrant-kvm/action/init_storage_pool.rb +19 -0
  24. data/lib/vagrant-kvm/action/is_paused.rb +20 -0
  25. data/lib/vagrant-kvm/action/is_running.rb +20 -0
  26. data/lib/vagrant-kvm/action/is_saved.rb +20 -0
  27. data/lib/vagrant-kvm/action/match_mac_address.rb +21 -0
  28. data/lib/vagrant-kvm/action/message_not_created.rb +16 -0
  29. data/lib/vagrant-kvm/action/message_will_not_destroy.rb +17 -0
  30. data/lib/vagrant-kvm/action/network.rb +69 -0
  31. data/lib/vagrant-kvm/action/package.rb +20 -0
  32. data/lib/vagrant-kvm/action/package_vagrantfile.rb +31 -0
  33. data/lib/vagrant-kvm/action/prepare_nfs_settings.rb +61 -0
  34. data/lib/vagrant-kvm/action/prune_nfs_exports.rb +20 -0
  35. data/lib/vagrant-kvm/action/resume.rb +25 -0
  36. data/lib/vagrant-kvm/action/setup_package_files.rb +51 -0
  37. data/lib/vagrant-kvm/action/share_folders.rb +76 -0
  38. data/lib/vagrant-kvm/action/suspend.rb +20 -0
  39. data/lib/vagrant-kvm/config.rb +31 -0
  40. data/lib/vagrant-kvm/driver/driver.rb +271 -0
  41. data/lib/vagrant-kvm/errors.rb +11 -0
  42. data/lib/vagrant-kvm/plugin.rb +73 -0
  43. data/lib/vagrant-kvm/provider.rb +104 -0
  44. data/lib/vagrant-kvm/util.rb +12 -0
  45. data/lib/vagrant-kvm/util/kvm_template_renderer.rb +20 -0
  46. data/lib/vagrant-kvm/util/network_definition.rb +106 -0
  47. data/lib/vagrant-kvm/util/vm_definition.rb +192 -0
  48. data/lib/vagrant-kvm/version.rb +5 -0
  49. data/locales/en.yml +4 -0
  50. data/templates/libvirt_domain.erb +64 -0
  51. data/vagrant-kvm.gemspec +58 -0
  52. metadata +191 -0
@@ -0,0 +1,11 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module ProviderKvm
5
+ module Errors
6
+ class VagrantKVMError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_kvm.errors")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The Vagrant KVM plugin must be run within Vagrant."
5
+ end
6
+
7
+ # This is a sanity check to make sure no one is attempting to install
8
+ # this into an early Vagrant version.
9
+ if Vagrant::VERSION < "1.1.0"
10
+ raise "The Vagrant KVM plugin is only compatible with Vagrant 1.1+"
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module ProviderKvm
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "KVM provider"
17
+ description <<-EOF
18
+ The KVM provider allows Vagrant to manage and control
19
+ QEMU/KVM virtual machines with libvirt.
20
+ EOF
21
+
22
+ config(:kvm, :provider) do
23
+ require_relative "config"
24
+ Config
25
+ end
26
+
27
+ provider(:kvm) do
28
+ # Setup logging and i18n
29
+ setup_logging
30
+ setup_i18n
31
+
32
+ # Return the provider
33
+ require_relative "provider"
34
+ Provider
35
+ end
36
+
37
+ # This initializes the internationalization strings.
38
+ def self.setup_i18n
39
+ I18n.load_path << File.expand_path("locales/en.yml", ProviderKvm.source_root)
40
+ I18n.reload!
41
+ end
42
+
43
+ # This sets up our log level to be whatever VAGRANT_LOG is.
44
+ def self.setup_logging
45
+ require "log4r"
46
+
47
+ level = nil
48
+ begin
49
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
50
+ rescue NameError
51
+ # This means that the logging constant wasn't found,
52
+ # which is fine. We just keep `level` as `nil`. But
53
+ # we tell the user.
54
+ level = nil
55
+ end
56
+
57
+ # Some constants, such as "true" resolve to booleans, so the
58
+ # above error checking doesn't catch it. This will check to make
59
+ # sure that the log level is an integer, as Log4r requires.
60
+ level = nil if !level.is_a?(Integer)
61
+
62
+ # Set the logging level on all "vagrant" namespaced
63
+ # logs as long as we have a valid level.
64
+ if level
65
+ logger = Log4r::Logger.new("vagrant_kvm")
66
+ logger.outputters = Log4r::Outputter.stderr
67
+ logger.level = level
68
+ logger = nil
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,104 @@
1
+ require "log4r"
2
+ require "vagrant"
3
+
4
+ module VagrantPlugins
5
+ module ProviderKvm
6
+ class Provider < Vagrant.plugin("2", :provider)
7
+ attr_reader :driver
8
+
9
+ def initialize(machine)
10
+ @logger = Log4r::Logger.new("vagrant_kvm")
11
+ @machine = machine
12
+
13
+ # This method will load in our driver, so we call it now to
14
+ # initialize it.
15
+ machine_id_changed
16
+ end
17
+
18
+ def action(name)
19
+ # Attempt to get the action method from the Action class if it
20
+ # exists, otherwise return nil to show that we don't support the
21
+ # given action.
22
+ action_method = "action_#{name}"
23
+ return Action.send(action_method) if Action.respond_to?(action_method)
24
+ nil
25
+ end
26
+
27
+ # If the machine ID changed, then we need to rebuild our underlying
28
+ # driver.
29
+ def machine_id_changed
30
+ id = @machine.id
31
+
32
+ begin
33
+ @logger.debug("Instantiating the driver for machine ID: #{@machine.id.inspect}")
34
+ @driver = Driver::Driver.new(id)
35
+ rescue Driver::Driver::VMNotFound
36
+ # The virtual machine doesn't exist, so we probably have a stale
37
+ # ID. Just clear the id out of the machine and reload it.
38
+ @logger.debug("VM not found! Clearing saved machine ID and reloading.")
39
+ id = nil
40
+ retry
41
+ end
42
+ end
43
+
44
+ # Returns the SSH info for accessing the VM.
45
+ def ssh_info
46
+ # If the VM is not created then we cannot possibly SSH into it, so
47
+ # we return nil.
48
+ return nil if state == :not_created
49
+
50
+ #ip_addr = @driver.read_ip(@machine.config.vm.base_mac)
51
+ return {
52
+ :host => read_machine_ip,
53
+ :port => "22" # XXX should be somewhere in default config
54
+ }
55
+ end
56
+
57
+ # XXX duplicated from prepare_nfs_settings
58
+ # Returns the IP address of the guest by looking at the first
59
+ # enabled host only network.
60
+ #
61
+ # @return [String]
62
+ def read_machine_ip
63
+ @machine.config.vm.networks.each do |type, options|
64
+ if type == :private_network && options[:ip].is_a?(String)
65
+ return options[:ip]
66
+ end
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ # Return the state of the VM
73
+ #
74
+ # @return [Symbol]
75
+ def state
76
+ # XXX: What happens if we destroy the VM but the UUID is still
77
+ # set here?
78
+
79
+ # Determine the ID of the state here.
80
+ state_id = nil
81
+ state_id = :not_created if !@driver.uuid
82
+ state_id = @driver.read_state if !state_id
83
+ state_id = :unknown if !state_id
84
+
85
+ # TODO Translate into short/long descriptions
86
+ short = state_id
87
+ long = I18n.t("vagrant.commands.status.#{state_id}")
88
+
89
+ # Return the state
90
+ Vagrant::MachineState.new(state_id, short, long)
91
+ end
92
+
93
+ # Returns a human-friendly string version of this provider which
94
+ # includes the machine's ID that this provider represents, if it
95
+ # has one.
96
+ #
97
+ # @return [String]
98
+ def to_s
99
+ id = @machine.id ? @machine.id : "new VM"
100
+ "QEMU/KVM (#{id})"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,12 @@
1
+ require "pathname"
2
+
3
+ module VagrantPlugins
4
+ module ProviderKvm
5
+ module Util
6
+ util_root = Pathname.new(File.expand_path("../util", __FILE__))
7
+ autoload :VmDefinition, util_root.join("vm_definition")
8
+ autoload :NetworkDefinition, util_root.join("network_definition")
9
+ autoload :KvmTemplateRenderer, util_root.join("kvm_template_renderer")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ require 'vagrant/util/template_renderer'
2
+
3
+ module VagrantPlugins
4
+ module ProviderKvm
5
+ module Util
6
+ # For TemplateRenderer
7
+ include Vagrant::Util
8
+ class KvmTemplateRenderer < TemplateRenderer
9
+
10
+ # Returns the full path to the template, taking into accoun the gem directory
11
+ # and adding the `.erb` extension to the end.
12
+ #
13
+ # @return [String]
14
+ def full_template_path
15
+ ProviderKvm.source_root.join('templates', "#{template}.erb").to_s.squeeze("/")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,106 @@
1
+ # Utility class to manage libvirt network definition
2
+ require "nokogiri"
3
+
4
+ module VagrantPlugins
5
+ module ProviderKvm
6
+ module Util
7
+ class NetworkDefinition
8
+ # Attributes of the Network
9
+ attr_reader :name
10
+ attr_reader :domain_name
11
+ attr_reader :base_ip
12
+
13
+ def initialize(name, definition=nil)
14
+ @name = name
15
+ if definition
16
+ doc = Nokogiri::XML(definition)
17
+ @forward = doc.at_css("network forward")["mode"] if doc.at_css("network forward")
18
+ @domain_name = doc.at_css("network domain")["name"] if doc.at_css("network domain")
19
+ @base_ip = doc.at_css("network ip")["address"]
20
+ @netmask = doc.at_css("network ip")["netmask"]
21
+ @range = {
22
+ :start => doc.at_css("network ip dhcp range")["start"],
23
+ :end => doc.at_css("network ip dhcp range")["end"]
24
+ }
25
+ @hosts = []
26
+ doc.css("network ip dhcp host").each do |host|
27
+ @hosts << {
28
+ :mac => host["mac"],
29
+ :name => host["name"],
30
+ :ip => host["ip"]
31
+ }
32
+ end
33
+ else
34
+ # create with defaults
35
+ # XXX defaults should move to config
36
+ @forward = "nat"
37
+ @domain_name = "vagrant.local"
38
+ @base_ip = "192.168.192.1"
39
+ @netmask = "255.255.255.0"
40
+ @range = {
41
+ :start => "192.168.192.100",
42
+ :end => "192.168.192.200"}
43
+ @hosts = []
44
+ end
45
+ end
46
+
47
+ def configure(config)
48
+ config = {
49
+ :forward => @forward,
50
+ :domain_name => @domain_name,
51
+ :base_ip => @base_ip,
52
+ :netmask => @netmask,
53
+ :range => @range,
54
+ :hosts => @hosts}.merge(config)
55
+
56
+ @forward = config[:forward]
57
+ @domain_name = config[:domain_name]
58
+ @base_ip = config[:base_ip]
59
+ @netmask = config[:netmask]
60
+ @range = config[:range]
61
+ @hosts = config[:hosts]
62
+ end
63
+
64
+ def as_xml
65
+ xml = <<-EOXML
66
+ <network>
67
+ <name>#{@name}</name>
68
+ <forward mode='#{@forward}'/>
69
+ <domain name='#{@domain_name}'/>
70
+ <ip address='#{@base_ip}' netmask='#{@netmask}'>
71
+ <dhcp>
72
+ <range start='#{@range[:start]}' end='#{@range[:end]}' />
73
+ </dhcp>
74
+ </ip>
75
+ </network>
76
+ EOXML
77
+ xml = inject_hosts(xml) if @hosts.length > 0
78
+ xml
79
+ end
80
+
81
+ def add_host(host)
82
+ cur_host = @hosts.detect {|h| h[:mac] == host[:mac]}
83
+ if cur_host
84
+ cur_host[:ip] = host[:ip]
85
+ cur_host[:name] = host[:name]
86
+ else
87
+ @hosts << {
88
+ :mac => host[:mac],
89
+ :name => host[:name],
90
+ :ip => host[:ip]}
91
+ end
92
+ end
93
+
94
+ def inject_hosts(xml)
95
+ doc = Nokogiri::XML(xml)
96
+ entry_point = doc.at_css("network ip dhcp range")
97
+ @hosts.each do |host|
98
+ entry_point.add_next_sibling "<host mac='#{host[:mac]}' name='#{host[:name]}' ip='#{host[:ip]}' />"
99
+ end
100
+ doc.to_xml
101
+ end
102
+
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,192 @@
1
+ # Utility class to translate ovf definition to libvirt XML
2
+ # and manage XML formatting for libvirt interaction
3
+ # Not a full OVF converter, only the minimal needed definition
4
+ require "nokogiri"
5
+
6
+ module VagrantPlugins
7
+ module ProviderKvm
8
+ module Util
9
+ class VmDefinition
10
+ # Attributes of the VM
11
+ attr_reader :name
12
+ attr_reader :cpus
13
+ attr_accessor :disk
14
+ attr_reader :mac
15
+ attr_reader :arch
16
+ attr_reader :network
17
+
18
+ def self.list_interfaces(definition)
19
+ nics = {}
20
+ ifcount = 0
21
+ doc = Nokogiri::XML(definition)
22
+ # look for user mode interfaces
23
+ doc.css("devices interface[type='user']").each do |item|
24
+ ifcount += 1
25
+ adapter = ifcount
26
+ nics[adapter] ||= {}
27
+ nics[adapter][:type] = :user
28
+ end
29
+ # look for interfaces on virtual network
30
+ doc.css("devices interface[type='netwok']").each do |item|
31
+ ifcount += 1
32
+ adapter = ifcount
33
+ nics[adapter] ||= {}
34
+ nics[adapter][:network] = item.at_css("source")["network"]
35
+ nics[adapter][:type] = :network
36
+ end
37
+ nics
38
+ end
39
+
40
+ def initialize(definition, source_type='libvirt')
41
+ @uuid = nil
42
+ @network = 'default'
43
+ if source_type == 'ovf'
44
+ create_from_ovf(definition)
45
+ else
46
+ create_from_libvirt(definition)
47
+ end
48
+ end
49
+
50
+ def create_from_ovf(definition)
51
+ doc = Nokogiri::XML(definition)
52
+ # we don't need no namespace
53
+ doc.remove_namespaces!
54
+ @name = doc.at_css("VirtualSystemIdentifier").content
55
+ devices = doc.css("VirtualHardwareSection Item")
56
+ for device in devices
57
+ case device.at_css("ResourceType").content
58
+ # CPU
59
+ when "3"
60
+ @cpus = device.at_css("VirtualQuantity").content
61
+ # Memory
62
+ when "4"
63
+ @memory = size_in_bytes(device.at_css("VirtualQuantity").content,
64
+ device.at_css("AllocationUnits").content)
65
+ end
66
+ end
67
+
68
+ # disk volume
69
+ diskref = doc.at_css("DiskSection Disk")["fileRef"]
70
+ @disk = doc.at_css("References File[id='#{diskref}']")["href"]
71
+
72
+ # mac address
73
+ # XXX we use only the first nic
74
+ @mac = format_mac(doc.at_css("Machine Hardware Adapter[enabled='true']")['MACAddress'])
75
+
76
+ # the architecture is not defined in the ovf file
77
+ # we try to guess from OSType
78
+ # see https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Main/include/ovfreader.h
79
+ @arch = doc.at_css("VirtualSystemIdentifier").
80
+ content[-2..-1] == '64' ? "x86_64" : "i686"
81
+ end
82
+
83
+ def create_from_libvirt(definition)
84
+ doc = Nokogiri::XML(definition)
85
+ @name = doc.at_css("domain name").content
86
+ @uuid = doc.at_css("domain uuid").content if doc.at_css("domain uuid")
87
+ memory_unit = doc.at_css("domain memory")["unit"]
88
+ @memory = size_in_bytes(doc.at_css("domain memory").content,
89
+ memory_unit)
90
+ @cpus = doc.at_css("domain vcpu").content
91
+ @arch = doc.at_css("domain os type")["arch"]
92
+ @disk = doc.at_css("devices disk source")["file"]
93
+ @mac = doc.at_css("devices interface mac")["address"]
94
+ @network = doc.at_css("devices interface source")["network"]
95
+ end
96
+
97
+ def as_libvirt
98
+ xml = KvmTemplateRenderer.render("libvirt_domain", {
99
+ :name => @name,
100
+ :uuid => @uuid,
101
+ :memory => size_from_bytes(@memory, "KiB"),
102
+ :cpus => @cpus,
103
+ :arch => @arch,
104
+ :disk => @disk,
105
+ :mac => format_mac(@mac),
106
+ :network => @network
107
+ })
108
+ xml
109
+ end
110
+
111
+ def get_memory(unit="bytes")
112
+ size_from_bytes(@memory, unit)
113
+ end
114
+
115
+ def set_mac(mac)
116
+ @mac = format_mac(mac)
117
+ end
118
+
119
+ # Takes a quantity and a unit
120
+ # returns quantity in bytes
121
+ # mib = true to use mebibytes, etc
122
+ # defaults to false because ovf MB != megabytes
123
+ def size_in_bytes(qty, unit, mib=false)
124
+ qty = qty.to_i
125
+ unit = unit.downcase
126
+ if !mib
127
+ case unit
128
+ when "kb", "kilobytes"
129
+ unit = "kib"
130
+ when "mb", "megabytes"
131
+ unit = "mib"
132
+ when "gb", "gigabytes"
133
+ unit = "gib"
134
+ end
135
+ end
136
+ case unit
137
+ when "b", "bytes"
138
+ qty.to_s
139
+ when "kb", "kilobytes"
140
+ (qty * 1000).to_s
141
+ when "kib", "kibibytes"
142
+ (qty * 1024).to_s
143
+ when "mb", "megabytes"
144
+ (qty * 1000000).to_s
145
+ when "m", "mib", "mebibytes"
146
+ (qty * 1048576).to_s
147
+ when "gb", "gigabytes"
148
+ (qty * 1000000000).to_s
149
+ when "g", "gib", "gibibytes"
150
+ (qty * 1073741824).to_s
151
+ else
152
+ raise ArgumentError, "Unknown unit #{unit}"
153
+ end
154
+ end
155
+
156
+ # Takes a qty and a unit
157
+ # returns byte quantity in that unit
158
+ def size_from_bytes(qty, unit)
159
+ qty = qty.to_i
160
+ case unit.downcase
161
+ when "b", "bytes"
162
+ qty.to_s
163
+ when "kb", "kilobytes"
164
+ (qty / 1000).to_s
165
+ when "kib", "kibibytes"
166
+ (qty / 1024).to_s
167
+ when "mb", "megabytes"
168
+ (qty / 1000000).to_s
169
+ when "m", "mib", "mebibytes"
170
+ (qty / 1048576).to_s
171
+ when "gb", "gigabytes"
172
+ (qty / 1000000000).to_s
173
+ when "g", "gib", "gibibytes"
174
+ (qty / 1073741824).to_s
175
+ else
176
+ raise ArgumentError, "Unknown unit #{unit}"
177
+ end
178
+ end
179
+
180
+ def format_mac(mac)
181
+ if mac.length == 12
182
+ mac = mac[0..1] + ":" + mac[2..3] + ":" +
183
+ mac[4..5] + ":" + mac[6..7] + ":" +
184
+ mac[8..9] + ":" + mac[10..11]
185
+ end
186
+ mac
187
+ end
188
+
189
+ end
190
+ end
191
+ end
192
+ end