ovirt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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