chef-provisioning-opennebula 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +532 -0
  4. data/Rakefile +6 -0
  5. data/lib/chef/provider/one_image.rb +244 -0
  6. data/lib/chef/provider/one_template.rb +95 -0
  7. data/lib/chef/provider/one_user.rb +105 -0
  8. data/lib/chef/provider/one_vnet.rb +122 -0
  9. data/lib/chef/provider/one_vnet_lease.rb +133 -0
  10. data/lib/chef/provisioning/driver_init/opennebula.rb +17 -0
  11. data/lib/chef/provisioning/opennebula_driver.rb +18 -0
  12. data/lib/chef/provisioning/opennebula_driver/credentials.rb +105 -0
  13. data/lib/chef/provisioning/opennebula_driver/driver.rb +572 -0
  14. data/lib/chef/provisioning/opennebula_driver/one_lib.rb +352 -0
  15. data/lib/chef/provisioning/opennebula_driver/resources.rb +20 -0
  16. data/lib/chef/provisioning/opennebula_driver/version.rb +30 -0
  17. data/lib/chef/resource/one_image.rb +65 -0
  18. data/lib/chef/resource/one_template.rb +46 -0
  19. data/lib/chef/resource/one_user.rb +50 -0
  20. data/lib/chef/resource/one_vnet.rb +51 -0
  21. data/lib/chef/resource/one_vnet_lease.rb +46 -0
  22. data/spec/integration/test_all_integration_spec.rb +102 -0
  23. data/spec/recipes/attach_back_one_vm_spec.rb +20 -0
  24. data/spec/recipes/attach_back_two_vm_spec.rb +20 -0
  25. data/spec/recipes/attach_one_image_spec.rb +20 -0
  26. data/spec/recipes/converge_back_one_vm_spec.rb +19 -0
  27. data/spec/recipes/converge_back_two_vm_spec.rb +19 -0
  28. data/spec/recipes/converge_bootstrap_vm_spec.rb +34 -0
  29. data/spec/recipes/create_back_one_vm_spec.rb +20 -0
  30. data/spec/recipes/create_back_two_vm_spec.rb +20 -0
  31. data/spec/recipes/create_bootstrap_vm_spec.rb +34 -0
  32. data/spec/recipes/create_one_image_spec.rb +21 -0
  33. data/spec/recipes/create_one_template_spec.rb +52 -0
  34. data/spec/recipes/delete_all_spec.rb +47 -0
  35. data/spec/recipes/driver_options_spec.rb +70 -0
  36. data/spec/recipes/instantiate_one_template_spec.rb +35 -0
  37. data/spec/recipes/snapshot_one_image_spec.rb +21 -0
  38. data/spec/recipes/snapshot_two_image_spec.rb +21 -0
  39. data/spec/spec_helper.rb +35 -0
  40. data/spec/support/opennebula_support.rb +64 -0
  41. metadata +168 -0
