chef-provisioning-opennebula 0.4.4 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +273 -4
  3. data/lib/chef/provider/one_flow_service.rb +346 -0
  4. data/lib/chef/provider/one_flow_template.rb +175 -0
  5. data/lib/chef/provider/one_image.rb +6 -6
  6. data/lib/chef/provider/one_template.rb +4 -6
  7. data/lib/chef/provider/one_user.rb +11 -14
  8. data/lib/chef/provider/one_vnet.rb +6 -9
  9. data/lib/chef/provider/one_vnet_lease.rb +3 -3
  10. data/lib/chef/provisioning/driver_init/opennebula.rb +1 -1
  11. data/lib/chef/provisioning/driver_init/server_version.rb +28 -0
  12. data/lib/chef/provisioning/opennebula_driver.rb +1 -1
  13. data/lib/chef/provisioning/opennebula_driver/credentials.rb +2 -2
  14. data/lib/chef/provisioning/opennebula_driver/driver.rb +132 -34
  15. data/lib/chef/provisioning/opennebula_driver/flow_lib.rb +491 -0
  16. data/lib/chef/provisioning/opennebula_driver/one_lib.rb +39 -49
  17. data/lib/chef/provisioning/opennebula_driver/resources.rb +2 -2
  18. data/lib/chef/provisioning/opennebula_driver/version.rb +2 -2
  19. data/lib/chef/resource/one_flow_service.rb +61 -0
  20. data/lib/chef/resource/one_flow_template.rb +53 -0
  21. data/lib/chef/resource/one_image.rb +2 -2
  22. data/lib/chef/resource/one_template.rb +1 -1
  23. data/lib/chef/resource/one_user.rb +1 -1
  24. data/lib/chef/resource/one_vnet.rb +2 -2
  25. data/lib/chef/resource/one_vnet_lease.rb +1 -1
  26. data/spec/config_sample.rb +12 -3
  27. data/spec/integration/test_all_integration_spec.rb +6 -272
  28. data/spec/integration/test_one_driver.rb +177 -0
  29. data/spec/integration/test_one_flow.rb +546 -0
  30. data/spec/recipes/OneDriver/{instantiate_one_template_spec.rb → allocate_change_profile.rb} +5 -7
  31. data/spec/recipes/OneDriver/{converge_back_two_vm_spec.rb → attach_one_image.rb} +4 -5
  32. data/spec/recipes/OneDriver/create_one_image.rb +20 -0
  33. data/spec/recipes/OneDriver/{create_one_template_int_spec.rb → create_one_template_ints.rb} +21 -4
  34. data/spec/recipes/OneDriver/create_one_template_mix.rb +51 -0
  35. data/spec/recipes/OneDriver/{create_one_image_spec.rb → create_one_template_strings.rb} +18 -7
  36. data/spec/recipes/OneDriver/{converge_back_one_vm_spec.rb → create_one_vnet.rb} +5 -5
  37. data/spec/recipes/OneDriver/delete/{OpenNebula-tpl-1-vm.rb → OpenNebula-test-img.rb} +2 -4
  38. data/spec/recipes/OneDriver/delete/{OpenNebula-back-1-vm.rb → OpenNebula-test-snap-img.rb} +2 -4
  39. data/spec/recipes/OneDriver/delete/OpenNebula-test-tpl-ints.rb +2 -4
  40. data/spec/recipes/OneDriver/delete/{OpenNebula-test-tpl.rb → OpenNebula-test-tpl-mix.rb} +2 -4
  41. data/spec/recipes/OneDriver/delete/OpenNebula-test-tpl-strings.rb +17 -0
  42. data/spec/recipes/OneDriver/delete/{OpenNebula-back-2-vm.rb → OpenNebula-test-vm-vnet.rb} +2 -4
  43. data/spec/recipes/OneDriver/delete/{OpenNebula-bootstrap-vm.rb → OpenNebula-test-vm.rb} +2 -4
  44. data/spec/recipes/OneDriver/delete/OpenNebula-test-vnet.rb +17 -0
  45. data/spec/recipes/OneDriver/{attach_back_one_vm_spec.rb → instantiate_one_template.rb} +7 -6
  46. data/spec/recipes/OneDriver/{converge_bootstrap_vm_spec.rb → instantiate_one_template_vnet.rb} +10 -8
  47. data/spec/recipes/OneDriver/snapshot_one_image.rb +19 -0
  48. data/spec/recipes/OneFlowService/action/boot.rb +19 -0
  49. data/spec/recipes/OneFlowService/action/delete.rb +18 -0
  50. data/spec/recipes/OneFlowService/action/delete_recreate.rb +18 -0
  51. data/spec/recipes/OneFlowService/action/hold.rb +19 -0
  52. data/spec/recipes/OneFlowService/action/poweroff.rb +18 -0
  53. data/spec/recipes/OneFlowService/action/poweroff_hard.rb +18 -0
  54. data/spec/recipes/OneFlowService/action/reboot.rb +18 -0
  55. data/spec/recipes/OneFlowService/action/reboot_hard.rb +18 -0
  56. data/spec/recipes/OneFlowService/action/release.rb +19 -0
  57. data/spec/recipes/OneFlowService/action/resume.rb +18 -0
  58. data/spec/recipes/OneFlowService/action/scale.rb +19 -0
  59. data/spec/recipes/OneFlowService/action/shutdown.rb +18 -0
  60. data/spec/recipes/OneFlowService/action/shutdown_hard.rb +18 -0
  61. data/spec/recipes/OneFlowService/action/shutdown_service.rb +17 -0
  62. data/spec/recipes/OneFlowService/action/snapshot_create.rb +18 -0
  63. data/spec/recipes/OneFlowService/action/stop.rb +18 -0
  64. data/spec/recipes/OneFlowService/action/suspend.rb +18 -0
  65. data/spec/recipes/OneFlowService/action/undeploy.rb +18 -0
  66. data/spec/recipes/OneFlowService/action/undeploy_hard.rb +18 -0
  67. data/spec/recipes/OneFlowService/chmod_simple_by_name.rb +19 -0
  68. data/spec/recipes/OneFlowService/chmod_simple_by_name_2.rb +18 -0
  69. data/spec/recipes/OneFlowService/chmod_tpl_opts.rb +31 -0
  70. data/spec/recipes/OneFlowService/delete/test_instance_template_options.rb +17 -0
  71. data/spec/recipes/OneFlowService/delete/test_role_action.rb +17 -0
  72. data/spec/recipes/OneFlowService/delete/test_simple_instance.rb +17 -0
  73. data/spec/recipes/OneFlowService/delete/test_simple_instance_by_id.rb +17 -0
  74. data/spec/recipes/{OneDriver/create_bootstrap_vm_spec.rb → OneFlowService/instance_role_action.rb} +19 -11
  75. data/spec/recipes/OneFlowService/instance_simple_by_id.rb +20 -0
  76. data/spec/recipes/OneFlowService/instance_simple_by_name.rb +18 -0
  77. data/spec/recipes/OneFlowService/instance_tpl_opts.rb +30 -0
  78. data/spec/recipes/OneFlowTemplate/chmod_simple_from_hash.rb +17 -0
  79. data/spec/recipes/OneFlowTemplate/chmod_update_simple_from_file.rb +19 -0
  80. data/spec/recipes/OneFlowTemplate/create_branch_from_one_id.rb +43 -0
  81. data/spec/recipes/OneFlowTemplate/create_branch_from_one_name.rb +20 -0
  82. data/spec/recipes/OneFlowTemplate/create_role_action_instance.rb +99 -0
  83. data/spec/recipes/OneFlowTemplate/create_simple_from_file.rb +18 -0
  84. data/spec/recipes/{OneDriver/attach_back_two_vm_spec.rb → OneFlowTemplate/create_simple_from_hash.rb} +8 -6
  85. data/spec/recipes/OneFlowTemplate/create_simple_from_web.rb +17 -0
  86. data/spec/recipes/{OneDriver/attach_one_image_spec.rb → OneFlowTemplate/create_simple_instance_tpl.rb} +8 -6
  87. data/spec/recipes/OneFlowTemplate/create_tpl_opts_from_file.rb +30 -0
  88. data/spec/recipes/OneFlowTemplate/create_tpl_opts_from_hash.rb +35 -0
  89. data/spec/recipes/OneFlowTemplate/delete/branch_from_one_id.rb +17 -0
  90. data/spec/recipes/OneFlowTemplate/delete/branch_from_one_name.rb +17 -0
  91. data/spec/recipes/OneFlowTemplate/delete/role_action_instance.rb +17 -0
  92. data/spec/recipes/OneFlowTemplate/delete/simple_from_file.rb +17 -0
  93. data/spec/recipes/OneFlowTemplate/delete/simple_from_hash.rb +17 -0
  94. data/spec/recipes/OneFlowTemplate/delete/simple_from_web.rb +17 -0
  95. data/spec/recipes/OneFlowTemplate/delete/simple_instance_tpl.rb +17 -0
  96. data/spec/recipes/OneFlowTemplate/delete/tpl_opts_from_file.rb +17 -0
  97. data/spec/recipes/OneFlowTemplate/delete/tpl_opts_from_hash.rb +17 -0
  98. data/spec/recipes/OneFlowTemplate/update_simple_from_hash.rb +57 -0
  99. data/spec/recipes/{driver_options_spec.rb → common.rb} +9 -3
  100. data/spec/spec_helper.rb +22 -17
  101. data/spec/support/opennebula_support.rb +75 -45
  102. metadata +114 -27
  103. data/spec/recipes/OneDriver/create_back_one_vm_spec.rb +0 -20
  104. data/spec/recipes/OneDriver/create_back_two_vm_spec.rb +0 -20
  105. data/spec/recipes/OneDriver/create_one_template_spec.rb +0 -21
  106. data/spec/recipes/OneDriver/delete/OpenNebula-bootstrap-img.rb +0 -19
  107. data/spec/recipes/OneDriver/delete/OpenNebula-snap-1-img.rb +0 -19
  108. data/spec/recipes/OneDriver/delete/OpenNebula-snap-2-img.rb +0 -19
  109. data/spec/recipes/OneDriver/snapshot_one_image_spec.rb +0 -21
  110. data/spec/recipes/OneDriver/snapshot_two_image_spec.rb +0 -21
