Class: Chef::Provisioning::OpenNebulaDriver::OneLib

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/provisioning/opennebula_driver/one_lib.rb

Overview

Implementation.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ OneLib

Returns a new instance of OneLib.



51
52
53
54
55
56
57
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 51

def initialize(args)
  @client = OpenNebula::Client.new(args[:credentials], args[:endpoint], args[:options])
  rc = @client.get_version
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  gem_version = Gem.loaded_specs['opennebula'].version.to_s.split('.').map(&:to_i)
  @version_ge_4_14 = gem_version[0] > 4 || (gem_version[0] == 4 && gem_version[1] >= 14)
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



49
50
51
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 49

def client
  @client
end

#version_ge_4_14Object (readonly)

Returns the value of attribute version_ge_4_14.



49
50
51
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 49

def version_ge_4_14
  @version_ge_4_14
end

Instance Method Details

#allocate_img(img_config) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 251

def allocate_img(img_config)
  template = <<-EOT
NAME       = #{img_config[:name]}
TYPE       = #{img_config[:type]}
FSTYPE     = #{img_config[:fstype]}
SIZE       = #{img_config[:size]}
PERSISTENT = #{img_config[:persistent] ? 'YES' : 'NO'}

DRIVER     = #{img_config[:driver]}
DEV_PREFIX = #{img_config[:prefix]}
  EOT

  img = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
  raise OpenNebulaException, img.message if OpenNebula.is_error?(img)

  rc = img.allocate(template, img_config[:datastore_id])
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)

  Chef::Log.debug("Allocated disk image #{img_config[:name]} (#{img.id})")
  img
end

#allocate_template(template_str) ⇒ Object



317
318
319
320
321
322
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 317

def allocate_template(template_str)
  tpl = OpenNebula::Template.new(OpenNebula::Template.build_xml, @client)
  rc = tpl.allocate(template_str) unless OpenNebula.is_error?(tpl)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  rc
end

#allocate_vm(template) ⇒ Object



160
161
162
163
164
165
166
167
168
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 160

def allocate_vm(template)
  vm = OpenNebula::VirtualMachine.new(OpenNebula::VirtualMachine.build_xml, @client)
  raise OpenNebulaException, vm.message if OpenNebula.is_error?(vm)

  Chef::Log.debug(template)
  rc = vm.allocate(template)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  vm
end

#allocate_vnet(template_str, cluster_id) ⇒ Object



303
304
305
306
307
308
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 303

def allocate_vnet(template_str, cluster_id)
  vnet = OpenNebula::Vnet.new(OpenNebula::Vnet.build_xml, @client)
  rc = vnet.allocate(template_str, cluster_id) unless OpenNebula.is_error?(vnet)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  vnet
end

#chmod_resource(res = nil, octet = nil) ⇒ Object



246
247
248
249
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 246

def chmod_resource(res = nil, octet = nil)
  rc = res.chmod_octet(octet) unless res.nil? || octet.nil?
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
end

#create_template(t, level = 0) ⇒ Object

This method will create a VM template from parameters provided in the ‘t’ Hash. The hash must have equivalent structure as the VM template.

We considered using OpenNebulaHelper::create_template for this, however it would require a backwards compatibility shim and/or making breaking changes to the API. In particular, our method is more attractive due to the nested nature of our Hash, versus specifying a long string for the :context attribute with embedded newlines. Our strategy provides a way to override specific values, while this is difficult to accomplish with OpenNebulaHelper::create_template.

Current template hash: {

"NAME" => "baz"
"CPU" => "1",
"VCPU" => "1",
"MEMORY" => "512",
"OS" => {
  "ARCH" => "x86_64"
},
"GRAPHICS" => {
  "LISTEN" => "0.0.0.0",
  "TYPE" => "vnc"
},
"CONTEXT" => {
  "FOO" => "BAR",
  "BAZ" => "QUX"
  "HOSTNAME" => "$NAME",
  "SSH_PUBLIC_KEY" => "$USER[SSH_PUBLIC_KEY]",
  "NETWORK" => "YES"
}

}

Using OpenNebulaHelper::create_template:

