class Gogetit::GogetLibvirt

Attributes

config[R]
conn[R]
logger[R]
maas[R]

Public Class Methods

new(conf, maas, logger) click to toggle source
# File lib/providers/libvirt.rb, line 16
def initialize(conf, maas, logger)
  @config = conf

  begin
    @conn = Libvirt::open(config[:libvirt][:nodes][0][:url])
  rescue Exception => e
    puts e
    abort("Unable to establish connection with"\
          " #{config[:libvirt][:nodes][0][:url]}")
  end

  @maas = maas
  @logger = logger
end

Public Instance Methods

add_nic(document, nic_conf) click to toggle source
# File lib/providers/libvirt.rb, line 443
def add_nic(document, nic_conf)
  logger.info("Calling <#{__method__.to_s}>")
  template = File.read(config[:lib_dir] + "/template/nic.xml")
  doc = Oga.parse_xml(template)

  nic_conf.each do |nic|
    doc = Oga.parse_xml(template)
    doc.at_xpath('interface/source').attribute('network').value = nic[:network]
    doc.at_xpath('interface/source').attribute('portgroup').value = nic[:portgroup]
    document.at_xpath('domain/devices').children << doc.at_xpath('interface')
  end

  document
end
configure_interfaces(ifaces, system_id) click to toggle source
# File lib/providers/libvirt.rb, line 107
def configure_interfaces(ifaces, system_id)

  # It assumes you only have a physical interfaces.
  interfaces = maas.interfaces([system_id])

  maas.interfaces(
    [system_id, interfaces[0]['id']],
    {
      'op' => 'unlink_subnet',
      'id' => interfaces[0]['links'][0]['id']
    }
  )

  # VLAN configuration
  ifaces.each_with_index do |iface,index|
    if index == 0
      params = {
        'op' => 'link_subnet',
        'mode' => 'STATIC',
        'subnet' => ifaces[0]['id'],
        'ip_address' => ifaces[0]['ip'],
        'default_gateway' => 'True',
        'force' => 'False'
      }
      maas.interfaces([system_id, interfaces[0]['id']], params)

    elsif index > 0
      params = {
        'op' => 'create_vlan',
        'vlan' => iface['vlan']['id'],
        'parent' => interfaces[0]['id']
      }
      maas.interfaces([system_id], params)

      interfaces = maas.interfaces([system_id])

      params = {
        'op' => 'link_subnet',
        'mode' => 'STATIC',
        'subnet' => ifaces[index]['id'],
        'ip_address' => ifaces[index]['ip'],
        'default_gateway' => 'False',
        'force' => 'False'
      }

      maas.interfaces([system_id, interfaces[index]['id']], params)
    end
  end
end
create(name, options = nil) click to toggle source
# File lib/providers/libvirt.rb, line 157
def create(name, options = nil)
  logger.info("Calling <#{__method__.to_s}>")
  abort("Domain #{name} already exists!"\
  " Please check both on MAAS and libvirt.") \
    if maas.domain_name_exists?(name) or domain_exists?(name)

  if options[:spec]
    domain = config[:libvirt][:specs][:"#{options[:spec]}"]
  else
    domain = config[:libvirt][:specs][:default]
  end

  ifaces = nil

  if options[:ipaddresses]
    ifaces = check_ip_available(options[:ipaddresses], maas)
    domain = generate_nics(ifaces, domain)
  elsif options[:vlans]
    #check_vlan_available(options[:vlans])
  else
    domain[:nic] = [
      {
        network: config[:default][:root_bridge],
        portgroup: config[:default][:root_bridge]
      }
    ]
  end

  domain[:name] = name
  domain[:uuid] = SecureRandom.uuid

  dom = conn.define_domain_xml(define_domain(domain))
  maas.refresh_pods

  system_id = maas.get_system_id(domain[:name])
  maas.wait_until_state(system_id, 'Ready')

  if options[:ipaddresses]
    configure_interfaces(ifaces, system_id)
  elsif options[:vlans]
    #check_vlan_available(options[:vlans])
  else
  end

  logger.info("Calling to deploy...")

  deploy(name, options)
end
create_volume(pool_name, volume_doc) click to toggle source
# File lib/providers/libvirt.rb, line 436
def create_volume(pool_name, volume_doc)
  logger.info("Calling <#{__method__.to_s}> to create volume in #{pool_name} pool.")
  pool = conn.lookup_storage_pool_by_name(pool_name)
  pool.create_volume_xml(volume_doc)
  pool.refresh
