virtualbox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +11 -0
- data/Rakefile +32 -0
- data/Readme.md +75 -0
- data/TODO +13 -0
- data/VERSION +1 -0
- data/lib/virtualbox.rb +11 -0
- data/lib/virtualbox/abstract_model.rb +95 -0
- data/lib/virtualbox/abstract_model/attributable.rb +193 -0
- data/lib/virtualbox/abstract_model/dirty.rb +164 -0
- data/lib/virtualbox/abstract_model/relatable.rb +163 -0
- data/lib/virtualbox/attached_device.rb +104 -0
- data/lib/virtualbox/command.rb +54 -0
- data/lib/virtualbox/dvd.rb +31 -0
- data/lib/virtualbox/errors.rb +7 -0
- data/lib/virtualbox/ext/subclass_listing.rb +24 -0
- data/lib/virtualbox/hard_drive.rb +169 -0
- data/lib/virtualbox/image.rb +94 -0
- data/lib/virtualbox/nic.rb +150 -0
- data/lib/virtualbox/storage_controller.rb +122 -0
- data/lib/virtualbox/vm.rb +287 -0
- data/test/test_helper.rb +25 -0
- data/test/virtualbox/abstract_model/attributable_test.rb +150 -0
- data/test/virtualbox/abstract_model/dirty_test.rb +66 -0
- data/test/virtualbox/abstract_model/relatable_test.rb +141 -0
- data/test/virtualbox/abstract_model_test.rb +146 -0
- data/test/virtualbox/attached_device_test.rb +92 -0
- data/test/virtualbox/command_test.rb +30 -0
- data/test/virtualbox/dvd_test.rb +58 -0
- data/test/virtualbox/ext/subclass_listing_test.rb +25 -0
- data/test/virtualbox/hard_drive_test.rb +161 -0
- data/test/virtualbox/image_test.rb +113 -0
- data/test/virtualbox/nic_test.rb +119 -0
- data/test/virtualbox/storage_controller_test.rb +79 -0
- data/test/virtualbox/vm_test.rb +313 -0
- metadata +103 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a DVD image stored by VirtualBox. These DVD images can be
|
3
|
+
# mounted onto virtual machines.
|
4
|
+
#
|
5
|
+
# # Finding all DVDs
|
6
|
+
#
|
7
|
+
# The only method at the moment of finding DVDs is to use {DVD.all}, which
|
8
|
+
# returns an array of {DVD}s.
|
9
|
+
#
|
10
|
+
# DVD.all
|
11
|
+
#
|
12
|
+
class DVD < Image
|
13
|
+
class <<self
|
14
|
+
# Returns an array of all available DVDs as DVD objects
|
15
|
+
def all
|
16
|
+
raw = Command.vboxmanage("list dvds")
|
17
|
+
parse_raw(raw)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Deletes the DVD from VBox managed list and also from disk.
|
22
|
+
# This method will fail if the disk is currently mounted to any
|
23
|
+
# virtual machine.
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
def destroy
|
27
|
+
Command.vboxmanage("closemedium dvd #{uuid} --delete")
|
28
|
+
return $?.to_i == 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# From: http://snippets.dzone.com/posts/show/2992
|
2
|
+
module VirtualBox::SubclassListing
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def subclasses(direct = false)
|
9
|
+
classes = []
|
10
|
+
if direct
|
11
|
+
ObjectSpace.each_object(Class) do |c|
|
12
|
+
next unless c.superclass == self
|
13
|
+
classes << c
|
14
|
+
end
|
15
|
+
else
|
16
|
+
ObjectSpace.each_object(Class) do |c|
|
17
|
+
next unless c.ancestors.include?(self) and (c != self)
|
18
|
+
classes << c
|
19
|
+
end
|
20
|
+
end
|
21
|
+
classes
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a hard disk which is registered with VirtualBox.
|
3
|
+
#
|
4
|
+
# # Finding a Hard Drive
|
5
|
+
#
|
6
|
+
# Hard drives can be found use {HardDrive.all} and {HardDrive.find}, which
|
7
|
+
# find all or a specific hard drive, respectively. Example below:
|
8
|
+
#
|
9
|
+
# VirtualBox::HardDrive.all
|
10
|
+
#
|
11
|
+
# Or:
|
12
|
+
#
|
13
|
+
# VirtualBox::HardDrive.find("Foo.vdi")
|
14
|
+
#
|
15
|
+
# # Creating a Hard Drive
|
16
|
+
#
|
17
|
+
# The hard drive is one of the few data items at the moment which supports
|
18
|
+
# new creation. Below is a simple example of how this works:
|
19
|
+
#
|
20
|
+
# hd = VirtualBox::HardDrive.new
|
21
|
+
# hd.location = "foo.vdi"
|
22
|
+
# hd.size = 2400 # in megabytes
|
23
|
+
# hd.save
|
24
|
+
#
|
25
|
+
# # You can now access other attributes, since its saved:
|
26
|
+
# hd.uuid
|
27
|
+
# hd.location # will return a full path now
|
28
|
+
#
|
29
|
+
# Although `VDI` is the default format for newly created hard drives, other
|
30
|
+
# formats are supported:
|
31
|
+
#
|
32
|
+
# hd = VirtualBox::HardDrive.new
|
33
|
+
# hd.format = "VMDK"
|
34
|
+
# hd.location = "bar.vmdk"
|
35
|
+
# hd.size = 9001 # Its over 9000! (If you don't understand the reference, just ignore this comment)
|
36
|
+
# hd.save
|
37
|
+
#
|
38
|
+
# Any formats listed by your VirtualBox installation's `VBoxManage list hddbackends` command
|
39
|
+
# can be used with the virtualbox gem.
|
40
|
+
#
|
41
|
+
# # Destroying Hard Drives
|
42
|
+
#
|
43
|
+
# Hard drives can also be deleted, which will completely remove them from
|
44
|
+
# disk. **This operation is not reversable**.
|
45
|
+
#
|
46
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
47
|
+
# hd.destroy
|
48
|
+
#
|
49
|
+
# # Cloning Hard Drives
|
50
|
+
#
|
51
|
+
# Hard drives can just as easily be cloned as they can be created or destroyed.
|
52
|
+
#
|
53
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
54
|
+
# cloned_hd = hd.clone("bar")
|
55
|
+
#
|
56
|
+
# In addition to simply cloning hard drives, this command can be used to
|
57
|
+
# clone to a different format:
|
58
|
+
#
|
59
|
+
# hd = VirtualBox::HardDrive.find("foo")
|
60
|
+
# hd.clone("bar", "VMDK") # Will clone and convert to VMDK format
|
61
|
+
#
|
62
|
+
# # Attributes
|
63
|
+
#
|
64
|
+
# Properties of the model are exposed using standard ruby instance
|
65
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
66
|
+
# below as available instance methods.
|
67
|
+
#
|
68
|
+
# These attributes can be accessed and modified via standard ruby-style
|
69
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
70
|
+
# listed below. If you aren't sure what this means or you can't understand
|
71
|
+
# why the below is listed, please read {Attributable}.
|
72
|
+
#
|
73
|
+
# attribute :uuid, :readonly => true
|
74
|
+
# attribute :location
|
75
|
+
# attribute :accessible, :readonly => true
|
76
|
+
# attribute :format, :default => "VDI"
|
77
|
+
# attribute :size
|
78
|
+
#
|
79
|
+
class HardDrive < Image
|
80
|
+
attribute :format, :default => "VDI"
|
81
|
+
attribute :size
|
82
|
+
|
83
|
+
class <<self
|
84
|
+
# Returns an array of all available hard drives as HardDrive
|
85
|
+
# objects.
|
86
|
+
#
|
87
|
+
# @return [Array<HardDrive>]
|
88
|
+
def all
|
89
|
+
raw = Command.vboxmanage("list hdds")
|
90
|
+
parse_blocks(raw).collect { |v| find(v[:uuid]) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Finds one specific hard drive by UUID or file name. If the
|
94
|
+
# hard drive can not be found, will return `nil`.
|
95
|
+
#
|
96
|
+
# @return [HardDrive]
|
97
|
+
def find(id)
|
98
|
+
raw = Command.vboxmanage("showhdinfo #{id}")
|
99
|
+
|
100
|
+
# Return nil if the hard drive doesn't exist
|
101
|
+
return nil if raw =~ /VERR_FILE_NOT_FOUND/
|
102
|
+
|
103
|
+
data = raw.split(/\n\n/).collect { |v| parse_block(v) }.find { |v| !v.nil? }
|
104
|
+
|
105
|
+
# Set equivalent fields
|
106
|
+
data[:format] = data[:"storage format"]
|
107
|
+
data[:size] = data[:"logical size"].split(/\s+/)[0] if data.has_key?(:"logical size")
|
108
|
+
|
109
|
+
# Return new object
|
110
|
+
new(data)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Clone hard drive, possibly also converting formats. All formats
|
115
|
+
# supported by your local VirtualBox installation are supported
|
116
|
+
# here. If no format is specified, the format of the source drive
|
117
|
+
# will be used.
|
118
|
+
#
|
119
|
+
# @param [String] outputfile The output file. This should be just a
|
120
|
+
# single filename, since VirtualBox will place it in the hard
|
121
|
+
# drives folder.
|
122
|
+
# @param [String] format The format to convert to.
|
123
|
+
# @return [HardDrive] The new, cloned hard drive.
|
124
|
+
def clone(outputfile, format="VDI")
|
125
|
+
raw = Command.vboxmanage("clonehd #{uuid} #{Command.shell_escape(outputfile)} --format #{format} --remember")
|
126
|
+
return nil unless raw =~ /UUID: (.+?)$/
|
127
|
+
|
128
|
+
self.class.find($1.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Creates a new hard drive.
|
132
|
+
#
|
133
|
+
# **This method should NEVER be called. Call {#save} instead.**
|
134
|
+
def create
|
135
|
+
raw = Command.vboxmanage("createhd --filename #{location} --size #{size} --format #{read_attribute(:format)} --remember")
|
136
|
+
return nil unless raw =~ /UUID: (.+?)$/
|
137
|
+
|
138
|
+
# Just replace our attributes with the newly created ones. This also
|
139
|
+
# will set new_record to false.
|
140
|
+
populate_attributes(self.class.find($1.to_s).attributes)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Saves the hard drive object. If the hard drive is new,
|
144
|
+
# this will create a new hard drive. Otherwise, it will
|
145
|
+
# save any other details about the existing hard drive.
|
146
|
+
#
|
147
|
+
# Currently, **saving existing hard drives does nothing**.
|
148
|
+
# This is a limitation of VirtualBox, rather than the library itself.
|
149
|
+
def save
|
150
|
+
if new_record?
|
151
|
+
# Create a new hard drive
|
152
|
+
create
|
153
|
+
else
|
154
|
+
super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Destroys the hard drive. This deletes the hard drive off
|
159
|
+
# of disk.
|
160
|
+
#
|
161
|
+
# **This operation is not reversable.**
|
162
|
+
#
|
163
|
+
# @return [Boolean]
|
164
|
+
def destroy
|
165
|
+
Command.vboxmanage("closemedium disk #{uuid} --delete")
|
166
|
+
return $?.to_i == 0
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'virtualbox/ext/subclass_listing'
|
2
|
+
|
3
|
+
module VirtualBox
|
4
|
+
# An abstract class which encapsulates the shared behaviour of
|
5
|
+
# images such as {HardDrive} and {DVD}.
|
6
|
+
#
|
7
|
+
# ## Attributes
|
8
|
+
#
|
9
|
+
# All images expose the following attributes. If you don't know how to read
|
10
|
+
# this than read {Attributable}.
|
11
|
+
#
|
12
|
+
# attribute :uuid, :readonly => true
|
13
|
+
# attribute :location
|
14
|
+
# attribute :accessible, :readonly => true
|
15
|
+
#
|
16
|
+
# @abstract
|
17
|
+
class Image < AbstractModel
|
18
|
+
include SubclassListing
|
19
|
+
|
20
|
+
attribute :uuid, :readonly => true
|
21
|
+
attribute :location
|
22
|
+
attribute :accessible, :readonly => true
|
23
|
+
|
24
|
+
class <<self
|
25
|
+
# Parses the raw output of virtualbox into image objects. Used by
|
26
|
+
# subclasses to parse the output of their respective listing functions.
|
27
|
+
#
|
28
|
+
# **This method typically won't be used except internally.**
|
29
|
+
#
|
30
|
+
# @return [Array<Image>]
|
31
|
+
def parse_raw(raw)
|
32
|
+
parse_blocks(raw).collect { |v| new(v) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Parses the blocks of the output from virtualbox. VirtualBox outputs
|
36
|
+
# image listing in "blocks" which are then parsed down to their attributes.
|
37
|
+
#
|
38
|
+
# **This method typically won't be used except internally.**
|
39
|
+
#
|
40
|
+
# @return [Array<Hash>]
|
41
|
+
def parse_blocks(raw)
|
42
|
+
raw.split(/\n\n/).collect { |v| parse_block(v.chomp) }.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
# Parses a single block from VirtualBox output.
|
46
|
+
#
|
47
|
+
# **This method typically won't be used except internally.**
|
48
|
+
#
|
49
|
+
# @return [Hash]
|
50
|
+
def parse_block(block)
|
51
|
+
return nil unless block =~ /^UUID:/i
|
52
|
+
hd = {}
|
53
|
+
|
54
|
+
# Parses each line which should be in the format:
|
55
|
+
# KEY: VALUE
|
56
|
+
block.lines.each do |line|
|
57
|
+
next unless line =~ /^(.+?):\s+(.+?)$/
|
58
|
+
hd[$1.downcase.to_sym] = $2.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# If we don't have a location but have a path, use that, as they
|
62
|
+
# are equivalent but not consistent.
|
63
|
+
hd[:location] = hd[:path] if hd.has_key?(:path)
|
64
|
+
|
65
|
+
hd
|
66
|
+
end
|
67
|
+
|
68
|
+
# Searches the subclasses which implement all method, searching for
|
69
|
+
# a matching UUID and returning that as the relationship.
|
70
|
+
#
|
71
|
+
# **This method typically won't be used except internally.**
|
72
|
+
#
|
73
|
+
# @return [Array<Image>]
|
74
|
+
def populate_relationship(caller, data)
|
75
|
+
return nil if data[:uuid].nil?
|
76
|
+
|
77
|
+
subclasses.each do |subclass|
|
78
|
+
next unless subclass.respond_to?(:all)
|
79
|
+
|
80
|
+
matching = subclass.all.find { |obj| obj.uuid == data[:uuid] }
|
81
|
+
return matching unless matching.nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# **This should never be called directly on {Image}.** Instead, initialize
|
87
|
+
# one of the subclasses.
|
88
|
+
def initialize(info=nil)
|
89
|
+
super()
|
90
|
+
|
91
|
+
populate_attributes(info) if info
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a single NIC (Network Interface Card) of a virtual machine.
|
3
|
+
#
|
4
|
+
# **Currently, new NICs can't be created, so the only way to get this
|
5
|
+
# object is through a {VM}'s `nics` relationship.**
|
6
|
+
#
|
7
|
+
# # Editing a NIC
|
8
|
+
#
|
9
|
+
# Nics can be modified directly in their relationship to other
|
10
|
+
# virtual machines. When {VM#save} is called, it will also save any
|
11
|
+
# changes to its relationships.
|
12
|
+
#
|
13
|
+
# vm = VirtualBox::VM.find("foo")
|
14
|
+
# vm.nics[0].macaddress = @new_mac_address
|
15
|
+
# vm.save
|
16
|
+
#
|
17
|
+
# # Attributes
|
18
|
+
#
|
19
|
+
# Properties of the model are exposed using standard ruby instance
|
20
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
21
|
+
# below as available instance methods.
|
22
|
+
#
|
23
|
+
# These attributes can be accessed and modified via standard ruby-style
|
24
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
25
|
+
# listed below. If you aren't sure what this means or you can't understand
|
26
|
+
# why the below is listed, please read {Attributable}.
|
27
|
+
#
|
28
|
+
# attribute :parent, :readonly => :readonly
|
29
|
+
# attribute :nic
|
30
|
+
# attribute :nictype
|
31
|
+
# attribute :macaddress
|
32
|
+
# attribute :cableconnected
|
33
|
+
# attribute :bridgeadapter
|
34
|
+
#
|
35
|
+
class Nic < AbstractModel
|
36
|
+
attribute :parent, :readonly => :readonly
|
37
|
+
attribute :nic
|
38
|
+
attribute :nictype
|
39
|
+
attribute :macaddress
|
40
|
+
attribute :cableconnected
|
41
|
+
attribute :bridgeadapter
|
42
|
+
|
43
|
+
class <<self
|
44
|
+
# Retrives the Nic data from human-readable vminfo. Since some data about
|
45
|
+
# nics is not exposed in the machine-readable virtual machine info, some
|
46
|
+
# extra parsing must be done to get these attributes. This method parses
|
47
|
+
# the nic-specific data from this human readable information.
|
48
|
+
#
|
49
|
+
# **This method typically won't be used except internally.**
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def nic_data(vmname)
|
53
|
+
raw = VM.human_info(vmname)
|
54
|
+
|
55
|
+
# Complicated chain of methods just maps parse_nic over each line,
|
56
|
+
# removing invalid ones, and then converting it into a single hash.
|
57
|
+
raw.lines.collect { |v| parse_nic(v) }.compact.inject({}) do |acc, obj|
|
58
|
+
acc.merge({ obj[0] => obj[1] })
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parses nic data out of a single line of the human readable output
|
63
|
+
# of vm info.
|
64
|
+
#
|
65
|
+
# **This method typically won't be used except internally.**
|
66
|
+
#
|
67
|
+
# @return [Array] First element is nic name, second is data.
|
68
|
+
def parse_nic(raw)
|
69
|
+
return unless raw =~ /^NIC\s(\d):\s+(.+?)$/
|
70
|
+
return if $2.to_s.strip == "disabled"
|
71
|
+
|
72
|
+
data = {}
|
73
|
+
nicname = "nic#{$1}"
|
74
|
+
$2.to_s.split(/,\s+/).each do |raw_property|
|
75
|
+
next unless raw_property =~ /^(.+?):\s+(.+?)$/
|
76
|
+
|
77
|
+
data[$1.downcase.to_sym] = $2.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
return nicname.to_sym, data
|
81
|
+
end
|
82
|
+
|
83
|
+
# Populates the nic relationship for anything which is related to it.
|
84
|
+
#
|
85
|
+
# **This method typically won't be used except internally.**
|
86
|
+
#
|
87
|
+
# @return [Array<Nic>]
|
88
|
+
def populate_relationship(caller, data)
|
89
|
+
nic_data = nic_data(caller.name)
|
90
|
+
|
91
|
+
relation = []
|
92
|
+
|
93
|
+
counter = 1
|
94
|
+
loop do
|
95
|
+
break unless data["nic#{counter}".to_sym]
|
96
|
+
|
97
|
+
nictype = nic_data["nic#{counter}".to_sym][:type] rescue nil
|
98
|
+
|
99
|
+
nic = new(counter, caller, data.merge({
|
100
|
+
"nictype#{counter}".to_sym => nictype
|
101
|
+
}))
|
102
|
+
relation.push(nic)
|
103
|
+
counter += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
relation
|
107
|
+
end
|
108
|
+
|
109
|
+
# Saves the relationship. This simply calls {#save} on every
|
110
|
+
# member of the relationship.
|
111
|
+
#
|
112
|
+
# **This method typically won't be used except internally.**
|
113
|
+
def save_relationship(caller, data)
|
114
|
+
# Just call save on each nic with the VM
|
115
|
+
data.each do |nic|
|
116
|
+
nic.save(caller.name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Since there is currently no way to create a _new_ nic, this is
|
122
|
+
# only used internally. Developers should NOT try to initialize their
|
123
|
+
# own nic objects.
|
124
|
+
def initialize(index, caller, data)
|
125
|
+
super()
|
126
|
+
|
127
|
+
@index = index
|
128
|
+
|
129
|
+
# Setup the index specific attributes
|
130
|
+
populate_data = {}
|
131
|
+
self.class.attributes.each do |name, options|
|
132
|
+
value = data["#{name}#{index}".to_sym]
|
133
|
+
populate_data[name] = value
|
134
|
+
end
|
135
|
+
|
136
|
+
populate_attributes(populate_data.merge({
|
137
|
+
:parent => caller
|
138
|
+
}))
|
139
|
+
end
|
140
|
+
|
141
|
+
# Saves a single attribute of the nic. This method is automatically
|
142
|
+
# called on {#save}.
|
143
|
+
#
|
144
|
+
# **This method typically won't be used except internally.**
|
145
|
+
def save_attribute(key, value, vmname)
|
146
|
+
Command.vboxmanage("modifyvm #{vmname} --#{key}#{@index} #{Command.shell_escape(value)}")
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|