virtualbox 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.1
1
+ 0.7.2
@@ -111,7 +111,6 @@ module VirtualBox
111
111
  # Go through and only save the loaded relationships, since
112
112
  # only those would be modified.
113
113
  self.class.relationships.each do |name, options|
114
- next if lazy_relationship?(name) && !loaded_relationship?(name)
115
114
  save_relationship(name, *args)
116
115
  end
117
116
 
@@ -226,6 +225,16 @@ module VirtualBox
226
225
  end
227
226
  end
228
227
 
228
+ # Gets the root machine of an AbstractModel by traversing a
229
+ # `parent` attribute until it reaches a type of {VM}.
230
+ #
231
+ # @return [VM]
232
+ def parent_machine
233
+ current = parent
234
+ current = current.parent while current && !current.is_a?(VM)
235
+ current
236
+ end
237
+
229
238
  # Creates a human-readable format for this model. This method overrides the
230
239
  # default `#<class>` syntax since this doesn't work well for AbstractModels.
231
240
  # Instead, it abbreviates it, instead showing all the attributes and their
@@ -1,3 +1,5 @@
1
+ require 'virtualbox/abstract_model/version_matcher'
2
+
1
3
  module VirtualBox
2
4
  class AbstractModel
3
5
  # Module which can be included into any other class which allows
@@ -37,6 +39,18 @@ module VirtualBox
37
39
  # The example above applies a default value to format. So if no value
38
40
  # is ever given to it, `format` would return `VDI`.
39
41
  #
42
+ # ## Attributes for a Specific VirtualBox Version
43
+ #
44
+ # Attributes change with different VirtualBox versions. One way to
45
+ # provide version-specific behavior is to specify the version
46
+ # which the attribute applies to.
47
+ #
48
+ # attribute :name, :version => "3.2"
49
+ # attribute :age, :version => "3.1"
50
+ #
51
+ # These versions only apply to major and minor releases of
52
+ # VirtualBox. Patch releases can't be specified (such as "3.2.4")
53
+ #
40
54
  # ## Populating Multiple Attributes
41
55
  #
42
56
  # Attributes can be mass populated using {#populate_attributes}. Below
@@ -118,6 +132,8 @@ module VirtualBox
118
132
  # puts model.expensive_attribute # => 42
119
133
  #
120
134
  module Attributable
135
+ include VersionMatcher
136
+
121
137
  def self.included(base)
122
138
  base.extend ClassMethods
123
139
  end
@@ -224,13 +240,16 @@ module VirtualBox
224
240
  # if specified.
225
241
  def read_attribute(name)
226
242
  if has_attribute?(name)
243
+ options = self.class.attributes[name]
244
+
245
+ assert_version_match(options[:version], VirtualBox.version) if options[:version]
227
246
  if lazy_attribute?(name) && !loaded_attribute?(name)
228
247
  # Load the lazy attribute
229
248
  load_attribute(name.to_sym)
230
249
  end
231
250
 
232
251
  if attributes[name].nil?
233
- self.class.attributes[name][:default]
252
+ options[:default]
234
253
  else
235
254
  attributes[name]
236
255
  end
@@ -268,4 +287,4 @@ module VirtualBox
268
287
  end
269
288
  end
270
289
  end
271
- end
290
+ end
@@ -23,6 +23,7 @@ module VirtualBox
23
23
  return unless has_attribute?(key)
24
24
  options = self.class.attributes[key.to_sym]
25
25
  return if options.has_key?(:property) && !options[:property]
26
+ return if options.has_key?(:version) && !version_match?(options[:version], VirtualBox.version)
26
27
  getter = options[:property] || options[:property_getter] || key.to_sym
27
28
  return unless getter
28
29
 
@@ -49,6 +50,7 @@ module VirtualBox
49
50
  options = self.class.attributes[key.to_sym]
50
51
  return if options[:readonly]
51
52
  return if options.has_key?(:property) && !options[:property]
53
+ return if options.has_key?(:version) && !version_match?(options[:version], VirtualBox.version)
52
54
 
53
55
  setter = options[:property] || options[:property_setter] || "#{key}=".to_sym
54
56
  return unless setter
@@ -93,4 +95,4 @@ module VirtualBox
93
95
  end
94
96
  end
95
97
  end
96
- end
98
+ end
@@ -1,3 +1,5 @@
1
+ require 'virtualbox/abstract_model/version_matcher'
2
+
1
3
  module VirtualBox
2
4
  class AbstractModel
3
5
  # Provides simple relationship features to any class. These relationships
