ovirt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.rspec +3 -0
- data/LICENSE.txt +13 -0
- data/README.md +35 -0
- data/lib/ovirt/api.rb +31 -0
- data/lib/ovirt/cdrom.rb +12 -0
- data/lib/ovirt/cluster.rb +27 -0
- data/lib/ovirt/data_center.rb +24 -0
- data/lib/ovirt/disk.rb +30 -0
- data/lib/ovirt/domain.rb +12 -0
- data/lib/ovirt/event.rb +492 -0
- data/lib/ovirt/event_monitor.rb +36 -0
- data/lib/ovirt/exception.rb +13 -0
- data/lib/ovirt/file.rb +13 -0
- data/lib/ovirt/group.rb +12 -0
- data/lib/ovirt/host.rb +44 -0
- data/lib/ovirt/host_nic.rb +38 -0
- data/lib/ovirt/inventory.rb +177 -0
- data/lib/ovirt/network.rb +16 -0
- data/lib/ovirt/nic.rb +31 -0
- data/lib/ovirt/object.rb +315 -0
- data/lib/ovirt/permission.rb +20 -0
- data/lib/ovirt/permit.rb +14 -0
- data/lib/ovirt/role.rb +13 -0
- data/lib/ovirt/service.rb +332 -0
- data/lib/ovirt/snapshot.rb +28 -0
- data/lib/ovirt/statistic.rb +37 -0
- data/lib/ovirt/storage.rb +14 -0
- data/lib/ovirt/storage_domain.rb +48 -0
- data/lib/ovirt/tag.rb +23 -0
- data/lib/ovirt/template.rb +185 -0
- data/lib/ovirt/user.rb +15 -0
- data/lib/ovirt/version.rb +3 -0
- data/lib/ovirt/vm.rb +327 -0
- data/lib/ovirt/vmpool.rb +14 -0
- data/lib/ovirt.rb +35 -0
- data/spec/event_spec.rb +26 -0
- data/spec/object_spec.rb +12 -0
- data/spec/service_spec.rb +30 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/template_spec.rb +141 -0
- data/spec/vm_spec.rb +189 -0
- metadata +224 -0
@@ -0,0 +1,332 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Ovirt
|
4
|
+
class Service
|
5
|
+
DEFAULT_OPTIONS = {}
|
6
|
+
REQUIRED_OPTIONS = [:server, :username, :password]
|
7
|
+
DEFAULT_PORT_3_0 = 8443
|
8
|
+
DEFAULT_PORT_3_1 = 443
|
9
|
+
DEFAULT_PORT = DEFAULT_PORT_3_1
|
10
|
+
DEFAULT_SCHEME = 'https'.freeze
|
11
|
+
SESSION_ID_KEY = 'JSESSIONID'.freeze
|
12
|
+
|
13
|
+
attr_accessor :session_id
|
14
|
+
|
15
|
+
def self.name_to_class(name)
|
16
|
+
Ovirt.const_get(name.camelize)
|
17
|
+
end
|
18
|
+
|
19
|
+
def xml_to_object(klass, xml)
|
20
|
+
klass.create_from_xml(self, xml)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(options={})
|
24
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
25
|
+
parse_domain_name
|
26
|
+
REQUIRED_OPTIONS.each { |key| raise "No #{key.to_s} specified" unless @options.has_key?(key) }
|
27
|
+
@password = @options.delete(:password)
|
28
|
+
@session_id = @options[:session_id]
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect # just like the default inspect, but WITHOUT @password
|
32
|
+
"#<#{self.class.name}:0x#{(self.object_id << 1).to_s(16).rjust(14,'0')} @options=#{@options.inspect}>"
|
33
|
+
end
|
34
|
+
|
35
|
+
def api(reload = false)
|
36
|
+
@api = nil if reload
|
37
|
+
@api ||= xml_to_object(Api, resource_get)
|
38
|
+
end
|
39
|
+
|
40
|
+
def product_info
|
41
|
+
@product_info ||= api[:product_info]
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
@name ||= product_info[:name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def vendor
|
49
|
+
@vendor ||= product_info[:vendor]
|
50
|
+
end
|
51
|
+
|
52
|
+
def version
|
53
|
+
@version ||= product_info[:version]
|
54
|
+
end
|
55
|
+
|
56
|
+
def version_string
|
57
|
+
@version_string ||= "#{version[:major]}.#{version[:minor]}.#{version[:revision]}.#{version[:build]}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def version_3_0?
|
61
|
+
version_string.starts_with?("3.0")
|
62
|
+
end
|
63
|
+
|
64
|
+
def summary
|
65
|
+
api(true)[:summary] # This is volatile information
|
66
|
+
end
|
67
|
+
|
68
|
+
def special_objects
|
69
|
+
@special_objects ||= api[:special_objects]
|
70
|
+
end
|
71
|
+
|
72
|
+
def blank_template
|
73
|
+
@blank_template ||= begin
|
74
|
+
href = special_objects[:"templates/blank"]
|
75
|
+
href.blank? ? nil : Template.find_by_href(self, href)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def root_tag
|
80
|
+
@root_tag ||= begin
|
81
|
+
href = special_objects[:"tags/root"]
|
82
|
+
href.blank? ? nil : Tag.find_by_href(self, href)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def iso_storage_domain
|
87
|
+
@iso_storage_domain ||= StorageDomain.iso_storage_domain(self)
|
88
|
+
end
|
89
|
+
|
90
|
+
def iso_images
|
91
|
+
iso_storage_domain.nil? ? [] : iso_storage_domain.iso_images
|
92
|
+
end
|
93
|
+
|
94
|
+
def disconnect
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_resource_by_ems_ref(uri_suffix, element_name = nil)
|
98
|
+
xml = resource_get(uri_suffix)
|
99
|
+
doc = Nokogiri::XML(xml)
|
100
|
+
element_name ||= doc.root.name
|
101
|
+
klass = self.class.name_to_class(element_name)
|
102
|
+
xml_to_object(klass, doc.root)
|
103
|
+
end
|
104
|
+
|
105
|
+
def standard_collection(uri_suffix, element_name = nil, paginate=false, sort_by=:name)
|
106
|
+
if paginate
|
107
|
+
doc = paginate_resource_get(uri_suffix, sort_by)
|
108
|
+
else
|
109
|
+
xml = resource_get(uri_suffix)
|
110
|
+
doc = Nokogiri::XML(xml)
|
111
|
+
end
|
112
|
+
element_name ||= uri_suffix.singularize
|
113
|
+
klass = self.class.name_to_class(element_name)
|
114
|
+
|
115
|
+
xml_path = uri_suffix == 'api' ? element_name : "#{element_name.pluralize}/#{element_name}"
|
116
|
+
objects = doc.xpath("//#{xml_path}")
|
117
|
+
objects.collect { |obj| xml_to_object(klass, obj) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def status(link)
|
121
|
+
response = resource_get(link)
|
122
|
+
|
123
|
+
node = Object.xml_to_nokogiri(response)
|
124
|
+
node.xpath('status/state').text
|
125
|
+
end
|
126
|
+
|
127
|
+
def api_uri(path = nil)
|
128
|
+
uri = "#{base_uri}/api"
|
129
|
+
unless path.nil?
|
130
|
+
parts = path.to_s.split('/')
|
131
|
+
parts.shift if parts.first == '' # Remove leading slash
|
132
|
+
parts.shift if parts.first == 'api' # We already have /api in our URI
|
133
|
+
uri += "/#{parts.join('/')}" unless parts.empty?
|
134
|
+
end
|
135
|
+
uri
|
136
|
+
end
|
137
|
+
|
138
|
+
def paginate_resource_get(path = nil, sort_by=:name, direction=:asc)
|
139
|
+
log_header = "#{self.class.name}#paginate_resource_get"
|
140
|
+
page = 1
|
141
|
+
full_xml = nil
|
142
|
+
loop do
|
143
|
+
uri = "#{path}?search=sortby%20#{sort_by}%20#{direction}%20page%20#{page}"
|
144
|
+
partial_xml_str = self.resource_get(uri)
|
145
|
+
if full_xml.nil?
|
146
|
+
full_xml = Nokogiri::XML(partial_xml_str)
|
147
|
+
else
|
148
|
+
partial_xml = Nokogiri::XML(partial_xml_str)
|
149
|
+
break if partial_xml.root.children.count == 0
|
150
|
+
$rhevm_log.debug "#{log_header}: Combining resource elements for <#{path}> from page:<#{page}>" if $rhevm_log && $rhevm_log.debug?
|
151
|
+
full_xml.root << partial_xml.root.children
|
152
|
+
end
|
153
|
+
page += 1
|
154
|
+
end
|
155
|
+
$rhevm_log.debug "#{log_header}: Combined elements for <#{path}>. Total elements:<#{full_xml.root.children.count}>" if $rhevm_log && $rhevm_log.debug?
|
156
|
+
return full_xml
|
157
|
+
end
|
158
|
+
|
159
|
+
def resource_get(path = nil)
|
160
|
+
resource_verb(path, :get)
|
161
|
+
end
|
162
|
+
|
163
|
+
def resource_put(path, payload, additional_headers={:content_type => :xml, :accept => :xml})
|
164
|
+
resource_verb(path, :put, payload, additional_headers)
|
165
|
+
end
|
166
|
+
|
167
|
+
def resource_post(path, payload, additional_headers={:content_type => :xml, :accept => :xml})
|
168
|
+
resource_verb(path, :post, payload, additional_headers)
|
169
|
+
end
|
170
|
+
|
171
|
+
def resource_delete(path)
|
172
|
+
resource_verb(path, :delete)
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_resource(path = nil)
|
176
|
+
require "rest-client"
|
177
|
+
RestClient::Resource.new(api_uri(path), resource_options)
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def resource_verb(path, verb, *args)
|
183
|
+
log_header = "#{self.class.name}#resource_#{verb}"
|
184
|
+
|
185
|
+
resource = create_resource(path)
|
186
|
+
$rhevm_log.info "#{log_header}: Sending URL: <#{resource.url}>" if $rhevm_log
|
187
|
+
$rhevm_log.debug "#{log_header}: With args: <#{args.inspect}>" if $rhevm_log.try(:debug?)
|
188
|
+
resource.send(verb, *args) do |response, request, result, &block|
|
189
|
+
case response.code
|
190
|
+
when 200
|
191
|
+
parse_normal_response(response, resource)
|
192
|
+
when 400, 409
|
193
|
+
parse_error_response(response)
|
194
|
+
else
|
195
|
+
response.return!(request, result, &block)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
rescue RestClient::Unauthorized
|
199
|
+
if self.session_id
|
200
|
+
self.session_id = nil
|
201
|
+
retry
|
202
|
+
else
|
203
|
+
raise
|
204
|
+
end
|
205
|
+
rescue RestClient::ResourceNotFound, Ovirt::Error
|
206
|
+
raise
|
207
|
+
rescue Exception => e
|
208
|
+
msg = "#{log_header}: class = #{e.class.name}, message=#{e.message}, URI=#{resource.url}"
|
209
|
+
if $rhevm_log.nil?
|
210
|
+
puts msg
|
211
|
+
else
|
212
|
+
$rhevm_log.error msg
|
213
|
+
end
|
214
|
+
raise
|
215
|
+
end
|
216
|
+
|
217
|
+
def parse_normal_response(response, resource)
|
218
|
+
parse_set_cookie_header(response.headers[:set_cookie])
|
219
|
+
if $rhevm_log
|
220
|
+
log_header = "#{self.class.name}#parse_normal_response"
|
221
|
+
$rhevm_log.info "#{log_header}: Return from URL: <#{resource.url}> Data length:#{response.length}"
|
222
|
+
$rhevm_log.debug "#{log_header}: Return from URL: <#{resource.url}> Data:#{response}" if $rhevm_log.debug?
|
223
|
+
end
|
224
|
+
response
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_error_response(response)
|
228
|
+
doc = Nokogiri::XML(response)
|
229
|
+
action = doc.xpath("action").first
|
230
|
+
node = action || doc
|
231
|
+
reason = node.xpath("fault/detail").text
|
232
|
+
raise Ovirt::Error, reason
|
233
|
+
end
|
234
|
+
|
235
|
+
def parse_set_cookie_header(set_cookie_header)
|
236
|
+
set_cookie_header = set_cookie_header.first if set_cookie_header.kind_of?(Array)
|
237
|
+
set_cookie_header.to_s.split(";").each do |kv|
|
238
|
+
k, v = kv.strip.split("=")
|
239
|
+
self.session_id = v if k == SESSION_ID_KEY
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def base_uri
|
244
|
+
if port.blank?
|
245
|
+
"#{scheme}://#{server}"
|
246
|
+
else
|
247
|
+
"#{scheme}://#{server}:#{port}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def resource_options
|
252
|
+
headers = merge_headers({ 'Prefer' => 'persistent-auth' })
|
253
|
+
options = { :ssl_version => :SSLv3 }
|
254
|
+
|
255
|
+
if self.session_id
|
256
|
+
headers[:cookie] = "#{SESSION_ID_KEY}=#{self.session_id}"
|
257
|
+
else
|
258
|
+
options[:user] = fully_qualified_username
|
259
|
+
options[:password] = password
|
260
|
+
end
|
261
|
+
|
262
|
+
options[:headers] = headers
|
263
|
+
options[:timeout] = timeout if timeout
|
264
|
+
options[:open_timeout] = open_timeout if open_timeout
|
265
|
+
options
|
266
|
+
end
|
267
|
+
|
268
|
+
def merge_headers(hash)
|
269
|
+
h = @options[:headers] || {}
|
270
|
+
h.merge(hash)
|
271
|
+
end
|
272
|
+
|
273
|
+
def authorization_header
|
274
|
+
@authorization_header ||= { :authorization => "Basic #{authorization_value}" }
|
275
|
+
end
|
276
|
+
|
277
|
+
def authorization_value
|
278
|
+
@authorization_value ||= begin
|
279
|
+
require "base64"
|
280
|
+
Base64.encode64 "#{fully_qualified_username}:#{password}"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def scheme
|
285
|
+
@options[:scheme] || DEFAULT_SCHEME
|
286
|
+
end
|
287
|
+
|
288
|
+
def server
|
289
|
+
@options[:server]
|
290
|
+
end
|
291
|
+
|
292
|
+
def port
|
293
|
+
@options[:port] || DEFAULT_PORT
|
294
|
+
end
|
295
|
+
|
296
|
+
def fully_qualified_username
|
297
|
+
domain.blank? ? username : "#{username}@#{domain}"
|
298
|
+
end
|
299
|
+
|
300
|
+
def username
|
301
|
+
@options[:username]
|
302
|
+
end
|
303
|
+
|
304
|
+
def password
|
305
|
+
@password
|
306
|
+
end
|
307
|
+
|
308
|
+
def domain
|
309
|
+
@options[:domain]
|
310
|
+
end
|
311
|
+
|
312
|
+
def timeout
|
313
|
+
@options[:timeout] # NetHTTPSession's read_timeout
|
314
|
+
end
|
315
|
+
|
316
|
+
def open_timeout
|
317
|
+
@options[:open_timeout] # NetHTTPSessions's open_timeout
|
318
|
+
end
|
319
|
+
|
320
|
+
# Parse domain out of the username string
|
321
|
+
def parse_domain_name
|
322
|
+
if @options[:domain].blank? && !@options[:username].blank?
|
323
|
+
if @options[:username].include?('\\')
|
324
|
+
@options[:domain], @options[:username] = username.split('\\')
|
325
|
+
elsif @options[:username].include?('/')
|
326
|
+
@options[:domain], @options[:username] = username.split('/')
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Snapshot < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:description, :snapshot_status, :type]
|
5
|
+
self.top_level_timestamps = [:date]
|
6
|
+
self.top_level_objects = [:vm]
|
7
|
+
|
8
|
+
def self.parse_xml(xml)
|
9
|
+
node, hash = xml_to_hash(xml)
|
10
|
+
|
11
|
+
hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(service, options = {})
|
15
|
+
super
|
16
|
+
@relationships[:disks] = self[:href] + "/disks"
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete
|
20
|
+
response = destroy
|
21
|
+
while self[:snapshot_status] == "locked" || self[:snapshot_status] == "ok"
|
22
|
+
sleep 2
|
23
|
+
break if (obj = self.class.find_by_href(@service, self[:href])).nil?
|
24
|
+
self.replace(obj)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Statistic < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :description, :type, :unit]
|
5
|
+
|
6
|
+
def self.parse_xml(xml)
|
7
|
+
node, hash = xml_to_hash(xml)
|
8
|
+
|
9
|
+
values = []
|
10
|
+
values_node = node.xpath('values').first
|
11
|
+
values_type = values_node['type']
|
12
|
+
values = values_node.xpath('value').collect do |v|
|
13
|
+
datum = v.xpath('datum').text
|
14
|
+
case values_type
|
15
|
+
when 'INTEGER'
|
16
|
+
datum = datum.to_i
|
17
|
+
when 'DECIMAL'
|
18
|
+
datum = datum.to_f
|
19
|
+
else
|
20
|
+
raise "unknown Values TYPE of <#{values_type}>"
|
21
|
+
end
|
22
|
+
datum
|
23
|
+
end
|
24
|
+
hash[:values] = values
|
25
|
+
|
26
|
+
[:vm, :nic, :disk].each do |type|
|
27
|
+
parent_node = node.xpath(type.to_s).first
|
28
|
+
next if parent_node.nil?
|
29
|
+
parent = hash_from_id_and_href(parent_node)
|
30
|
+
parent[:type] = type
|
31
|
+
hash[:parent] = parent
|
32
|
+
end
|
33
|
+
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class StorageDomain < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :type, :storage_format]
|
5
|
+
self.top_level_booleans = [:master]
|
6
|
+
self.top_level_integers = [:available, :used, :committed]
|
7
|
+
self.top_level_objects = [:data_center]
|
8
|
+
|
9
|
+
def self.element_name
|
10
|
+
"storage_domain"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse_xml(xml)
|
14
|
+
node, hash = xml_to_hash(xml)
|
15
|
+
|
16
|
+
parse_first_node(node, :status, hash, :node => [:state])
|
17
|
+
parse_first_node(node, :storage, hash, :node => [:type, :address, :path])
|
18
|
+
parse_first_node(node, :storage, hash, :attribute => [:id])
|
19
|
+
|
20
|
+
node.xpath('storage/volume_group').each do |vg|
|
21
|
+
node.xpath('storage').each do |storage_node|
|
22
|
+
parse_first_node(storage_node, :volume_group, hash[:storage], :attribute => [:id])
|
23
|
+
end
|
24
|
+
|
25
|
+
vg_hash = hash[:storage][:volume_group]
|
26
|
+
unless vg_hash.blank?
|
27
|
+
parse_first_node(vg, :logical_unit, vg_hash, :attribute => [:id])
|
28
|
+
|
29
|
+
unless vg_hash.blank?
|
30
|
+
parse_first_node(vg, :logical_unit, vg_hash,
|
31
|
+
:node => [:address, :port, :target, :username, :serial, :vendor_id, :product_id, :lun_mapping, :portal, :size, :paths])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.iso_storage_domain(service)
|
40
|
+
all(service).detect { |s| s[:type] == "iso" }
|
41
|
+
end
|
42
|
+
|
43
|
+
def iso_images
|
44
|
+
return [] if self[:type] != "iso"
|
45
|
+
@service.standard_collection(relationships[:files], 'file')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/ovirt/tag.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Tag < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :description]
|
5
|
+
self.top_level_objects = [:host, :user, :vm]
|
6
|
+
|
7
|
+
def self.parse_xml(xml)
|
8
|
+
node, hash = xml_to_hash(xml)
|
9
|
+
|
10
|
+
parent_node = node.xpath('parent').first
|
11
|
+
unless parent_node.nil?
|
12
|
+
tag_node = parent_node.xpath('tag').first
|
13
|
+
unless tag_node.nil?
|
14
|
+
parent = hash_from_id_and_href(tag_node)
|
15
|
+
parent[:type] = 'tag'
|
16
|
+
hash[:parent] = parent
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class Template < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :description, :type]
|
5
|
+
self.top_level_booleans = [:stateless]
|
6
|
+
self.top_level_integers = [:memory]
|
7
|
+
self.top_level_timestamps = [:creation_time]
|
8
|
+
self.top_level_objects = [:cluster]
|
9
|
+
|
10
|
+
def self.parse_xml(xml)
|
11
|
+
node, hash = xml_to_hash(xml)
|
12
|
+
|
13
|
+
parse_first_node(node, :status, hash, :node => [:state])
|
14
|
+
|
15
|
+
parse_first_node(node, :display, hash,
|
16
|
+
:node => [:type, :address],
|
17
|
+
:node_to_i => [:port, :monitors])
|
18
|
+
|
19
|
+
parse_first_node(node, :usb, hash,
|
20
|
+
:node_to_bool => [:enabled])
|
21
|
+
|
22
|
+
parse_first_node_with_hash(node, 'cpu/topology', hash.store_path(:cpu, :topology, {}),
|
23
|
+
:attribute_to_i => [:sockets, :cores])
|
24
|
+
|
25
|
+
parse_first_node(node, :high_availability, hash,
|
26
|
+
:node_to_bool => [:enabled],
|
27
|
+
:node_to_i => [:priority])
|
28
|
+
|
29
|
+
parse_first_node(node, :os, hash,
|
30
|
+
:attribute => [:type],
|
31
|
+
:node => [:kernel, :initrd, :cmdline])
|
32
|
+
|
33
|
+
hash[:os][:boot_order] = boot_order = []
|
34
|
+
#Collect boot order
|
35
|
+
node.xpath('os/boot').each do |boot|
|
36
|
+
dev = boot['dev']
|
37
|
+
boot_order << {:dev => dev} unless dev.blank?
|
38
|
+
end
|
39
|
+
|
40
|
+
hash[:custom_attributes] = []
|
41
|
+
node.xpath('custom_properties/custom_property').each do |ca|
|
42
|
+
hash[:custom_attributes] << {:name => ca[:name], :value => ca[:value]}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
hash
|
47
|
+
end
|
48
|
+
|
49
|
+
def os_type
|
50
|
+
self.attributes.fetch_path(:os, :type) || 'unassigned'
|
51
|
+
end
|
52
|
+
|
53
|
+
def getCfg(snap=nil)
|
54
|
+
#mor = snap ? getSnapMor(snap) : @vmMor
|
55
|
+
cfgProps = self.attributes
|
56
|
+
|
57
|
+
raise MiqException::MiqVimError, "Failed to retrieve configuration information for VM" if cfgProps.nil?
|
58
|
+
|
59
|
+
cfgHash = {}
|
60
|
+
cfgHash['displayname'] = cfgProps[:name]
|
61
|
+
cfgHash['guestos'] = cfgProps.fetch_path(:os, :type)
|
62
|
+
cfgHash['memsize'] = cfgProps[:memory] / 1048576 # in MB
|
63
|
+
cfgHash['numvcpu'] = cfgProps.fetch_path(:cpu, :sockets)
|
64
|
+
|
65
|
+
# Collect disk information
|
66
|
+
self.attributes[:disks] = self.send(:disks, :disk) if self[:disks].nil?
|
67
|
+
self.disks.each_with_index do |disk, idx|
|
68
|
+
storage_domain = disk[:storage_domains].first
|
69
|
+
storage_id = storage_domain && storage_domain[:id]
|
70
|
+
disk_key = disk[:image_id].blank? ? :id : :image_id
|
71
|
+
file_path = storage_id && File.join('/dev', storage_id, disk[disk_key])
|
72
|
+
|
73
|
+
tag = "scsi0:#{idx}"
|
74
|
+
cfgHash["#{tag}.present"] = "true"
|
75
|
+
cfgHash["#{tag}.devicetype"] = "disk"
|
76
|
+
cfgHash["#{tag}.filename"] = file_path.to_s
|
77
|
+
cfgHash["#{tag}.format"] = disk[:format]
|
78
|
+
#cfgHash["#{tag}.mode"] = dev['backing']['diskMode']
|
79
|
+
end
|
80
|
+
return cfgHash
|
81
|
+
end
|
82
|
+
|
83
|
+
REQUIRED_CLONE_PARAMETERS = [:name, :cluster]
|
84
|
+
CLONE_ATTRIBUTES_WITH_SCALARS = [:memory, :stateless, :type]
|
85
|
+
CLONE_ATTRIBUTES_WITH_HASHES = [:display, :usb, :cpu, :high_availability]
|
86
|
+
ALLOWED_CLONE_TYPES = [:full, :linked, :skeletal]
|
87
|
+
|
88
|
+
def create_vm(options = {})
|
89
|
+
options = options.dup
|
90
|
+
determine_clone_type(options)
|
91
|
+
options[:storage] = Object.object_to_id(options[:storage]) if options[:storage]
|
92
|
+
|
93
|
+
case options[:clone_type]
|
94
|
+
when :full; clone_to_vm(options)
|
95
|
+
when :linked; clone_to_vm(options)
|
96
|
+
when :skeletal; clone_to_vm_via_blank_template(options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def determine_clone_type(options)
|
103
|
+
# Return the clone_type from the options if it matches one of the types in the allowed array
|
104
|
+
# otherwise return the first type from the allowed array as a default
|
105
|
+
options[:clone_type] = ALLOWED_CLONE_TYPES.include?(options[:clone_type]) ? options[:clone_type] : ALLOWED_CLONE_TYPES.first
|
106
|
+
end
|
107
|
+
|
108
|
+
def clone_to_vm_via_blank_template(options)
|
109
|
+
# Create a VM based the VM on the blank template using parameters from this template
|
110
|
+
# Disks are created from scratch, not copied
|
111
|
+
(CLONE_ATTRIBUTES_WITH_SCALARS + CLONE_ATTRIBUTES_WITH_HASHES).each do |key|
|
112
|
+
options[key] ||= self[key]
|
113
|
+
end
|
114
|
+
options[:os_type] ||= self.os_type
|
115
|
+
|
116
|
+
skeleton_options = options.dup
|
117
|
+
skeleton_options[:clone_type] = :linked
|
118
|
+
vm = @service.blank_template.create_vm(skeleton_options)
|
119
|
+
|
120
|
+
create_new_disks_from_template(vm, options)
|
121
|
+
vm
|
122
|
+
end
|
123
|
+
|
124
|
+
def create_new_disks_from_template(vm, options)
|
125
|
+
self.disks.each do |disk_object|
|
126
|
+
disk_options = disk_object.attributes_for_new_disk
|
127
|
+
disk_options[:sparse] = options[:sparse] unless options[:sparse].nil?
|
128
|
+
disk_options[:storage] = options[:storage] unless options[:storage].blank?
|
129
|
+
vm.create_disk(disk_options)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def clone_to_vm(options)
|
134
|
+
# Create a VM based on this template
|
135
|
+
REQUIRED_CLONE_PARAMETERS.each do |key|
|
136
|
+
raise ArgumentError, "#{key.inspect} cannot be blank" if options[key].blank?
|
137
|
+
end
|
138
|
+
|
139
|
+
response = @service.resource_post(:vms, build_clone_xml(options))
|
140
|
+
Vm.create_from_xml(@service, response)
|
141
|
+
rescue Ovirt::Error => err
|
142
|
+
raise VmAlreadyExists, err.message if err.message.include?("VM with the same name already exists")
|
143
|
+
raise
|
144
|
+
end
|
145
|
+
|
146
|
+
def build_clone_xml(options)
|
147
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
148
|
+
xml.vm do
|
149
|
+
xml.name options[:name]
|
150
|
+
xml.cluster(:id => Object.object_to_id(options[:cluster]))
|
151
|
+
xml.template(:id => self[:id])
|
152
|
+
|
153
|
+
CLONE_ATTRIBUTES_WITH_SCALARS.each do |key|
|
154
|
+
xml.send("#{key}_", options[key] || self[key])
|
155
|
+
end
|
156
|
+
|
157
|
+
CLONE_ATTRIBUTES_WITH_HASHES.each do |key|
|
158
|
+
xml.send("#{key}_") do
|
159
|
+
hash = options[key] || self[key]
|
160
|
+
hash.each { |k, v| xml.send("#{k}_", v) } unless hash.nil?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
xml.os(:type => options[:os_type] || self.os_type) do
|
165
|
+
xml.boot(:dev => 'hd')
|
166
|
+
end
|
167
|
+
|
168
|
+
if options[:clone_type] == :full
|
169
|
+
xml.disks do
|
170
|
+
xml.clone_ true
|
171
|
+
self.disks.each do |disk_object|
|
172
|
+
xml.disk(:id => disk_object.attributes[:id]) do
|
173
|
+
xml.sparse options[:sparse] unless options[:sparse].nil?
|
174
|
+
xml.storage_domains { xml.storage_domain(:id => options[:storage]) } if options[:storage]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
builder.doc.root.to_xml
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
data/lib/ovirt/user.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Ovirt
|
2
|
+
class User < Object
|
3
|
+
|
4
|
+
self.top_level_strings = [:name, :description, :domain, :user_name]
|
5
|
+
self.top_level_booleans = [:logged_in]
|
6
|
+
|
7
|
+
def self.parse_xml(xml)
|
8
|
+
node, hash = xml_to_hash(xml)
|
9
|
+
groups_node = node.xpath('groups').first
|
10
|
+
hash[:groups] = groups_node.xpath('group').collect { |group_node| group_node.text } unless groups_node.nil?
|
11
|
+
|
12
|
+
hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|