class Chef::Provisioning::OpenNebulaDriver::OneLib

Implementation.

Attributes

client[R]
version_ge_4_14[R]

Public Class Methods

new(args) click to toggle source
# 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

Public Instance Methods

allocate_img(img_config) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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

}

# 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) click to toggle source
# 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) click to toggle source

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.

# 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 = {}) click to toggle source

TODO: Always return an array

# 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) click to toggle source

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
# 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) click to toggle source
# 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) click to toggle source
# 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) { || ... } click to toggle source

Retry ONE operation

# 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) click to toggle source
# 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) click to toggle source
# File lib/chef/provisioning/opennebula_driver/one_lib.rb, line 403
def template_from_hash(options)
  options[:template]
end
template_from_one(options) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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

Private Instance Methods

_get_pool(type) click to toggle source

TODO: add filtering to pool retrieval (type, start, end, user)

# File lib/chef/provisioning/opennebula_driver/one_lib.rb, line 85
def _get_pool(type)
  Chef::Log.warn("Use of deprecated pool type '#{type}' detected. " \
                 "Switch to symbol form; i.e. '[:#{type}]' to use the " \
                 "'OpenNebula::#{type}Pool'.")

  case type
  when 'acl'
    OpenNebula::AclPool.new(@client)
  when 'cluster'
    OpenNebula::ClusterPool.new(@client)
  when 'datastore'
    OpenNebula::DatastorePool.new(@client)
  when 'doc'
    OpenNebula::DocumentPool.new(@client)
  when 'jsondoc'
    OpenNebula::DocumentPoolJSON.new(@client)
  when 'group'
    OpenNebula::GroupPool.new(@client)
  when 'host'
    OpenNebula::HostPool.new(@client)
  when 'image', 'img'
    OpenNebula::ImagePool.new(@client, -1)
  when 'secgroup'
    OpenNebula::SecurityGroupPool.new(@client)
  when 'tpl', 'vmtemplate', 'template'
    OpenNebula::TemplatePool.new(@client, -1)
  when 'user'
    OpenNebula::UserPool.new(@client)
  when 'vdc'
    OpenNebula::VdcPool.new(@client)
  when 'vm'
    OpenNebula::VirtualMachinePool.new(@client)
  when 'vnet'
    OpenNebula::VirtualNetworkPool.new(@client)
  else
    fail "Invalid pool type '#{type}' specified."
  end
end