:name => 'baz'
:cpu => 1,
:vcpu => 1,
:memory => 512,
:arch => 'x86_64',
:vnc => true,
:context => "FOO=\"BAR\"\nBAZ=\"QUX\"\nHOSTNAME=\"$NAME\"",
:ssh => true,
:net_context => true



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 455

def create_template(t, level = 0)
  tpl = ""
  count = t.size
  index = 1
  t.each do |k, v|
    if v.is_a?(Hash)
      level.times { tpl << "  " }
      # DISK and NIC is just for backward compatibility
      # it should be replaced by Array
      k = 'DISK' if k =~ /^DISK/
      k = 'NIC' if k =~ /^NIC/
      tpl << "#{k} = [\n"
      tpl << create_template(v, (level + 1))
      (level - 1).times { tpl << "  " }
      tpl << "]\n"
    elsif v.is_a?(Array)
      level.times { tpl << "  " }
      v.each do |e|
        tpl << "#{k} = [\n"
        tpl << create_template(e, (level + 1))
        tpl << "]\n"
      end
    else
      comma = (index < count) && level > 0
      level.times { tpl << "  " }
      txt = v.is_a?(String) ? "#{k} = \"#{v.gsub(/(?<!\\)\"/, '\"')}\"" : "#{k} = \"#{v}\""
      tpl << txt
      tpl << (comma ? ",\n" : "\n")
      index += 1
    end
  end
  tpl
end

#get_disk_id(vm, disk_name) ⇒ Object



296
297
298
299
300
301
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 296

def get_disk_id(vm, disk_name)
  fail "VM cannot be nil" if vm.nil?
  disk_id = nil
  vm.each('TEMPLATE/DISK') { |disk| disk_id = disk['DISK_ID'].to_i if disk['IMAGE'] == disk_name }
  disk_id
end

#get_pool(type) ⇒ Object

This function provides a more readable way to return a OpenNebula::*Pool back to a caller. The caller simply needs to pass the pool type in symbol form to us, and we send back the pool. The idea is that passing :template will give us back OpenNebula::TemplatePool, etc. for consistency with the OpenNebula API calls. Note that we are still supporting the old API, while logging a warning that the old format is deprecated. Users should expect the old format to disappear in a future release.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 68

def get_pool(type)
  fail "pool type must be specified" if type.nil?
  key = type.capitalize
  key = :SecurityGroup  if key == :Securitygroup
  key = :VirtualMachine if key == :Virtualmachine
  key = :VirtualNetwork if key == :Virtualnetwork
  if key == :Documentpooljson # Doesn't match the template below
    return OpenNebula::DocumentPoolJSON.new(@client)
  end

  pool_class = Object.const_get("OpenNebula::#{key}Pool")
  pool_class.new(@client)
rescue NameError
  _get_pool(type.to_s) # This will raise an exception if invalid.
end

#get_resource(resource_type, filter = {}) ⇒ Object

TODO: Always return an array



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 126

def get_resource(resource_type, filter = {})
  fail "resource_type must be specified" if resource_type.nil?

  # Ensure the hash key is correct when searching
  hash_key = resource_type.to_s.upcase
  hash_key = 'VMTEMPLATE' if hash_key == 'TPL' || hash_key == 'TEMPLATE'

  if filter.empty?
    Chef::Log.warn("get_resource: 'name' or 'id' must be provided")
    return nil
  end
  pool = get_pool(resource_type)

  if resource_type.to_s != 'user' && filter[:id] && !filter[:id].nil?
    pool.info!(-2, filter[:id].to_i, filter[:id].to_i)
    return pool.first
  end

  if resource_type.to_s == 'user'
    pool.info
  else
    pool.info!(-2, -1, -1)
  end
  resources = []
  pool.each do |res|
    next unless res.name == filter[:name]
    next if filter[:uname] && res.to_hash[hash_key]['UNAME'] != filter[:uname]
    resources << res
  end
  return nil if resources.empty?
  return resources[0] if resources.size == 1
  resources
end

#get_template(name, options) ⇒ Object

This will retrieve a VM template from one of the following locations:

