class Fog::Libvirt::Compute::Server
Constants
- DOMAIN_CLEANUP_REGEXP
Locale-friendly removal of non-alpha nums
Attributes
The following attributes are only needed when creating a new vm
TODO: Add depreciation warning
The following attributes are only needed when creating a new vm
TODO: Add depreciation warning
rubocop:enable Metrics
Public Class Methods
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 }
# File lib/fog/libvirt/models/compute/server.rb, line 50 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
# File lib/fog/libvirt/models/compute/server.rb, line 266 def cloud_init_volume_name "#{name}-cloud-init.iso" end
# File lib/fog/libvirt/models/compute/server.rb, line 257 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
# File lib/fog/libvirt/models/compute/server.rb, line 93 def destroy(options={ :destroy_volumes => false, :flags => 0 }) poweroff unless stopped? if options.fetch(:flags, 0).zero? service.vm_action(uuid, :undefine) else service.vm_action(uuid, :undefine, options[:flags]) end volumes.each { |vol| vol.destroy } if options[:destroy_volumes] true end
# File lib/fog/libvirt/models/compute/server.rb, line 89 def disk_path volumes.first.path if volumes and volumes.first end
# File lib/fog/libvirt/models/compute/server.rb, line 240 def generate_config_iso(user_data, &blk) Dir.mktmpdir('config') do |wd| generate_config_iso_in_dir(wd, user_data, &blk) end end
# File lib/fog/libvirt/models/compute/server.rb, line 246 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
# File lib/fog/libvirt/models/compute/server.rb, line 85 def mac nics.first.mac if nics && nics.first end
# File lib/fog/libvirt/models/compute/server.rb, line 59 def new? uuid.nil? end
# File lib/fog/libvirt/models/compute/server.rb, line 110 def poweroff action_status = service.vm_action(uuid, :destroy) reload action_status end
# File lib/fog/libvirt/models/compute/server.rb, line 153 def private_ip_address ip_address(:private) end
# File lib/fog/libvirt/models/compute/server.rb, line 157 def public_ip_address ip_address(:public) end
# File lib/fog/libvirt/models/compute/server.rb, line 138 def ready? state == "running" end
# File lib/fog/libvirt/models/compute/server.rb, line 104 def reboot action_status = service.vm_action(uuid, :reboot) reload action_status end
# File lib/fog/libvirt/models/compute/server.rb, line 122 def resume action_status = service.vm_action(uuid, :resume) reload action_status end
# File lib/fog/libvirt/models/compute/server.rb, line 63 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
Transfers a file
# File lib/fog/libvirt/models/compute/server.rb, line 185 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
Sets up a new key
# File lib/fog/libvirt/models/compute/server.rb, line 197 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
# File lib/fog/libvirt/models/compute/server.rb, line 116 def shutdown action_status = service.vm_action(uuid, :shutdown) reload action_status end
# File lib/fog/libvirt/models/compute/server.rb, line 161 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
# File lib/fog/libvirt/models/compute/server.rb, line 171 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
# File lib/fog/libvirt/models/compute/server.rb, line 74 def start return true if active? action_status = service.vm_action(uuid, :create) reload action_status end
# File lib/fog/libvirt/models/compute/server.rb, line 134 def stopped? state == "shutoff" end
# File lib/fog/libvirt/models/compute/server.rb, line 128 def suspend action_status = service.vm_action(uuid, :suspend) reload action_status end
rubocop:disable Metrics
# File lib/fog/libvirt/models/compute/server.rb, line 271 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) xml.os 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 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.dig(:model, :name) || "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 => 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 => "hdc", :bus => "ide") xml.readonly xml.address(:type => "drive", :controller => 0, :bus => 1, :unit => 0) end end nics.each do |nic| xml.interface(:type => nic.type) do 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.password = display[:password] unless display[:password].empty? xml.video do xml.model(:type => "cirrus", :vram => 9216, :heads => 1) end end end end end builder.to_xml end
# File lib/fog/libvirt/models/compute/server.rb, line 81 def update_autostart(value) service.update_autostart(uuid, value) end
# File lib/fog/libvirt/models/compute/server.rb, line 229 def update_display attrs = {} service.update_display attrs.merge(:uuid => uuid) reload end
can’t use deprecate method, as the value is part of the display hash
# File lib/fog/libvirt/models/compute/server.rb, line 235 def vnc_port Fog::Logger.deprecation("#{self.class} => #vnc_port is deprecated, use #display[:port] instead [light_black](#{caller.first})[/]") display[:port] end
# File lib/fog/libvirt/models/compute/server.rb, line 148 def volumes # lazy loading of volumes @volumes ||= (@volumes_path || []).map{ |path| service.volumes.all(:path => path).first }.compact end
Private Instance Methods
This tests the library version before redefining the address method for this instance to use a method compatible with earlier libvirt libraries, or uses the dhcp method from more recent releases.
# File lib/fog/libvirt/models/compute/server.rb, line 447 def addresses(service_arg=service, options={}) addresses_method = self.method(:addresses_dhcp) # check if ruby-libvirt was compiled against a new enough version # that can use dhcp_leases, as otherwise it will not provide the # method dhcp_leases on any of the network objects. has_dhcp_leases = true begin service.networks.first.dhcp_leases(self.mac) rescue NoMethodError has_dhcp_leases = false rescue # assume some other odd exception. end # if ruby-libvirt not compiled with support, or remote library is # too old (must be newer than 1.2.8), then use old fallback if not has_dhcp_leases or service.libversion() < 1002008 addresses_method = self.method(:addresses_ip_command) end # replace current definition for this instance with correct one for # detected libvirt to perform check once for connection (class << self; self; end).class_eval do define_method(:addresses, addresses_method) end addresses(service_arg, options) end
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 578 def addresses_dhcp(service_arg=service, options={}) mac=self.mac ip_address = nil nic = self.nics.find {|nic| nic.mac==mac} if !nic.nil? net = service.networks.all(:name => nic.network).first if !net.nil? leases = net.dhcp_leases(mac, 0) # Assume the lease expiring last is the current IP address ip_address = leases.sort_by { |lse| lse["expirytime"] }.last["ipaddr"] if !leases.empty? end end return { :public => [ip_address], :private => [ip_address] } end
This retrieves the ip address of the mac address using ip_command 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 531 def addresses_ip_command(service_arg=service, options={}) mac=self.mac # Aug 24 17:34:41 juno arpwatch: new station 10.247.4.137 52:54:00:88:5a:0a eth0.4 # Aug 24 17:37:19 juno arpwatch: changed ethernet address 10.247.4.137 52:54:00:27:33:00 (52:54:00:88:5a:0a) eth0.4 # Check if another ip_command string was provided ip_command_global=service_arg.ip_command.nil? ? 'grep $mac /var/log/arpwatch.log|sed -e "s/new station//"|sed -e "s/changed ethernet address//g" |sed -e "s/reused old ethernet //" |tail -1 |cut -d ":" -f 4-| cut -d " " -f 3' : service_arg.ip_command ip_command_local=options[:ip_command].nil? ? ip_command_global : options[:ip_command] ip_command="mac=#{mac}; server_name=#{name.gsub(DOMAIN_CLEANUP_REGEXP, '_')}; "+ip_command_local ip_address=nil if service_arg.uri.ssh_enabled? ip_address=ssh_ip_command(ip_command, service_arg.uri) else # It's not ssh enabled, so we assume it is if service_arg.uri.transport=="tls" raise Fog::Errors::Error.new("TlS remote transport is not currently supported, only ssh") end ip_address=local_ip_command(ip_command) end # The Ip-address command has been run either local or remote now if ip_address=="" #The grep didn't find an ip address result" ip_address=nil else # To be sure that the command didn't return another random string # We check if the result is an actual ip-address # otherwise we return nil unless ip_address=~/^(\d{1,3}\.){3}\d{1,3}$/ raise Fog::Errors::Error.new( "The result of #{ip_command} does not have valid ip-address format\n"+ "Result was: #{ip_address}\n" ) end end return { :public => [ip_address], :private => [ip_address]} end
# File lib/fog/libvirt/models/compute/server.rb, line 613 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
# File lib/fog/libvirt/models/compute/server.rb, line 680 def default_display {:port => '-1', :listen => '127.0.0.1', :type => 'vnc', :password => '' } end
# File lib/fog/libvirt/models/compute/server.rb, line 640 def default_iso_dir "/var/lib/libvirt/images" end
# File lib/fog/libvirt/models/compute/server.rb, line 644 def default_volume_name "#{name}.#{volume_format_type || 'img'}" end
# File lib/fog/libvirt/models/compute/server.rb, line 648 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, :virtio_rng => {}, } end
# File lib/fog/libvirt/models/compute/server.rb, line 599 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
# File lib/fog/libvirt/models/compute/server.rb, line 607 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
# File lib/fog/libvirt/models/compute/server.rb, line 595 def ip_address(key) addresses[key].nil? ? nil : addresses[key].first end
# File lib/fog/libvirt/models/compute/server.rb, line 505 def local_ip_command(ip_command) # Execute the ip_command locally # Initialize empty ip_address string ip_address="" IO.popen("#{ip_command}") do |p| p.each_line do |l| ip_address+=l end status=Process.waitpid2(p.pid)[1].exitstatus if status!=0 raise Fog::Errors::Error.new("The command #{ip_command} failed to execute with a clean exit code") end end #Strip any new lines from the string ip_address.chomp end
# File lib/fog/libvirt/models/compute/server.rb, line 428 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
# File lib/fog/libvirt/models/compute/server.rb, line 475 def ssh_ip_command(ip_command, uri) # Retrieve the parts we need from the service to setup our ssh options user=uri.user #could be nil host=uri.host keyfile=uri.keyfile port=uri.port # Setup the options ssh_options={} ssh_options[:keys]=[ keyfile ] unless keyfile.nil? ssh_options[:port]=port unless keyfile.nil? ssh_options[:paranoid]=true if uri.no_verify? begin result=Fog::SSH.new(host, user, ssh_options).run(ip_command) rescue Errno::ECONNREFUSED raise Fog::Errors::Error.new("Connection was refused to host #{host} to retrieve the ip_address for #{mac}") rescue Net::SSH::AuthenticationFailed raise Fog::Errors::Error.new("Error authenticating over ssh to host #{host} and user #{user}") end # Check for a clean exit code if result.first.status == 0 return result.first.stdout.strip else # We got a failure executing the command raise Fog::Errors::Error.new("The command #{ip_command} failed to execute with a clean exit code") end end
# File lib/fog/libvirt/models/compute/server.rb, line 671 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