@@ -0,0 +1,491 @@
1
+ # Copyright 2016, BlackBerry Limited
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rest-client'
16
+ require 'json'
17
+ require 'set'
18
+
19
+ #
20
+ # Implementation.
21
+ #
22
+ class Chef
23
+ #
24
+ # Module extension.
25
+ #
26
+ module Provisioning
27
+ #
28
+ # Module extension.
29
+ #
30
+ module OpenNebulaDriver
31
+ #
32
+ # ONE error.
33
+ #
34
+ class OpenNebulaException < Exception
35
+ end
36
+
37
+ #
38
+ # Implementation.
39
+ #
40
+ class FlowLib
41
+ attr_accessor :flow_url, :username, :password
42
+
43
+ # STATE NUMBERS TO SYMBOLS
44
+
45
+ # SERVICE:
46
+ SERVICE_PENDING = 0
47
+ SERVICE_DEPLOYING = 1
48
+ SERVICE_RUNNING = 2
49
+ SERVICE_UNDEPLOYING = 3
50
+ SERVICE_DONE = 5
51
+ SERVICE_FAILED_DEPLOYING = 7
52
+ SERVICE_SCALING = 8
53
+ SERVICE_FAILED_SCALING = 9
54
+ SERVICE_COOLDOWN = 10
55
+
56
+ # ROLE:
57
+ ROLE_NOT_EXIST = nil
58
+ ROLE_NO_VMS = -1
59
+ ROLE_PENDING = 1
60
+ ROLE_RUNNING = 3
61
+ ROLE_STOPPED = 4
62
+ ROLE_SUSPENDED = 5
63
+ ROLE_POWEROFF = 8
64
+ ROLE_UNDEPLOYED = 9
65
+
66
+ def initialize(url, one_auth)
67
+ @flow_url = url
68
+ @username, _, @password = one_auth.rpartition(':')
69
+ end
70
+
71
+ ###########
72
+ # HELPERS #
73
+ ###########
74
+
75
+ # Performs some basic verifications on a template
76
+ # Also adds in default values
77
+ def normalize_template(name, driver, template, allow_no_roles = false, allow_no_vm_template = false)
78
+ template = {
79
+ :deployment => 'straight',
80
+ :name => name,
81
+ :ready_status_gate => false,
82
+ :description => '',
83
+ :roles => [],
84
+ :custom_attrs => {}
85
+ }.merge(template)
86
+
87
+ fail "You must specify at least 1 role for template '#{name}'" if template[:roles].empty? && !allow_no_roles
88
+ id_cache = {}
89
+ template[:roles].map! do |role|
90
+ fail "Some roles in template '#{name}' are missing a name." if role[:name].nil?
91
+ fail "Role '#{role[:name]}' in template '#{name}' is missing a vm_template." if role[:vm_template].nil? && !allow_no_vm_template
92
+ new_role = {
93
+ :cardinality => role[:min_vms] || 1,
94
+ :elasticity_policies => [],
95
+ :scheduled_policies => []
96
+ }.merge(role)
97
+ if role[:vm_template].is_a?(String)
98
+ if id_cache[role[:vm_template]].nil?
99
+ template_from_one = driver.one.get_resource(:template, :name => role[:vm_template])
100
+ fail "Could not find a template with the name '#{role[:vm_template]}'" if template_from_one.nil?
101
+ id = template_from_one.to_hash['VMTEMPLATE']['ID'].to_i
102
+ id_cache[role[:vm_template]] = id
103
+ new_role[:vm_template] = id
104
+ else
105
+ new_role[:vm_template] = id_cache[role[:vm_template]]
106
+ end
107
+ end
108
+ new_role
109
+ end
110
+ template
111
+ end
112
+
113
+ # Helper for merge_template
114
+ def special_merge_hash(base_hash, new_hash, delete_roles = true)
115
+ keys_to_delete = []
116
+ merged = base_hash.merge(new_hash) do |key, oldval, newval|
117
+ # This is probably a redundant fail ...
118
+ fail 'Class of values in the templates must remain the same. If you want to delete an entry, set it to nil.' unless
119
+ oldval.is_a?(newval.class) || newval.is_a?(NilClass) || key == :vm_template
120
+ case newval
121
+ when NilClass
122
+ keys_to_delete.push(key)
123
+ nil
124
+ when Array
125
+ if key == :roles
126
+ new_array = []
127
+ old_as_hash = Hash[oldval.collect { |role| [role[:name], role] }]
128
+
129
+ newval.each do |role|
130
+ if role.key?(:delete_role) && delete_roles
131
+ old_as_hash.delete(role[:name])
132
+ next
133
+ end
134
+ fail 'All roles must have a name.' if role[:name].nil?
135
+ if old_as_hash.key?(role[:name])
136
+ new_array.push(special_merge_hash(old_as_hash[role[:name]], role))
137
+ old_as_hash.delete(role[:name])
138
+ else
139
+ new_array.push(role)
140
+ end
141
+ end
142
+
143
+ new_array + old_as_hash.values
144
+ else
145
+ newval
146
+ end
147
+ when Hash
148
+ special_merge_hash(oldval, newval)
149
+ else
150
+ newval
151
+ end
152
+ end
153
+ keys_to_delete.each { |key| merged.delete(key) }
154
+ merged
155
+ end
156
+
157
+ # Performs a overwrite-merge of two OneFlow templates, any key with nil value will be deleted
158
+ def merge_template(base_tpl, new_tpl, overwrite_name = false, delete_roles = true)
159
+ fail 'Service template name changing is not supported.' if new_tpl.key?(:name) && !overwrite_name && base_tpl[:name] != new_tpl[:name]
160
+ special_merge_hash(base_tpl, new_tpl, delete_roles)
161
+ end
162
+
163
+ # Issues warnings for a failsafe override
164
+ def override_failsafe_warn
165
+ Chef::Log.warn('You have chose to use an action that is untested / partially implemented.')
166
+ Chef::Log.warn('Specifically, the driver will send the appropriate POST request to the Flow API')
167
+ Chef::Log.warn('But the driver will not verify that the action ran successfully, or ran at all.')
168
+ Chef::Log.warn('Moreover, the driver will not wait for the action complete, as in, the action will')
169
+ Chef::Log.warn('run asynchronously, meaning dependent actions after this one may fail.')
170
+ Chef::Log.warn('Use at your own risk. Please report any issues.')
171
+ end
172
+
173
+ # Validate the attributes period and number
174
+ def validate_role_action(period, number)
175
+ fail "Make sure 'period' >= 0" if !period.empty? && period.to_i < 0
176
+ fail "Make sure 'number' >= 0" if !number.empty? && number.to_i < 0
177
+ end
178
+
179
+ # REST call to flow api
180
+ def request(method, url, payload = '{}')
181
+ case payload
182
+ when Hash
183
+ RestClient::Request.execute(
184
+ method: method,
185
+ url: @flow_url + url,
186
+ user: @username,
187
+ password: @password,
188
+ payload: payload.to_json
189
+ )
190
+ when String
191
+ JSON.parse(payload)
192
+ RestClient::Request.execute(
193
+ method: method,
194
+ url: @flow_url + url,
195
+ user: @username,
196
+ password: @password,
197
+ payload: payload
198
+ )
199
+ else
200
+ fail 'Payload must be hash or json string.'
201
+ end
202
+ rescue JSON::ParserError
203
+ fail 'Malformed json string.'
204
+ rescue RestClient::ResourceNotFound, RestClient::BadRequest, RestClient::InternalServerError => e
205
+ raise OpenNebulaException, "#{e}\nThere's a problem. Here's a hint:\n#{e.response}"
206
+ end
207
+
208
+ # Converts all arrays to sets
209
+ def recursive_array_to_set(object)
210
+ case object
211
+ when Array
212
+ return object.map { |e| recursive_array_to_set(e) }.to_set
213
+ when Hash
214
+ object.each do |key, value|
215
+ object[key] = recursive_array_to_set(value)
216
+ end
217
+ return object
218
+ else
219
+ return object
220
+ end
221
+ end
222
+
223
+ # Checks if two hashes are equal, ignore array order
224
+ def hash_eq?(hash1, hash2)
225
+ recursive_array_to_set(Marshal.load(Marshal.dump(hash1))) == recursive_array_to_set(Marshal.load(Marshal.dump(hash2)))
226
+ end
227
+
228
+ # Returns all of the IDs of a service or template that matches a name
229
+ def get_ids(type, name)
230
+ response = request(:get, type == :template ? '/service_template' : '/service')
231
+ ids = []
232
+ data = JSON.parse(response, :symbolize_names => true)
233
+ return [] if data[:DOCUMENT_POOL][:DOCUMENT].nil?
234
+ data[:DOCUMENT_POOL][:DOCUMENT].each { |e| ids.push(e[:ID].to_i) if e[:NAME] == name }
235
+ ids
236
+ end
237
+
238
+ # Gets a single ID of a service or template, fails if there's not exactly 1, or returns nil if there 0 and nil_if_none
239
+ def get_unique_id(type, name, nil_if_none = false)
240
+ matches = get_ids(type, name)
241
+ if matches.empty?
242
+ return nil if nil_if_none
243
+ fail "There are no OneFlow #{type}s with the name '#{name}'"
244
+ elsif matches.length > 1
245
+ fail "There are multiple OneFlow #{type}s with the name '#{name}'"
246
+ else
247
+ matches[0]
248
+ end
249
+ end
250
+
251
+ # Check if a service or template exists
252
+ def exists?(type, name)
253
+ !get_ids(type, name).empty?
254
+ end
255
+
256
+ # Gets permission of service or template
257
+ def get_permissions(type, id)
258
+ id = id.to_s
259
+ response = request(:get, type == :template ? '/service_template' : '/service')
260
+ data = JSON.parse(response, :symbolize_names => true)[:DOCUMENT_POOL][:DOCUMENT]
261
+ data.each do |tpl|
262
+ next unless tpl[:ID] == id
263
+ perms = tpl[:PERMISSIONS]
264
+ mode = ''
265
+ [:OWNER_U, :OWNER_M, :OWNER_A, :GROUP_U, :GROUP_M, :GROUP_A, :OTHER_U, :OTHER_M, :OTHER_A].each { |m| mode += perms[m] }
266
+ return mode.to_i(2).to_s(8)
267
+ end
268
+ fail "#{type} with id=#{id} does not exist."
269
+ end
270
+
271
+ # Wrapper for get_ids
272
+ def get_template_ids(name)
273
+ get_ids(:template, name)
274
+ end
275
+
276
+ # Wrapper for get_unique_id
277
+ def get_unique_template_id(name, nil_if_none = false)
278
+ get_unique_id(:template, name, nil_if_none)
279
+ end
280
+
281
+ # Wrapper for exists?
282
+ def template_exists?(name)
283
+ exists?(:template, name)
284
+ end
285
+
286
+ # Gets a template given a template ID
287
+ def get_template(id)
288
+ response = request(:get, "/service_template/#{id}")
289
+ JSON.parse(response, :symbolize_names => true)[:DOCUMENT][:TEMPLATE][:BODY]
290
+ end
291
+
292
+ # Wrapper for get_permissions
293
+ def get_template_permissions(id)
294
+ get_permissions(:template, id)
295
+ end
296
+
297
+ # Wrapper for get_ids
298
+ def get_service_ids(name)
299
+ get_ids(:service, name)
300
+ end
301
+
302
+ # Wrapper for get_unique_id
303
+ def get_unique_service_id(name, nil_if_none = false)
304
+ get_unique_id(:service, name, nil_if_none)
305
+ end
306
+
307
+ # Wrapper for exists?
308
+ def service_exists?(name)
309
+ exists?(:service, name)
310
+ end
311
+
312
+ # Wrapper for get_permissions
313
+ def get_service_permissions(id)
314
+ get_permissions(:service, id)
315
+ end
316
+
317
+ # Returns the template of a service with runtime content removed
318
+ def get_reduced_service_template(id)
319
+ response = request(:get, "/service/#{id}")
320
+ template_from_one = JSON.parse(response, :symbolize_names => true)[:DOCUMENT][:TEMPLATE][:BODY]
321
+ service_name = template_from_one[:name]
322
+ [:log, :name, :state, :custom_attrs_values].each { |key| template_from_one.delete(key) }
323
+ template_from_one[:roles].each do |role|
324
+ role[:nodes].each do |node|
325
+ unless node[:running]
326
+ Chef::Log.warn("A node in role '#{node[:vm_info][:VM][:USER_TEMPLATE][:ROLE_NAME]}' of service '#{service_name}' is not normal!")
327
+ end
328
+ end
329
+ [:cardinality, :nodes, :state, :disposed_nodes, :cooldown_end, :last_vmname, :user_inputs_values, :vm_template_contents].each { |key| role.delete(key) }
330
+ end
331
+ template_from_one
332
+ end
333
+
334
+ # Gets the state of a service
335
+ # 0 => PENDING, 1 => DEPLOYING, 2 => RUNNING, 3 => UNDEPLOYING, 5 => DONE,
336
+ # 7 => FAILED_DEPLOYING, 8 => SCALING, 9 => FAILED_SCALING, 10 => COOLDOWN
337
+ def get_service_state(id)
338
+ response = request(:get, "/service/#{id}")
339
+ JSON.parse(response, :symbolize_names => true)[:DOCUMENT][:TEMPLATE][:BODY][:state].to_i
340
+ end
341
+
342
+ # Returns a role of a service
343
+ def get_role(service_id, role_name)
344
+ response = request(:get, "/service/#{service_id}")
345
+ JSON.parse(response, :symbolize_names => true)[:DOCUMENT][:TEMPLATE][:BODY][:roles].each { |role| return role if role[:name] == role_name }
346
+ fail "#{role_name} doesn't seem to exist!"
347
+ end
348
+
349
+ # Returns the state of a role
350
+ # It seems that regardless of the state of the VMs, the state of a role will be RUNNING
351
+ # So I will be doing a workaround where I will return that it's SUSPENDED if all of the VMs are SUSPENDED
352
+ # nil => role doesn't exist, -1 => there are no VMs, 1 => PENDING,
353
+ # 3 => RUNNING, 4 => STOPPED, 5 => SUSPENDED, 8 => POWEROFF, 9 => UNDEPLOYED
354
+ def get_role_state(id, role_name)
355
+ role = get_role(id, role_name)
356
+ return -1 if role[:cardinality].to_i == 0
357
+ state_counter = { 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 8 => 0, 9 => 0 }
358
+ role[:nodes].each do |node|
359
+ state = node[:vm_info][:VM][:STATE].to_i
360
+ fail "UNSUPPORTED STATE #{state}" if state_counter[state].nil?
361
+ state_counter[state] += 1
362
+ return state if state_counter[state] == role[:cardinality].to_i
363
+ end
364
+ state_counter # Return the hash of counters if states are staggered
365
+ end
366
+
367
+ # Gets the cardinality of a role
368
+ def get_role_cardinality(id, role_name)
369
+ get_role(id, role_name)[:cardinality].to_i
370
+ end
371
+
372
+ # Creates a post request for an action
373
+ def perform_action(url, action, params)
374
+ request(:post, url, "{\"action\":{\"perform\":\"#{action}\",\"params\":#{params.to_json}}}")
375
+ end
376
+
377
+ ####################
378
+ # TEMPLATE ACTIONS #
379
+ ####################
380
+
381
+ # Creates a template in ONE
382
+ def create_template(payload)
383
+ request(:post, '/service_template', payload)
384
+ true
385
+ end
386
+
387
+ # Updates a template in ONE
388
+ def update_template(template_name, payload)
389
+ request(:put, "/service_template/#{get_unique_template_id(template_name)}", payload)
390
+ end
391
+
392
+ # Deletes a template in ONE
393
+ def delete_template(template_id)
394
+ request(:delete, "/service_template/#{template_id}")
395
+ end
396
+
397
+ # Spawns a service from a template
398
+ def instantiate_template(tid, template, service_name)
399
+ url = "/service_template/#{tid}/action"
400
+ perform_action(url, 'instantiate', :merge_template => template)
401
+
402
+ service_id = get_unique_service_id(service_name)
403
+ state = nil
404
+ while state != SERVICE_RUNNING
405
+ Chef::Log.info("Waiting for RUNNING for '#{service_name}'")
406
+ sleep(15)
407
+ state = get_service_state(service_id)
408
+ fail "Service failed to deploy ...\nThere's probably something wrong with your template." if state == SERVICE_FAILED_DEPLOYING
409
+ end
410
+ end
411
+
412
+ # Modifies the permissions of a template
413
+ def chmod_template(template_id, octet)
414
+ url = "/service_template/#{template_id}/action"
415
+ perform_action(url, 'chmod', :octet => octet)
416
+ end
417
+
418
+ ###################
419
+ # SERVICE ACTIONS #
420
+ ###################
421
+
422
+ # Deletes a service in ONE
423
+ def delete_service(service_id)
424
+ request(:delete, "/service/#{service_id}")
425
+ end
426
+
427
+ # Performs shutdown on an entire service
428
+ def shutdown_service(service_name, sid)
429
+ url = "/service/#{sid}/action"
430
+ perform_action(url, 'shutdown', {})
431
+
432
+ state = nil
433
+ while state != SERVICE_DONE
434
+ Chef::Log.info("Waiting for SHUTDOWN COMPLETE for '#{service_name}'")
435
+ sleep(15)
436
+ state = get_service_state(sid)
437
+ fail 'Service failed to shutdown ...' unless Set[SERVICE_RUNNING, SERVICE_UNDEPLOYING, SERVICE_DONE].include?(state)
438
+ end
439
+ end
440
+
441
+ # Performs the recover action on a service
442
+ def recover_service(service_id, service_name)
443
+ url = "/service/#{service_id}/action"
444
+ perform_action(url, 'recover', {})
445
+
446
+ state = nil
447
+ while state != SERVICE_RUNNING
448
+ Chef::Log.info("Waiting for RUNNING for '#{service_name}'")
449
+ sleep(15)
450
+ state = get_service_state(service_id)
451
+ fail 'Service failed to recover ...' if state == SERVICE_FAILED_DEPLOYING
452
+ end
453
+ end
454
+
455
+ # Modifies the permissions of a service
456
+ def chmod_service(sid, octet)
457
+ url = "/service/#{sid}/action"
458
+ perform_action(url, 'chmod', :octet => octet)
459
+ end
460
+
461
+ # Performs an action on a role
462
+ def role_action(sid, role_name, action, period, number, desired_state = nil)
463
+ validate_role_action(period, number)
464
+ url = "/service/#{sid}/role/#{role_name}/action"
465
+ perform_action(url, action, :period => period, :number => number)
466
+
467
+ state = nil
468
+ while state != desired_state
469
+ Chef::Log.info("Waiting for #{action} to complete")
470
+ sleep(15)
471
+ state = get_role_state(sid, role_name)
472
+ fail "#{action} failed. Got unsupported state #{state}" unless Set[ROLE_NO_VMS, ROLE_PENDING, ROLE_RUNNING, ROLE_STOPPED, ROLE_SUSPENDED, ROLE_POWEROFF, ROLE_UNDEPLOYED].include?(state)
473
+ end
474
+ end
475
+
476
+ # Scales a role to a new cardinality
477
+ def role_scale(service_id, service_name, role_name, card, force)
478
+ request(:put, "/service/#{service_id}/role/#{role_name}", :cardinality => card, :force => force)
479
+
480
+ state = nil
481
+ while state != SERVICE_RUNNING
482
+ Chef::Log.info("Waiting for RUNNING for '#{service_name}'")
483
+ sleep(15)
484
+ state = get_service_state(service_id)
485
+ fail 'Service failed to scale ...' if state == SERVICE_FAILED_SCALING
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end
491
+ end