libvirt 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +6 -6
- data/Rakefile +21 -0
- data/lib/libvirt/connection.rb +1 -1
- data/lib/libvirt/domain.rb +8 -0
- data/lib/libvirt/exception.rb +9 -0
- data/lib/libvirt/spec.rb +1 -0
- data/lib/libvirt/spec/device.rb +27 -0
- data/lib/libvirt/spec/device/disk.rb +25 -5
- data/lib/libvirt/spec/device/graphics.rb +39 -0
- data/lib/libvirt/spec/device/input.rb +40 -0
- data/lib/libvirt/spec/device/interface.rb +47 -0
- data/lib/libvirt/spec/device/sound.rb +32 -0
- data/lib/libvirt/spec/device/video.rb +47 -0
- data/lib/libvirt/spec/device/video_model.rb +53 -0
- data/lib/libvirt/spec/domain.rb +80 -5
- data/lib/libvirt/spec/domain/clock.rb +35 -0
- data/lib/libvirt/spec/domain/memtune.rb +29 -0
- data/lib/libvirt/spec/domain/os_booting.rb +27 -1
- data/lib/libvirt/spec/util.rb +43 -0
- data/lib/libvirt/version.rb +1 -1
- data/libvirt.gemspec +3 -4
- data/test/libvirt/domain_test.rb +6 -0
- data/test/libvirt/spec/device_test.rb +21 -0
- data/test/libvirt/spec/devices/disk_test.rb +21 -87
- data/test/libvirt/spec/devices/graphics_test.rb +25 -0
- data/test/libvirt/spec/devices/input_test.rb +25 -0
- data/test/libvirt/spec/devices/interface_test.rb +35 -0
- data/test/libvirt/spec/devices/sound_test.rb +20 -0
- data/test/libvirt/spec/devices/video_model_test.rb +36 -0
- data/test/libvirt/spec/domain/clock_test.rb +20 -0
- data/test/libvirt/spec/domain/os_booting_test.rb +31 -0
- data/test/libvirt/spec/domain_test.rb +123 -1
- metadata +32 -7
data/lib/libvirt/spec/domain.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
+
require 'libvirt/spec/domain/clock'
|
2
|
+
require 'libvirt/spec/domain/memtune'
|
1
3
|
require 'libvirt/spec/domain/os_booting'
|
2
4
|
|
3
5
|
module Libvirt
|
4
6
|
module Spec
|
5
7
|
# A specification of a domain. This translates directly down to XML
|
6
8
|
# which can be used to define and launch domains on a node by libvirt.
|
7
|
-
#
|
8
|
-
# **Note:** This class may only be temporary, and the functionality
|
9
|
-
# may be merged back into {Domain}. Also, the interface will likely
|
10
|
-
# change.
|
11
9
|
class Domain
|
10
|
+
include Util
|
11
|
+
|
12
12
|
attr_accessor :hypervisor
|
13
13
|
attr_accessor :name
|
14
14
|
attr_accessor :uuid
|
@@ -16,17 +16,72 @@ module Libvirt
|
|
16
16
|
attr_accessor :os
|
17
17
|
attr_accessor :memory
|
18
18
|
attr_accessor :current_memory
|
19
|
+
attr_accessor :memory_backing
|
20
|
+
attr_accessor :memtune
|
19
21
|
attr_accessor :vcpu
|
22
|
+
attr_accessor :features
|
20
23
|
|
21
24
|
attr_accessor :on_poweroff
|
22
25
|
attr_accessor :on_reboot
|
23
26
|
attr_accessor :on_crash
|
24
27
|
|
28
|
+
attr_accessor :clock
|
25
29
|
attr_accessor :devices
|
26
30
|
|
27
|
-
|
31
|
+
# Initializes a domain specification. If a valid XML string for a domain
|
32
|
+
# is given, the it will attempt to be parsed into the structure. This
|
33
|
+
# is still very experimental. As such, if there is something which is
|
34
|
+
# found which is not parseable, an {Exception::UnparseableSpec} exception
|
35
|
+
# will be raised. Catch this and inspect the message for more information.
|
36
|
+
#
|
37
|
+
# @param [String] xml XML spec to attempt to parse into the structure.
|
38
|
+
def initialize(xml=nil)
|
28
39
|
@os = OSBooting.new
|
40
|
+
@memtune = Memtune.new
|
41
|
+
@features = []
|
42
|
+
@clock = Clock.new
|
29
43
|
@devices = []
|
44
|
+
|
45
|
+
load!(xml) if xml
|
46
|
+
end
|
47
|
+
|
48
|
+
# Attempts to load the attributes from an XML specification. **Warning:
|
49
|
+
# this will overwrite any already set attributes which exist in the XML.**
|
50
|
+
#
|
51
|
+
# @param [String] xml XML spec to attempt to parse into the structure.
|
52
|
+
def load!(xml)
|
53
|
+
root = Nokogiri::XML(xml).root
|
54
|
+
try(root.xpath("//domain[@type]"), :preserve => true) { |result| self.hypervisor = result["type"].to_sym }
|
55
|
+
try(root.xpath("//domain/name")) { |result| self.name = result.text }
|
56
|
+
try(root.xpath("//domain/uuid")) { |result| self.uuid = result.text }
|
57
|
+
try(root.xpath("//domain/memory")) { |result| self.memory = result.text }
|
58
|
+
try(root.xpath("//domain/currentMemory")) { |result| self.current_memory = result.text }
|
59
|
+
try(root.xpath("//domain/vcpu")) { |result| self.vcpu = result.text }
|
60
|
+
|
61
|
+
try(root.xpath("//domain/on_poweroff")) { |result| self.on_poweroff = result.text.to_sym }
|
62
|
+
try(root.xpath("//domain/on_reboot")) { |result| self.on_reboot = result.text.to_sym }
|
63
|
+
try(root.xpath("//domain/on_crash")) { |result| self.on_crash = result.text.to_sym }
|
64
|
+
|
65
|
+
try(root.xpath("//domain/clock")) { |result| self.clock = Clock.new(result) }
|
66
|
+
try(root.xpath("//domain/os")) { |result| self.os = OSBooting.new(result) }
|
67
|
+
|
68
|
+
try(root.xpath("//domain/devices")) do |result|
|
69
|
+
self.devices = []
|
70
|
+
|
71
|
+
result.element_children.each do |device|
|
72
|
+
self.devices << Device.load!(device)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
try(root.xpath("//domain/features")) do |result|
|
77
|
+
self.features = []
|
78
|
+
|
79
|
+
result.element_children.each do |feature|
|
80
|
+
self.features << feature.name.to_sym
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
raise_if_unparseables(root.xpath("//domain/*"))
|
30
85
|
end
|
31
86
|
|
32
87
|
# Returns the XML for this specification. This XML may be passed
|
@@ -52,11 +107,31 @@ module Libvirt
|
|
52
107
|
xml.currentMemory current_memory if current_memory
|
53
108
|
xml.vcpu vcpu if vcpu
|
54
109
|
|
110
|
+
if memory_backing == :huge_pages
|
111
|
+
xml.memoryBacking do
|
112
|
+
xml.hugepages
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Memtune handles whether or not to render itself
|
117
|
+
memtune.to_xml(xml)
|
118
|
+
|
119
|
+
if !features.empty?
|
120
|
+
xml.features do
|
121
|
+
features.each do |feature|
|
122
|
+
xml.send(feature)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
55
127
|
# Lifecycle control
|
56
128
|
xml.on_poweroff on_poweroff if on_poweroff
|
57
129
|
xml.on_reboot on_reboot if on_reboot
|
58
130
|
xml.on_crash on_crash if on_crash
|
59
131
|
|
132
|
+
# Clock
|
133
|
+
clock.to_xml(xml)
|
134
|
+
|
60
135
|
# Devices
|
61
136
|
if !devices.empty?
|
62
137
|
xml.devices do
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Libvirt
|
2
|
+
module Spec
|
3
|
+
class Domain
|
4
|
+
# Time keeping configuration for a domain.
|
5
|
+
class Clock
|
6
|
+
include Util
|
7
|
+
|
8
|
+
attr_accessor :offset
|
9
|
+
|
10
|
+
# Initializes a clock specification. This should never be called
|
11
|
+
# directly. Instead, use a {Libvirt::Spec::Domain} spec, which
|
12
|
+
# contains a clock attribute.
|
13
|
+
def initialize(xml=nil)
|
14
|
+
load!(xml) if xml
|
15
|
+
end
|
16
|
+
|
17
|
+
# Loads clock information from the given XML string. This shouldn't
|
18
|
+
# be called directly, since the domain spec automatically calls
|
19
|
+
# this.
|
20
|
+
def load!(root)
|
21
|
+
root = Nokogiri::XML(root).root if !root.is_a?(Nokogiri::XML::Element)
|
22
|
+
try(root.xpath("//clock[@offset]"), :preserve => true) { |result| self.offset = result["offset"].to_sym }
|
23
|
+
raise_if_unparseables(root.xpath("//clock/*"))
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_xml(parent=Nokogiri::XML::Builder.new)
|
27
|
+
return if !offset
|
28
|
+
|
29
|
+
parent.clock :offset => offset
|
30
|
+
parent.to_xml
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Libvirt
|
2
|
+
module Spec
|
3
|
+
class Domain
|
4
|
+
# Allows the modification of details regarding the memory tuneable
|
5
|
+
# parameters for this domain.
|
6
|
+
class Memtune
|
7
|
+
attr_accessor :hard_limit
|
8
|
+
attr_accessor :soft_limit
|
9
|
+
attr_accessor :swap_hard_limit
|
10
|
+
attr_accessor :min_guarantee
|
11
|
+
|
12
|
+
def to_xml(parent=Nokogiri::XML::Builder.new)
|
13
|
+
# If nothing has been modified, then don't do anything
|
14
|
+
return if !hard_limit && !soft_limit &&
|
15
|
+
!swap_hard_limit && !min_guarantee
|
16
|
+
|
17
|
+
parent.memtune do |m|
|
18
|
+
m.hard_limit hard_limit if hard_limit
|
19
|
+
m.soft_limit soft_limit if soft_limit
|
20
|
+
m.swap_hard_limit swap_hard_limit if swap_hard_limit
|
21
|
+
m.min_guarantee min_guarantee if min_guarantee
|
22
|
+
end
|
23
|
+
|
24
|
+
parent.to_xml
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -11,6 +11,8 @@ module Libvirt
|
|
11
11
|
#
|
12
12
|
# TODO: Host bootloader and direct kernel bootloader options.
|
13
13
|
class OSBooting
|
14
|
+
include Util
|
15
|
+
|
14
16
|
attr_accessor :type
|
15
17
|
attr_accessor :arch
|
16
18
|
attr_accessor :loader # Part of the BIOS bootloader
|
@@ -26,8 +28,13 @@ module Libvirt
|
|
26
28
|
attr_accessor :initrd
|
27
29
|
attr_accessor :cmdline
|
28
30
|
|
29
|
-
|
31
|
+
# Initializes an OS booting specification. This shouldn't be
|
32
|
+
# called directly since the domain spec automatically loads
|
33
|
+
# this.
|
34
|
+
def initialize(xml=nil)
|
30
35
|
@boot = []
|
36
|
+
|
37
|
+
load!(xml) if xml
|
31
38
|
end
|
32
39
|
|
33
40
|
# Enables or disables the interactive boot menu prompt on guest startup.
|
@@ -36,6 +43,25 @@ module Libvirt
|
|
36
43
|
@bootmenu_enabled = !!value
|
37
44
|
end
|
38
45
|
|
46
|
+
# Loads the OS booting information from the given XML string. This
|
47
|
+
# shouldn't be called directly, since the domain spec automatically
|
48
|
+
# calls this.
|
49
|
+
def load!(root)
|
50
|
+
root = Nokogiri::XML(root).root if !root.is_a?(Nokogiri::XML::Element)
|
51
|
+
try(root.xpath("//os/type[@arch]"), :preserve => true) { |result| self.arch = result["arch"].to_sym }
|
52
|
+
try(root.xpath("//os/type")) { |result| self.type = result.text.to_sym }
|
53
|
+
|
54
|
+
try(root.xpath("//os/boot"), :multi => true) do |results|
|
55
|
+
self.boot = []
|
56
|
+
|
57
|
+
results.each do |result|
|
58
|
+
self.boot << result["dev"].to_sym
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
raise_if_unparseables(root.xpath("//os/*"))
|
63
|
+
end
|
64
|
+
|
39
65
|
# Convert just the OS booting section to its XML representation.
|
40
66
|
def to_xml(parent=Nokogiri::XML::Builder.new)
|
41
67
|
parent.os do |os|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Libvirt
|
2
|
+
module Spec
|
3
|
+
# Utility methods for the spec classes. This module is typically
|
4
|
+
# included for each class.
|
5
|
+
module Util
|
6
|
+
# Tries the given XML search, running the block if there are any
|
7
|
+
# results. This allows a concise syntax for loading data from
|
8
|
+
# XML which may or may not exist.
|
9
|
+
#
|
10
|
+
# **Warning:** By default, the result of the search given will
|
11
|
+
# be removed from the XML tree. See the options below for information
|
12
|
+
# on how to avoid this.
|
13
|
+
#
|
14
|
+
# An additional parameter supports options given as a hash. This
|
15
|
+
# allows for the following to be set:
|
16
|
+
#
|
17
|
+
# * `multi` - If true, then all results will be returned, not just
|
18
|
+
# the first one.
|
19
|
+
# * `preserve` - If true, then the node will not be deleted after
|
20
|
+
# yielding to the block.
|
21
|
+
#
|
22
|
+
# @param [Array] search_result
|
23
|
+
# @param [Hash] options Additional options, which are outlined
|
24
|
+
# above.
|
25
|
+
def try(search_result, options=nil)
|
26
|
+
options ||= {}
|
27
|
+
return if search_result.empty?
|
28
|
+
search_result = search_result.first if !options[:multi]
|
29
|
+
yield search_result
|
30
|
+
search_result.remove if !options[:preserve]
|
31
|
+
end
|
32
|
+
|
33
|
+
# This will raise an {Exception::UnparseableSpec} exception if there
|
34
|
+
# are any search results given. This is meant as a helper to reduce
|
35
|
+
# the duplicity of this feature across specs.
|
36
|
+
#
|
37
|
+
# @param [Array] search_result
|
38
|
+
def raise_if_unparseables(search_result)
|
39
|
+
raise Exception::UnparseableSpec, search_result if !search_result.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/libvirt/version.rb
CHANGED
data/libvirt.gemspec
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require "libvirt/version"
|
2
|
+
require File.expand_path("../lib/libvirt/version", __FILE__)
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = "libvirt"
|
@@ -9,8 +8,8 @@ Gem::Specification.new do |s|
|
|
9
8
|
s.authors = ["Mitchell Hashimoto"]
|
10
9
|
s.email = ["mitchell.hashimoto@gmail.com"]
|
11
10
|
s.homepage = "http://rubygems.org/gems/libvirt"
|
12
|
-
s.summary = "A ruby client library providing
|
13
|
-
s.description = "A ruby client library providing
|
11
|
+
s.summary = "A ruby client library providing an interface to libvirt via FFI."
|
12
|
+
s.description = "A ruby client library providing an interface to libvirt via FFI."
|
14
13
|
|
15
14
|
s.rubyforge_project = "libvirt"
|
16
15
|
|
data/test/libvirt/domain_test.rb
CHANGED
@@ -90,6 +90,12 @@ Protest.describe("domain") do
|
|
90
90
|
assert !result.empty?
|
91
91
|
end
|
92
92
|
|
93
|
+
should "provide the spec object for the domain" do
|
94
|
+
result = nil
|
95
|
+
assert_nothing_raised { result = @instance.spec }
|
96
|
+
assert result.is_a?(Libvirt::Spec::Domain)
|
97
|
+
end
|
98
|
+
|
93
99
|
should "return result of active status" do
|
94
100
|
@instance.start
|
95
101
|
assert @instance.active?
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
Protest.describe("Domain devices") do
|
4
|
+
setup do
|
5
|
+
@klass = Libvirt::Spec::Device
|
6
|
+
end
|
7
|
+
|
8
|
+
should "parse from XML" do
|
9
|
+
@instance = @klass.load!("<disk type='file'></disk>")
|
10
|
+
assert @instance.is_a?(Libvirt::Spec::Device::Disk)
|
11
|
+
assert_equal :file, @instance.type
|
12
|
+
end
|
13
|
+
|
14
|
+
should "be able to get defined classes by name" do
|
15
|
+
assert_equal Libvirt::Spec::Device::Disk, @klass.get(:disk)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "raise an exception if an unknown device name is given" do
|
19
|
+
assert_raises(NameError) { @klass.get(:foo) }
|
20
|
+
end
|
21
|
+
end
|
@@ -5,103 +5,37 @@ Protest.describe("Disk device spec") do
|
|
5
5
|
@klass = Libvirt::Spec::Device::Disk
|
6
6
|
end
|
7
7
|
|
8
|
-
context "
|
9
|
-
|
10
|
-
@instance = @klass.new(
|
8
|
+
context "initialization and parsing XML" do
|
9
|
+
should "parse the type" do
|
10
|
+
@instance = @klass.new("<disk type='file'></disk>")
|
11
|
+
assert_equal :file, @instance.type
|
11
12
|
end
|
12
13
|
|
13
|
-
should "
|
14
|
-
|
14
|
+
should "parse the device attribute" do
|
15
|
+
@instance = @klass.new("<disk device='disk'></disk>")
|
16
|
+
assert_equal :disk, @instance.device
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
assert_not_element @instance.to_xml, "//disk/source"
|
21
|
-
end
|
22
|
-
|
23
|
-
should "output source attr as dev for block devices" do
|
24
|
-
@instance.type = :block
|
25
|
-
@instance.source = "foo"
|
26
|
-
assert_xpath @instance.source, @instance.to_xml, "//disk/source/@dev"
|
27
|
-
end
|
28
|
-
|
29
|
-
should "output source attr as file for other devices" do
|
30
|
-
@instance.type = :file
|
31
|
-
@instance.source = "foo"
|
32
|
-
assert_xpath @instance.source, @instance.to_xml, "//disk/source/@file"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context "target" do
|
37
|
-
should "not output target if not specified" do
|
38
|
-
@instance.target_dev = nil
|
39
|
-
assert_not_element @instance.to_xml, "//disk/target"
|
40
|
-
end
|
41
|
-
|
42
|
-
should "not output target if only bus is given" do
|
43
|
-
@instance.target_dev = nil
|
44
|
-
@instance.target_bus = "foo"
|
45
|
-
assert_not_element @instance.to_xml, "//disk/target"
|
46
|
-
end
|
47
|
-
|
48
|
-
should "output specified dev on target" do
|
49
|
-
@instance.target_dev = "foo"
|
50
|
-
assert_xpath @instance.target_dev, @instance.to_xml, "//disk/target/@dev"
|
51
|
-
end
|
52
|
-
|
53
|
-
should "output specified bus on target" do
|
54
|
-
@instance.target_dev = "foo"
|
55
|
-
@instance.target_bus = "bar"
|
56
|
-
assert_xpath @instance.target_bus, @instance.to_xml, "//disk/target/@bus"
|
57
|
-
end
|
19
|
+
should "parse the file from the source" do
|
20
|
+
@instance = @klass.new("<disk type='file'><source file='foo'/></disk>")
|
21
|
+
assert_equal 'foo', @instance.source
|
58
22
|
end
|
59
23
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
assert_not_element @instance.to_xml, "//disk/driver"
|
64
|
-
end
|
65
|
-
|
66
|
-
should "output with specified driver" do
|
67
|
-
@instance.driver = "foo"
|
68
|
-
assert_xpath @instance.driver, @instance.to_xml, "//disk/driver/@name"
|
69
|
-
end
|
70
|
-
|
71
|
-
should "output with specified type if given" do
|
72
|
-
@instance.driver = "foo"
|
73
|
-
@instance.driver_type = "bar"
|
74
|
-
assert_xpath @instance.driver_type, @instance.to_xml, "//disk/driver/@type"
|
75
|
-
end
|
76
|
-
|
77
|
-
should "output with specified cache if given" do
|
78
|
-
@instance.driver = "foo"
|
79
|
-
@instance.driver_cache = "bar"
|
80
|
-
assert_xpath @instance.driver_cache, @instance.to_xml, "//disk/driver/@cache"
|
81
|
-
end
|
24
|
+
should "parse the dev from the source" do
|
25
|
+
@instance = @klass.new("<disk type='file'><source dev='bar'/></disk>")
|
26
|
+
assert_equal 'bar', @instance.source
|
82
27
|
end
|
83
28
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
should "be shareable if specified" do
|
90
|
-
@instance.shareable = true
|
91
|
-
assert_element @instance.to_xml, "//disk/shareable"
|
92
|
-
end
|
29
|
+
should "parse the target" do
|
30
|
+
@instance = @klass.new("<disk type='file'><target bus='foo' dev='bar'/></disk>")
|
31
|
+
assert_equal 'foo', @instance.target_bus
|
32
|
+
assert_equal 'bar', @instance.target_dev
|
93
33
|
end
|
94
34
|
|
95
|
-
|
96
|
-
|
97
|
-
@
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
should "have serial if specified" do
|
102
|
-
@instance.serial = "foobar"
|
103
|
-
assert_xpath @instance.serial, @instance.to_xml, "//disk/serial"
|
104
|
-
end
|
35
|
+
should "raise an exception if unsupported tags exist" do
|
36
|
+
assert_raises(Libvirt::Exception::UnparseableSpec) {
|
37
|
+
@klass.new("<disk><foo/></disk>")
|
38
|
+
}
|
105
39
|
end
|
106
40
|
end
|
107
41
|
end
|