end
define_domain(domain) click to toggle source
# File lib/providers/libvirt.rb, line 334
def define_domain(domain)
  logger.info("Calling <#{__method__.to_s}>")
  template = File.read(config[:lib_dir] + '/template/domain.xml')
  doc = Oga.parse_xml(template)

  name = domain[:name]
  doc.at_xpath('domain/name').inner_text = name
  uuid = domain[:uuid]
  doc.at_xpath('domain/uuid').inner_text = uuid
  vcpu = domain[:vcpu].to_s
  doc.at_xpath('domain/vcpu').inner_text = vcpu
  memory = domain[:memory].to_s
  doc.at_xpath('domain/memory').inner_text = memory
  doc.at_xpath('domain/currentMemory').inner_text = memory

  doc = define_volumes(doc, domain)
  doc = add_nic(doc, domain[:nic])

  # print_xml(doc)
  # volumes.each do |v|
  #   print_xml(v)
  # end

  return Oga::XML::Generator.new(doc).to_xml
end
define_volumes(document, domain) click to toggle source
# File lib/providers/libvirt.rb, line 385
def define_volumes(document, domain)
  logger.info("Calling <#{__method__.to_s}>")
  disk_template = File.read(config[:lib_dir] + '/template/disk.xml')
  disk_doc = Oga.parse_xml(disk_template)
  volume_template = File.read(config[:lib_dir] + '/template/volume.xml')
  volume_doc = Oga.parse_xml(volume_template)

  defined_volumes = []

  # For root volume
  pool_path = get_pool_path(domain[:disk][:root][:pool])
  volume_name = "#{domain[:name]}_root_sda.qcow2"
  volume_file = pool_path + "/" + volume_name
  disk_doc.at_xpath('disk/source').attribute('file').value = volume_file
  document.at_xpath('domain/devices').children << disk_doc.at_xpath('disk')

  volume_doc.at_xpath('volume/name').inner_text = volume_name
  volume_doc.at_xpath('volume/target/path').inner_text = volume_file
  volume_doc.at_xpath('volume/capacity').inner_text = \
    domain[:disk][:root][:capacity].to_s

  create_volume(domain[:disk][:root][:pool], \
                Oga::XML::Generator.new(volume_doc).to_xml)
  defined_volumes << volume_doc

  # For data(secondary) volumes
  if domain[:disk][:data] != [] and domain[:disk][:data] != nil
    disk_index = 98
    domain[:disk][:data].each do |v|
      pool_path = get_pool_path(v[:pool])
      volume_index = "sd" + disk_index.chr
      volume_name = "#{domain[:name]}_data_#{volume_index}.qcow2"
      volume_file = pool_path + "/" + volume_name
      disk_doc = Oga.parse_xml(disk_template)
      disk_doc.at_xpath('disk/source').attribute('file').value = volume_file
      disk_doc.at_xpath('disk/target').attribute('dev').value = volume_index
      document.at_xpath('domain/devices').children << disk_doc.at_xpath('disk')

      volume_doc = Oga.parse_xml(volume_template)
      volume_doc.at_xpath('volume/name').inner_text = volume_name
      volume_doc.at_xpath('volume/target/path').inner_text = volume_file
      volume_doc.at_xpath('volume/capacity').inner_text = v[:capacity].to_s
      create_volume(v[:pool], Oga::XML::Generator.new(volume_doc).to_xml)
      defined_volumes << volume_doc
      disk_index += 1
    end
  end

  return document
end
deploy(name, options = nil) click to toggle source
# File lib/providers/libvirt.rb, line 244
def deploy(name, options = nil)
  logger.info("Calling <#{__method__.to_s}>")
  abort("The machine, '#{name}', doesn't exist.") \
    unless maas.machine_exists?(name)

  system_id = maas.get_system_id(name)
  maas.wait_until_state(system_id, 'Ready')

  logger.info("Calling to deploy...")

  if options[:distro].nil? or options[:distro].empty?
    distro = 'bionic'
  else
    distro = options[:distro]
  end

  user_data = Base64.encode64(
    "#cloud-config\n" +
    YAML.dump(
      generate_cloud_init_config(options, config)
    )[4..-1]
  )

  maas.conn.request(
    :post,
    ['machines', system_id],
    {
      'op' => 'deploy',
      'distro_series' => distro,
      'user_data' => user_data
    }
  )

  maas.wait_until_state(system_id, 'Deployed')

  fqdn = name + '.' + maas.get_domain

  distro_name = maas.get_distro_name(system_id)

  if config[:default][:user] == config[:cloud_init][:users][0]['name']
    default_user = config[:default][:user]
  else
    default_user = distro_name
  end

  wait_until_available(fqdn, default_user)

  # To enable serial console to use 'virsh console'
  if distro_name == 'ubuntu'
    commands = [
      'sudo systemctl enable serial-getty@ttyS0.service',
      'sudo systemctl start serial-getty@ttyS0.service'
    ]
    run_through_ssh(fqdn, default_user, commands)
  end

  logger.info("#{name} has been created.")
  puts "ssh #{default_user}@#{name}"

  info = {}
  info[:distro] = distro_name
  info[:default_user] = default_user

  { result: true, info: info }
