virtualbox 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +11 -0
  3. data/Rakefile +32 -0
  4. data/Readme.md +75 -0
  5. data/TODO +13 -0
  6. data/VERSION +1 -0
  7. data/lib/virtualbox.rb +11 -0
  8. data/lib/virtualbox/abstract_model.rb +95 -0
  9. data/lib/virtualbox/abstract_model/attributable.rb +193 -0
  10. data/lib/virtualbox/abstract_model/dirty.rb +164 -0
  11. data/lib/virtualbox/abstract_model/relatable.rb +163 -0
  12. data/lib/virtualbox/attached_device.rb +104 -0
  13. data/lib/virtualbox/command.rb +54 -0
  14. data/lib/virtualbox/dvd.rb +31 -0
  15. data/lib/virtualbox/errors.rb +7 -0
  16. data/lib/virtualbox/ext/subclass_listing.rb +24 -0
  17. data/lib/virtualbox/hard_drive.rb +169 -0
  18. data/lib/virtualbox/image.rb +94 -0
  19. data/lib/virtualbox/nic.rb +150 -0
  20. data/lib/virtualbox/storage_controller.rb +122 -0
  21. data/lib/virtualbox/vm.rb +287 -0
  22. data/test/test_helper.rb +25 -0
  23. data/test/virtualbox/abstract_model/attributable_test.rb +150 -0
  24. data/test/virtualbox/abstract_model/dirty_test.rb +66 -0
  25. data/test/virtualbox/abstract_model/relatable_test.rb +141 -0
  26. data/test/virtualbox/abstract_model_test.rb +146 -0
  27. data/test/virtualbox/attached_device_test.rb +92 -0
  28. data/test/virtualbox/command_test.rb +30 -0
  29. data/test/virtualbox/dvd_test.rb +58 -0
  30. data/test/virtualbox/ext/subclass_listing_test.rb +25 -0
  31. data/test/virtualbox/hard_drive_test.rb +161 -0
  32. data/test/virtualbox/image_test.rb +113 -0
  33. data/test/virtualbox/nic_test.rb +119 -0
  34. data/test/virtualbox/storage_controller_test.rb +79 -0
  35. data/test/virtualbox/vm_test.rb +313 -0
  36. 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,7 @@
1
+ module VirtualBox
2
+ # Gem specific exceptions will reside under this namespace for easy
3
+ # documentation and searching.
4
+ module Errors
5
+ class Exception < ::Exception; end
6
+ end
7
+ 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