opennebula 5.12.9 → 6.0.0.1

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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ActionManager.rb +1 -1
  3. data/lib/CommandManager.rb +1 -1
  4. data/lib/DriverExecHelper.rb +44 -28
  5. data/lib/OpenNebulaDriver.rb +8 -4
  6. data/lib/VirtualMachineDriver.rb +9 -2
  7. data/lib/cloud/CloudClient.rb +3 -3
  8. data/lib/datacenter.rb +1257 -0
  9. data/lib/datastore.rb +1025 -0
  10. data/lib/distributed_firewall.rb +280 -0
  11. data/lib/file_helper.rb +370 -0
  12. data/lib/host.rb +1517 -0
  13. data/lib/logical_port.rb +50 -0
  14. data/lib/logical_switch.rb +77 -0
  15. data/lib/memoize.rb +74 -0
  16. data/lib/models.rb +32 -0
  17. data/lib/models/role.rb +1126 -0
  18. data/lib/models/service.rb +709 -0
  19. data/lib/network.rb +699 -0
  20. data/lib/nsx_client.rb +144 -0
  21. data/lib/nsx_component.rb +28 -0
  22. data/lib/nsx_constants.rb +149 -0
  23. data/lib/nsx_driver.rb +78 -0
  24. data/lib/nsx_error.rb +77 -0
  25. data/lib/nsx_rule.rb +193 -0
  26. data/lib/nsxt_client.rb +176 -0
  27. data/lib/nsxt_dfw.rb +196 -0
  28. data/lib/nsxt_logical_port.rb +94 -0
  29. data/lib/nsxt_rule.rb +188 -0
  30. data/lib/nsxt_tz.rb +38 -0
  31. data/lib/nsxv_client.rb +176 -0
  32. data/lib/nsxv_dfw.rb +202 -0
  33. data/lib/nsxv_logical_port.rb +107 -0
  34. data/lib/nsxv_rule.rb +172 -0
  35. data/lib/nsxv_tz.rb +41 -0
  36. data/lib/opaque_network.rb +134 -0
  37. data/lib/opennebula.rb +5 -2
  38. data/lib/opennebula/acl.rb +1 -1
  39. data/lib/opennebula/acl_pool.rb +1 -1
  40. data/lib/opennebula/client.rb +10 -2
  41. data/lib/opennebula/cluster.rb +1 -1
  42. data/lib/opennebula/cluster_pool.rb +1 -1
  43. data/lib/opennebula/datastore.rb +1 -1
  44. data/lib/opennebula/datastore_pool.rb +1 -1
  45. data/lib/opennebula/document.rb +8 -29
  46. data/lib/opennebula/document_json.rb +42 -12
  47. data/lib/opennebula/document_pool.rb +1 -1
  48. data/lib/opennebula/document_pool_json.rb +1 -1
  49. data/lib/opennebula/error.rb +4 -1
  50. data/lib/opennebula/flow.rb +23 -0
  51. data/lib/opennebula/flow/grammar.rb +1195 -0
  52. data/lib/opennebula/flow/service_pool.rb +190 -0
  53. data/lib/opennebula/flow/service_template.rb +608 -0
  54. data/lib/opennebula/flow/service_template_ext.rb +84 -0
  55. data/lib/opennebula/flow/service_template_pool.rb +32 -0
  56. data/lib/opennebula/flow/validator.rb +499 -0
  57. data/lib/opennebula/group.rb +1 -1
  58. data/lib/opennebula/group_pool.rb +1 -1
  59. data/lib/opennebula/hook.rb +5 -12
  60. data/lib/opennebula/hook_log.rb +1 -1
  61. data/lib/opennebula/hook_pool.rb +1 -1
  62. data/lib/opennebula/host.rb +1 -1
  63. data/lib/opennebula/host_pool.rb +1 -1
  64. data/lib/opennebula/image.rb +19 -14
  65. data/lib/opennebula/image_pool.rb +1 -1
  66. data/lib/opennebula/ldap_auth.rb +1 -1
  67. data/lib/opennebula/ldap_auth_spec.rb +1 -1
  68. data/lib/opennebula/lockable_ext.rb +163 -0
  69. data/lib/opennebula/marketplace.rb +1 -1
  70. data/lib/opennebula/marketplace_pool.rb +1 -1
  71. data/lib/opennebula/marketplaceapp.rb +9 -119
  72. data/lib/opennebula/marketplaceapp_ext.rb +586 -0
  73. data/lib/opennebula/marketplaceapp_pool.rb +1 -1
  74. data/lib/opennebula/oneflow_client.rb +4 -3
  75. data/lib/opennebula/pool.rb +6 -3
  76. data/lib/opennebula/pool_element.rb +1 -1
  77. data/lib/opennebula/security_group.rb +1 -1
  78. data/lib/opennebula/security_group_pool.rb +1 -1
  79. data/lib/opennebula/server_cipher_auth.rb +1 -1
  80. data/lib/opennebula/server_x509_auth.rb +1 -1
  81. data/lib/opennebula/ssh_auth.rb +1 -1
  82. data/lib/opennebula/system.rb +1 -1
  83. data/lib/opennebula/template.rb +4 -13
  84. data/lib/opennebula/template_ext.rb +342 -0
  85. data/lib/opennebula/template_pool.rb +1 -1
  86. data/lib/opennebula/user.rb +26 -2
  87. data/lib/opennebula/user_pool.rb +1 -1
  88. data/lib/opennebula/utils.rb +1 -1
  89. data/lib/opennebula/vdc.rb +1 -1
  90. data/lib/opennebula/vdc_pool.rb +1 -1
  91. data/lib/opennebula/virtual_machine.rb +26 -206
  92. data/lib/opennebula/virtual_machine_ext.rb +469 -0
  93. data/lib/opennebula/virtual_machine_pool.rb +1 -1
  94. data/lib/opennebula/virtual_network.rb +4 -10
  95. data/lib/opennebula/virtual_network_pool.rb +1 -1
  96. data/lib/opennebula/virtual_router.rb +4 -12
  97. data/lib/opennebula/virtual_router_pool.rb +1 -1
  98. data/lib/opennebula/vm_group.rb +4 -11
  99. data/lib/opennebula/vm_group_pool.rb +1 -1
  100. data/lib/opennebula/vntemplate.rb +4 -13
  101. data/lib/opennebula/vntemplate_pool.rb +1 -1
  102. data/lib/opennebula/wait_ext.rb +257 -0
  103. data/lib/opennebula/x509_auth.rb +1 -1
  104. data/lib/opennebula/xml_element.rb +1 -1
  105. data/lib/opennebula/xml_pool.rb +1 -1
  106. data/lib/opennebula/xml_utils.rb +1 -1
  107. data/lib/opennebula/zone.rb +1 -1
  108. data/lib/opennebula/zone_pool.rb +1 -1
  109. data/lib/rest_client.rb +201 -0
  110. data/lib/scripts_common.rb +183 -0
  111. data/lib/transport_zone.rb +43 -0
  112. data/lib/vcenter_driver.rb +13 -12
  113. data/lib/vcenter_importer.rb +616 -0
  114. data/lib/vi_client.rb +281 -0
  115. data/lib/vi_helper.rb +313 -0
  116. data/lib/virtual_machine.rb +3477 -0
  117. data/lib/virtual_wire.rb +158 -0
  118. data/lib/vm_device.rb +80 -0
  119. data/lib/vm_disk.rb +202 -0
  120. data/lib/vm_folder.rb +69 -0
  121. data/lib/vm_helper.rb +30 -0
  122. data/lib/vm_monitor.rb +303 -0
  123. data/lib/vm_nic.rb +70 -0
  124. data/lib/vm_template.rb +1963 -0
  125. data/lib/vmm_importer.rb +121 -0
  126. metadata +140 -27
