opennebula 5.12.12 → 5.12.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) 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 +1 -1
  5. data/lib/OpenNebulaDriver.rb +1 -1
  6. data/lib/VirtualMachineDriver.rb +1 -1
  7. data/lib/cloud/CloudClient.rb +3 -3
  8. data/lib/models/role.rb +1095 -0
  9. data/lib/models/service.rb +648 -0
  10. data/lib/models/service_pool.rb +166 -0
  11. data/lib/models/service_template.rb +503 -0
  12. data/lib/models/service_template_pool.rb +32 -0
  13. data/lib/models.rb +32 -0
  14. data/lib/opennebula/acl.rb +1 -1
  15. data/lib/opennebula/acl_pool.rb +1 -1
  16. data/lib/opennebula/client.rb +1 -1
  17. data/lib/opennebula/cluster.rb +1 -1
  18. data/lib/opennebula/cluster_pool.rb +1 -1
  19. data/lib/opennebula/datastore.rb +1 -1
  20. data/lib/opennebula/datastore_pool.rb +1 -1
  21. data/lib/opennebula/document.rb +1 -1
  22. data/lib/opennebula/document_json.rb +1 -1
  23. data/lib/opennebula/document_pool.rb +1 -1
  24. data/lib/opennebula/document_pool_json.rb +1 -1
  25. data/lib/opennebula/error.rb +1 -1
  26. data/lib/opennebula/group.rb +1 -1
  27. data/lib/opennebula/group_pool.rb +1 -1
  28. data/lib/opennebula/hook.rb +1 -1
  29. data/lib/opennebula/hook_log.rb +1 -1
  30. data/lib/opennebula/hook_pool.rb +1 -1
  31. data/lib/opennebula/host.rb +1 -1
  32. data/lib/opennebula/host_pool.rb +1 -1
  33. data/lib/opennebula/image.rb +1 -1
  34. data/lib/opennebula/image_pool.rb +1 -1
  35. data/lib/opennebula/ldap_auth.rb +1 -1
  36. data/lib/opennebula/ldap_auth_spec.rb +1 -1
  37. data/lib/opennebula/marketplace.rb +1 -1
  38. data/lib/opennebula/marketplace_pool.rb +1 -1
  39. data/lib/opennebula/marketplaceapp.rb +1 -1
  40. data/lib/opennebula/marketplaceapp_pool.rb +1 -1
  41. data/lib/opennebula/oneflow_client.rb +1 -1
  42. data/lib/opennebula/pool.rb +1 -1
  43. data/lib/opennebula/pool_element.rb +1 -1
  44. data/lib/opennebula/security_group.rb +1 -1
  45. data/lib/opennebula/security_group_pool.rb +1 -1
  46. data/lib/opennebula/server_cipher_auth.rb +1 -1
  47. data/lib/opennebula/server_x509_auth.rb +1 -1
  48. data/lib/opennebula/ssh_auth.rb +1 -1
  49. data/lib/opennebula/system.rb +1 -1
  50. data/lib/opennebula/template.rb +1 -1
  51. data/lib/opennebula/template_pool.rb +1 -1
  52. data/lib/opennebula/user.rb +1 -1
  53. data/lib/opennebula/user_pool.rb +1 -1
  54. data/lib/opennebula/utils.rb +1 -1
  55. data/lib/opennebula/vdc.rb +1 -1
  56. data/lib/opennebula/vdc_pool.rb +1 -1
  57. data/lib/opennebula/virtual_machine.rb +1 -1
  58. data/lib/opennebula/virtual_machine_pool.rb +1 -1
  59. data/lib/opennebula/virtual_network.rb +1 -1
  60. data/lib/opennebula/virtual_network_pool.rb +1 -1
  61. data/lib/opennebula/virtual_router.rb +1 -1
  62. data/lib/opennebula/virtual_router_pool.rb +1 -1
  63. data/lib/opennebula/vm_group.rb +1 -1
  64. data/lib/opennebula/vm_group_pool.rb +1 -1
  65. data/lib/opennebula/vntemplate.rb +1 -1
  66. data/lib/opennebula/vntemplate_pool.rb +1 -1
  67. data/lib/opennebula/x509_auth.rb +1 -1
  68. data/lib/opennebula/xml_element.rb +1 -1
  69. data/lib/opennebula/xml_pool.rb +1 -1
  70. data/lib/opennebula/xml_utils.rb +1 -1
  71. data/lib/opennebula/zone.rb +1 -1
  72. data/lib/opennebula/zone_pool.rb +1 -1
  73. data/lib/opennebula.rb +2 -2
  74. data/lib/vcenter_driver.rb +1 -1
  75. metadata +50 -2
