class Fog::Libvirt::Compute::Server

Constants

DOMAIN_CLEANUP_REGEXP

Locale-friendly removal of non-alpha nums

Attributes

iso_dir[RW]
The following attributes are only needed when creating a new vm

TODO: Add depreciation warning

iso_file[RW]
The following attributes are only needed when creating a new vm

TODO: Add depreciation warning

network_bridge_name[RW]
network_interface_type[RW]
network_nat_network[RW]
password[RW]
user_data[RW]
volume_allocation[RW]
volume_capacity[RW]
volume_format_type[RW]
volume_name[RW]
volume_path[RW]
volume_pool_name[RW]
volume_template_name[RW]
volumes_path[RW]

rubocop:enable Metrics

xml[R]

Public Class Methods

new(attributes={} ) click to toggle source

Can be created by passing in :xml => “<xml to create domain/server>” or by providing :template_options => {

             :name => "", :cpus => 1, :memory_size => 256 , :volume_template
}
Calls superclass method
# File lib/fog/libvirt/models/compute/server.rb, line 55
def initialize(attributes={} )
  @xml = attributes.delete(:xml)
  verify_boot_order(attributes[:boot_order])
  super defaults.merge(attributes)
  initialize_nics
  initialize_volumes
  @user_data = attributes.delete(:user_data)
end

Public Instance Methods

cloud_init_volume_name() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 276
def cloud_init_volume_name
  "#{name}-cloud-init.iso"
end
create_user_data_iso() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 267
def create_user_data_iso
  generate_config_iso(user_data) do |iso|
    vol = service.volumes.create(:name => cloud_init_volume_name, :capacity => "#{File.size(iso)}b", :allocation => "0G")
    vol.upload_image(iso)
    @iso_file = cloud_init_volume_name
    @iso_dir = File.dirname(vol.path) if vol.path
  end
end
destroy(options={ :destroy_volumes => false, :flags => ::Libvirt::Domain::UNDEFINE_NVRAM }) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 98
def destroy(options={ :destroy_volumes => false, :flags => ::Libvirt::Domain::UNDEFINE_NVRAM })
  poweroff unless stopped?
  flags = options.fetch(:flags, ::Libvirt::Domain::UNDEFINE_NVRAM)
  if flags.zero?
    service.vm_action(uuid, :undefine)
  else
    # the test driver doesn't support UNDEFINE_NVRAM
    if service.uri.driver == 'test'
      flags ^= ::Libvirt::Domain::UNDEFINE_NVRAM
    end
    service.vm_action(uuid, :undefine, flags)
  end
  volumes.each { |vol| vol.destroy } if options[:destroy_volumes]
  true
end
disk_path() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 94
def disk_path
  volumes.first.path if volumes and volumes.first
end
generate_config_iso(user_data, &blk) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 250
def generate_config_iso(user_data, &blk)
  Dir.mktmpdir('config') do |wd|
    generate_config_iso_in_dir(wd, user_data, &blk)
  end
end
generate_config_iso_in_dir(dir_path, user_data, &blk) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 256
def generate_config_iso_in_dir(dir_path, user_data, &blk)
  FileUtils.touch(File.join(dir_path, "meta-data"))
  File.open(File.join(dir_path, 'user-data'), 'w') { |f| f.write user_data }

  isofile = Tempfile.new(['init', '.iso']).path
  unless system("genisoimage -output #{isofile} -volid cidata -joliet -rock #{File.join(dir_path, 'user-data')} #{File.join(dir_path, 'meta-data')}")
    raise Fog::Errors::Error.new("Couldn't generate cloud-init iso disk with genisoimage.")
  end
  blk.call(isofile)
end
halt()

alias methods

Alias for: poweroff
mac() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 90
def mac
  nics&.first&.mac
end
new?() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 64
def new?
  uuid.nil?
end
poweroff() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 120
def poweroff
  action_status = service.vm_action(uuid, :destroy)
  reload
  action_status
end
Also aliased as: halt
private_ip_address() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 163
def private_ip_address
  ip_address(:private)
end
public_ip_address() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 167
def public_ip_address
  ip_address(:public)
end
ready?() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 148
def ready?
  state == "running"
end
reboot() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 114
def reboot
  action_status = service.vm_action(uuid, :reboot)
  reload
  action_status
end
resume() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 132
def resume
  action_status = service.vm_action(uuid, :resume)
  reload
  action_status
end
save() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 68
def save
  raise Fog::Errors::Error.new('Saving an existing server may create a duplicate') unless new?
  create_or_clone_volume unless xml or @volumes
  create_user_data_iso if user_data
  @xml ||= to_xml
  self.id = (persistent ? service.define_domain(xml) : service.create_domain(xml)).uuid
  reload