@@ -0,0 +1,586 @@
1
+ # -------------------------------------------------------------------------- #
2
+ # Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
3
+ # #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may #
5
+ # not use this file except in compliance with the License. You may obtain #
6
+ # a copy of the License at #
7
+ # #
8
+ # http://www.apache.org/licenses/LICENSE-2.0 #
9
+ # #
10
+ # Unless required by applicable law or agreed to in writing, software #
11
+ # distributed under the License is distributed on an "AS IS" BASIS, #
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
13
+ # See the License for the specific language governing permissions and #
14
+ # limitations under the License. #
15
+ #--------------------------------------------------------------------------- #
16
+
17
+ # Module to decorate MarketApp class with additional helpers not directly
18
+ # exposed through the OpenNebula XMLRPC API. The extensions include
19
+ # - export helper that creates OpenNebula related objects from a given app.
20
+ #
21
+ # rubocop:disable Style/ClassAndModuleChildren
22
+ module OpenNebula::MarketPlaceAppExt
23
+
24
+ include OpenNebula
25
+
26
+ def self.extend_object(obj)
27
+ if !obj.is_a?(OpenNebula::MarketPlaceApp)
28
+ raise StandardError, "Cannot extended #{obj.class} " \
29
+ 'with MarketPlaceAppExt'
30
+ end
31
+
32
+ class << obj
33
+
34
+ ####################################################################
35
+ # Public extended interface
36
+ ####################################################################
37
+ # Exports this app to a suitable OpenNebula object
38
+ # @param appid [Integer] id of the marketplace app
39
+ # @param options [Hash] to control the export behavior
40
+ # dsid [Integer] datastore to save images
41
+ # f_dsid [Integer] file datastore to save kernels
42
+ # name [String] of the new object
43
+ # vmtemplate_name [String] name for the VM Template, if the App
44
+ # has one complex [Boolean] true to only create image
45
+ #
46
+ # @return [Hash, OpenNebula::Error] with the ID and type of
47
+ # the created objects. Instead of an ID, the array may
48
+ # contain OpenNebula::Error with specific object creation errors
49
+ # {
50
+ # :vm => [ vm ids/OpenNebula::Error ],
51
+ # :vmtemplate => [ vmtemplates ids/OpenNebula::Error ],
52
+ # :image => [ vm ids/OpenNebula::Error ]
53
+ # }
54
+ # rc_info = {
55
+ # :image => [],
56
+ # :image_type => nil,
57
+ # :vmtemplate => [],
58
+ # :service_template => []
59
+ # }
60
+ def export(options = {})
61
+ # Get App information and check for errors
62
+ rc = info
63
+ return rc if OpenNebula.is_error?(rc)
64
+ return Error.new('App is not READY') if state_str != 'READY'
65
+
66
+ if options[:dsid].nil?
67
+ return Error.new('Missing datastore id')
68
+ end
69
+
70
+ if options[:name].nil?
71
+ return Error.new('Missing name to export app')
72
+ end
73
+
74
+ case type_str
75
+ when 'IMAGE'
76
+ export_image(options)
77
+ when 'VMTEMPLATE'
78
+ options[:notemplate] = true
79
+
80
+ export_vm_template(options)
81
+ when 'SERVICE_TEMPLATE'
82
+ export_service_template(options)
83
+ else
84
+ Error.new("App type #{type_str} not supported")
85
+ end
86
+ end
87
+
88
+ ####################################################################
89
+ # Private methods
90
+ ####################################################################
91
+ private
92
+
93
+ # Exports an OpenNebula Image from this marketplace app
94
+ # @param options to export the image
95
+ # :vmtemplate_name [String] name of new image and template
96
+ # :url_args [String] optional URL arguments
97
+ # :dsid [String] Datastore id to create the image
98
+ # :f_dsid [String] Files Datastore id
99
+ # :notemplate [Bool] if true do not create vm_template (if any)
100
+ # :template [Integer] Template id to use with image
101
+ # :default_template [String] Default template id for vCenter
102
+ # @return [Hash]
103
+ # :image [Array] of the new image
104
+ # :image_type [String] of the new image (CONTEXT, KERNEL, CDROM)
105
+ # :vmtemplate [Array] of the associated vm_template (if any)
106
+ # :service_template [Array] empty []
107
+ def export_image(options)
108
+ rc_info = {
109
+ :image => [],
110
+ :image_type => nil,
111
+ :vmtemplate => [],
112
+ :service_template => []
113
+ }
114
+
115
+ #---------------------------------------------------------------
116
+ # Create the image template
117
+ #---------------------------------------------------------------
118
+ tmpl = ''
119
+
120
+ if self['APPTEMPLATE64']
121
+ tmpl = Base64.decode64(self['APPTEMPLATE64'])
122
+ end
123
+
124
+ tmpl << <<-EOT
125
+ NAME = "#{options[:name]}"
126
+ FROM_APP = "#{self['ID']}"
127
+ EOT
128
+
129
+ if options[:url_args]
130
+ tmpl << "URL_ARGS=\"#{options[:url_args]}\"\n"
131
+ end
132
+
133
+ #---------------------------------------------------------------
134
+ # Kernel or context images stored in a files datastore
135
+ #---------------------------------------------------------------
136
+ dsid = options[:dsid]
137
+
138
+ if tmpl.match(/^\s*TYPE\s*=\s*KERNEL|CONTEXT\s/i)
139
+ if options[:f_dsid]
140
+ dsid = options[:f_dsid]
141
+ else
142
+ dspool = DatastorePool.new(@client)
143
+
144
+ rc = dspool.info
145
+ return { :image => [rc] } if OpenNebula.is_error?(rc)
146
+
147
+ file_ds = dspool.select {|d| d['TYPE'].to_i == 2 }
148
+ dsid = file_ds.first['ID'].to_i
149
+ end
150
+ end
151
+
152
+ ds = OpenNebula::Datastore.new_with_id(dsid, @client)
153
+ rc = ds.info
154
+
155
+ is_vcenter = !OpenNebula.is_error?(rc) &&
156
+ (ds['TEMPLATE/DRIVER'] == 'vcenter')
157
+
158
+ if is_vcenter && options[:template].nil?
159
+ options = update_options_with_template(options)
160
+ end
161
+
162
+ #---------------------------------------------------------------
163
+ # Allocate the image in OpenNebula
164
+ #---------------------------------------------------------------
165
+ image = Image.new(Image.build_xml, @client)
166
+ rc = image.allocate(tmpl, dsid)
167
+
168
+ if OpenNebula.is_error?(rc)
169
+ rc_info[:image] = [rc]
170
+
171
+ return rc_info
172
+ end
173
+
174
+ image.info
175
+
176
+ image.delete_element('TEMPLATE/FORMAT')
177
+ image.delete_element('TEMPLATE/DRIVER')
178
+ image.delete_element('TEMPLATE/DEV_PREFIX') if is_vcenter
179
+
180
+ image.update(image.template_xml)
181
+
182
+ rc_info[:image] = [image.id]
183
+ rc_info[:image_type] = image.type_str
184
+
185
+ #---------------------------------------------------------------
186
+ # Created an associated VMTemplate if needed
187
+ #---------------------------------------------------------------
188
+ if (self['TEMPLATE/VMTEMPLATE64'].nil? && !is_vcenter) ||
189
+ options[:notemplate] ||
190
+ options[:template] == -1
191
+ return rc_info
192
+ end
193
+
194
+ if !options[:template].nil?
195
+ template_id = options[:template]
196
+
197
+ if template_id < 0
198
+ raise 'Invalid option, template_id must be a valid ID'
199
+ end
200
+
201
+ template = Template.new_with_id(template_id, @client)
202
+
203
+ vmtpl_id = template.clone(
204
+ options[:vmtemplate_name] || options[:name]
205
+ )
206
+
207
+ tmpl << <<-EOT
208
+
209
+ NAME = "#{options[:vmtemplate_name] || options[:name]}"
210
+ DISK = [ IMAGE_ID = "#{image.id}" ]
211
+ EOT
212
+
213
+ template = Template.new_with_id(vmtpl_id, @client)
214
+
215
+ template.update(tmpl, true)
216
+
217
+ rc_info[:vmtemplate] = [vmtpl_id]
218
+ else
219
+ tmpl = Base64.decode64(self['TEMPLATE/VMTEMPLATE64'])
220
+ tmpl << <<-EOT
221
+
222
+ NAME = "#{options[:vmtemplate_name] || options[:name]}"
223
+ DISK = [ IMAGE_ID = "#{image.id}" ]
224
+ EOT
225
+
226
+ vmtpl = Template.new(Template.build_xml, @client)
227
+
228
+ rc = vmtpl.allocate(tmpl)
229
+ rc = vmtpl.id unless OpenNebula.is_error?(rc)
230
+
231
+ rc_info[:vmtemplate] = [rc]
232
+ end
233
+
234
+ rc_info
235
+ end
236
+
237
+ # Export complex template from marketplace
238
+ #
239
+ # @param name [Hash] Export options
240
+ # @param ds_id [Integer] Datastore ID to export child apps
241
+ def export_vm_template(options)
242
+ rc = {
243
+ :image => [],
244
+ :image_type => nil,
245
+ :vmtemplate => [],
246
+ :service_template => []
247
+ }
248
+
249
+ vmtmpl = export_recursive('//DISK', options) do |disks|
250
+ create_vm_template(options, disks)
251
+ end
252
+
253
+ rc[:vmtemplate] = [vmtmpl[0]]
254
+
255
+ unless OpenNebula.is_error?(vmtmpl[0])
256
+ rc[:image] += vmtmpl[1]
257
+ rc[:vmtemplate] += vmtmpl[2] unless options[:notemplate]
258
+ end
259
+
260
+ rc
261
+ end
262
+
263
+ # Export service template from marketplace
264
+ #
265
+ # @param name [Hash] Export options
266
+ # @param ds_id [Integer] Datastore ID to export child apps
267
+ def export_service_template(options)
268
+ rc = {
269
+ :image => [],
270
+ :image_type => nil,
271
+ :vmtemplate => [],
272
+ :service_template => []
273
+ }
274
+
275
+ stmpl = export_recursive('//ROLE', options) do |roles|
276
+ create_service_template(options, roles)
277
+ end
278
+
279
+ rc[:service_template] = [stmpl[0]]
280
+
281
+ unless OpenNebula.is_error?(stmpl[0])
282
+ rc[:image] += stmpl[1]
283
+ rc[:vmtemplate] += stmpl[2]
284
+ end
285
+
286
+ rc
287
+ end
288
+
289
+ def update_options_with_template(options, _validate = false)
290
+ vcenterrc_path =
291
+ "#{VAR_LOCATION}/remotes/etc/vmm/vcenter/vcenterrc"
292
+
293
+ if File.file?(vcenterrc_path)
294
+ config_vcenter = YAML.load_file(vcenterrc_path)
295
+
296
+ if config_vcenter.key?(:default_template)
297
+ options[:template] =
298
+ config_vcenter[:default_template]
299
+
300
+ options
301
+ else
302
+ raise "Couldn't find default_template " \
303
+ 'configuration in vcenterrc conf ' \
304
+ 'file. Please use the --template ' \
305
+ 'file to define a VM Template ID if ' \
306
+ 'needed or add default_template to' \
307
+ ' vcenterrc conf file'
308
+ end
309
+ else
310
+ raise "Couldn't find vcenterrc conf file. " \
311
+ ' Please use the --template file to define' \
312
+ ' a VM Template ID if needed.'
313
+ end
314
+ end
315
+
316
+ # Creates a VM template based on the APPTEMPLATE64 attribute
317
+ # @param [Hash] options
318
+ # :export_name [String] name of the vm template
319
+ # @param [Hash] disks exported disks from related apps.
320
+ # As returned by the export_image method
321
+ #
322
+ # @return [Integer, OpenNebula::Error] template id or error
323
+ def create_vm_template(options, disks)
324
+ dsid = options[:dsid]
325
+ ds = OpenNebula::Datastore.new_with_id(dsid, @client)
326
+ rc = ds.info
327
+
328
+ is_vcenter =
329
+ !OpenNebula.is_error?(rc) &&
330
+ (ds['TEMPLATE/DRIVER'] == 'vcenter')
331
+
332
+ if is_vcenter
333
+ if options[:template].nil?
334
+ options = update_options_with_template(options)
335
+ end
336
+
337
+ template_id = options[:template]
338
+
339
+ if template_id < 0
340
+ return
341
+ end
342
+
343
+ template = Template.new_with_id(template_id, @client)
344
+
345
+ vmtpl_id = template.clone(
346
+ options[:vmtemplate_name] || options[:name]
347
+ )
348
+
349
+ vmtpl = Template.new_with_id(vmtpl_id, @client)
350
+ rc = vmtpl.info
351
+ else
352
+ # ----------------------------------------------------------
353
+ # Allocate Template
354
+ # ----------------------------------------------------------
355
+ if self['TEMPLATE/APPTEMPLATE64'].nil?
356
+ return Error.new(
357
+ "Missing APPTEMPLATE64 for App #{id}"
358
+ )
359
+ end
360
+
361
+ tmpl = Base64.decode64(self['TEMPLATE/APPTEMPLATE64'])
362
+
363
+ tmpl << "\nNAME=\"#{options[:name]}\"\n"
364
+
365
+ vmtpl = Template.new(Template.build_xml, @client)
366
+ rc = vmtpl.allocate(tmpl)
367
+ end
368
+
369
+ return rc if OpenNebula.is_error?(rc)
370
+
371
+ # --------------------------------------------------------------
372
+ # Update disk information in template
373
+ # --------------------------------------------------------------
374
+ vmtpl.info
375
+
376
+ context = []
377
+
378
+ disks.each do |_app, disk|
379
+ id = disk[:image]
380
+
381
+ case disk[:image_type]
382
+ when 'IMAGE', 'OS', 'DATABLOCK', 'CDROM'
383
+ vmtpl.add_element('TEMPLATE',
384
+ 'DISK' => { 'IMAGE_ID' => id.first })
385
+ when 'CONTEXT'
386
+ context << "$FILE[IMAGE_ID=\"#{id}\"]"
387
+ when 'KERNEL'
388
+ if !vmtpl.has_elements?('TEMPLATE/OS')
389
+ vmtpl.add_element('TEMPLATE', 'OS'=>{})
390
+ end
391
+
392
+ vmtpl.add_element(
393
+ 'TEMPLATE/OS',
394
+ 'KERNEL_DS' => "$FILE[IMAGE_ID=#{id}]"
395
+ )
396
+ end
397
+ end
398
+
399
+ if !context.empty?
400
+ if !vmtpl.has_elements?('TEMPLATE/CONTEXT')
401
+ vmtpl.add_element('TEMPLATE', 'CONTEXT' => {})
402
+ end
403
+
404
+ vmtpl.add_element('TEMPLATE/CONTEXT',
405
+ 'FILES_DS' => context.join(' '))
406
+ end
407
+
408
+ # --------------------------------------------------------------
409
+ # Update template information in OpenNebula
410
+ # --------------------------------------------------------------
411
+ vmtpl.update(vmtpl.template_xml)
412
+
413
+ vmtpl.id
414
+ end
415
+
416
+ # Creates a Service template based on the VMTEMPLATE64 attribute
417
+ # @return [Integer, OpenNebula::Error] template id or error
418
+ def create_service_template(options, roles)
419
+ # --------------------------------------------------------------
420
+ # Allocate Template
421
+ # --------------------------------------------------------------
422
+ if self['TEMPLATE/APPTEMPLATE64'].nil?
423
+ return Error.new("Missing APPTEMPLATE64 for App #{id}")
424
+ end
425
+
426
+ tmpl = Base64.decode64(self['TEMPLATE/APPTEMPLATE64'])
427
+ tmpl = JSON.parse(tmpl)
428
+
429
+ tmpl['name'] = options[:name]
430
+
431
+ # --------------------------------------------------------------
432
+ # Append template IDs to each role information
433
+ # --------------------------------------------------------------
434
+ tmpl['roles'].each do |role|
435
+ t_id = roles.find {|_, v| v[:names].include?(role['name']) }
436
+
437
+ if t_id.nil? || t_id[1].nil? || t_id[1][:vmtemplate].nil?
438
+ next
439
+ end
440
+
441
+ role['vm_template'] = nil
442
+ role['vm_template'] = t_id[1][:vmtemplate][0]
443
+ end
444
+
445
+ # --------------------------------------------------------------
446
+ # Allocate Service template in OpenNebula
447
+ # --------------------------------------------------------------
448
+ stmpl = ServiceTemplate.new(ServiceTemplate.build_xml, @client)
449
+
450
+ rc = stmpl.allocate(tmpl.to_json)
451
+ rc = stmpl.id unless OpenNebula.is_error?(rc)
452
+
453
+ rc
454
+ end
455
+
456
+ # Export complex template from marketplace
457
+ #
458
+ # @param xpath [String] Xpath to search childs
459
+ # @param options [Hash] Export options
460
+ # @param ds_id [Integer] Datastore ID to export child apps
461
+ def export_recursive(xpath, options)
462
+ # Get marketplace apps pool to find roles apps
463
+ pool = OpenNebula::MarketPlaceAppPool.new(@client)
464
+ rc = pool.info
465
+
466
+ return rc if OpenNebula.is_error?(rc)
467
+
468
+ # Apps that have been already exported
469
+ #
470
+ # app_name =>
471
+ # :vmtempalte = exported template ID
472
+ # :image = exported image ID
473
+ # :image_type = type (KERNEL, CONTEXT...) of image
474
+ # :names = [name_a, name_b, ...]
475
+ exported = {}
476
+ idx = 0
477
+
478
+ # Store IDs of created resources
479
+ images = []
480
+ templates = []
481
+
482
+ # Iterate over all childs
483
+ rc = retrieve_xmlelements(xpath).each do |obj|
484
+ # Get name and app information
485
+ obj_name = obj['NAME']
486
+ app = obj['APP']
487
+
488
+ # If the app was already exported, do not export it again
489
+ if exported[app]
490
+ exported[app][:names] << obj_name
491
+ next
492
+ end
493
+
494
+ # Find app in pool
495
+ obj = pool.find {|p| p['NAME'] == app }
496
+
497
+ break Error.new("App `#{app}` not found") unless obj
498
+
499
+ obj.extend(MarketPlaceAppExt)
500
+
501
+ rc = obj.export(
502
+ :dsid => options[:dsid],
503
+ :name => "#{options[:name]}-#{idx}",
504
+ :notemplate => options[:notemplate]
505
+ )
506
+
507
+ image = rc[:image].first if rc[:image]
508
+ vmtemplate = rc[:vmtemplate].first if rc[:vmtemplate]
509
+
510
+ break image if OpenNebula.is_error?(image)
511
+ break vmtemplate if OpenNebula.is_error?(vmtemplate)
512
+
513
+ idx += 1
514
+
515
+ # Update exported hash with information
516
+ exported[app] = rc
517
+ exported[app][:names] = [obj_name]
518
+
519
+ # Add IDs to return object
520
+ images << image
521
+ templates << vmtemplate
522
+ end
523
+
524
+ if block_given? && !OpenNebula.is_error?(rc)
525
+ rc = yield(exported)
526
+ end
527
+
528
+ if OpenNebula.is_error?(rc)
529
+ rc_rbck = rollback_export(exported, xpath != '//DISK')
530
+
531
+ return rc_rbck if OpenNebula.is_error?(rc_rbck)
532
+ end
533
+
534
+ [rc, images, templates]
535
+ end
536
+
537
+ # Delete templates/images in case something went wrong
538
+ #
539
+ # @param exported [Hash] Exported apps information
540
+ # @param template [Boolean] True to delete VM Template
541
+ # False to delete images
542
+ #
543
+ # @return [nil | OpenNebula::Error]
544
+ def rollback_export(exported, is_template)
545
+ ret = ''
546
+
547
+ if is_template
548
+ obj_factory = lambda {|v|
549
+ id = v[:vmtemplate].first
550
+ [Template.new_with_id(id, @client),
551
+ "Error deleting template #{id}"]
552
+ }
553
+
554
+ delete_method = 'delete(true)'
555
+ else
556
+ obj_factory = lambda {|v|
557
+ id = v[:image].first
558
+ [Image.new_with_id(id, @client),
559
+ "Error deleting image #{id}"]
560
+ }
561
+
562
+ delete_method = 'delete'
563
+ end
564
+
565
+ exported.each do |_, v|
566
+ obj, err_msg = obj_factory.call(v)
567
+
568
+ next unless OpenNebula.is_error?(obj.send(delete_method))
569
+
570
+ ret << err_msg
571
+ end
572
+
573
+ if ret.empty?
574
+ nil
575
+ else
576
+ Error.new(ret) unless ret.empty?
577
+ end
578
+ end
579
+
580
+ end
581
+
582
+ super
583
+ end
584
+
585
+ end
586
+ # rubocop:enable Style/ClassAndModuleChildren