vagrant-kvm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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