rescue => e
  raise Fog::Errors::Error.new("Error saving the server: #{e}")
end
scp(local_path, remote_path, upload_options = {}) click to toggle source

Transfers a file

# File lib/fog/libvirt/models/compute/server.rb, line 195
def scp(local_path, remote_path, upload_options = {})
  requires :ssh_ip_address, :username

  scp_options = {}
  scp_options[:password] = password unless self.password.nil?
  scp_options[:key_data] = [private_key] if self.private_key
  scp_options[:proxy]= ssh_proxy unless self.ssh_proxy.nil?

  Fog::SCP.new(ssh_ip_address, username, scp_options).upload(local_path, remote_path, upload_options)
end
setup(credentials = {}) click to toggle source

Sets up a new key

# File lib/fog/libvirt/models/compute/server.rb, line 207
def setup(credentials = {})
  requires :public_key, :ssh_ip_address, :username

  credentials[:proxy]= ssh_proxy unless ssh_proxy.nil?
  credentials[:password] = password unless self.password.nil?
  credentials[:key_data] = [private_key] if self.private_key

  commands = [
    %{mkdir .ssh},
    #              %{passwd -l #{username}}, #Not sure if we need this here
    #              %{echo "#{Fog::JSON.encode(attributes)}" >> ~/attributes.json}
  ]
  if public_key
    commands << %{echo "#{public_key}" >> ~/.ssh/authorized_keys}
  end

  # wait for domain to be ready
  Timeout::timeout(360) do
    begin
      Timeout::timeout(8) do
        Fog::SSH.new(ssh_ip_address, username, credentials.merge(:timeout => 4)).run('pwd')
      end
    rescue Errno::ECONNREFUSED
      sleep(2)
      retry
    rescue Net::SSH::AuthenticationFailed, Timeout::Error
      retry
    end
  end
  Fog::SSH.new(ssh_ip_address, username, credentials).run(commands)
end
shutdown() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 126
def shutdown
  action_status = service.vm_action(uuid, :shutdown)
  reload
  action_status
end
Also aliased as: stop
ssh(commands) click to toggle source
Calls superclass method
# File lib/fog/libvirt/models/compute/server.rb, line 171
def ssh(commands)
  requires :ssh_ip_address, :username

  ssh_options={}
  ssh_options[:password] = password unless password.nil?
  ssh_options[:proxy]= ssh_proxy unless ssh_proxy.nil?

  super(commands, ssh_options)
end
ssh_proxy() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 181
def ssh_proxy
  begin
    require 'net/ssh/proxy/command'
  rescue LoadError
    Fog::Logger.warning("'net/ssh' missing, please install and try again.")
    exit(1)
  end
  # if this is a direct connection, we don't need a proxy to be set.
  return nil unless connection.uri.ssh_enabled?
  user_string= service.uri.user ? "-l #{service.uri.user}" : ""
  Net::SSH::Proxy::Command.new("ssh #{user_string} #{service.uri.host} nc %h %p")
end
start() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 79
def start
  return true if active?
  action_status = service.vm_action(uuid, :create)
  reload
  action_status
end
stop()
Alias for: shutdown
stopped?() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 144
def stopped?
  state == "shutoff"
end
suspend() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 138
def suspend
  action_status = service.vm_action(uuid, :suspend)
  reload
  action_status
end
to_xml() click to toggle source

rubocop:disable Metrics

