ovirt 0.1.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.
@@ -0,0 +1,177 @@
1
+ module Ovirt
2
+ class Inventory
3
+ attr_accessor :service
4
+
5
+ def initialize(options={})
6
+ @service = Service.new(options)
7
+ end
8
+
9
+ def api
10
+ standard_collection('api').first
11
+ end
12
+
13
+ def capabilities
14
+ standard_collection("capabilities")
15
+ end
16
+
17
+ def clusters
18
+ standard_collection("clusters")
19
+ end
20
+
21
+ def datacenters
22
+ standard_collection("datacenters", "data_center")
23
+ end
24
+
25
+ def domains
26
+ standard_collection("domains")
27
+ end
28
+
29
+ def events(since = nil)
30
+ if since.nil?
31
+ standard_collection("events")
32
+ else
33
+ standard_collection("events?from=#{since}", "event")
34
+ end
35
+ end
36
+
37
+ def groups
38
+ standard_collection("groups")
39
+ end
40
+
41
+ def hosts
42
+ standard_collection("hosts", nil, true)
43
+ end
44
+
45
+ def networks
46
+ standard_collection("networks")
47
+ end
48
+
49
+ def roles
50
+ standard_collection("roles")
51
+ end
52
+
53
+ def storagedomains
54
+ standard_collection("storagedomains", "storage_domain", true)
55
+ end
56
+
57
+ def tags
58
+ standard_collection("tags")
59
+ end
60
+
61
+ def templates
62
+ standard_collection("templates")
63
+ end
64
+
65
+ def users
66
+ standard_collection("users")
67
+ end
68
+
69
+ def vms
70
+ standard_collection("vms", nil, true)
71
+ end
72
+
73
+ def vmpools
74
+ standard_collection("vmpools")
75
+ end
76
+
77
+ def get_vm(path)
78
+ vm_guid = File.basename(path, '.*')
79
+ vm = get_resource_by_ems_ref("/api/vms/#{vm_guid}") rescue nil
80
+ vm = get_resource_by_ems_ref("/api/templates/#{vm_guid}") if vm.blank?
81
+ return vm
82
+ end
83
+
84
+ def get_resource_by_ems_ref(uri_suffix, element_name = nil)
85
+ @service.get_resource_by_ems_ref(uri_suffix, element_name)
86
+ end
87
+
88
+ def refresh
89
+ # TODO: Change to not return native objects to the caller. The caller
90
+ # should just expect raw data.
91
+ primary_items = collect_primary_items
92
+ collect_secondary_items(primary_items)
93
+ end
94
+
95
+ private
96
+
97
+ def standard_collection(uri_suffix, element_name = nil, paginate=false, sort_by=:name)
98
+ @service.standard_collection(uri_suffix, element_name, paginate, sort_by)
99
+ end
100
+
101
+ # TODO: Remove this key/method translation and just use the method name as
102
+ # the key directly.
103
+ PRIMARY_ITEMS = {
104
+ # Key RHEVM API method
105
+ :cluster => :clusters,
106
+ :vmpool => :vmpools,
107
+ :network => :networks,
108
+ :storage => :storagedomains,
109
+ :datacenter => :datacenters,
110
+ :host => :hosts,
111
+ :vm => :vms,
112
+ :template => :templates
113
+ }
114
+
115
+ SECONDARY_ITEMS = {
116
+ # Key RHEVM API methods
117
+ :datacenter => [:storagedomains],
118
+ :host => [:statistics, :host_nics], # :cdroms, tags
119
+ :vm => [:disks, :snapshots, :nics],
120
+ :template => [:disks]
121
+ }
122
+
123
+ def primary_item_jobs
124
+ PRIMARY_ITEMS.to_a
125
+ end
126
+
127
+ # Returns all combinations of primary resources and the methods to run on those resources.
128
+ #
129
+ # > secondary_item_jobs({:vm, => [v1, v2]})
130
+ # => [[v1, :disks], [v1, :snapshots], [v1, :nics], [v2, :disks], [v2, :snapshots], [v2, :nics]]
131
+ def secondary_item_jobs(primary_items)
132
+ SECONDARY_ITEMS.collect do |key, methods|
133
+ primary_items[key].product(methods)
134
+ end.flatten(1)
135
+ end
136
+
137
+ def collect_primary_items
138
+ jobs = primary_item_jobs
139
+
140
+ results = collect_in_parallel(jobs) do |_, method|
141
+ self.send(method)
142
+ end
143
+
144
+ jobs.zip(results).each_with_object({}) do |((key, _), result), hash|
145
+ hash[key] = result
146
+ end
147
+ end
148
+
149
+ def collect_secondary_items(primary_items)
150
+ jobs = secondary_item_jobs(primary_items)
151
+
152
+ results = collect_in_parallel(jobs) do |resource, method|
153
+ resource.send(method) rescue nil
154
+ end
155
+
156
+ jobs.zip(results).each do |(resource, method), result|
157
+ resource.attributes[method] = result
158
+ end
159
+
160
+ primary_items
161
+ end
162
+
163
+ def collect_in_parallel(jobs, &block)
164
+ require 'parallel'
165
+ Parallel.map(jobs, :in_threads => num_threads, &block)
166
+ end
167
+
168
+ def num_threads
169
+ use_threads? ? 8 : 0
170
+ end
171
+
172
+ # HACK: VCR is not threadsafe, and so tests running under VCR fail
173
+ def use_threads?
174
+ !defined?(VCR)
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,16 @@
1
+ module Ovirt
2
+ class Network < Object
3
+
4
+ self.top_level_strings = [:name, :description]
5
+ self.top_level_booleans = [:stp, :display]
6
+ self.top_level_objects = [:data_center, :cluster, :vlan]
7
+
8
+ def self.parse_xml(xml)
9
+ node, hash = xml_to_hash(xml)
10
+
11
+ parse_first_node(node, :status, hash, :node => [:state])
12
+
13
+ hash
14
+ end
15
+ end
16
+ end
data/lib/ovirt/nic.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Ovirt
2
+ class Nic < Object
3
+
4
+ self.top_level_strings = [:name, :interface]
5
+ self.top_level_objects = [:vm, :network]
6
+
7
+ def self.parse_xml(xml)
8
+ node, hash = xml_to_hash(xml)
9
+
10
+ parse_first_node(node, :network, hash, :node => [:name])
11
+ parse_first_node(node, :mac, hash, :attribute => [:address])
12
+
13
+ hash
14
+ end
15
+
16
+ def attributes_for_new_nic
17
+ attrs = attributes.dup
18
+ attrs[:network_id] = self[:network][:id]
19
+ attrs
20
+ end
21
+
22
+ def apply_options!(options)
23
+ update! do |xml|
24
+ xml.name options[:name] if options[:name]
25
+ xml.interface options[:interface] if options[:interface]
26
+ xml.network(:id => options[:network_id]) if options[:network_id]
27
+ xml.mac(:address => options[:mac_address]) if options[:mac_address]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,315 @@
1
+ module Ovirt
2
+ class Object
3
+ def self.create_from_xml(service, xml)
4
+ self.new(service, parse_xml(xml))
5
+ end
6
+
7
+ def self.xml_to_relationships(xml)
8
+ node = xml_to_nokogiri(xml)
9
+ relationships = {}
10
+ node.xpath('link').each do |link|
11
+ relationships[link['rel'].to_sym] = link['href']
12
+ end
13
+
14
+ relationships
15
+ end
16
+
17
+ def self.xml_to_actions(xml)
18
+ node = xml_to_nokogiri(xml)
19
+ actions = {}
20
+ node.xpath('actions/link').each do |link|
21
+ actions[link['rel'].to_sym] = link['href']
22
+ end
23
+
24
+ actions
25
+ end
26
+
27
+ def self.hash_from_id_and_href(node)
28
+ hash = {}
29
+ [:id, :href].each { |key| hash[key] = node[key.to_s] unless node.nil? || node[key.to_s].nil? }
30
+ hash
31
+ end
32
+
33
+ def self.parse_boolean(what)
34
+ return true if what == 'true'
35
+ return false if what == 'false'
36
+ raise "Cannot parse boolean for value: <#{what.inspect}>"
37
+ end
38
+
39
+ def self.parse_first_text(node, hash, key, modifier=nil)
40
+ text_node = node.xpath(key.to_s).first
41
+ value = text_node.text unless text_node.nil?
42
+ self.set_value(value, hash, key, modifier)
43
+ end
44
+
45
+ def self.parse_attribute(node, hash, key, modifier=nil)
46
+ value = node[key.to_s]
47
+ self.set_value(value, hash, key, modifier)
48
+ end
49
+
50
+ def self.set_value(value, hash, key, modifier)
51
+ return if value.nil?
52
+ hash[key] = case modifier
53
+ when :to_i then value.to_i
54
+ when :to_f then value.to_f
55
+ when :to_bool then self.parse_boolean(value)
56
+ else value
57
+ end
58
+ end
59
+
60
+ def self.parse_first_node(node, path, hash, options)
61
+ self.parse_first_node_with_hash(node, path, nh = {}, options)
62
+ unless nh.empty?
63
+ hash[path.to_sym] = hash[path.to_sym].nil? ? nh : hash[path.to_sym].merge(nh)
64
+ end
65
+ nh
66
+ end
67
+
68
+ def self.parse_first_node_with_hash(node, path, hash, options)
69
+ xnode = node.xpath(path.to_s).first
70
+ unless xnode.blank?
71
+ options[:attribute].to_a.each {|key| self.parse_attribute( xnode, hash, key)}
72
+ options[:attribute_to_i].to_a.each {|key| self.parse_attribute( xnode, hash, key, :to_i)}
73
+ options[:attribute_to_f].to_a.each {|key| self.parse_attribute( xnode, hash, key, :to_f)}
74
+ options[:node].to_a.each {|key| self.parse_first_text(xnode, hash, key)}
75
+ options[:node_to_i].to_a.each {|key| self.parse_first_text(xnode, hash, key, :to_i)}
76
+ options[:node_to_bool].to_a.each {|key| self.parse_first_text(xnode, hash, key, :to_bool)}
77
+ end
78
+ end
79
+
80
+ def self.top_level_objects=(keys)
81
+ @top_level_objects = keys
82
+ end
83
+
84
+ def self.top_level_objects
85
+ @top_level_objects ||= []
86
+ end
87
+
88
+ def self.top_level_strings=(keys)
89
+ @top_level_strings = keys
90
+ end
91
+
92
+ def self.top_level_strings
93
+ @top_level_strings ||= []
94
+ end
95
+
96
+ def self.top_level_integers=(keys)
97
+ @top_level_integers = keys
98
+ end
99
+
100
+ def self.top_level_integers
101
+ @top_level_integers ||= []
102
+ end
103
+
104
+ def self.top_level_booleans=(keys)
105
+ @top_level_booleans = keys
106
+ end
107
+
108
+ def self.top_level_booleans
109
+ @top_level_booleans ||= []
110
+ end
111
+
112
+ def self.top_level_timestamps=(keys)
113
+ @top_level_timestamps = keys
114
+ end
115
+
116
+ def self.top_level_timestamps
117
+ @top_level_timestamps ||= []
118
+ end
119
+
120
+
121
+ def self.xml_to_hash(xml)
122
+ node = xml_to_nokogiri(xml)
123
+ hash = hash_from_id_and_href(node)
124
+ hash[:relationships] = xml_to_relationships(node)
125
+ hash[:actions] = xml_to_actions(node)
126
+
127
+ top_level_objects.each do |key|
128
+ object_node = node.xpath(key.to_s).first
129
+ hash[key] = hash_from_id_and_href(object_node) unless object_node.nil?
130
+ end
131
+
132
+ top_level_strings.each do |key|
133
+ object_node = node.xpath(key.to_s).first
134
+ hash[key] = object_node.text unless object_node.nil?
135
+ end
136
+
137
+ top_level_integers.each do |key|
138
+ object_node = node.xpath(key.to_s).first
139
+ hash[key] = object_node.text.to_i unless object_node.nil?
140
+ end
141
+
142
+ top_level_booleans.each do |key|
143
+ object_node = node.xpath(key.to_s).first
144
+ hash[key] = parse_boolean(object_node.text) unless object_node.nil?
145
+ end
146
+
147
+ top_level_timestamps.each do |key|
148
+ object_node = node.xpath(key.to_s).first
149
+ hash[key] = Time.parse(object_node.text) unless object_node.nil?
150
+ end
151
+
152
+ return node, hash
153
+ end
154
+
155
+ def self.xml_to_nokogiri(xml)
156
+ if xml.kind_of?(Nokogiri::XML::Element)
157
+ nokogiri = xml
158
+ else
159
+ nokogiri = Nokogiri::XML(xml).root
160
+ end
161
+ nokogiri
162
+ end
163
+
164
+ def self.href_from_creation_status_link(link)
165
+ # "/api/vms/5024ab49-19b5-4176-9568-c004d1c9f256/creation_status/d0e45003-d490-4551-9911-05b3bec682dc"
166
+ # => "/api/vms/5024ab49-19b5-4176-9568-c004d1c9f256"
167
+ link.split("/")[0,4].join("/")
168
+ end
169
+
170
+ def self.href_to_guid(href)
171
+ href.split("/").last
172
+ end
173
+
174
+ def self.object_to_id(object)
175
+ case object
176
+ when Ovirt::Object
177
+ object = object[:id]
178
+ when String
179
+ raise ArgumentError, "object must be a valid guid" unless object.guid?
180
+ object = href_to_guid(object)
181
+ else
182
+ raise ArgumentError, "object must be a valid guid or an Ovirt Object"
183
+ end
184
+ object
185
+ end
186
+
187
+ def self.api_endpoint
188
+ namespace.last.pluralize.downcase
189
+ end
190
+
191
+ def self.element_names
192
+ element_name.pluralize
193
+ end
194
+
195
+ def self.element_name
196
+ api_endpoint.singularize
197
+ end
198
+
199
+ def self.all_xml_objects(service)
200
+ response = service.resource_get(api_endpoint)
201
+ doc = Nokogiri::XML(response)
202
+ objects = doc.xpath("//#{element_names}/#{element_name}")
203
+ end
204
+
205
+ def self.all(service)
206
+ all_xml_objects(service).collect { |xml| self.create_from_xml(service, xml) }
207
+ end
208
+
209
+ def self.find_by_name(service, name)
210
+ all_xml_objects(service).each do |xml|
211
+ obj = self.create_from_xml(service, xml)
212
+ return obj if obj[:name] == name
213
+ end
214
+ nil
215
+ end
216
+
217
+ def self.find_by_id(service, id)
218
+ find_by_href(service, "#{api_endpoint}/#{id}")
219
+ end
220
+
221
+ def self.find_by_href(service, href)
222
+ response = service.resource_get(href)
223
+ doc = Nokogiri::XML(response)
224
+ xml = doc.xpath("//#{element_name}").first
225
+ self.create_from_xml(service, xml)
226
+ rescue RestClient::ResourceNotFound
227
+ return nil
228
+ end
229
+
230
+ attr_accessor :attributes, :operations, :relationships, :service
231
+
232
+ def initialize(service, options = {})
233
+ @service = service
234
+ @relationships = options.delete(:relationships) || {}
235
+ @operations = options.delete(:actions) || {}
236
+ @attributes = options
237
+ end
238
+
239
+ def replace(obj)
240
+ @relationships = obj.relationships
241
+ @operations = obj.operations
242
+ @attributes = obj.attributes
243
+ end
244
+
245
+ def reload
246
+ self.replace(self.class.find_by_href(@service, self[:href]))
247
+ end
248
+
249
+ def method_missing(m, *args)
250
+ if @relationships.has_key?(m)
251
+ rel_str = m.to_s
252
+ rel_str = 'storage_domains' if rel_str == 'storagedomains'
253
+ rel_str = 'data_centers' if rel_str == 'datacenters'
254
+ singular = rel_str.singularize
255
+ klass = singular.camelize.constantize
256
+ xml = @service.resource_get(@relationships[m])
257
+ doc = Nokogiri::XML(xml)
258
+ return doc.root.xpath(singular).collect { |node| klass.create_from_xml(@service, node) }
259
+ end
260
+
261
+ return operation(m, args) if @operations.has_key?(m)
262
+
263
+ super
264
+ end
265
+
266
+ def operation(method, *args)
267
+ if @operations.has_key?(method.to_sym)
268
+ builder = Nokogiri::XML::Builder.new do |xml|
269
+ xml.action { yield xml if block_given? }
270
+ end
271
+ data = builder.doc.root.to_xml
272
+
273
+ @service.resource_post(@operations[method.to_sym], data)
274
+ else
275
+ raise "Method:<#{method}> is not available for object <#{self.class.name}>"
276
+ end
277
+ end
278
+
279
+ def destroy
280
+ @service.resource_delete(@attributes[:href])
281
+ end
282
+
283
+ def class_suffix
284
+ self.class.name[5..-1]
285
+ end
286
+
287
+ def api_endpoint
288
+ self[:href] || "#{self.class.api_endpoint}/#{self[:id]}"
289
+ end
290
+
291
+ def update!(&block)
292
+ response = update(&block)
293
+
294
+ obj = self.class.create_from_xml(@service, response)
295
+ self.replace(obj)
296
+ end
297
+
298
+ def update
299
+ builder = Nokogiri::XML::Builder.new do |xml|
300
+ xml.send(namespace.last.downcase) { yield xml if block_given? }
301
+ end
302
+ data = builder.doc.root.to_xml
303
+
304
+ @service.resource_put(api_endpoint, data)
305
+ end
306
+
307
+ def keys
308
+ @attributes.keys
309
+ end
310
+
311
+ def [](key)
312
+ @attributes[key]
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,20 @@
1
+ module Ovirt
2
+ class Permission < Object
3
+
4
+ self.top_level_objects = [:role, :user]
5
+
6
+ def self.parse_xml(xml)
7
+ node, hash = xml_to_hash(xml)
8
+
9
+ [:template].each do |type|
10
+ subject_node = node.xpath(type.to_s).first
11
+ next if subject_node.nil?
12
+ subject = hash_from_id_and_href(subject_node)
13
+ subject[:type] = type
14
+ hash[:subject] = subject
15
+ end
16
+
17
+ hash
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module Ovirt
2
+ class Permit < Object
3
+
4
+ self.top_level_strings = [:name]
5
+ self.top_level_booleans = [:administrative]
6
+ self.top_level_objects = [:role]
7
+
8
+ def self.parse_xml(xml)
9
+ node, hash = xml_to_hash(xml)
10
+
11
+ hash
12
+ end
13
+ end
14
+ end
data/lib/ovirt/role.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Ovirt
2
+ class Role < Object
3
+
4
+ self.top_level_strings = [:name, :description]
5
+ self.top_level_booleans = [:administrative, :mutable]
6
+
7
+ def self.parse_xml(xml)
8
+ node, hash = xml_to_hash(xml)
9
+
10
+ hash
11
+ end
12
+ end
13
+ end