@@ -119,6 +121,8 @@ module VirtualBox
119
121
  # relationship, it will first load the relationship, since destroy typically
120
122
  # depends on some data of the relationship.
121
123
  module Relatable
124
+ include VersionMatcher
125
+
122
126
  def self.included(base)
123
127
  base.extend ClassMethods
124
128
  end
@@ -181,6 +185,9 @@ module VirtualBox
181
185
  # Reads a relationship. This is equivalent to {Attributable#read_attribute},
182
186
  # but for relationships.
183
187
  def read_relationship(name)
188
+ options = self.class.relationships_hash[name.to_sym]
189
+ assert_version_match(options[:version], VirtualBox.version) if options[:version]
190
+
184
191
  if lazy_relationship?(name) && !loaded_relationship?(name)
185
192
  load_relationship(name)
186
193
  end
@@ -209,6 +216,8 @@ module VirtualBox
209
216
  # calls `save_relationship` on the relationship class.
210
217
  def save_relationship(name, *args)
211
218
  options = self.class.relationships_hash[name]
219
+ return if lazy_relationship?(name) && !loaded_relationship?(name)
220
+ return if options[:version] && !version_match?(options[:version], VirtualBox.version)
212
221
  return unless relationship_class(name).respond_to?(:save_relationship)
213
222
  relationship_class(name).save_relationship(self, relationship_data[name], *args)
214
223
  end
@@ -225,6 +234,7 @@ module VirtualBox
225
234
  def populate_relationship(name, data)
226
235
  options = self.class.relationships_hash[name]
227
236
  return unless relationship_class(name).respond_to?(:populate_relationship)
237
+ return if options[:version] && !version_match?(options[:version], VirtualBox.version)
228
238
  relationship_data[name] = relationship_class(name).populate_relationship(self, data)
229
239
  end
230
240
 
@@ -315,4 +325,4 @@ module VirtualBox
315
325
  end
316
326
  end
317
327
  end
318
- end
328
+ end
@@ -0,0 +1,33 @@
1
+ module VirtualBox
2
+ class AbstractModel
3
+ module VersionMatcher
4
+ # Asserts that two versions match. Otherwise raises an
5
+ # exception.
6
+ def assert_version_match(req, cur)
7
+ if !version_match?(req, cur)
8
+ message = "Required version: #{req}; Current: #{cur}"
9
+ raise Exceptions::UnsupportedVersionException.new(message)
10
+ end
11
+ end
12
+
13
+ # Checks if a given version requirement matches the current
14
+ # version.
15
+ #
16
+ # @return [Boolean]
17
+ def version_match?(requirement, current)
18
+ split_version(requirement) == split_version(current)
19
+ end
20
+
21
+ # Splits a version string into a two-item array with the parts
22
+ # of the version, respectively. If the version has more than two
23
+ # parts, the rest are ignored.
24
+ #
25
+ # @param [String] version
26
+ # @return [Array]
27
+ def split_version(version)
28
+ version.split(/\./)[0,2]
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -5,6 +5,9 @@ module VirtualBox
5
5
  # file should be conditionally loaded based on OS, so that Windows users
6
6
  # don't have to wait for all this translation to occur.
7
7
  def self.setup(version)
8
+ # TODO: This is so hacky and hard to maintain. Can we
9
+ # programatically get the modules in a namespace and
10
+ # instantiate them somehow?
8
11
  for_version version do
9
12
  create_interface(:NSISupports)
10
13
  create_interface(:NSIException, :NSISupports)
@@ -39,8 +42,13 @@ module VirtualBox
39
42
 
40
43
  create_interface(:HostUSBDevice, :USBDevice)
41
44
  create_interface(:HostUSBDeviceFilter, :USBDeviceFilter)
45
+
46
+ # 3.2.x interfaces
47
+ if version == "3.2.x"
48
+ create_interface(:NATEngine, :NSISupports)
49
+ end
42
50
  end
43
51
  end
44
52
  end
45
53
  end
46
- end
54
+ end
@@ -10,6 +10,7 @@ module VirtualBox
10
10
  class MediumCreationFailedException < Exception; end
11
11
  class MediumNotUpdatableException < Exception; end
12
12
  class ReadonlyVMStateException < Exception; end
13
+ class UnsupportedVersionException < Exception; end
13
14
 
14
15
  class FFIException < Exception
15
16
  attr_accessor :data
@@ -35,4 +36,4 @@ module VirtualBox
35
36
  class InvalidSessionStateException < FFIException; end
36
37
  class ObjectInUseException < FFIException; end
37
38
  end