@@ -0,0 +1,648 @@
1
+ # -------------------------------------------------------------------------- #
2
+ # Copyright 2002-2023, 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 OpenNebula
18
+
19
+ # Service class as wrapper of DocumentJSON
20
+ class Service < DocumentJSON
21
+
22
+ attr_reader :roles, :client
23
+
24
+ DOCUMENT_TYPE = 100
25
+
26
+ STATE = {
27
+ 'PENDING' => 0,
28
+ 'DEPLOYING' => 1,
29
+ 'RUNNING' => 2,
30
+ 'UNDEPLOYING' => 3,
31
+ 'WARNING' => 4,
32
+ 'DONE' => 5,
33
+ 'FAILED_UNDEPLOYING' => 6,
34
+ 'FAILED_DEPLOYING' => 7,
35
+ 'SCALING' => 8,
36
+ 'FAILED_SCALING' => 9,
37
+ 'COOLDOWN' => 10
38
+ }
39
+
40
+ STATE_STR = %w[
41
+ PENDING
42
+ DEPLOYING
43
+ RUNNING
44
+ UNDEPLOYING
45
+ WARNING
46
+ DONE
47
+ FAILED_UNDEPLOYING
48
+ FAILED_DEPLOYING
49
+ SCALING
50
+ FAILED_SCALING
51
+ COOLDOWN
52
+ ]
53
+
54
+ TRANSIENT_STATES = %w[
55
+ DEPLOYING
56
+ UNDEPLOYING
57
+ SCALING
58
+ COOLDOWN
59
+ ]
60
+
61
+ FAILED_STATES = %w[
62
+ FAILED_DEPLOYING
63
+ FAILED_UNDEPLOYING
64
+ FAILED_SCALING
65
+ ]
66
+
67
+ RECOVER_DEPLOY_STATES = %w[
68
+ FAILED_DEPLOYING
69
+ DEPLOYING
70
+ PENDING
71
+ ]
72
+
73
+ RECOVER_UNDEPLOY_STATES = %w[
74
+ FAILED_UNDEPLOYING
75
+ UNDEPLOYING
76
+ ]
77
+
78
+ RECOVER_SCALE_STATES = %w[
79
+ FAILED_SCALING
80
+ SCALING
81
+ ]
82
+
83
+ LOG_COMP = 'SER'
84
+
85
+ # Returns the service state
86
+ # @return [Integer] the service state
87
+ def state
88
+ @body['state'].to_i
89
+ end
90
+
91
+ # Returns the service strategy
92
+ # @return [String] the service strategy
93
+ def strategy
94
+ @body['deployment']
95
+ end
96
+
97
+ # Returns the string representation of the service state
98
+ # @return the state string
99
+ def state_str
100
+ STATE_STR[state]
101
+ end
102
+
103
+ # Returns true if the service is in transient state
104
+ # @return true if the service is in transient state, false otherwise
105
+ def transient_state?
106
+ TRANSIENT_STATES.include? STATE_STR[state]
107
+ end
108
+
109
+ # Return true if the service is in failed state
110
+ # @return true if the service is in failed state, false otherwise
111
+ def failed_state?
112
+ FAILED_STATES.include? STATE_STR[state]
113
+ end
114
+
115
+ # Return true if the service can be undeployed
116
+ # @return true if the service can be undeployed, false otherwise
117
+ def can_undeploy?
118
+ if (transient_state? && state != Service::STATE['UNDEPLOYING']) ||
119
+ state == Service::STATE['DONE'] || failed_state?
120
+ false
121
+ else
122
+ true
123
+ end
124
+ end
125
+
126
+ def can_recover_deploy?
127
+ RECOVER_DEPLOY_STATES.include? STATE_STR[state]
128
+ end
129
+
130
+ def can_recover_undeploy?
131
+ RECOVER_UNDEPLOY_STATES.include? STATE_STR[state]
132
+ end
133
+
134
+ def can_recover_scale?
135
+ RECOVER_SCALE_STATES.include? STATE_STR[state]
136
+ end
137
+
138
+ # Returns the running_status_vm option
139
+ # @return [true, false] true if the running_status_vm option is enabled
140
+ def report_ready?
141
+ @body['ready_status_gate']
142
+ end
143
+
144
+ def uname
145
+ self['UNAME']
146
+ end
147
+
148
+ def gid
149
+ self['GID'].to_i
150
+ end
151
+
152
+ # Replaces this object's client with a new one
153
+ # @param [OpenNebula::Client] owner_client the new client
154
+ def replace_client(owner_client)
155
+ @client = owner_client
156
+ end
157
+
158
+ # Sets a new state
159
+ # @param [Integer] the new state
160
+ # @return [true, false] true if the value was changed
161
+ # rubocop:disable Naming/AccessorMethodName
162
+ def set_state(state)
163
+ # rubocop:enable Naming/AccessorMethodName
164
+ if state < 0 || state > STATE_STR.size
165
+ return false
166
+ end
167
+
168
+ @body['state'] = state.to_i
169
+
170
+ msg = "New state: #{STATE_STR[state]}"
171
+ Log.info LOG_COMP, msg, id
172
+ log_info(msg)
173
+
174
+ true
175
+ end
176
+
177
+ # Returns true if all the nodes are correctly deployed
178
+ # @return [true, false] true if all the nodes are correctly deployed
179
+ def all_roles_running?
180
+ @roles.each do |_name, role|
181
+ if role.state != Role::STATE['RUNNING']
182
+ return false
183
+ end
184
+ end
185
+
186
+ true
187
+ end
188
+
189
+ # Returns true if all the nodes are in done state
190
+ # @return [true, false] true if all the nodes are correctly deployed
191
+ def all_roles_done?
192
+ @roles.each do |_name, role|
193
+ if role.state != Role::STATE['DONE']
194
+ return false
195
+ end
196
+ end
197
+
198
+ true
199
+ end
200
+
201
+ # Create a new service based on the template provided
202
+ # @param [String] template_json
203
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
204
+ # otherwise
205
+ def allocate(template_json)
206
+ template = JSON.parse(template_json)
207
+ template['state'] = STATE['PENDING']
208
+
209
+ if template['roles']
210
+ template['roles'].each do |elem|
211
+ elem['state'] ||= Role::STATE['PENDING']
212
+ end
213
+ end
214
+
215
+ super(template.to_json, template['name'])
216
+ end
217
+
218
+ # Recover a failed service.
219
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
220
+ # otherwise
221
+ def recover
222
+ if [Service::STATE['FAILED_DEPLOYING']].include?(state)
223
+ @roles.each do |_name, role|
224
+ if role.state == Role::STATE['FAILED_DEPLOYING']
225
+ role.set_state(Role::STATE['PENDING'])
226
+ end
227
+ end
228
+
229
+ set_state(Service::STATE['DEPLOYING'])
230
+
231
+ elsif state == Service::STATE['FAILED_SCALING']
232
+ @roles.each do |_name, role|
233
+ if role.state == Role::STATE['FAILED_SCALING']
234
+ role.set_state(Role::STATE['SCALING'])
235
+ end
236
+ end
237
+
238
+ set_state(Service::STATE['SCALING'])
239
+
240
+ elsif state == Service::STATE['FAILED_UNDEPLOYING']
241
+ @roles.each do |_name, role|
242
+ if role.state == Role::STATE['FAILED_UNDEPLOYING']
243
+ role.set_state(Role::STATE['RUNNING'])
244
+ end
245
+ end
246
+
247
+ set_state(Service::STATE['UNDEPLOYING'])
248
+
249
+ elsif state == Service::STATE['COOLDOWN']
250
+ @roles.each do |_name, role|
251
+ if role.state == Role::STATE['COOLDOWN']
252
+ role.set_state(Role::STATE['RUNNING'])
253
+ end
254
+ end
255
+
256
+ set_state(Service::STATE['RUNNING'])
257
+
258
+ elsif state == Service::STATE['WARNING']
259
+ @roles.each do |_name, role|
260
+ if role.state == Role::STATE['WARNING']
261
+ role.recover_warning
262
+ end
263
+ end
264
+ else
265
+ OpenNebula::Error.new('Action recover: Wrong state' \
266
+ " #{state_str}")
267
+ end
268
+ end
269
+
270
+ # Delete the service. All the VMs are also deleted from OpenNebula.
271
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
272
+ # otherwise
273
+ def delete
274
+ networks = JSON.parse(self['TEMPLATE/BODY'])['networks_values']
275
+
276
+ networks.each do |net|
277
+ next unless net[net.keys[0]].key? 'template_id'
278
+
279
+ net_id = net[net.keys[0]]['id'].to_i
280
+
281
+ rc = OpenNebula::VirtualNetwork
282
+ .new_with_id(net_id, @client).delete
283
+
284
+ if OpenNebula.is_error?(rc)
285
+ log_info("Error deleting vnet #{net_id}: #{rc}")
286
+ end
287
+ end if networks
288
+
289
+ super()
290
+ end
291
+
292
+ # Retrieves the information of the Service and all its Nodes.
293
+ #
294
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
295
+ # otherwise
296
+ def info
297
+ rc = super
298
+ if OpenNebula.is_error?(rc)
299
+ return rc
300
+ end
301
+
302
+ @roles = {}
303
+
304
+ if @body['roles']
305
+ @body['roles'].each do |elem|
306
+ elem['state'] ||= Role::STATE['PENDING']
307
+ role = Role.new(elem, self)
308
+ @roles[role.name] = role
309
+ end
310
+ end
311
+
312
+ nil
313
+ end
314
+
315
+ # Retrieves the information of the Service and all its Nodes.
316
+ #
317
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
318
+ # otherwise
319
+ def info_roles
320
+ @roles = {}
321
+
322
+ if @body['roles']
323
+ @body['roles'].each do |elem|
324
+ elem['state'] ||= Role::STATE['PENDING']
325
+ role = Role.new(elem, self)
326
+ @roles[role.name] = role
327
+ end
328
+ end
329
+
330
+ nil
331
+ end
332
+
333
+ # Add an info message in the service information that will be stored
334
+ # in OpenNebula
335
+ # @param [String] message
336
+ def log_info(message)
337
+ add_log(Logger::INFO, message)
338
+ end
339
+
340
+ # Add an error message in the service information that will be stored
341
+ # in OpenNebula
342
+ # @param [String] message
343
+ def log_error(message)
344
+ add_log(Logger::ERROR, message)
345
+ end
346
+
347
+ # Changes the owner/group
348
+ #
349
+ # @param [Integer] uid the new owner id. Use -1 to leave the current one
350
+ # @param [Integer] gid the new group id. Use -1 to leave the current one
351
+ #
352
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
353
+ # otherwise
354
+ def chown(uid, gid)
355
+ old_uid = self['UID'].to_i
356
+ old_gid = self['GID'].to_i
357
+
358
+ rc = super(uid, gid)
359
+
360
+ if OpenNebula.is_error?(rc)
361
+ return rc
362
+ end
363
+
364
+ @roles.each do |_name, role|
365
+ rc = role.chown(uid, gid)
366
+
367
+ break if rc[0] == false
368
+ end
369
+
370
+ if rc[0] == false
371
+ log_error('Chown operation failed, will try to rollback ' \
372
+ 'all VMs to the old user and group')
373
+
374
+ update
375
+
376
+ super(old_uid, old_gid)
377
+
378
+ @roles.each do |_name, role|
379
+ role.chown(old_uid, old_gid)
380
+ end
381
+
382
+ return OpenNebula::Error.new(rc[1])
383
+ end
384
+
385
+ nil
386
+ end
387
+
388
+ # Updates a role
389
+ # @param [String] role_name
390
+ # @param [String] template_json
391
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
392
+ # otherwise
393
+ def update_role(role_name, template_json)
394
+ if ![Service::STATE['RUNNING'], Service::STATE['WARNING']]
395
+ .include?(state)
396
+
397
+ return OpenNebula::Error.new('Update role: Wrong state' \
398
+ " #{state_str}")
399
+ end
400
+
401
+ template = JSON.parse(template_json)
402
+
403
+ # TODO: Validate template?
404
+
405
+ role = @roles[role_name]
406
+
407
+ if role.nil?
408
+ return OpenNebula::Error.new("ROLE \"#{role_name}\" " \
409
+ 'does not exist')
410
+ end
411
+
412
+ rc = role.update(template)
413
+
414
+ if OpenNebula.is_error?(rc)
415
+ return rc
416
+ end
417
+
418
+ # TODO: The update may not change the cardinality, only
419
+ # the max and min vms...
420
+
421
+ role.set_state(Role::STATE['SCALING'])
422
+
423
+ role.set_default_cooldown_duration
424
+
425
+ set_state(Service::STATE['SCALING'])
426
+
427
+ update
428
+ end
429
+
430
+ def shutdown_action
431
+ @body['shutdown_action']
432
+ end
433
+
434
+ # Replaces the template contents
435
+ #
436
+ # @param template_json [String] New template contents
437
+ # @param append [true, false] True to append new attributes instead of
438
+ # replace the whole template
439
+ #
440
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
441
+ # otherwise
442
+ def update(template_json = nil, append = false)
443
+ if template_json
444
+ template = JSON.parse(template_json)
445
+
446
+ if append
447
+ rc = info
448
+
449
+ if OpenNebula.is_error? rc
450
+ return rc
451
+ end
452
+
453
+ template = @body.merge(template)
454
+ end
455
+
456
+ template_json = template.to_json
457
+ end
458
+
459
+ super(template_json, append)
460
+ end
461
+
462
+ # Replaces the raw template contents
463
+ #
464
+ # @param template [String] New template contents, in the form KEY = VAL
465
+ # @param append [true, false] True to append new attributes instead of
466
+ # replace the whole template
467
+ #
468
+ # @return [nil, OpenNebula::Error] nil in case of success, Error
469
+ # otherwise
470
+ def update_raw(template_raw, append = false)
471
+ super(template_raw, append)
472
+ end
473
+
474
+ def deploy_networks
475
+ body = JSON.parse(self['TEMPLATE/BODY'])
476
+
477
+ return if body['networks_values'].nil?
478
+
479
+ body['networks_values'].each do |net|
480
+ rc = create_vnet(net) if net[net.keys[0]].key?('template_id')
481
+
482
+ if OpenNebula.is_error?(rc)
483
+ return rc
484
+ end
485
+
486
+ rc = reserve(net) if net[net.keys[0]].key?('reserve_from')
487
+
488
+ if OpenNebula.is_error?(rc)
489
+ return rc
490
+ end
491
+ end
492
+
493
+ # Replace $attibute by the corresponding value
494
+ resolve_attributes(body)
495
+
496
+ # @body = template.to_hash
497
+
498
+ update_body(body)
499
+ end
500
+
501
+ def delete_networks
502
+ vnets = @body['networks_values']
503
+ vnets_failed = []
504
+
505
+ return if vnets.nil?
506
+
507
+ vnets.each do |vnet|
508
+ next unless vnet[vnet.keys[0]].key?('template_id') ||
509
+ vnet[vnet.keys[0]].key?('reserve_from')
510
+
511
+ vnet_id = vnet[vnet.keys[0]]['id'].to_i
512
+
513
+ rc = OpenNebula::VirtualNetwork
514
+ .new_with_id(vnet_id, @client).delete
515
+
516
+ if OpenNebula.is_error?(rc)
517
+ vnets_failed << vnet_id
518
+ end
519
+ end
520
+
521
+ vnets_failed
522
+ end
523
+
524
+ def can_scale?
525
+ state == Service::STATE['RUNNING']
526
+ end
527
+
528
+ private
529
+
530
+ # Maximum number of log entries per service
531
+ # TODO: Make this value configurable
532
+ MAX_LOG = 50
533
+
534
+ def update_body(body)
535
+ @body = body
536
+
537
+ # Update @roles attribute with the new @body content
538
+ @roles = {}
539
+ if @body['roles']
540
+ @body['roles'].each do |elem|
541
+ elem['state'] ||= Role::STATE['PENDING']
542
+ role = Role.new(elem, self)
543
+ @roles[role.name] = role
544
+ end
545
+ end
546
+
547
+ # Update @xml attribute with the new body content
548
+ @xml.at_xpath('/DOCUMENT/TEMPLATE/BODY').children[0].content = @body
549
+ end
550
+
551
+ # @param [Logger::Severity] severity
552
+ # @param [String] message
553
+ def add_log(severity, message)
554
+ severity_str = Logger::SEV_LABEL[severity][0..0]
555
+
556
+ @body['log'] ||= []
557
+ @body['log'] << {
558
+ :timestamp => Time.now.to_i,
559
+ :severity => severity_str,
560
+ :message => message
561
+ }
562
+
563
+ # Truncate the number of log entries
564
+ @body['log'] = @body['log'].last(MAX_LOG)
565
+ end
566
+
567
+ def create_vnet(net)
568
+ extra = ''
569
+
570
+ extra = net[net.keys[0]]['extra'] if net[net.keys[0]].key? 'extra'
571
+
572
+ vntmpl_id = OpenNebula::VNTemplate
573
+ .new_with_id(net[net.keys[0]]['template_id']
574
+ .to_i, @client).instantiate(get_vnet_name(net), extra)
575
+
576
+ # TODO, check which error should be returned
577
+ return vntmpl_id if OpenNebula.is_error?(vntmpl_id)
578
+
579
+ net[net.keys[0]]['id'] = vntmpl_id
580
+
581
+ true
582
+ end
583
+
584
+ def reserve(net)
585
+ get_vnet_name(net)
586
+ extra = net[net.keys[0]]['extra'] if net[net.keys[0]].key? 'extra'
587
+
588
+ return false if !extra || extra.empty?
589
+
590
+ extra.concat("\nNAME=\"#{get_vnet_name(net)}\"\n")
591
+
592
+ reserve_id = OpenNebula::VirtualNetwork
593
+ .new_with_id(net[net.keys[0]]['reserve_from']
594
+ .to_i, @client).reserve_with_extra(extra)
595
+
596
+ return reserve_id if OpenNebula.is_error?(reserve_id)
597
+
598
+ net[net.keys[0]]['id'] = reserve_id
599
+
600
+ true
601
+ end
602
+
603
+ def get_vnet_name(net)
604
+ "#{net.keys[0]}-#{id}"
605
+ end
606
+
607
+ def resolve_attributes(template)
608
+ template['roles'].each do |role|
609
+ if role['vm_template_contents']
610
+ # $CUSTOM1_VAR Any word character
611
+ # (letter, number, underscore)
612
+ role['vm_template_contents'].scan(/\$(\w+)/).each do |key|
613
+ # Check if $ var value is in custom_attrs_values
614
+ unless template['custom_attrs_values'].nil?
615
+ if template['custom_attrs_values'].key?(key[0])
616
+ role['vm_template_contents'].gsub!(
617
+ '$'+key[0],
618
+ template['custom_attrs_values'][key[0]]
619
+ )
620
+ next
621
+ end
622
+ end
623
+
624
+ # Check if $ var value is in networks
625
+ net = template['networks_values']
626
+ .find {|att| att.key? key[0] }
627
+
628
+ next if net.nil?
629
+
630
+ role['vm_template_contents'].gsub!(
631
+ '$'+key[0],
632
+ net[net.keys[0]]['id'].to_s
633
+ )
634
+ end
635
+ end
636
+
637
+ next unless role['user_inputs_values']
638
+
639
+ role['vm_template_contents'] ||= ''
640
+ role['user_inputs_values'].each do |key, value|
641
+ role['vm_template_contents'] += "\n#{key}=\"#{value}\""
642
+ end
643
+ end
644
+ end
645
+
646
+ end
647
+
648
+ end