Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/fog/libvirt/compute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class Compute < Fog::Service
request :get_node_info
request :update_autostart
request :update_display
request :upload_iso
request :attach_iso
request :detach_iso
request :destroy_iso
request :libversion

module Shared
Expand Down
36 changes: 32 additions & 4 deletions lib/fog/libvirt/models/compute/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ def initialize(attributes={} )
@user_data = attributes.delete(:user_data)
end

def upload_iso(file_path, volume_name = nil, pool_name = nil)
raise ArgumentError, "file_path is a required parameter" if file_path.nil?

volume_name ||= File.basename(file_path)
pool_name ||= default_iso_pool_name
service.upload_iso(pool_name, volume_name, file_path)
end

def attach_iso(path, options = {})
raise ArgumentError, "path is a required parameter" if path.nil?

iso_path = File.absolute_path?(path) ? path : File.join(iso_dir || default_iso_dir, path)
service.attach_iso(uuid, iso_path, options)
end

def detach_iso(options = {})
service.detach_iso(uuid, options)
end

def destroy_iso(volume_name, pool_name = nil)
raise ArgumentError, "volume_name is a required parameter" if volume_name.nil?

iso_name = File.basename(volume_name)
pool_name ||= service.volumes.all(:name => iso_name).first&.pool_name || default_iso_pool_name
service.destroy_iso(pool_name, iso_name)
end

def new?
uuid.nil?
end
Expand Down Expand Up @@ -531,14 +558,15 @@ def create_or_clone_volume
@volumes.nil? ? @volumes = [volume] : @volumes << volume
end

def default_iso_dir
"/var/lib/libvirt/images"
end

def default_volume_name
"#{name}.#{volume_format_type || 'img'}"
end

def default_iso_pool_name
volume = volumes&.first
volume&.pool_name || "default"
end