:template_name - template located in OpenNebula
:template_id   - template located in OpenNebula
:template_file - local file containing the VM template
:template      - Hash containing equivalent structure as a VM template


348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 348

def get_template(name, options)
  t_hash = nil
  if !options[:template_name].nil? || !options[:template_id].nil?
    t_hash = template_from_one(options)
  elsif !options[:template_file].nil?
    t_hash = template_from_file(options)
  elsif !options[:template].nil?
    t_hash = template_from_hash(options)
  else
    fail "To create a VM you must specify one of ':template', " \
         "':template_id', or ':template_name' option " \
         "in ':bootstrap_options'"
  end
  fail "Inavlid VM template : #{t_hash}" if t_hash.nil? || t_hash.empty?
  tpl_updates = options[:template_options] || {}
  if options[:user_variables]
    Chef::Log.warn("':user_variables' will be deprecated in next " \
                   "version in favour of ':template_options'")
    recursive_merge(tpl_updates, options[:user_variables])
  end
  recursive_merge(t_hash, tpl_updates) unless tpl_updates.empty?
  if options[:enforce_chef_fqdn]
    Chef::Log.warn(':enforce_chef_fqdn has been deprecated.  VM name ' \
                   'will be set to the machine resource name.')
  end
  # FQDN is the machine resource name, unless overridden by e.g. cloud-init
  t_hash['NAME'] = name
  unless t_hash['CONTEXT']['SSH_PUBLIC_KEY']
    t_hash['CONTEXT']['SSH_PUBLIC_KEY'] = "$USER[SSH_PUBLIC_KEY]"
  end
  tpl = create_template(t_hash)
  Chef::Log.debug(tpl)
  tpl
end

#recursive_merge(dest, source) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 324

def recursive_merge(dest, source)
  source.each do |k, v|
    if source[k].is_a?(Hash)
      if dest[k].is_a?(Array)
        dest[k] = v.dup
      else
        dest[k] = {} unless dest[k]
        recursive_merge(dest[k], v)
      end
    elsif source[k].is_a?(Array)
      dest[k] = v.dup
    else
      dest[k] = v
    end
  end
end

#rename_vm(res, name) ⇒ Object



213
214
215
216
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 213

def rename_vm(res, name)
  rc = res.rename(name)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
end

#retry_one(msg = "retry_one", retries = 3, retry_delay = 5, return_nil = false, return_error = false) ⇒ Object

Retry ONE operation



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 191

def retry_one(msg = "retry_one", retries = 3, retry_delay = 5, return_nil = false, return_error = false)
  return nil unless block_given?
  count = 1
  rc = nil
  loop do
    rc = yield
    Chef::Log.info(msg)
    if OpenNebula.is_error?(rc)
      return rc if return_error
    elsif rc.nil?
      return rc if return_nil
    else
      return rc
    end
    sleep retry_delay
    break if count == retries
    count += 1
  end
  Chef::Log.info("FAIL: one_lib.retry_one '#{msg}'")
  rc
end

#template_from_file(options) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 390

def template_from_file(options)
  t_hash = nil
  doc = OpenNebula::CustomObject.new(OpenNebula::CustomObject.build_xml, @client)
  unless OpenNebula.is_error?(doc)
    rc = doc.allocate(File.read(options[:template_file]).to_s)
    fail "Failed to allocate OpenNebula document: #{rc.message}" if OpenNebula.is_error?(rc)
    doc.info!
    t_hash = doc.to_hash['DOCUMENT']['TEMPLATE']
    doc.delete
  end
  t_hash
end

#template_from_hash(options) ⇒ Object



403
404
405
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 403

def template_from_hash(options)
  options[:template]
end

#template_from_one(options) ⇒ Object



383
384
385
386
387
388
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 383

def template_from_one(options)
  t = get_resource(:template, :name => options[:template_name]) if options[:template_name]
  t = get_resource(:template, :id => options[:template_id]) if options[:template_id]
  fail "Template '#{options}' does not exist" if t.nil?
  t.to_hash["VMTEMPLATE"]["TEMPLATE"]
end

#update_template(template_id, template_str) ⇒ Object



