virtualbox 0.1.1 → 0.2.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.
- data/.gitignore +2 -1
- data/.yardopts +3 -0
- data/Rakefile +15 -1
- data/Readme.md +3 -0
- data/TODO +0 -3
- data/VERSION +1 -1
- data/docs/GettingStarted.md +196 -0
- data/lib/virtualbox/abstract_model/attributable.rb +15 -16
- data/lib/virtualbox/abstract_model/dirty.rb +13 -2
- data/lib/virtualbox/abstract_model/relatable.rb +59 -10
- data/lib/virtualbox/abstract_model.rb +23 -1
- data/lib/virtualbox/attached_device.rb +144 -19
- data/lib/virtualbox/command.rb +11 -2
- data/lib/virtualbox/dvd.rb +52 -4
- data/lib/virtualbox/exceptions.rb +13 -0
- data/lib/virtualbox/hard_drive.rb +37 -8
- data/lib/virtualbox/image.rb +37 -0
- data/lib/virtualbox/nic.rb +1 -1
- data/lib/virtualbox/proxies/collection.rb +29 -0
- data/lib/virtualbox/storage_controller.rb +11 -0
- data/lib/virtualbox/vm.rb +105 -5
- data/lib/virtualbox.rb +2 -1
- data/test/virtualbox/abstract_model/dirty_test.rb +17 -0
- data/test/virtualbox/abstract_model/relatable_test.rb +49 -1
- data/test/virtualbox/abstract_model_test.rb +32 -1
- data/test/virtualbox/attached_device_test.rb +184 -2
- data/test/virtualbox/command_test.rb +36 -0
- data/test/virtualbox/dvd_test.rb +43 -0
- data/test/virtualbox/hard_drive_test.rb +52 -1
- data/test/virtualbox/image_test.rb +79 -0
- data/test/virtualbox/nic_test.rb +10 -0
- data/test/virtualbox/proxies/collection_test.rb +45 -0
- data/test/virtualbox/storage_controller_test.rb +12 -0
- data/test/virtualbox/vm_test.rb +118 -10
- data/virtualbox.gemspec +8 -3
- metadata +8 -3
- data/lib/virtualbox/errors.rb +0 -7
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/Rakefile
CHANGED
@@ -26,8 +26,22 @@ end
|
|
26
26
|
begin
|
27
27
|
require 'yard'
|
28
28
|
YARD::Rake::YardocTask.new do |t|
|
29
|
-
t.options = ['--main', 'Readme.md', '--markup', 'markdown'
|
29
|
+
t.options = ['--main', 'Readme.md', '--markup', 'markdown']
|
30
|
+
t.options += ['--title', 'VirtualBox Ruby Library Documentation']
|
30
31
|
end
|
31
32
|
rescue LoadError
|
32
33
|
puts "Yard not available. Install it with: gem install yard"
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'rcov/rcovtask'
|
38
|
+
Rcov::RcovTask.new do |t|
|
39
|
+
t.libs << "test"
|
40
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
41
|
+
t.output_dir = "test/coverage"
|
42
|
+
t.verbose = true
|
43
|
+
end
|
44
|
+
rescue LoadError
|
45
|
+
puts "Rcov not available. Coverage data tasks not available."
|
46
|
+
puts "Install it with: gem install rcov"
|
33
47
|
end
|
data/Readme.md
CHANGED
@@ -21,6 +21,9 @@ the gem, you must set the path to your `VBoxManage` binary:
|
|
21
21
|
The virtualbox gem is modeled after ActiveRecord. If you've used ActiveRecord, you'll
|
22
22
|
feel very comfortable using the virtualbox gem.
|
23
23
|
|
24
|
+
There is a [quick getting started guide](http://mitchellh.github.com/virtualbox/file.GettingStarted.html) to
|
25
|
+
get you acquainted with the conventions of the virtualbox gem.
|
26
|
+
|
24
27
|
Complete documentation can be found at [http://mitchellh.github.com/virtualbox](http://mitchellh.github.com/virtualbox).
|
25
28
|
|
26
29
|
Below are some examples:
|
data/TODO
CHANGED
@@ -4,9 +4,6 @@ Not all these features will make it into initial releases of virtualbox ruby gem
|
|
4
4
|
But they will definitely all make it for version 1.0.
|
5
5
|
|
6
6
|
* Creating a VM from scratch (non-import)
|
7
|
-
* Pause/Resume VMs
|
8
|
-
* Exporting a VM
|
9
|
-
* Modifying attached devices
|
10
7
|
* Parsing bridged IFs
|
11
8
|
* Shared folders
|
12
9
|
* Snapshots
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# Getting Started with the VirtualBox Gem
|
2
|
+
|
3
|
+
* [Basic Conventions](#basic-conventions)
|
4
|
+
* [Finding Models](#bc-finding-models)
|
5
|
+
* [Accessing Models](#bc-accessing-models)
|
6
|
+
* [Modifying Models](#bc-modifying-models)
|
7
|
+
* [Saving Models](#bc-saving-models)
|
8
|
+
|
9
|
+
<a name="basic-conventions"></a>
|
10
|
+
# Basic Conventions
|
11
|
+
|
12
|
+
The entire virtualbox library follows a few conventions to make sure
|
13
|
+
things work uniformly across the entire codebase, and so that nothing
|
14
|
+
should surprise any developers once they understand these conventions.
|
15
|
+
|
16
|
+
When browsing the documentation, you'll probably notice that a lot of the
|
17
|
+
classes inherit from {VirtualBox::AbstractModel}. This just means that all
|
18
|
+
these classes act the same way! Every {VirtualBox::AbstractModel AbstractModel}
|
19
|
+
shares the following behaviors:
|
20
|
+
|
21
|
+
* Finding
|
22
|
+
* Accessing
|
23
|
+
* Modifying
|
24
|
+
* Saving
|
25
|
+
|
26
|
+
These behaviors should be similar if not the exact same across all
|
27
|
+
virtualbox models. Each of these behaviors is covered below.
|
28
|
+
|
29
|
+
<a name="bc-finding-models"></a>
|
30
|
+
## Finding Models
|
31
|
+
|
32
|
+
All data models have a `find` or `all` method (or sometimes both!) These
|
33
|
+
methods do what you expect them to: `all` will return an array of all instances
|
34
|
+
of that model which is typically unordered. `find` will allow you to find a
|
35
|
+
specific instance of that model, typically by name or UUID. Below are a couple
|
36
|
+
examples of this.
|
37
|
+
|
38
|
+
### All
|
39
|
+
|
40
|
+
This example uses {VirtualBox::HardDrive}. As you can see, its just an
|
41
|
+
unmodified ruby `Array` which is returned by `all`. This can be used find,
|
42
|
+
sort, enumerate, etc.
|
43
|
+
|
44
|
+
drives = VirtualBox::HardDrive.all
|
45
|
+
puts "You have #{drives.length} hard drives!"
|
46
|
+
|
47
|
+
drives.each do |drive|
|
48
|
+
puts "Drive: #{drive.uuid}"
|
49
|
+
end
|
50
|
+
|
51
|
+
In the case that `all` returns an empty array, this simply means that none
|
52
|
+
of that model exist.
|
53
|
+
|
54
|
+
### Find
|
55
|
+
|
56
|
+
This example uses {VirtualBox::VM}, which will probably be the most common
|
57
|
+
model you search for.
|
58
|
+
|
59
|
+
vm = VirtualBox::VM.find("MyVM")
|
60
|
+
puts "This VM has #{vm.memory} MB of RAM allocated to it."
|
61
|
+
|
62
|
+
Find can also be used with UUIDs:
|
63
|
+
|
64
|
+
vm = VirtualBox::VM.find("3d0f87b4-50f7-4fc5-ad89-93375b1b32a3")
|
65
|
+
puts "This VM's name is: #{vm.name}"
|
66
|
+
|
67
|
+
When a find fails, it will return `nil`.
|
68
|
+
|
69
|
+
<a name="bc-accessing-models"></a>
|
70
|
+
## Accessing Models
|
71
|
+
|
72
|
+
Every model has an _attribute list_ associated with it. These attributes are
|
73
|
+
what can be accessed on the model via the typical ruby attribute accessing
|
74
|
+
syntax with the `.` (dot) operator. Because these methods are generated
|
75
|
+
dynamically, they don't show up as methods in the documentation. Because of this,
|
76
|
+
attributes are listed for every model in their overviews. For examples, see the
|
77
|
+
overviews of {VirtualBox::VM}, {VirtualBox::HardDrive}, etc.
|
78
|
+
|
79
|
+
In addition to an attribute list, many models also have _relationships_.
|
80
|
+
Relationships are, for our purposes, similar enough to attributes that they
|
81
|
+
can be treated the same. Relationship accessing methods are also dynamically
|
82
|
+
generated, so they are listed within the overviews of the models as well (if they
|
83
|
+
have any). Relationships allow two models to show that they are connected in some
|
84
|
+
way, and can therefore be accessed through each other.
|
85
|
+
|
86
|
+
### Attributes
|
87
|
+
|
88
|
+
Reading attributes is simple. Let's use a {VirtualBox::VM} as an example:
|
89
|
+
|
90
|
+
vm = VirtualBox::VM.find("FooVM")
|
91
|
+
|
92
|
+
# Accessing attributes:
|
93
|
+
vm.memory
|
94
|
+
vm.name
|
95
|
+
vm.boot1
|
96
|
+
vm.ioapic
|
97
|
+
|
98
|
+
### Relationships
|
99
|
+
|
100
|
+
Relationships are read the exact same way as attributes. Again using a
|
101
|
+
{VirtualBox::VM} as an example:
|
102
|
+
|
103
|
+
vm = VirtualBox::VM.find("FooVM")
|
104
|
+
|
105
|
+
# storage_controllers is a relationship containing an array of all the
|
106
|
+
# storage controllers on this VM
|
107
|
+
vm.storage_controllers.each do |sc|
|
108
|
+
puts "Storage Controller: #{sc.uuid}"
|
109
|
+
end
|
110
|
+
|
111
|
+
The difference from an attribute is that while attributes are typically ruby
|
112
|
+
primitives such as `String` or `Boolean`, relationship objects are always other
|
113
|
+
virtualbox models such as {VirtualBox::StorageController}.
|
114
|
+
|
115
|
+
<a name="bc-modifying-models"></a>
|
116
|
+
## Modifying Models
|
117
|
+
|
118
|
+
In addition to simply reading attributes and relationships, most can be modified
|
119
|
+
as well. I say "most" because some attributes are `readonly` and some relationships
|
120
|
+
simply don't support being directly modified (though their objects may, I'll get to
|
121
|
+
this in a moment). By looking at the attribute list it is easy to spot a readonly
|
122
|
+
attribute, which will have the `:readonly` option set to `true`. Below is an example
|
123
|
+
of what you might see in the overview of some model:
|
124
|
+
|
125
|
+
attribute :uuid, :readonly => true
|
126
|
+
|
127
|
+
In the above case, you could read the `uuid` attribute as normal, but it wouldn't support
|
128
|
+
modification (and you'll simply get a `NoMethodError` if you try to set it).
|
129
|
+
|
130
|
+
Relationships are a little bit trickier, since when discussing modifying a relationship,
|
131
|
+
it could either be taken to mean the items _in_ the relationship, or the relationship
|
132
|
+
itself. A good rule of thumb, assuming there exists a relationship `foos`,is if you ever
|
133
|
+
want to do `object.foos =` something, then you're _modifying the relationship_ and _not_
|
134
|
+
the objects. But if you ever do `object.foos[0].destroy`, then you're _modifying the
|
135
|
+
relationship objects_ and _not_ the relationship itself.
|
136
|
+
|
137
|
+
### Attributes
|
138
|
+
|
139
|
+
Attributes which support modification are modified like standard ruby attributes. The
|
140
|
+
following example uses {VirtualBox::HardDrive}:
|
141
|
+
|
142
|
+
hd = VirtualBox::HardDrive.new
|
143
|
+
hd.size = 2000 # megabytes
|
144
|
+
hd.format = "VMDK"
|
145
|
+
|
146
|
+
As you can see, there is nothing sneaky going on here, and does what you expect.
|
147
|
+
|
148
|
+
### Relationships
|
149
|
+
|
150
|
+
Modifying relationships, on the other hand, is a little different. If the model supports
|
151
|
+
modifying the relationship (which it'll note in its respective documentation), then
|
152
|
+
you can set it just like an attribute. Below, we use {VirtualBox::AttachedDevice} as
|
153
|
+
an example:
|
154
|
+
|
155
|
+
ad = VirtualBox::AttachedDevice.new
|
156
|
+
|
157
|
+
# Attached devices have an image relationship
|
158
|
+
ad.image = VirtualBox::DVD.empty_drive
|
159
|
+
|
160
|
+
If a relationship doesn't support setting it, it will raise a {VirtualBox::Exceptions::NonSettableRelationshipException}.
|
161
|
+
|
162
|
+
**Note**: Below is an example of modifying a relationship object, rather than a
|
163
|
+
relationship itself. The example below uses {VirtualBox::VM}.
|
164
|
+
|
165
|
+
vm = VirtualBox::VM.find("FooVM")
|
166
|
+
vm.storage_controllers[0].name = "Foo Controller"
|
167
|
+
|
168
|
+
<a name="bc-saving-models"></a>
|
169
|
+
## Saving Models
|
170
|
+
|
171
|
+
Saving models is _really_ easy: you simply call `save`. That's all! Well, there are
|
172
|
+
some subtleties, but that's the basic idea. `save` will typically **also save relationships**
|
173
|
+
so if you modify a relationship object or relationship itself, calling `save` on the
|
174
|
+
parent object will typically save the relationships as well. `save` always returns
|
175
|
+
`true` or `false` depending on whether the operation was a success or not. If you'd like
|
176
|
+
instead to know why a `save` failed, you can call the method with a `true` parameter
|
177
|
+
which sets `raise_errors` to `true` and will raise a {VirtualBox::Exceptions::CommandFailedException}
|
178
|
+
if there is a failure. The message on this object contains the reason.
|
179
|
+
|
180
|
+
Below is an example of saving a simple {VirtualBox::VM} object:
|
181
|
+
|
182
|
+
vm = VirtualBox::VM.find("FooVM")
|
183
|
+
|
184
|
+
# Double the memory
|
185
|
+
vm.memory = vm.memory.to_i * 2
|
186
|
+
|
187
|
+
# This will return true/false depending on success
|
188
|
+
vm.save
|
189
|
+
|
190
|
+
Below is an example where an exception will be raised if an error occurs:
|
191
|
+
|
192
|
+
vm = VirtualBox::VM.find("FooVM")
|
193
|
+
vm.memory = "INVALID"
|
194
|
+
|
195
|
+
# This will raise an exception, since the memory is invalid
|
196
|
+
vm.save(true)
|
@@ -92,7 +92,21 @@ module VirtualBox
|
|
92
92
|
# @option options [Symbol] :populate_key (attribute name) Specifies
|
93
93
|
# a custom populate key to use for {Attributable#populate_attributes}
|
94
94
|
def attribute(name, options = {})
|
95
|
-
|
95
|
+
name = name.to_sym
|
96
|
+
attributes[name] = options
|
97
|
+
|
98
|
+
# Create the method for reading this attribute
|
99
|
+
define_method(name) { read_attribute(name) }
|
100
|
+
|
101
|
+
# Create the writer method for it unless the attribute is readonly,
|
102
|
+
# then remove the method if it exists
|
103
|
+
if !options[:readonly]
|
104
|
+
define_method("#{name}=") do |value|
|
105
|
+
write_attribute(name, value)
|
106
|
+
end
|
107
|
+
elsif method_defined?("#{name}=")
|
108
|
+
undef_method("#{name}=")
|
109
|
+
end
|
96
110
|
end
|
97
111
|
|
98
112
|
# Returns the hash of attributes and their associated options.
|
@@ -173,21 +187,6 @@ module VirtualBox
|
|
173
187
|
name = name.to_sym
|
174
188
|
has_attribute?(name) && self.class.attributes[name][:readonly]
|
175
189
|
end
|
176
|
-
|
177
|
-
# Method missing is used to add dynamic handlers for accessors and
|
178
|
-
# setters. Due to the general ugliness of `method_missing`, this may
|
179
|
-
# change in the near future, but for now it is what it is.
|
180
|
-
def method_missing(meth, *args)
|
181
|
-
meth_string = meth.to_s
|
182
|
-
|
183
|
-
if has_attribute?(meth)
|
184
|
-
read_attribute(meth)
|
185
|
-
elsif meth_string =~ /^(.+?)=$/ && has_attribute?($1) && !readonly_attribute?($1)
|
186
|
-
write_attribute($1.to_sym, *args)
|
187
|
-
else
|
188
|
-
super
|
189
|
-
end
|
190
|
-
end
|
191
190
|
end
|
192
191
|
end
|
193
192
|
end
|
@@ -74,6 +74,13 @@ module VirtualBox
|
|
74
74
|
# obj.clear_dirty!(:name)
|
75
75
|
# obj.name_changed? # => false
|
76
76
|
#
|
77
|
+
# If no specific field is speciied, `clear_dirty!` will clear the dirty
|
78
|
+
# status on the entire model.
|
79
|
+
#
|
80
|
+
# obj.changed? # => assume true
|
81
|
+
# obj.clear_dirty!
|
82
|
+
# obj.changed? # => false
|
83
|
+
#
|
77
84
|
module Dirty
|
78
85
|
# Manages dirty state for an attribute. This method will handle
|
79
86
|
# setting the dirty state of an attribute (or even clearing it
|
@@ -104,8 +111,12 @@ module VirtualBox
|
|
104
111
|
# Clears dirty state for a field.
|
105
112
|
#
|
106
113
|
# @param [Symbol] key The field to clear dirty state.
|
107
|
-
def clear_dirty!(key)
|
108
|
-
|
114
|
+
def clear_dirty!(key=nil)
|
115
|
+
if key.nil?
|
116
|
+
@changed_attributes = {}
|
117
|
+
else
|
118
|
+
changes.delete(key)
|
119
|
+
end
|
109
120
|
end
|
110
121
|
|
111
122
|
# Ignores any dirty changes during the duration of the block.
|
@@ -39,6 +39,37 @@ module VirtualBox
|
|
39
39
|
# # Accessing through an instance "instance"
|
40
40
|
# instance.bacons # => whatever Bacon.populate_relationship created
|
41
41
|
#
|
42
|
+
# # Settable Relationships
|
43
|
+
#
|
44
|
+
# It is often convenient that relationships become "settable." That is,
|
45
|
+
# for a relationship `foos`, there would exist a `foos=` method. This is
|
46
|
+
# possible by implementing the `set_relationship` method on the relationship
|
47
|
+
# class. Consider the following relationship:
|
48
|
+
#
|
49
|
+
# relationship :foos, Foo
|
50
|
+
#
|
51
|
+
# If `Foo` has the `set_relationship` method, then it will be called by
|
52
|
+
# `foos=`. It is expected to return the new value for the relationship. To
|
53
|
+
# facilitate this need, the `set_relationship` method is given three
|
54
|
+
# parameters: caller, old value, and new value. An example implementation,
|
55
|
+
# albeit a silly one, is below:
|
56
|
+
#
|
57
|
+
# class Foo
|
58
|
+
# def self.set_relationship(caller, old_value, new_value)
|
59
|
+
# return "Changed to: #{new_value}"
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# In this case, the following behavior would occur:
|
64
|
+
#
|
65
|
+
# instance.foos # => assume "foo"
|
66
|
+
# instance.foos = "bar"
|
67
|
+
# instance.foos # => "Changed to: bar"
|
68
|
+
#
|
69
|
+
# If the relationship class _does not implement_ the `set_relationship`
|
70
|
+
# method, then a {Exceptions::NonSettableRelationshipException} will be raised if
|
71
|
+
# a user attempts to set that relationship.
|
72
|
+
#
|
42
73
|
# # Dependent Relationships
|
43
74
|
#
|
44
75
|
# By setting `:dependent => :destroy` on relationships, {AbstractModel}
|
@@ -63,8 +94,16 @@ module VirtualBox
|
|
63
94
|
# @option options [Symbol] :dependent (nil) - If set to `:destroy`
|
64
95
|
# {AbstractModel#destroy} will propagate through to relationships.
|
65
96
|
def relationship(name, klass, options = {})
|
97
|
+
name = name.to_sym
|
98
|
+
|
66
99
|
@relationships ||= {}
|
67
100
|
@relationships[name] = { :klass => klass }.merge(options)
|
101
|
+
|
102
|
+
# Define the method to read the relationship
|
103
|
+
define_method(name) { relationship_data[name] }
|
104
|
+
|
105
|
+
# Define the method to set the relationship
|
106
|
+
define_method("#{name}=") { |*args| set_relationship(name, *args) }
|
68
107
|
end
|
69
108
|
|
70
109
|
# Returns a hash of all the relationships.
|
@@ -147,16 +186,26 @@ module VirtualBox
|
|
147
186
|
self.class.relationships.has_key?(key.to_sym)
|
148
187
|
end
|
149
188
|
|
150
|
-
#
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
189
|
+
# Sets a relationship to the given value. This is not guaranteed to
|
190
|
+
# do anything, since "set_relationship" will be called on the class
|
191
|
+
# that the relationship is associated with and its expected to return
|
192
|
+
# the resulting relationship to set.
|
193
|
+
#
|
194
|
+
# If the relationship class doesn't respond to the set_relationship
|
195
|
+
# method, then an exception {Exceptions::NonSettableRelationshipException} will
|
196
|
+
# be raised.
|
197
|
+
#
|
198
|
+
# This method is called by the "magic" method of `relationship=`.
|
199
|
+
#
|
200
|
+
# @param [Symbol] key Relationship key.
|
201
|
+
# @param [Object] value The new value of the relationship.
|
202
|
+
def set_relationship(key, value)
|
203
|
+
key = key.to_sym
|
204
|
+
relationship = self.class.relationships[key]
|
205
|
+
return unless relationship
|
206
|
+
|
207
|
+
raise Exceptions::NonSettableRelationshipException.new unless relationship[:klass].respond_to?(:set_relationship)
|
208
|
+
relationship_data[key] = relationship[:klass].set_relationship(self, relationship_data[key], value)
|
160
209
|
end
|
161
210
|
end
|
162
211
|
end
|
@@ -18,10 +18,24 @@ module VirtualBox
|
|
18
18
|
# is {HardDrive#save} which will create a new hard drive if it didn't
|
19
19
|
# previously exist, or save an old one if it did exist.
|
20
20
|
def new_record?
|
21
|
-
|
21
|
+
new_record! if @new_record.nil?
|
22
22
|
@new_record
|
23
23
|
end
|
24
24
|
|
25
|
+
# Explicitly resets the model to a new record. If you're using this
|
26
|
+
# method outside of virtualbox library core, you should really be
|
27
|
+
# asking yourself "why?"
|
28
|
+
def new_record!
|
29
|
+
@new_record = true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Explicitly sets the model to not be a new record. If you're using
|
33
|
+
# this method outside of virtualbox library core, you should really
|
34
|
+
# be asking yourself "why?"
|
35
|
+
def existing_record!
|
36
|
+
@new_record = false
|
37
|
+
end
|
38
|
+
|
25
39
|
# Saves the model attributes and relationships.
|
26
40
|
#
|
27
41
|
# The method can be passed any arbitrary arguments, which are
|
@@ -81,6 +95,14 @@ module VirtualBox
|
|
81
95
|
super
|
82
96
|
end
|
83
97
|
|
98
|
+
# Overwrites {Relatable#set_relationship} to set the dirty state of the
|
99
|
+
# relationship. See {Dirty#set_dirty!} as well.
|
100
|
+
def set_relationship(key, value)
|
101
|
+
existing = relationship_data[key]
|
102
|
+
new_value = super
|
103
|
+
set_dirty!(key, existing, new_value)
|
104
|
+
end
|
105
|
+
|
84
106
|
# Destroys the model. The exact behaviour of this method is expected to be
|
85
107
|
# defined on the subclasses. This method on AbstractModel simply
|
86
108
|
# propagates the destroy to the dependent relationships. For more information
|
@@ -2,9 +2,39 @@ module VirtualBox
|
|
2
2
|
# Represents an device which is attached to a storage controller. An example
|
3
3
|
# of such a device would be a CD or hard drive attached to an IDE controller.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# # Creating a New Attached Device
|
6
|
+
#
|
7
|
+
# Creating a new attached device is simple. The following is a simple example
|
8
|
+
# of creating a DVD with an empty drive:
|
9
|
+
#
|
10
|
+
# ad = VirtualBox::AttachedDevice.new
|
11
|
+
# ad.port = 0
|
12
|
+
# ad.image = VirtualBox::DVD.empty_drive
|
13
|
+
# storage_controller.devices << ad
|
14
|
+
# ad.save
|
15
|
+
#
|
16
|
+
# The only quirk is that the attached device **must** be attached to a
|
17
|
+
# storage controller. The above assumes that `storage_controller` exists,
|
18
|
+
# which adds the device.
|
19
|
+
#
|
20
|
+
# Any {Image} subclass can be set to the `image` relationship.
|
21
|
+
#
|
22
|
+
# The following is an example using {VM.find}:
|
23
|
+
#
|
24
|
+
# # First creating the new device...
|
25
|
+
# ad = VirtualBox::AttachedDevice.new
|
26
|
+
# ad.port = 0
|
27
|
+
# ad.image = VirtualBox::DVD.empty_drive
|
28
|
+
#
|
29
|
+
# # Now attaching to existing VM
|
30
|
+
# vm = VirtualBox::VM.find("FooVM")
|
31
|
+
# vm.storage_controllers[0].devices << ad
|
32
|
+
# vm.save
|
33
|
+
#
|
34
|
+
# The interesting thing in this example is that the `save` method is called on
|
35
|
+
# the virtual machine rather than the AttachedDevice. This will actually work
|
36
|
+
# as expected! Saving a virtual machine automatically saves all it's relationships
|
37
|
+
# as well.
|
8
38
|
#
|
9
39
|
# # Attributes and Relationships
|
10
40
|
#
|
@@ -40,8 +70,7 @@ module VirtualBox
|
|
40
70
|
#
|
41
71
|
class AttachedDevice < AbstractModel
|
42
72
|
attribute :parent, :readonly => true
|
43
|
-
attribute :uuid
|
44
|
-
attribute :medium
|
73
|
+
attribute :uuid, :readonly => true
|
45
74
|
attribute :port
|
46
75
|
relationship :image, Image
|
47
76
|
|
@@ -52,7 +81,7 @@ module VirtualBox
|
|
52
81
|
#
|
53
82
|
# @return [Array<AttachedDevice>]
|
54
83
|
def populate_relationship(caller, data)
|
55
|
-
relation =
|
84
|
+
relation = Proxies::Collection.new(caller)
|
56
85
|
|
57
86
|
counter = 0
|
58
87
|
loop do
|
@@ -71,20 +100,87 @@ module VirtualBox
|
|
71
100
|
def destroy_relationship(caller, data, *args)
|
72
101
|
data.each { |v| v.destroy(*args) }
|
73
102
|
end
|
103
|
+
|
104
|
+
# Saves the relationship. This simply calls {#save} on every
|
105
|
+
# member of the relationship.
|
106
|
+
#
|
107
|
+
# **This method typically won't be used except internally.**
|
108
|
+
def save_relationship(caller, data)
|
109
|
+
# Just call save on each nic with the VM
|
110
|
+
data.each do |ad|
|
111
|
+
ad.save
|
112
|
+
end
|
113
|
+
end
|
74
114
|
end
|
75
115
|
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
116
|
+
# @overload initialize(data={})
|
117
|
+
# Creates a new AttachedDevice which is a new record. This
|
118
|
+
# should be attached to a storage controller and saved.
|
119
|
+
# @param [Hash] data (optional) A hash which contains initial attribute
|
120
|
+
# values for the AttachedDevice.
|
121
|
+
# @overload initialize(index, caller, data)
|
122
|
+
# Creates an AttachedDevice for a relationship. **This should
|
123
|
+
# never be called except internally.**
|
124
|
+
# @param [Integer] index Index of the port
|
125
|
+
# @param [Object] caller The parent
|
126
|
+
# @param [Hash] data A hash of data which must be used
|
127
|
+
# to extract the relationship data.
|
128
|
+
def initialize(*args)
|
80
129
|
super()
|
81
130
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
131
|
+
if args.length == 3
|
132
|
+
populate_from_data(*args)
|
133
|
+
elsif args.length == 1
|
134
|
+
populate_attributes(*args)
|
135
|
+
new_record!
|
136
|
+
elsif args.empty?
|
137
|
+
return
|
138
|
+
else
|
139
|
+
raise NoMethodError.new
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Saves or creates an attached device.
|
144
|
+
#
|
145
|
+
# @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
|
146
|
+
# will be raised if the command failed.
|
147
|
+
# @return [Boolean] True if command was successful, false otherwise.
|
148
|
+
def save(raise_errors=false)
|
149
|
+
raise Exceptions::NoParentException.new if parent.nil?
|
150
|
+
raise Exceptions::InvalidObjectException.new("Image must be set") if image.nil?
|
151
|
+
return true unless changed?
|
152
|
+
|
153
|
+
# If the port changed, we have to destroy the old one, then create
|
154
|
+
# a new one
|
155
|
+
destroy({:port => port_was}, raise_errors) if port_changed? && !port_was.nil?
|
156
|
+
|
157
|
+
Command.vboxmanage("storageattach #{Command.shell_escape(parent.parent.name)} --storagectl #{Command.shell_escape(parent.name)} --port #{port} --device 0 --type #{image.image_type} --medium #{medium}")
|
158
|
+
existing_record!
|
159
|
+
clear_dirty!
|
160
|
+
|
161
|
+
true
|
162
|
+
rescue Exceptions::CommandFailedException
|
163
|
+
raise if raise_errors
|
164
|
+
false
|
165
|
+
end
|
166
|
+
|
167
|
+
# Medium of the attached image. This attribute will be dependent
|
168
|
+
# on the attached image and will return one of the following values:
|
169
|
+
#
|
170
|
+
# * **none** - There is no attached image
|
171
|
+
# * **emptydrive** - An image with an empty drive is attached (see
|
172
|
+
# {DVD.empty_drive})
|
173
|
+
# * **image uuid** - The image's UUID
|
174
|
+
#
|
175
|
+
# @return [String]
|
176
|
+
def medium
|
177
|
+
if image.nil?
|
178
|
+
"none"
|
179
|
+
elsif image.empty_drive?
|
180
|
+
"emptydrive"
|
181
|
+
else
|
182
|
+
image.uuid
|
183
|
+
end
|
88
184
|
end
|
89
185
|
|
90
186
|
# Destroys the attached device. By default, this only removes any
|
@@ -94,11 +190,40 @@ module VirtualBox
|
|
94
190
|
#
|
95
191
|
# @option options [Boolean] :destroy_image (false) If true, will also
|
96
192
|
# destroy the image associated with device.
|
97
|
-
|
193
|
+
# @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
|
194
|
+
# will be raised if the command failed.
|
195
|
+
# @return [Boolean] True if command was successful, false otherwise.
|
196
|
+
def destroy(options={}, raise_errors=false)
|
98
197
|
# parent = storagecontroller
|
99
198
|
# parent.parent = vm
|
100
|
-
|
101
|
-
|
199
|
+
destroy_port = options[:port] || port
|
200
|
+
Command.vboxmanage("storageattach #{Command.shell_escape(parent.parent.name)} --storagectl #{Command.shell_escape(parent.name)} --port #{destroy_port} --device 0 --medium none")
|
201
|
+
image.destroy(raise_errors) if options[:destroy_image] && image
|
202
|
+
rescue Exceptions::CommandFailedException
|
203
|
+
raise if raise_errors
|
204
|
+
false
|
205
|
+
end
|
206
|
+
|
207
|
+
# Relationship callback when added to a collection. This is automatically
|
208
|
+
# called by any relationship collection when this object is added.
|
209
|
+
def added_to_relationship(parent)
|
210
|
+
write_attribute(:parent, parent)
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
# Populates the model based on data from a parsed vminfo. This
|
216
|
+
# method is used to create a model which already exists and is
|
217
|
+
# part of a relationship.
|
218
|
+
#
|
219
|
+
# **This method should never be called except internally.**
|
220
|
+
def populate_from_data(index, caller, data)
|
221
|
+
populate_attributes({
|
222
|
+
:parent => caller,
|
223
|
+
:port => index,
|
224
|
+
:medium => data["#{caller.name}-#{index}-0".downcase.to_sym],
|
225
|
+
:uuid => data["#{caller.name}-ImageUUID-#{index}-0".downcase.to_sym]
|
226
|
+
})
|
102
227
|
end
|
103
228
|
end
|
104
229
|
end
|