@@ -0,0 +1,352 @@
1
+ # Copyright 2015, BlackBerry, Inc.
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 'opennebula'
16
+
17
+ require 'opennebula/document'
18
+
19
+ #
20
+ # Sample Document definition.
21
+ #
22
+ module OpenNebula
23
+ #
24
+ # Implementation.
25
+ #
26
+ class CustomObject < Document
27
+ DOCUMENT_TYPE = 555
28
+ end
29
+ end
30
+
31
+ #
32
+ # Implementation.
33
+ #
34
+ class Chef
35
+ #
36
+ # Module extention.
37
+ #
38
+ module Provisioning
39
+ #
40
+ # Module extention.
41
+ #
42
+ module OpenNebulaDriver
43
+ #
44
+ # ONE error.
45
+ #
46
+ class OpenNebulaException < Exception
47
+ end
48
+
49
+ #
50
+ # Implementation.
51
+ #
52
+ class OneLib
53
+ attr_accessor :client
54
+
55
+ def initialize(credentials, endpoint, options = {})
56
+ @client = OpenNebula::Client.new(credentials, endpoint, options)
57
+ rc = @client.get_version
58
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
59
+ end
60
+
61
+ # TODO: add filtering to pool retrieval (type, start, end, user)
62
+ def get_pool(type)
63
+ fail "pool type must be specified" if type.nil?
64
+ case type
65
+ when 'acl'
66
+ OpenNebula::AclPool.new(@client)
67
+ when 'cluster'
68
+ OpenNebula::ClusterPool.new(@client)
69
+ when 'datastore'
70
+ OpenNebula::DatastorePool.new(@client)
71
+ when 'doc'
72
+ OpenNebula::DocumentPool.new(@client)
73
+ when 'jsondoc'
74
+ OpenNebula::DocumentPoolJSON.new(@client)
75
+ when 'group'
76
+ OpenNebula::GroupPool.new(@client)
77
+ when 'host'
78
+ OpenNebula::HostPool.new(@client)
79
+ when 'img'
80
+ OpenNebula::ImagePool.new(@client, -1)
81
+ when 'secgroup'
82
+ OpenNebula::SecurityGroupPool.new(@client)
83
+ when 'tpl'
84
+ OpenNebula::TemplatePool.new(@client, -1)
85
+ when 'user'
86
+ OpenNebula::UserPool.new(@client)
87
+ when 'vdc'
88
+ OpenNebula::VdcPool.new(@client)
89
+ when 'vm'
90
+ OpenNebula::VirtualMachinePool.new(@client)
91
+ when 'vnet'
92
+ OpenNebula::VirtualNetworkPool.new(@client)
93
+ else
94
+ fail "Invalid pool type specified."
95
+ end
96
+ end
97
+
98
+ def get_resource(resource_type, filter = {})
99
+ if filter.empty?
100
+ Chef::Log.warn("get_resource: 'name' or 'id' must be provided")
101
+ return nil
102
+ end
103
+ pool = get_pool(resource_type)
104
+
105
+ if resource_type != 'user' && filter[:id] && !filter[:id].nil?
106
+ pool.info!(-2, filter[:id].to_i, filter[:id].to_i)
107
+ return pool.first
108
+ end
109
+
110
+ if resource_type == 'user'
111
+ pool.info
112
+ else
113
+ pool.info!(-2, -1, -1) if resource_type != 'user'
114
+ end
115
+ resources = []
116
+ pool.each do |res|
117
+ resources << res if res.name == filter[:name]
118
+ end
119
+ return nil if resources.size == 0
120
+ return resources[0] if resources.size == 1
121
+ resources
122
+ end
123
+
124
+ def allocate_vm(template)
125
+ vm = OpenNebula::VirtualMachine.new(OpenNebula::VirtualMachine.build_xml, @client)
126
+ raise OpenNebulaException, vm.message if OpenNebula.is_error?(vm)
127
+
128
+ Chef::Log.debug(template)
129
+ rc = vm.allocate(template)
130
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
131
+ vm
132
+ end
133
+
134
+ def wait_for_vm(id, end_state = nil)
135
+ end_state ||= 'RUNNING'
136
+ vm = get_resource('vm', :id => id)
137
+ fail "Did not find VM with ID: #{id}" unless vm
138
+ while vm.lcm_state_str != end_state.upcase
139
+ vm.info
140
+ Chef::Log.debug("Waiting for VM '#{id}' to be in '#{end_state.upcase}' state: '#{vm.lcm_state_str}'")
141
+ fail "'#{vm.name}'' failed. Current state: #{vm.state_str}" if vm.state_str == 'FAILED' || vm.lcm_state_str == 'FAILURE'
142
+ sleep(2)
143
+ end
144
+ vm
145
+ end
146
+
147
+ def upload_img(name, ds_id, path, driver, description, type, prefix, persistent, pub, target, disk_type, source, size, fstype)
148
+ template = <<-EOTPL
149
+ NAME = #{name}
150
+ PATH = \"#{path}\"
151
+ DRIVER = #{driver}
152
+ DESCRIPTION = \"#{description}\"
153
+ EOTPL
154
+
155
+ template << "TYPE = #{type}\n" unless type.nil?
156
+ template << "PERSISTENT = YES\n" if !persistent.nil? && persistent
157
+ template << "DEV_PREFIX = #{prefix}\n" unless prefix.nil?
158
+ template << "PUBLIC = YES\n" if !pub.nil? && pub
159
+ template << "TARGET = #{target}\n" unless target.nil?
160
+ template << "DISK_TYPE = #{disk_type}\n" unless disk_type.nil?
161
+ template << "SOURCE = #{source}\n" unless source.nil?
162
+ template << "SIZE = #{size}" unless size.nil?
163
+ template << "FSTYPE = #{fstype}\n" unless fstype.nil?
164
+
165
+ Chef::Log.debug("\n#{template}")
166
+
167
+ image = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
168
+ raise OpenNebulaException, image.message if OpenNebula.is_error?(image)
169
+ rc = image.allocate(template, ds_id)
170
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
171
+ Chef::Log.debug("Waiting for image '#{name}' (#{image.id}) to be ready")
172
+ wait_for_img(name, image.id)
173
+ end
174
+
175
+ def allocate_img(name, size, ds_id, type, fstype, driver, prefix, persistent)
176
+ template = <<-EOT
177
+ NAME = #{name}
178
+ TYPE = #{type}
179
+ FSTYPE = #{fstype}
180
+ SIZE = #{size}
181
+ PERSISTENT = #{persistent ? 'YES' : 'NO'}
182
+
183
+ DRIVER = #{driver}
184
+ DEV_PREFIX = #{prefix}
185
+ EOT
186
+
187
+ img = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
188
+ raise OpenNebulaException, img.message if OpenNebula.is_error?(img)
189
+
190
+ rc = img.allocate(template, ds_id)
191
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
192
+
193
+ Chef::Log.debug("Allocated disk image #{name} (#{img.id})")
194
+ img
195
+ end
196
+
197
+ def wait_for_img(name, img_id)
198
+ cur_state = nil
199
+ image = nil
200
+ state = 'INIT'
201
+ pool = get_pool('img')
202
+ while state == 'INIT' || state == 'LOCKED'
203
+ pool.info!(-2, img_id, img_id)
204
+ pool.each do |img|
205
+ next unless img.id == img_id
206
+ cur_state = img.state_str
207
+ image = img
208
+ Chef::Log.debug("Image #{img_id} state: '#{cur_state}'")
209
+ state = cur_state
210
+ break
211
+ end
212
+ sleep(2)
213
+ end
214
+ fail "Failed to create #{name} image. State = '#{state}'" if state != 'READY'
215
+ Chef::Log.info("Image #{name} is in READY state")
216
+ image
217
+ end
218
+
219
+ def get_disk_id(vm, disk_name)
220
+ fail "VM cannot be nil" if vm.nil?
221
+ disk_id = nil
222
+ vm.each('TEMPLATE/DISK') { |disk| disk_id = disk['DISK_ID'].to_i if disk['IMAGE'] == disk_name }
223
+ disk_id
224
+ end
225
+
226
+ def allocate_vnet(template_str, cluster_id)
227
+ vnet = OpenNebula::Vnet.new(OpenNebula::Vnet.build_xml, @client)
228
+ rc = vnet.allocate(template_str, cluster_id) unless OpenNebula.is_error?(vnet)
229
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
230
+ vnet
231
+ end
232
+
233
+ def allocate_template(template_str)
234
+ tpl = OpenNebula::Template.new(OpenNebula::Template.build_xml, @client)
235
+ rc = tpl.allocate(template_str) unless OpenNebula.is_error?(tpl)
236
+ raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
237
+ rc
238
+ end
239
+
240
+ def recursive_merge(dest, source)
241
+ source.each do |k, v|
242
+ if source[k].is_a?(Hash)
243
+ if dest[k].is_a?(Array)
244
+ dest[k] = v.dup
245
+ else
246
+ dest[k] = {} unless dest[k]
247
+ recursive_merge(dest[k], v)
248
+ end
249
+ elsif source[k].is_a?(Array)
250
+ dest[k] = v.dup
251
+ else
252
+ dest[k] = v
253
+ end
254
+ end
255
+ end
256
+
257
+ #
258
+ # This will retrieve a VM template from one of the following locations:
259
+ # :template_name - template located in OpenNebula
260
+ # :template_id - template located in OpenNebula
261
+ # :template_file - local file containing the VM template
262
+ # :template - Hash containing equivalent structure as a VM template
263
+ #
264
+ def get_template(name, options)
265
+ t_hash = nil
266
+ if !options[:template_name].nil? || !options[:template_id].nil?
267
+ t_hash = template_from_one(options)
268
+ elsif !options[:template_file].nil?
269
+ t_hash = template_from_file(options)
270
+ elsif !options[:template].nil?
271
+ t_hash = template_from_hash(options)
272
+ else
273
+ fail "To create a VM you must specify one of ':template', ':template_id', ':template_name', or ':template' options in ':bootstrap_options'"
274
+ end
275
+ fail "Inavlid VM template : #{t_hash}" if t_hash.nil? || t_hash.empty?
276
+ tpl_updates = options[:template_options] || {}
277
+ if options[:user_variables]
278
+ Chef::Log.warn("':user_variables' will be deprecated in next version in favour of ':template_options'") if options.key?(:user_variables)
279
+ recursive_merge(tpl_updates, options[:user_variables])
280
+ end
281
+ recursive_merge(t_hash, tpl_updates) unless tpl_updates.empty?
282
+ t_hash['NAME'] = options[:enforce_chef_fqdn] ? name : name.split('.')[0]
283
+ tpl = create_template(t_hash)
284
+ Chef::Log.debug(tpl)
285
+ tpl
286
+ end
287
+
288
+ def template_from_one(options)
289
+ t = get_resource('tpl', :name => options[:template_name]) if options[:template_name]
290
+ t = get_resource('tpl', :id => options[:template_id]) if options[:template_id]
291
+ fail "Template '#{options}' does not exist" if t.nil?
292
+ t.to_hash["VMTEMPLATE"]["TEMPLATE"]
293
+ end
294
+
295
+ def template_from_file(options)
296
+ t_hash = nil
297
+ doc = OpenNebula::CustomObject.new(OpenNebula::CustomObject.build_xml, @client)
298
+ unless OpenNebula.is_error?(doc)
299
+ rc = doc.allocate("#{File.read(options[:template_file])}")
300
+ fail "Failed to allocate OpenNebula document: #{rc.message}" if OpenNebula.is_error?(rc)
301
+ doc.info!
302
+ t_hash = doc.to_hash['DOCUMENT']['TEMPLATE']
303
+ doc.delete
304
+ end
305
+ t_hash
306
+ end
307
+
308
+ def template_from_hash(options)
309
+ Chef::Log.debug("TEMPLATE_JSON: #{options[:template]}")
310
+ options[:template]
311
+ end
312
+
313
+ #
314
+ # This method will create a VM template from parameters provided
315
+ # in the 't' Hash. The hash must have equivalent structure as the
316
+ # VM template.
317
+ #
318
+ def create_template(t, level = 0)
319
+ tpl = ""
320
+ count = t.size
321
+ index = 1
322
+ t.each do |k, v|
323
+ if v.is_a?(Hash)
324
+ level.times { tpl << " " }
325
+ # DISK and NIC is just for backward compatibility
326
+ # it should be replaced by Array
327
+ k = 'DISK' if k =~ /^DISK/
328
+ k = 'NIC' if k =~ /^NIC/
329
+ tpl << "#{k} = [\n"
330
+ tpl << create_template(v, (level + 1))
331
+ (level - 1).times { tpl << " " }
332
+ tpl << "]\n"
333
+ elsif v.is_a?(Array)
334
+ level.times { tpl << " " }
335
+ v.each do |e|
336
+ tpl << "#{k} = [\n"
337
+ tpl << create_template(e, (level + 1))
338
+ tpl << "]\n"
339
+ end
340
+ else
341
+ comma = (index < count) && level > 0
342
+ level.times { tpl << " " }
343
+ tpl << "#{k} = \"#{v}\"" << (comma ? ",\n" : "\n")
344
+ index += 1
345
+ end
346
+ end
347
+ tpl
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2015, BlackBerry, Inc.
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
+ resources = %w( image template vnet vnet_lease user )
16
+
17
+ resources.each do |r|
18
+ Chef::Log.debug "OpenNebula driver loading resource: one_#{r}"
19
+ require "chef/resource/one_#{r}"
20
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright 2015, BlackBerry, Inc.
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
+ #
16
+ # Implementation of Version class.
17
+ #
18
+ class Chef
19
+ #
20
+ # Extending module.
21
+ #
22
+ module Provisioning
23
+ #
24
+ # Extending module.
25
+ #
26
+ module OpenNebulaDriver
27
+ VERSION = '0.3.4'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright 2015, BlackBerry, Inc.
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 'chef/provider/one_image'
16
+
17
+ #
18
+ # Implementation of Resource class.
19
+ #
20
+ class Chef
21
+ #
22
+ # Implementation of Resource class.
23
+ #
24
+ class Resource
25
+ #
26
+ # Implementation of Resource class.
27
+ #
28
+ class OneImage < Chef::Resource::LWRPBase
29
+ resource_name :one_image
30
+
31
+ actions :allocate, :create, :destroy, :attach, :snapshot, :upload, :download
32
+ default_action :create
33
+
34
+ attribute :name, :kind_of => String, :name_attribute => true
35
+ attribute :size, :kind_of => Integer
36
+ attribute :datastore_id, :kind_of => Integer
37
+ attribute :driver_options, :kind_of => Hash
38
+ attribute :type, :kind_of => String, :equal_to => %w(OS CDROM DATABLOCK KERNEL RAMDISK CONTEXT)
39
+ attribute :description, :kind_of => String
40
+ attribute :fs_type, :kind_of => String
41
+ attribute :img_driver, :kind_of => String, :default => 'qcow2'
42
+ attribute :prefix, :kind_of => String, :equal_to => %w(vd xvd sd hd)
43
+ attribute :persistent, :kind_of => [TrueClass, FalseClass]
44
+ attribute :public, :kind_of => [TrueClass, FalseClass]
45
+ attribute :disk_type, :kind_of => String
46
+ attribute :source, :kind_of => String
47
+ attribute :target, :kind_of => String
48
+ attribute :machine_id, :kind_of => [String, Integer]
49
+ attribute :disk_id, :kind_of => [String, Integer]
50
+ attribute :image_file, :kind_of => String
51
+ attribute :cache, :kind_of => String
52
+ attribute :driver
53
+
54
+ attribute :download_url, :kind_of => String
55
+ attribute :image_id, :kind_of => Integer
56
+
57
+ def initialize(*args)
58
+ super
59
+ @chef_environment = run_context.cheffish.current_environment
60
+ @chef_server = run_context.cheffish.current_chef_server
61
+ @driver = run_context.chef_provisioning.current_driver
62
+ end
63
+ end
64
+ end
65
+ end