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.
- 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
|