# File lib/fog/libvirt/models/compute/server.rb, line 281
def to_xml
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.domain(:type => domain_type) do
      xml.name(name)
      xml.memory(memory_size)

      if hugepages
        xml.memoryBacking do
          xml.hugepages
        end
      end

      xml.vcpu(cpus)
      os_tags = {}

      os_tags[:firmware] = firmware if firmware == 'efi'

      xml.os(**os_tags) do
        type = xml.type(os_type, :arch => arch)
        type[:machine] = "q35" if ["i686", "x86_64"].include?(arch)

        boot_order.each do |dev|
          xml.boot(:dev => dev)
        end

        loader_attributes&.each do |key, value|
          xml.loader(key => value)
        end

        if firmware == "efi" && firmware_features&.any?
          xml.firmware do
            firmware_features.each_pair do |key, value|
              xml.feature(:name => key, :enabled => value)
            end
          end
        end
      end

      xml.features do
        xml.acpi
        xml.apic
      end

      unless cpu.empty?
        if cpu.dig(:model, :name)
          xml.cpu do
            xml.model(cpu.dig(:model, :name), :fallback => cpu.dig(:model, :fallback) || "allow")
          end
        else
          xml.cpu(
            :mode => cpu.fetch(:mode, "host-passthrough"),
            :check => cpu.fetch(:check, "none"),
            :migratable => cpu.fetch(:migratable, "on")
          )
        end
      end

      xml.clock(:offset => "utc") do
        xml.timer(:name => "rtc", :tickpolicy => "catchup")
        xml.timer(:name => "pit", :tickpolicy => "delay")
        xml.timer(:name => "hpet", :present => "no")
      end

      xml.devices do
        ceph_args = read_ceph_args

        volumes.each_with_index do |volume, index|
          target_device = "vd#{('a'..'z').to_a[index]}"
          if ceph_args && volume.pool_name.include?(ceph_args["libvirt_ceph_pool"])
            xml.disk(:type => "network", :device => "disk") do
              xml.driver(:name => "qemu", :type => volume.format_type, :cache => "writeback", :discard => "unmap")
              xml.source(:protocol => "rbd", :name => volume.path)

              ceph_args["monitor"]&.split(",")&.each do |monitor|
                xml.host(:name => monitor, :port => ceph_args["port"])
              end

              xml.auth(:username => ceph_args["auth_username"]) do
                if ceph_args.key?("auth_uuid")
                  xml.secret(:type => "ceph", :uuid => ceph_args["auth_uuid"])
                else
                  xml.secret(:type => "ceph", :usage => ceph_args["auth_usage"])
                end
              end

              xml.target(:dev => target_device, :bus => ceph_args["bus_type"] == "virtio" ? "virtio" : "scsi")
            end
          else
            is_block = volume.path.start_with?("/dev/")
            xml.disk(:type => is_block ? "block" : "file", :device => "disk") do
              xml.driver(:name => "qemu", :type => volume.format_type)
              if is_block
                xml.source(:dev => volume.path)
              else
                xml.source(:file => volume.path)
              end
              xml.target(:dev => target_device, :bus => "virtio")
            end
          end
        end

        if iso_file
          xml.disk(:type => "file", :device => "cdrom") do
            xml.driver(:name => "qemu", :type => "raw")
            xml.source(:file => "#{iso_dir}/#{iso_file}")
            xml.target(:dev => "sda", :bus => "scsi")
            xml.readonly
            xml.address(:type => "drive", :controller => 0, :bus => 0, :unit => 0)
          end
        end

        nics.each do |nic|
          xml.interface(:type => nic.type) do
            xml.mac(:address => nic.mac) if nic.mac
            if nic.type == "bridge"
              xml.source(:bridge => nic.bridge)
            else
              xml.source(:network => nic.network)
            end
            xml.model(:type => nic.model)
          end
        end

        if guest_agent
          xml.channel(:type => "unix") do
            xml.target(:type => "virtio", :name => "org.qemu.guest_agent.0")
          end
        end

        xml.rng(:model => "virtio") do
          xml.backend(virtio_rng[:backend_path], :model => virtio_rng.fetch(:backend_model, "random"))
        end

        if arch == "s390x"
          xml.controller(:type => "scsi", :index => "0", :model => "virtio-scsi")
          xml.console(:type => "pty") do
            xml.target(:type => "sclp")
          end
          xml.memballoon(:model => "virtio")
        else
          xml.serial(:type => "pty") do
            xml.target(:port => 0)
          end
          xml.console(:type => "pty") do
            xml.target(:port => 0)
          end
          xml.input(:type => "tablet", :bus => "usb")
          xml.input(:type => "mouse", :bus => "ps2")

          graphics = xml.graphics(:type => display[:type])
          if display[:port].empty?
            graphics.port = display[:port]
            graphics.autoport = "no"
          else
            graphics.port = -1
            graphics.autoport = "yes"
          end
          graphics.listen = display[:listen] unless display[:listen].empty?
          graphics.passwd = display[:password] if display[:password] && !display[:password].empty?

          xml.video do
            xml.model(video)
          end
        end
      end
    end
  end

  builder.to_xml
end
update_autostart(value) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 86
def update_autostart(value)
  service.update_autostart(uuid, value)
end
update_display(attrs = {}) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 239
def update_display attrs = {}
  service.update_display attrs.merge(:uuid => uuid)
  reload
end
vnc_port() click to toggle source

can't use deprecate method, as the value is part of the display hash

# File lib/fog/libvirt/models/compute/server.rb, line 245
def vnc_port
  Fog::Logger.deprecation("#{self.class} => #vnc_port is deprecated, use #display[:port] instead [light_black](#{caller.first})[/]")
  display[:port]