310
311
312
313
314
315
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 310

def update_template(template_id, template_str)
  template = OpenNebula::Template.new(OpenNebula::Template.build_xml(template_id), @client)
  rc = template.update(template_str) unless OpenNebula.is_error?(template)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  rc
end

#upload_img(img_config) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 218

def upload_img(img_config)
  template = <<-EOTPL
NAME        = #{img_config[:name]}
PATH        = \"#{img_config[:path]}\"
DRIVER      = #{img_config[:driver]}
DESCRIPTION = \"#{img_config[:description]}\"
  EOTPL

  template << "TYPE        = #{img_config[:type]}\n" unless img_config[:type].nil?
  template << "DEV_PREFIX  = #{img_config[:prefix]}\n" unless img_config[:prefix].nil?
  template << "TARGET      = #{img_config[:target]}\n" unless img_config[:target].nil?
  template << "DISK_STYPE  = #{img_config[:disk_type]}\n" unless img_config[:disk_type].nil?
  template << "SOURCE      = #{img_config[:source]}\n" unless img_config[:source].nil?
  template << "SIZE        = #{img_config[:size]}\n" unless img_config[:size].nil?
  template << "FSTYPE      = #{img_config[:fs_type]}\n" unless img_config[:fs_type].nil?
  template << "PUBLIC      = #{img_config[:public] ? 'YES' : 'NO'}\n" unless img_config[:public].nil?
  template << "PERSISTENT  = #{img_config[:persistent] ? 'YES' : 'NO'}\n" unless img_config[:persistent].nil?

  Chef::Log.debug("\n#{template}")
  image = OpenNebula::Image.new(OpenNebula::Image.build_xml, @client)
  raise OpenNebulaException, image.message if OpenNebula.is_error?(image)
  rc = image.allocate(template, img_config[:datastore_id].to_i)
  raise OpenNebulaException, rc.message if OpenNebula.is_error?(rc)
  Chef::Log.debug("Waiting for image '#{img_config[:name]}' (#{image.id}) to be ready")
  wait_for_img(img_config[:name], image.id)
  chmod_resource(image, img_config[:mode])
end

#wait_for_img(name, img_id) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 273

def wait_for_img(name, img_id)
  cur_state = nil
  image = nil
  state = 'INIT'
  pool = get_pool(:image)

  retry_one("wait for IMAGE #{img_id} to be ready", 600, 2) do
    pool.info!(-2, img_id, img_id)
    pool.each do |img|
      next unless img.id == img_id
      cur_state = img.state_str
      image = img
      Chef::Log.debug("Image #{img_id} state: '#{cur_state}'")
      state = cur_state
      break
    end
    (state == 'INIT' || state == 'LOCKED') ? nil : true
  end
  fail "Failed to create #{name} image. State = '#{state}'" if state != 'READY'
  Chef::Log.info("Image #{name} is in READY state")
  image
end

#wait_for_vm(id, end_state = nil) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/chef/provisioning/opennebula_driver/one_lib.rb', line 170

def wait_for_vm(id, end_state = nil)
  end_state ||= 'RUNNING'
  vm = get_resource(:virtualmachine, :id => id)
  fail "Did not find VM with ID: #{id}" unless vm

  # Wait up to 10 min for the VM to be ready
  rc = retry_one("wait for VM #{id} to be ready", 300, 2) do
    vm.info
    if vm.lcm_state_str != 'LCM_INIT'
      short_lcm = OpenNebula::VirtualMachine::SHORT_LCM_STATES[vm.lcm_state_str]
      fail "'#{vm.name}'' failed.  Current state: #{vm.lcm_state_str}" if short_lcm == 'fail'
    end
    fail "'#{vm.name}'' failed.  Current state: #{vm.state_str}" if vm.state_str == 'FAILED'
    Chef::Log.info("current state #{vm.name}: '#{vm.lcm_state_str}'  short: '#{short_lcm}'")
    vm.lcm_state_str.casecmp(end_state) == 0 ? true : nil
  end
  fail "wait_for_vm timed out: '#{id}'" if rc.nil?
  vm
end