end
destroy(name) click to toggle source
# File lib/providers/libvirt.rb, line 206
def destroy(name)
  logger.info("Calling <#{__method__.to_s}>")

  info = release(name)[:info]
  maas.conn.request(
    :delete,
    [
      'machines',
      info[:machine]['system_id']
    ]
  )

  pools = []
  conn.list_storage_pools.each do |name|
    pools << self.conn.lookup_storage_pool_by_name(name)
  end

  dom = conn.lookup_domain_by_name(name)
  info[:domain_xml] = dom.xml_desc

  dom.destroy if dom.active?
  Oga.parse_xml(dom.xml_desc).xpath('domain/devices/disk/source').each do |d|
    pool_path = d.attribute('file').value.split('/')[0..2].join('/')
    pools.each do |p|
      if Oga.parse_xml(p.xml_desc).at_xpath('pool/target/path')\
        .inner_text == pool_path
        logger.info("Deleting volume in #{p.name} pool.")
        p.lookup_volume_by_name(d.attribute('file').value.split('/')[3]).delete
      end
    end
  end
  dom.undefine

  logger.info("#{name} has been destroyed.")

  { result: true, info: info }
end
domain_exists?(name) click to toggle source
# File lib/providers/libvirt.rb, line 40
def domain_exists?(name)
  logger.info("Calling <#{__method__.to_s}>")
  get_domain_list.each do |d|
    return true if d == name
  end
  false
end
generate_nics(ifaces, domain) click to toggle source
# File lib/providers/libvirt.rb, line 61
def generate_nics(ifaces, domain)
  abort("There is no dns server specified for the gateway network.") \
    unless ifaces[0]['dns_servers'][0]
  abort("There is no gateway specified for the gateway network.") \
    unless ifaces[0]['gateway_ip']

  # It seems the first IP has to belong to the untagged VLAN in the Fabric.
  abort("The first IP you entered does not belong to the untagged"\
  " VLAN in the Fabric.") \
    unless ifaces[0]['vlan']['name'] == 'untagged'

  domain[:ifaces] = ifaces
  domain[:nic] = []

  ifaces.each_with_index do |iface,index|
    if index == 0
      if iface['vlan']['name'] == 'untagged'
        nic = {
          network: config[:default][:root_bridge],
          portgroup: config[:default][:root_bridge]
        }
      elsif iface['vlan']['name'] != 'untagged'
        nic = {
          network: config[:default][:root_bridge],
          portgroup: config[:default][:root_bridge] + '-' + \
          iface['vlan']['vid'].to_s
        }
      end
      domain[:nic].push(nic)
    elsif index > 0
      # Only if the fisrt interface has untagged VLAN,
      # it will be configured with VLANs.
      # This will not be hit as of now and might be deprecated.
      if ifaces[0]['vlan']['name'] != 'untagged'
        nic = {
          network: config[:default][:root_bridge],
          portgroup: config[:default][:root_bridge] + '-' + \
          iface['vlan']['vid'].to_s
        }
        domain[:nic].push(nic)
      end
    end
  end
  return domain
end
get_domain_list() click to toggle source
# File lib/providers/libvirt.rb, line 31
def get_domain_list
  logger.info("Calling <#{__method__.to_s}>")
  domains = []
  conn.list_all_domains.each do |d|
    domains << d.name
  end
  domains
end
get_domain_xml(domain_name) click to toggle source
# File lib/providers/libvirt.rb, line 56
def get_domain_xml(domain_name)
  logger.info("Calling <#{__method__.to_s}>")
   Oga.parse_xml(conn.lookup_domain_by_name(domain_name).xml_desc)
end
get_mac_addr(domain_name) click to toggle source
# File lib/providers/libvirt.rb, line 48
def get_mac_addr(domain_name)
  logger.info("Calling <#{__method__.to_s}>")
   Oga.parse_xml(conn.lookup_domain_by_name(domain_name).xml_desc)
    .at_xpath('domain/devices/interface[1]/mac')
    .attribute('address')
    .value
end
get_pool_path(pool) click to toggle source
# File lib/providers/libvirt.rb, line 368
def get_pool_path(pool)
  logger.info("Calling <#{__method__.to_s}>")
  path = nil
  conn.list_all_storage_pools.each do |p|
    if p.name == pool
      pool_doc = Oga.parse_xml(p.xml_desc)
      path = pool_doc.at_xpath('pool/target/path').inner_text
    end
  end

  if path
    return path
  else
    raise 'No such pool found.'
  end
end
print_xml(doc) click to toggle source
release(name) click to toggle source
# File lib/providers/libvirt.rb, line 310
def release(name)
  logger.info("Calling <#{__method__.to_s}>")

  abort("Machine #{name} does not exist."\
  " Please check both on MAAS and libvirt.") \
    if not maas.machine_exists?(name)

  system_id = maas.get_system_id(name)

  info = {}
  info[:machine] = \
    maas.conn.request(:get, ['machines', system_id])

  if maas.get_machine_state(system_id) == 'Deployed'
    logger.info("Calling to release...")
    maas.conn.request(:post, ['machines', system_id], {'op' => 'release'})
    maas.wait_until_state(system_id, 'Ready')
  end

  logger.info("#{name} has been released.")

  { result: true, info: info }
end