end
volumes() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 158
def volumes
  # lazy loading of volumes
  @volumes ||= (@volumes_path || []).map{ |path| service.volumes.all(:path => path).first }.compact
end

Private Instance Methods

addresses(service_arg=service, options={}) click to toggle source

This retrieves the ip address of the mac address using dhcp_leases It returns an array of public and private ip addresses Currently only one ip address is returned, but in the future this could be multiple if the server has multiple network interface

# File lib/fog/libvirt/models/compute/server.rb, line 475
def addresses(service_arg=service, options={})
  ip_address = nil
  if (nic = self.nics&.first)
    net = service.networks.all(:name => nic.network).first
    # Assume the lease expiring last is the current IP address
    ip_address = net&.dhcp_leases(nic.mac)&.max_by { |lse| lse["expirytime"] }&.dig("ipaddr")
  end

  return { :public => [ip_address], :private => [ip_address] }
end
create_or_clone_volume() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 507
def create_or_clone_volume
  options = {:name => volume_name || default_volume_name}
  # Check if a disk template was specified
  if volume_template_name
    template_volume = service.volumes.all(:name => volume_template_name).first
    raise Fog::Errors::Error.new("Template #{volume_template_name} not found") unless template_volume
    begin
      volume = template_volume.clone("#{options[:name]}")
    rescue => e
      raise Fog::Errors::Error.new("Error creating the volume : #{e}")
    end
  else
    # If no template volume was given, let's create our own volume
    options[:pool_name]   = volume_pool_name   if volume_pool_name
    options[:format_type] = volume_format_type if volume_format_type
    options[:capacity]    = volume_capacity    if volume_capacity
    options[:allocation]  = volume_allocation  if volume_allocation

    begin
      volume = service.volumes.create(options)
    rescue => e
      raise Fog::Errors::Error.new("Error creating the volume : #{e}")
    end
  end
  @volumes.nil? ? @volumes = [volume] : @volumes << volume
end
default_display() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 576
def default_display
  {:port => '-1', :listen => '127.0.0.1', :type => 'vnc' }
end
default_iso_dir() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 534
def default_iso_dir
  "/var/lib/libvirt/images"
end
default_volume_name() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 538
def default_volume_name
  "#{name}.#{volume_format_type || 'img'}"
end
defaults() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 542
def defaults
  {
    :persistent             => true,
    :cpus                   => 1,
    :memory_size            => 256 * 1024,
    :name                   => randomized_name,
    :os_type                => "hvm",
    :arch                   => "x86_64",
    :domain_type            => "kvm",
    :autostart              => false,
    :iso_dir                => default_iso_dir,
    :network_interface_type => "network",
    :network_nat_network    => "default",
    :network_bridge_name    => "br0",
    :boot_order             => %w[hd cdrom network],
    :display                => default_display,
    :cpu                    => {},
    :hugepages              => false,
    :guest_agent            => true,
    :video                  => {:type => "cirrus", :vram => 9216, :heads => 1},
    :virtio_rng             => {},
    :firmware_features      => { "secure-boot" => "no" },
  }
end
initialize_nics() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 493
def initialize_nics
  if nics
    nics.map! { |nic| nic.is_a?(Hash) ? service.nics.new(nic) : nic }
  else
    self.nics = [service.nics.new({:type => network_interface_type, :bridge => network_bridge_name, :network => network_nat_network})]
  end
end
initialize_volumes() click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 501
def initialize_volumes
  if attributes[:volumes] && !attributes[:volumes].empty?
    @volumes = attributes[:volumes].map { |vol| vol.is_a?(Hash) ? service.volumes.new(vol) : vol }
  end
end
ip_address(key) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 489
def ip_address(key)
  addresses[key]&.first
end
read_ceph_args(path = "/etc/foreman/ceph.conf") click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 456
def read_ceph_args(path = "/etc/foreman/ceph.conf")
  return unless File.file?(path)

  args = {}

  File.readlines(path).each do |line|
    pair = line.strip.split("=")
    key = pair[0]
    value = pair[1]
    args[key] = value
  end

  args
end
verify_boot_order(order = []) click to toggle source
# File lib/fog/libvirt/models/compute/server.rb, line 567
def verify_boot_order order = []
  valid_boot_media = %w[cdrom fd hd network]
  if order
    order.each do |b|
      raise "invalid boot order, possible values are any combination of: #{valid_boot_media.join(', ')}" unless valid_boot_media.include?(b)
    end
  end
end