def defaults
{
:persistent => true,
Expand Down
7 changes: 7 additions & 0 deletions lib/fog/libvirt/models/compute/util/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
module Fog
module Libvirt
module Util
DEFAULT_CDROM_TARGET_DEV = "sdc".freeze
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a host is created with a cdrom it uses sda as the default:

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

DEFAULT_CDROM_BUS = "sata".freeze

def xml_element(xml, path, attribute=nil)
xml = Nokogiri::XML(xml)
attribute.nil? ? (xml/path).first.text : (xml/path).first[attribute.to_sym]
Expand All @@ -17,6 +20,10 @@ def xml_elements(xml, path, attribute=nil)
def randomized_name
"fog-#{(SecureRandom.random_number*10E14).to_i.round}"
end

def default_iso_dir
"/var/lib/libvirt/images"
end
end
end
end
61 changes: 61 additions & 0 deletions lib/fog/libvirt/requests/compute/attach_iso.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require "nokogiri"

module Fog
module Libvirt
class Compute
module Shared
include Fog::Libvirt::Util

def attach_iso(uuid, iso_path, options = {})
raise ArgumentError, "uuid is a required parameter" if uuid.nil?
raise ArgumentError, "iso_path is a required parameter" if iso_path.nil?

options ||= {}
raise ArgumentError, "options must be a hash" unless options.is_a?(Hash)

target_dev = options.fetch(:target_dev, DEFAULT_CDROM_TARGET_DEV)
bus = options.fetch(:bus, DEFAULT_CDROM_BUS)
flags = options.fetch(:flags, ::Libvirt::Domain::AFFECT_CONFIG)

resolved_iso_path = File.absolute_path?(iso_path) ? iso_path : File.join(default_iso_dir, iso_path)
xml = attach_cdrom_xml(resolved_iso_path, target_dev, bus)

domain = client.lookup_domain_by_uuid(uuid)
begin
domain.attach_device(xml, flags)
rescue ::Libvirt::Error => e
begin
domain.update_device(xml, flags)
rescue ::Libvirt::Error
raise e
end
end

# if we get no exception, we assume the operation was successful
true
end

private

def attach_cdrom_xml(iso_path, target_dev, bus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually attach anything and is only the CDROM XML so naming wise I'd suggest.

Suggested change
def attach_cdrom_xml(iso_path, target_dev, bus)
def cdrom_xml(iso_path, target_dev, bus)

Nokogiri::XML::Builder.new do |x|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a different implementation than what's default when a host is created:

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

If the host already has a cdrom drive, shouldn't it reuse that?

x.disk(:type => "file", :device => "cdrom") do
x.driver(:name => "qemu", :type => "raw")
x.source(:file => iso_path)
x.target(:dev => target_dev, :bus => bus)
x.readonly
end
end.to_xml
end
end

class Real
include Shared
end

class Mock
include Shared
end
end
end
end
39 changes: 39 additions & 0 deletions lib/fog/libvirt/requests/compute/destroy_iso.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module Fog
module Libvirt
class Compute
module Shared
def destroy_iso(pool_name, volume_name)
raise ArgumentError, "pool_name is a required parameter" if pool_name.nil?
raise ArgumentError, "volume_name is a required parameter" if volume_name.nil?

pool = client.lookup_storage_pool_by_name(pool_name)
begin
pool.lookup_volume_by_name(volume_name).delete
rescue ::Libvirt::RetrieveError
# already absent, treat as success if not present afterwards
end

# if the ISO is absent, then we are good
volume_absent?(pool, volume_name)
end

private

def volume_absent?(pool, volume_name)
pool.lookup_volume_by_name(volume_name)
false
rescue ::Libvirt::RetrieveError
true
end
end

class Real
include Shared
end

class Mock
include Shared
end
end
end
end
81 changes: 81 additions & 0 deletions lib/fog/libvirt/requests/compute/detach_iso.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require "nokogiri"

module Fog
module Libvirt
class Compute
module Shared
include Fog::Libvirt::Util

def detach_iso(uuid, options = {})
raise ArgumentError, "uuid is a required parameter" if uuid.nil?

options ||= {}
raise ArgumentError, "options must be a hash" unless options.is_a?(Hash)

target_dev = options.fetch(:target_dev, DEFAULT_CDROM_TARGET_DEV)
bus = options.fetch(:bus, DEFAULT_CDROM_BUS)
flags = options.fetch(:flags, ::Libvirt::Domain::AFFECT_CONFIG)
domain = client.lookup_domain_by_uuid(uuid)
domain_active = domain.active?
flags = effective_detach_iso_flags(flags, domain_active)

if domain_active
domain.update_device(eject_cdrom_xml(target_dev, bus), flags)
begin
domain.detach_device(detach_cdrom_xml(target_dev, bus), flags)
rescue ::Libvirt::Error
# Some backends don't allow to detach the cdrom if the host is running.
# In this case, we just eject the cdrom and leave it attached to the VM.
# Return true that maybe the ISO file can be removed in further steps.
true
end
else
begin
domain.detach_device(detach_cdrom_xml(target_dev, bus), flags)
true
rescue ::Libvirt::Error
false
end
end
end

private

def detach_cdrom_xml(target_dev, bus)
Nokogiri::XML::Builder.new do |x|
x.disk(:type => "file", :device => "cdrom") do
x.target(:dev => target_dev, :bus => bus)
end
end.to_xml
end

def eject_cdrom_xml(target_dev, bus)
Nokogiri::XML::Builder.new do |x|
x.disk(:type => "file", :device => "cdrom", :tray => "open") do
x.driver(:name => "qemu", :type => "raw")
x.target(:dev => target_dev, :bus => bus)
x.readonly
end
end.to_xml
end

def effective_detach_iso_flags(flags, domain_active)
return flags unless (flags & ::Libvirt::Domain::AFFECT_CONFIG) == ::Libvirt::Domain::AFFECT_CONFIG
return flags unless domain_active

flags | ::Libvirt::Domain::AFFECT_LIVE
rescue ::Libvirt::Error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this raised? I don't see API calls here so what could raise an exception?

flags
end
end

class Real
include Shared
end

class Mock
include Shared
end
end
end
end
54 changes: 54 additions & 0 deletions lib/fog/libvirt/requests/compute/upload_iso.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require "nokogiri"

module Fog
module Libvirt
class Compute
module Shared
def upload_iso(pool_name, volume_name, file_path)
raise ArgumentError, "pool_name is a required parameter" if pool_name.nil?
raise ArgumentError, "volume_name is a required parameter" if volume_name.nil?
raise ArgumentError, "file_path is a required parameter" if file_path.nil?

pool = client.lookup_storage_pool_by_name(pool_name)
pool.lookup_volume_by_name(volume_name).delete if pool.list_volumes.include?(volume_name)

create_volume(pool_name, iso_volume_xml(volume_name, file_path))
upload_volume(pool_name, volume_name, file_path)

volume = pool.lookup_volume_by_name(volume_name)
{
:pool_name => pool_name,
:name => volume_name,
:key => volume.key,
:path => volume.path
}
end

private

def iso_volume_xml(volume_name, file_path)
iso_size = File.size(file_path)

Nokogiri::XML::Builder.new do |x|
x.volume do
x.name(volume_name)
x.allocation(0, :unit => "B")
x.capacity(iso_size, :unit => "B")
x.target do
x.format(:type => "raw")
end
end
end.to_xml
end
end

class Real
include Shared
end

class Mock
include Shared
end
end
end
end
2 changes: 1 addition & 1 deletion tests/libvirt/compute_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
%w{ create_domain create_volume define_domain define_pool destroy_interface destroy_network get_node_info
update_autostart list_domains
list_interfaces list_networks list_pools list_pool_volumes list_volumes pool_action vm_action volume_action
dhcp_leases }.each do |request|
dhcp_leases upload_iso attach_iso detach_iso destroy_iso }.each do |request|
test("it should respond to #{request}") { compute.respond_to? request }
end
end
Expand Down
3 changes: 3 additions & 0 deletions tests/libvirt/models/compute/server_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
%w{ start stop destroy reboot suspend }.each do |action|
test(action) { server.respond_to? action }
end
%w{ upload_iso attach_iso detach_iso destroy_iso }.each do |action|
test(action) { server.respond_to? action }
end
%w{ start reboot suspend stop }.each do |action|
test("#{action} returns successfully") {
begin
Expand Down
Loading