38
- end
39
+ end
@@ -121,7 +121,7 @@ module VirtualBox
121
121
  # @param [Hash] data The initial attributes to populate.
122
122
  def initialize(data={})
123
123
  super()
124
- populate_attributes(data)
124
+ populate_attributes(data) if !data.empty?
125
125
  end
126
126
 
127
127
  # Validates a forwarded port.
@@ -0,0 +1,71 @@
1
+ module VirtualBox
2
+ # Represents a NAT engine for a given {NetworkAdapter}. This data is
3
+ # available through the `nat_driver` relationship on
4
+ # {NetworkAdapter} only if the adapter is a NAT adapter.
5
+ class NATEngine < AbstractModel
6
+ attribute :parent, :readonly => true, :property => false
7
+ attribute :network
8
+ attribute :tftp_prefix
9
+ attribute :tftp_boot_file
10
+ attribute :tftp_next_server
11
+ attribute :alias_mode
12
+ attribute :dns_pass_domain
13
+ attribute :dns_proxy
14
+ attribute :dns_use_host_resolver
15
+ attribute :interface, :readonly => true, :property => false
16
+ relationship :forwarded_ports, :NATForwardedPort
17
+
18
+ class << self
19
+ # Populates the NAT engine for anything which is related to it.
20
+ #
21
+ # **This method typically won't be used except internally.**
22
+ #
23
+ # @return [NATEngine]
24
+ def populate_relationship(caller, inat)
25
+ return nil if inat.nil?
26
+ new(caller, inat)
27
+ end
28
+
29
+ # Saves the relationship. This simply calls {#save} on every
30
+ # member of the relationship.
31
+ #
32
+ # **This method typically won't be used except internally.**
33
+ def save_relationship(caller, item)
34
+ item.save
35
+ end
36
+ end
37
+
38
+ def initialize(caller, inat)
39
+ super()
40
+ initialize_attributes(caller, inat)
41
+ end
42
+
43
+ # Initializes the attributes of an existing NAT engine.
44
+ def initialize_attributes(parent, inat)
45
+ write_attribute(:parent, parent)
46
+ write_attribute(:interface, inat)
47
+
48
+ # Load the interface attributes associated with this model
49
+ load_interface_attributes(inat)
50
+ populate_relationships(inat)
51
+
52
+ # Clear dirty and set as existing
53
+ clear_dirty!
54
+ existing_record!
55
+ end
56
+
57
+ def save
58
+ modify_engine do |nat|
59
+ save_changed_interface_attributes(nat)
60
+ save_relationships
61
+ end
62
+ end
63
+
64
+ # Helper method to get the mutable `INATEngine` interface.
65
+ def modify_engine
66
+ parent.modify_adapter do |adapter|
67
+ yield adapter.nat_driver
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,171 @@
1
+ module VirtualBox
2
+ # When a VM uses NAT as its NIC type, VirtualBox acts like its
3
+ # own private router for all virtual machines. Because of this,
4
+ # the host machine can't access services within the guest machine.
5
+ # To get around this, NAT supports port forwarding, which allows the
6
+ # guest machine services to be forwarded to some port on the host
7
+ # machine. Port forwarding is done completely through {ExtraData}, but
8
+ # is a complicated enough procedure that this class was made to
9
+ # faciliate it.
10
+ #
11
+ # **Note:** After changing any forwarded ports, the entire VirtualBox
12
+ # process must be restarted completely for them to take effect. When
13
+ # working with the ruby library, this isn't so much of an issue, but
14
+ # if you have any VMs running, they must all be shut down and restarted.
15
+ #
16
+ # # Adding a new Forwarded Port
17
+ #
18
+ # Since forwarded ports rely on being part of a {VM}, we're going to
19
+ # assume that `vm` points to a {VM} which has already been found.
20
+ #
21
+ # port = VirtualBox::NATForwardedPort.new
22
+ # port.name = "apache" # This can be anything
23
+ # port.guestport = 80
24
+ # port.hostport = 8080
25
+ # vm.network_adapters[0].nat_driver.forwarded_ports << port
26
+ # port.save # Or vm.save
27
+ #
28
+ # # Modifying an Existing Forwarded Port
29
+ #
30
+ # This is assuming that `vm` is a local variable storing a {VM} object
31
+ # which has already been found.
32
+ #
33
+ # ports = vm.network_adapters[0].nat_driver.forwarded_ports
34
+ # ports.first.hostport = 1919
35
+ # vm.save
36
+ #
37
+ # # Deleting a Forwarded Port
38
+ #
39
+ # To delete a forwarded port, you simply destroy it like any other model:
40
+ #
41
+ # ports = vm.network_adapters[0].nat_driver.forwarded_ports
42
+ # ports.first.destroy
43
+ #
44
+ # # Attributes and Relationships
45
+ #
46
+ # Properties of the model are exposed using standard ruby instance
47
+ # methods which are generated on the fly. Because of this, they are not listed
48
+ # below as available instance methods.
49
+ #
50
+ # These attributes can be accessed and modified via standard ruby-style
51
+ # `instance.attribute` and `instance.attribute=` methods. The attributes are
52
+ # listed below.
53
+ #
54
+ # Relationships are also accessed like attributes but can't be set. Instead,
55
+ # they are typically references to other objects such as an {AttachedDevice} which
56
+ # in turn have their own attributes which can be modified.
57
+ #
58
+ # ## Attributes
59
+ #
60
+ # This is copied directly from the class header, but lists all available
61
+ # attributes. If you don't understand what this means, read {Attributable}.
62
+ #
63
+ # attribute :parent, :readonly => true
64
+ # attribute :name
65
+ # attribute :protocol, :default => "TCP"
66
+ # attribute :guestport
67
+ # attribute :hostport
68
+ #
69
+ class NATForwardedPort < AbstractModel
70
+ attribute :parent, :readonly => true, :property => false
71
+ attribute :parent_collection, :readonly => true, :property => false
72
+ attribute :name
73
+ attribute :protocol, :default => :tcp
74
+ attribute :guestport
75
+ attribute :hostport
76
+
77
+ class << self
78
+ # Populates a relationship with another model.
79
+ #
80
+ # **This method typically won't be used except internally.**
81
+ #
82
+ # @return [Array<NATForwardedPort>]
83
+ def populate_relationship(caller, interface)
84
+ relation = Proxies::Collection.new(caller)
85
+
86
+ interface.redirects.each do |key, value|
87
+ parts = key.split(",")
88
+
89
+ port = new({
90
+ :parent => caller,
91
+ :parent_collection => relation,
92
+ :name => parts[0],
93
+ :protocol => COM::Util.versioned_interface(:NATProtocol).index(parts[1]),
94
+ :guestport => parts[5],
95
+ :hostport => parts[3]
96
+ })
97
+
98
+ port.existing_record!
99
+
100
+ relation.push(port)
101
+ end
102
+
103
+ relation
104
+ end
105
+
106
+ # Saves the relationship. This simply calls {#save} on every
107
+ # member of the relationship.
108
+ #
109
+ # **This method typically won't be used except internally.**
110
+ def save_relationship(caller, data)
111
+ data.dup.each do |fp|
112
+ fp.save
113
+ end
114
+ end
115
+ end
116
+
117
+ # @param [Hash] data The initial attributes to populate.
118
+ def initialize(data={})
119
+ super()
120
+ populate_attributes(data) if !data.empty?
121
+ end
122
+
123
+ # Validates a forwarded port.
124
+ def validate
125
+ super
126
+
127
+ validates_presence_of :parent
128
+ validates_presence_of :name
129
+ validates_presence_of :guestport
130
+ validates_presence_of :hostport
131
+ end
132
+
133
+ # Saves the forwarded port.
134
+ #
135
+ # @return [Boolean] True if command was successful, false otherwise.
136
+ def save
137
+ return true if !new_record? && !changed?
138
+ raise Exceptions::ValidationFailedException.new(errors) if !valid?
139
+ destroy if !new_record?
140
+
141
+ parent.modify_engine do |nat|
142
+ nat.add_redirect(name, protocol, "", hostport, "", guestport)
143
+ end
144
+
145
+ clear_dirty!
146
+ existing_record!
147
+ true
148
+ end
149
+
150
+ # Destroys the port forwarding mapping.
151
+ #
152
+ # @return [Boolean] True if command was successful, false otherwise.
153
+ def destroy
154
+ return if new_record?
155
+ previous_name = name_changed? ? name_was : name
156
+ parent.modify_engine do |nat|
157
+ nat.remove_redirect(previous_name)
158
+ end
159
+ parent_collection.delete(self, true) if parent_collection
160
+ new_record!
161
+ true
162
+ end
163
+
164
+ # Relationship callback when added to a collection. This is automatically
165
+ # called by any relationship collection when this object is added.
166
+ def added_to_relationship(proxy)
167
+ write_attribute(:parent, proxy.parent)
168
+ write_attribute(:parent_collection, proxy)
169
+ end
170
+ end
171
+ end