diff --git a/lib/fog/compute/libvirt.rb b/lib/fog/compute/libvirt.rb index bdf265237..dd3a338a9 100644 --- a/lib/fog/compute/libvirt.rb +++ b/lib/fog/compute/libvirt.rb @@ -32,13 +32,39 @@ module Fog attr_reader :uri def initialize(options={}) - @uri = ::Fog::Compute::LibvirtUtil::URI.new(options[:libvirt_uri]) + @uri = ::Fog::Compute::LibvirtUtil::URI.new(enhance_uri(options[:libvirt_uri])) # libvirt is part of the gem => ruby-libvirt require 'libvirt' + + @connection = ::Libvirt::open(@uri.uri) end + def enhance_uri(uri) + require 'cgi' + append="" + + # on macosx, chances are we are using libvirt through homebrew + # the client will default to a socket location based on it's own location (/opt) + # we conveniently point it to /var/run/libvirt/libvirt-sock + # if no socket option has been specified explicitly + + if RUBY_PLATFORM =~ /darwin/ + querystring=::URI.parse(uri).query + if querystring.nil? + append="?socket=/var/run/libvirt/libvirt-sock" + else + if !::CGI.parse(querystring).has_key?("socket") + append="&socket=/var/run/libvirt/libvirt-sock" + end + end + end + newuri=uri+append + return newuri + end + + # hack to provide 'requests' def method_missing(method_sym, *arguments, &block) if @connection.respond_to?(method_sym) diff --git a/lib/fog/compute/models/libvirt/interface.rb b/lib/fog/compute/models/libvirt/interface.rb index 741a46276..1d718020b 100644 --- a/lib/fog/compute/models/libvirt/interface.rb +++ b/lib/fog/compute/models/libvirt/interface.rb @@ -6,12 +6,15 @@ module Fog class Interface < Fog::Model - identity :id + identity :name attribute :mac - attribute :name attribute :xml_desc + def save + raise Fog::Errors::Error.new('Creating a new interface is not yet implemented. Contributions welcome!') + end + def destroy requires :raw raw.delete @@ -28,7 +31,6 @@ module Fog @raw = new_raw raw_attributes = { - :id => new_raw.name, :name => new_raw.name, :mac => new_raw.mac, :xml_desc => new_raw.xml_desc, diff --git a/lib/fog/compute/models/libvirt/interfaces.rb b/lib/fog/compute/models/libvirt/interfaces.rb index b567e31bb..08ec22230 100644 --- a/lib/fog/compute/models/libvirt/interfaces.rb +++ b/lib/fog/compute/models/libvirt/interfaces.rb @@ -9,21 +9,51 @@ module Fog model Fog::Compute::Libvirt::Interface - def all + def all(filter=nil) data=[] - connection.list_interfaces.each do |ifname| - interface=connection.lookup_interface_by_name(ifname) - data << { :raw => interface } - end + if filter.nil? + connection.list_interfaces.each do |ifname| + interface=connection.lookup_interface_by_name(ifname) + data << { :raw => interface } + end + connection.list_defined_interfaces.each do |ifname| + interface=connection.lookup_interface_by_name(ifname) + data << { :raw => interface } + end + + else + interface=nil + begin + interface=get_by_name(filter[:name]) if filter.has_key?(:name) + interface=get_by_mac(filter[:mac]) if filter.has_key?(:mac) + rescue ::Libvirt::RetrieveError + return nil + end + data << { :raw => interface} + end + load(data) end + + def get(key) + self.all(:name => name).first + end + + # Retrieve the interface by name + def get_by_name(name) + interface=connection.lookup_interface_by_name(name) + return interface + # new(:raw => interface) + end # Retrieve the interface by name - def get(name) - interface=connection.lookup_interface_by_name(name) - new(:raw => interface) + def get_by_mac(mac) + interface=connection.lookup_interface_by_mac(mac) + return interface + # new(:raw => interface) end + end #class end #Class diff --git a/lib/fog/compute/models/libvirt/network.rb b/lib/fog/compute/models/libvirt/network.rb index 524e5d33f..aadd0c2de 100644 --- a/lib/fog/compute/models/libvirt/network.rb +++ b/lib/fog/compute/models/libvirt/network.rb @@ -9,15 +9,12 @@ module Fog include Fog::Compute::LibvirtUtil - identity :id - attribute :uuid + identity :uuid + attribute :name attribute :bridge_name - attribute :xml_desc - - attr_reader :template_path,:network_mode,:bridge_name - + ##https://www.redhat.com/archives/libvirt-users/2011-May/msg00091.html # Bridged VLAN @@ -27,17 +24,16 @@ module Fog # http://wiki.libvirt.org/page/Networking #http://wiki.libvirt.org/page/VirtualNetworking#Virtual_network_switches def initialize(attributes = {}) - - @template_path = attributes[:template_path] || "network.xml.erb" - @network_mode = attributes[:network_mode] || "nat" - @bridge_name = attributes[:bridge_name] || "virbr0" - template_xml + super + end + + def save + raise Fog::Errors::Error.new('Creating a new network is not yet implemented. Contributions welcome!') end def destroy() requires :raw raw.destroy -# raw.undefine true end @@ -51,7 +47,6 @@ module Fog @raw = new_raw raw_attributes = { - :id => new_raw.uuid, :uuid => new_raw.uuid, :name => new_raw.name, :bridge_name => new_raw.bridge_name, diff --git a/lib/fog/compute/models/libvirt/networks.rb b/lib/fog/compute/models/libvirt/networks.rb index 359ef8727..78c3a1e24 100644 --- a/lib/fog/compute/models/libvirt/networks.rb +++ b/lib/fog/compute/models/libvirt/networks.rb @@ -9,20 +9,49 @@ module Fog model Fog::Compute::Libvirt::Network - def all + def all(filter=nil) data=[] - connection.list_networks.each do |networkname| - network=connection.lookup_network_by_name(networkname) - data << { :raw => network } - end + if filter.nil? + connection.list_networks.each do |networkname| + network=connection.lookup_network_by_name(networkname) + data << { :raw => network } + end + connection.list_defined_networks.each do |networkname| + network=connection.lookup_network_by_name(networkname) + data << { :raw => network} + end + else + network=nil + begin + network=get_by_uuid(filter[:uuid]) if filter.has_key?(:uuid) + network=get_by_name(filter[:name]) if filter.has_key?(:name) + rescue ::Libvirt::RetrieveError + return nil + end + data << { :raw => network} + end + load(data) end - # Retrieve the network by uuid def get(uuid) - network=connection.lookup_network_by_uuid(uuid) - new(:raw => network) + self.all(:uuid => uuid).first end + + # Retrieve the network by uuid + def get_by_uuid(uuid) + network=connection.lookup_network_by_uuid(uuid) + return network + # new(:raw => network) + end + + # Retrieve the network by name + def get_by_name(name) + network=connection.lookup_network_by_name(name) + return network + # new(:raw => network) + end + end #class diff --git a/lib/fog/compute/models/libvirt/pool.rb b/lib/fog/compute/models/libvirt/pool.rb index 4dd1cf809..9f850b280 100644 --- a/lib/fog/compute/models/libvirt/pool.rb +++ b/lib/fog/compute/models/libvirt/pool.rb @@ -6,7 +6,7 @@ module Fog class Pool < Fog::Model - identity :id + identity :uuid # These attributes are only used for creation attribute :xml @@ -25,7 +25,7 @@ module Fog requires :xml unless xml.nil? pool=nil - if create_persistent + if self.create_persistent pool=connection.connection.define_storage_pool_xml(xml) else pool=connection.connection.create_storage_pool_xml(xml) @@ -33,6 +33,7 @@ module Fog self.raw=pool true else + raise Fog::Errors::Error.new('Creating a new pool requires proper xml') false end end @@ -163,12 +164,12 @@ module Fog end - # Retrieves the allocated disk space of the pool + # Retrieves the volumes of this pool def volumes volumes=Array.new @raw.list_volumes.each do |volume| - fog_volume=connection.volumes.get(:name => volume) + fog_volume=connection.volumes.all(:name => volume).first volumes << fog_volume end return volumes @@ -184,7 +185,8 @@ module Fog @raw = new_raw raw_attributes = { - :id => new_raw.uuid, + :uuid => new_raw.uuid, + } merge_attributes(raw_attributes) diff --git a/lib/fog/compute/models/libvirt/pools.rb b/lib/fog/compute/models/libvirt/pools.rb index 741670189..e6d366520 100644 --- a/lib/fog/compute/models/libvirt/pools.rb +++ b/lib/fog/compute/models/libvirt/pools.rb @@ -9,40 +9,48 @@ module Fog model Fog::Compute::Libvirt::Pool - def all + def all(filter=nil) data=[] - connection.list_storage_pools.each do |poolname| - pool=connection.lookup_storage_pool_by_name(poolname) - data << { :raw => pool } - end - connection.list_defined_storage_pools.each do |poolname| - data << { - :raw => connection.lookup_storage_pool_by_name(poolname) - } + if filter.nil? + connection.list_storage_pools.each do |poolname| + pool=connection.lookup_storage_pool_by_name(poolname) + data << { :raw => pool } + end + connection.list_defined_storage_pools.each do |poolname| + data << { + :raw => connection.lookup_storage_pool_by_name(poolname) + } + end + else + pool=nil + begin + pool=get_by_uuid(filter[:uuid]) if filter.has_key?(:uuid) + pool=get_by_name(filter[:name]) if filter.has_key?(:name) + rescue ::Libvirt::RetrieveError + return nil + end + data << { :raw => pool} end - load(data) end - # Retrieve the pool by type - def get(param) - pool=nil - pool=get_by_uuid(param[:uuid]) if param.has_key?(:uuid) - pool=get_by_name(param[:name]) if param.has_key?(:name) - return pool + def get(uuid) + self.all(:uuid => uuid).first end - + + private # Retrieve the pool by uuid def get_by_uuid(uuid) pool=connection.lookup_storage_pool_by_uuid(uuid) - new(:raw => pool) + return pool end # Retrieve the pool by name def get_by_name(name) pool=connection.lookup_storage_pool_by_name(name) - new(:raw => pool) + return pool +# new(:raw => pool) end end #class diff --git a/lib/fog/compute/models/libvirt/server.rb b/lib/fog/compute/models/libvirt/server.rb index 285adcf10..64250a326 100644 --- a/lib/fog/compute/models/libvirt/server.rb +++ b/lib/fog/compute/models/libvirt/server.rb @@ -1,5 +1,10 @@ require 'fog/core/model' require 'fog/compute/models/libvirt/util' +require 'net/ssh/proxy/command' +require 'rexml/document' +require 'erb' +require 'securerandom' + module Fog module Compute @@ -11,227 +16,433 @@ module Fog identity :id , :aliases => 'uuid' - attribute :memory_size - attribute :name - attribute :os, :aliases => :os_type_id - attribute :xml_desc - attribute :cpus - attribute :arch - attribute :bridge_name - - attr_writer :private_key, :private_key_path, :public_key, :public_key_path, :username - attr_reader :bridge_name,:arch, :cpus,:bridge_name, :name,:template_path,:memory_size,:os,:volume_path + attribute :poolname + attribute :xml + attribute :create_persistent + attribute :template_options + attribute :template_erb - def initialize(attributes = {}) - super - end - - def create(attributes = {}) - @name = attributes[:name] || raise("we need a name") - @bridge_name = attributes[:bridge_name] || "br0" - @cpus = attributes[:cpus] || 1 - @memory_size = attributes[:memory_size] || 256 - @username = attributes[:username] || "mccloud" - @os = attributes[:os] || "hvm" - @arch = attributes[:arch] || "x86_64" - @template_path = attributes[:template_path] || "guest.xml.erb" - # super - volume=connection.volumes.get("ubuntu-10_10_amd64.qcow2").clone("#{name}.qcow2") - @volume_path=volume.path - connection.define_domain_xml(template_xml) + attr_accessor :password + attr_writer :private_key, :private_key_path, :public_key, :public_key_path, :username + + # Can be created by passing in :xml => "" + # or by providing :template_options => { + # :name => "", :cpus => 1, :memory_size => 256 , :volume_template + # :} + # + # @returns server/domain created + def initialize(attributes={} ) + self.xml ||= nil unless attributes[:xml] + self.create_persistent ||=true unless attributes[:create_persistent] + self.template_options ||=nil unless attributes[:template_options] + super + end + + def save - end - - def start - requires :raw - - unless @raw.active? - begin - @raw.create - rescue - print "An error occured :",$!,"\n" - end + raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if id + + # first check if we have either xml or template_options + if xml.nil? && template_options.nil? + raise Fog::Errors::Error.new('Creating a new domain/server requires either xml or passing template_options') end - end - - def destroy(options={ :destroy_volumes => false}) - - #connection.volumes(name).destroy - requires :raw - if @raw.active? - @raw.destroy - end - @raw.undefine - end + if !xml.nil? && !template_options.nil? + raise Fog::Errors::Error.new('Creating a new domain/server requires either xml or passing template_options,not both') + end - - def ready? - - status == :running - end + # We have a template, let's generate some xml for it + if !template_options.nil? - def reboot - requires :raw - - @raw.reboot - end + template_defaults={ + :cpus => 1, + :memory_size => 256, + :arch => "x86_64", + :os => "hvm", + :domain_type => "kvm", + :name => "fog-#{SecureRandom.random_number*10E14.to_i.round}", + + # Network options + :interface_type => "nat", #or "bridge" + :nat_network_name => "default", + :bridge_name => "br0", + + # Disk options + :disk_type => "raw", + :disk_size => 10, + :disk_size_unit => "G", + :disk_allocate => 1, + :disk_allocate_unit => "G", + :disk_extension => "img", + :disk_template_name => nil, + :poolname => nil, + + # DVD options + :iso_file => nil , + :iso_dir => "/var/lib/libvirt/images/" + } - def halt - requires :raw - - @raw.shutdown - end - - def resume - requires :raw - - @raw.resume - end - - def suspend - requires :raw - - @raw.suspend - end - - def status - state=case @raw.info.state - when 0 then :nostate - when 1 then :running - when 2 then :paused - when 3 then :shuttingdown - when 4 then :shutoff - when 5 then :crashed + template_options2=template_defaults.merge(template_options) + template_options={ :disk_path => "#{template_options2[:name]}.#{template_options2[:disk_extension]}"}.merge(template_options2) + + if !template_options[:disk_template_name].nil? + # Clone the volume + volume=connection.volumes.allocate(:name => template_options[:disk_template_name]).clone("#{template_options[:disk_path]}") + template_options[:disk_path]=volume.path + else + # If no template volume was given, let's create our own volume + volume=connection.volumes.create(:template_options => { + :name => "#{template_options[:disk_name]}", + :extension => "#{template_options[:disk_extension]}", + :type => "#{template_options[:disk_type]}", + :size => "#{template_options[:disk_size]}", + :size_unit => "#{template_options[:disk_size_unit]}", + :allocate => "#{template_options[:disk_allocate]}", + :size_unit => "#{template_options[:disk_size_unit]}" }) + + template_options[:disk_path]=volume.path + + end + validate_template_options(template_options) + + xml=xml_from_template(template_options) + + end + + # We either now have xml provided by the user or generated by the template + if !xml.nil? + domain=nil + if create_persistent + domain=connection.define_domain_xml(xml) + else + domain=connection.create_domain_xml(xml) + end + self.raw=domain + end + end + + def validate_template_options(template_options) + #if template_options[:disk_template_name].nil? + # raise Fog::Errors::Error.new('In order to make the disk boot, we require a template volume we can clone') + #end + + end + + def xml_from_template(template_options) + + # We only want specific variables for ERB + vars = ErbBinding.new(template_options) + template_path=File.join(File.dirname(__FILE__),"templates","server.xml.erb") + template=File.open(template_path).readlines.join + erb = ERB.new(template) + vars_binding = vars.send(:get_binding) + result=erb.result(vars_binding) + return result + end + + def username + @username ||= 'root' + end + + def start + requires :raw + + unless @raw.active? + begin + @raw.create + true + rescue + false + end + end + end + + def destroy(options={ :destroy_volumes => false}) + + #connection.volumes(name).destroy + requires :raw + if @raw.active? + @raw.destroy + end + @raw.undefine + end + + + def reboot + requires :raw + + @raw.reboot + end + + + def halt + requires :raw + + @raw.shutdown + end + + def resume + requires :raw + + @raw.resume + end + + def suspend + requires :raw + + @raw.suspend + end + + def state + state=case @raw.info.state + when 0 then :nostate + when 1 then :running + when 2 then :paused + when 3 then :shuttingdown + when 4 then :shutoff + when 5 then :crashed end return state end - - def save() - - raise Fog::Errors::Error.new('Updating an existing server is not yet implemented. Contributions welcome!') + + def ready? + state == :running end - def scp(local_path, remote_path, upload_options = {}) - raise 'Not Implemented' - requires :addresses, :username - - scp_options = {} - scp_options[:key_data] = [private_key] if private_key - Fog::SCP.new(addresses['public'].first, username, options).upload(local_path, remote_path, scp_options) - end - def setup(credentials = {}) - requires :addresses, :identity, :public_key, :username - Fog::SSH.new(addresses['public'].first, username, credentials).run([ - %{mkdir .ssh}, - %{echo "#{public_key}" >> ~/.ssh/authorized_keys}, - %{echo "#{attributes.to_json}" >> ~/attributes.json}, - %{echo "#{metadata.to_json}" >> ~/metadata.json} - ]) - rescue Errno::ECONNREFUSED - sleep(1) - retry - end - - - ##Note this requires arpwatch to be running - ##and chmod o+x /var/lib/arpwatch - - def addresses - mac=self.mac - options={} - ipaddress=nil - if connected_by_ssh? - #command="arp -an|grep #{mac}|cut -d ' ' -f 2| cut -d '(' -f 2| cut -d ')' -f 1" - #command="grep #{mac} /var/log/daemon.log |sed -e 's/^.*address //'|cut -d ' ' -f 1" - command="grep #{mac} /var/lib/arpwatch/arp.dat|cut -f 2" - result=Fog::SSH.new(connection.hostname, "patrick.debois", options).run(command) - if result.first.status == 0 - ipaddress=result.first.stdout.strip - #TODO check for valid IP - #TODO check time validity - else - #cat /var/log/daemon.log|grep "52:54:00:52:f6:22"| - end - else - #local execute arp -an to get the ip + def stop + requires :raw + + @raw.shutdown end - return { 'public' => [ipaddress], 'private' => [ipaddress]} - end + def mac - def ssh(commands) - requires :addresses, :identity, :username - - options = {} - #options[:key_data] = [private_key] if private_key - - require 'net/ssh/proxy/command' - options={ :password => "mccloud"} - if connected_by_ssh? - relay=connection.hostname - proxy = Net::SSH::Proxy::Command.new('ssh -l patrick.debois '+relay+' nc %h %p') - options[:proxy]= proxy - end - #Fog::SSH.new("192.168.122.48", "vagrant", options).run(commands) - Fog::SSH.new(addresses['public'].first, "mccloud", options).run(commands) - - end - - def stop - requires :raw - - @raw.shutdown - end - - def username - @username ||= 'root' - end - - def mac - require "rexml/document" - require 'erb' - mac = document("domain/devices/interface/mac", "address") return mac + end + + def vnc_port + + port = document("domain/devices/graphics[@type='vnc']", "port") + return port + end + + def name + requires :raw + raw.name + end + + def uuid + requires :raw + raw.uuid + end + + def memory_size + requires :raw + raw.memory_size + end + + def cpus + requires :raw + raw.cpus + end + + def os_type + requires :raw + raw.os_type + end + + def xml_desc + requires :raw + raw.xml_desc + end + + ##Note this requires arpwatch to be running + ##and chmod o+x /var/lib/arpwatch + + def addresses + mac=self.mac + ipaddress=nil + if @connection.uri.ssh_enabled? + #command="arp -an|grep #{mac}|cut -d ' ' -f 2| cut -d '(' -f 2| cut -d ')' -f 1" + #command="grep #{mac} /var/log/daemon.log |sed -e 's/^.*address //'|cut -d ' ' -f 1" + # TODO: check if this files exists + # Check if it is readable + command="grep #{mac} /var/lib/arpwatch/arp.dat|cut -f 2|tail -1" + + # TODO: we need to take the time into account, when IP's are re-allocated, we might be executing + # On the wrong host + + # We can get the host, the + user=connection.uri.user #could be nil + host=connection.uri.host + keyfile=connection.uri.keyfile + port=connection.uri.port + + options={} + options[:keys]=[ keyfile ] unless keyfile.nil? + options[:port]=port unless keyfile.nil? + options[:paranoid]=true if connection.uri.no_verify? + + result=Fog::SSH.new(host, user, options).run(command) + if result.first.status == 0 + ipaddress=result.first.stdout.strip + #TODO check for valid IP + #TODO check time validity + else + # We couldn't retrieve any IP information + return { :public => nil , :private => nil} + end + else + # TODO for locat execute + #No ssh just do it locally + #cat /var/log/daemon.log|grep "52:54:00:52:f6:22"| + # or local execute arp -an to get the ip (as a last resort) + + end + return { :public => [ipaddress], :private => [ipaddress]} + end + + def private_ip_address + ip_address(:private) + end + + def public_ip_address + ip_address(:public) + end + + def ip_address(key) + ips=addresses[key] + unless ips.nil? + return ips.first + else + return nil + end + end + + def private_key_path + @private_key_path ||= Fog.credentials[:private_key_path] + @private_key_path &&= File.expand_path(@private_key_path) + end + + def private_key + @private_key ||= private_key_path && File.read(private_key_path) + end + + def public_key_path + @public_key_path ||= Fog.credentials[:public_key_path] + @public_key_path &&= File.expand_path(@public_key_path) + end + + def public_key + @public_key ||= public_key_path && File.read(public_key_path) + end + + def ssh(commands) + requires :public_ip_address, :username + + #requires :password, :private_key + ssh_options={} + ssh_options[:password] = password unless password.nil? + ssh_options[:key_data] = [private_key] if private_key + ssh_options[:proxy]= ssh_proxy unless ssh_proxy.nil? + + Fog::SSH.new(public_ip_address, @username, ssh_options).run(commands) + + end + + + def ssh_proxy + proxy=nil + if @connection.uri.ssh_enabled? + relay=connection.uri.host + user_string="" + user_string="-l #{connection.uri.user}" unless connection.uri.user.nil? + proxy = Net::SSH::Proxy::Command.new("ssh #{user_string} "+relay+" nc %h %p") + return proxy + else + return nil + # This is a direct connection, so we don't need a proxy to be set + end + end + + # Transfers a file + def scp(local_path, remote_path, upload_options = {}) + requires :public_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(public_ip_address, username, scp_options).upload(local_path, remote_path, upload_options) + end + + + # Sets up a new key + def setup(credentials = {}) + requires :public_key, :public_ip_address, :username + require 'multi_json' + + credentials[:proxy]= ssh_proxy unless ssh_proxy.nil? + credentials[:password] = password unless self.password.nil? + credentails[:key_data] = [private_key] if self.private_key + + commands = [ + %{mkdir .ssh}, +# %{passwd -l #{username}}, #Not sure if we need this here +# %{echo "#{MultiJson.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(public_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(public_ip_address, username, credentials).run(commands) + end + + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.uuid, + } + + merge_attributes(raw_attributes) + end + + + # finds a value from xml + def document path, attribute=nil + xml = REXML::Document.new(xml_desc) + attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute] + end + + end - private - - def raw - @raw - end - - def raw=(new_raw) - @raw = new_raw - - raw_attributes = { - :id => new_raw.uuid, - :memory_size => new_raw.info.max_mem , - :name => new_raw.name, - :cpus => new_raw.info.nr_virt_cpu, - :os_type_id => new_raw.os_type, - :xml_desc => new_raw.xml_desc, - - } - - merge_attributes(raw_attributes) - end - - # finds a value from xml - def document path, attribute=nil - xml = REXML::Document.new(xml_desc) - attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute] - end - - def connected_by_ssh? - return connection.uri.include?("+ssh") - end end - end - end -end + end diff --git a/lib/fog/compute/models/libvirt/servers.rb b/lib/fog/compute/models/libvirt/servers.rb index 1eadb4531..389010276 100644 --- a/lib/fog/compute/models/libvirt/servers.rb +++ b/lib/fog/compute/models/libvirt/servers.rb @@ -9,21 +9,34 @@ module Fog model Fog::Compute::Libvirt::Server - def all - - data = connection.list_defined_domains.map do |machine| - { - :raw => connection.lookup_domain_by_name(machine) - } + def all(filter=nil) + data=[] + if filter.nil? + connection.list_defined_domains.map do |server| + data << { :raw => connection.lookup_domain_by_name(domain) } + end + + connection.list_domains.each do |domain| + data << { :raw => connection.lookup_domain_by_id(domain) } + end + else + domain=nil + begin + domain=self.get_by_name(filter[:name]) if filter.has_key?(:name) + domain=self.get_by_uuid(filter[:uuid]) if filter.has_key?(:uuid) + + rescue ::Libvirt::RetrieveError + return nil + end + data << { :raw => domain } end - connection.list_domains.each do |machine| - data << { - :raw => connection.lookup_domain_by_id(machine) - } - end load(data) end + + def get(key) + self.all(:key => key).first + end def bootstrap(new_attributes = {}) raise 'Not Implemented' @@ -34,16 +47,19 @@ module Fog # server end + private # Retrieve the server by uuid - def get(server_id) - machine=connection.lookup_domain_by_uuid(server_id) - new(:raw => machine) + def get_by_uuid(uuid) + server=connection.lookup_domain_by_uuid(uuid) + return server +# new(:raw => machine) end # Retrieve the server by name def get_by_name(name) - machine=connection.lookup_domain_by_name(name) - new(:raw => machine) + server=connection.lookup_domain_by_name(name) + return server +# new(:raw => machine) end end #class diff --git a/lib/fog/compute/models/libvirt/templates/guest.xml.erb b/lib/fog/compute/models/libvirt/templates/guest.xml.erb deleted file mode 100644 index fdb90a0f8..000000000 --- a/lib/fog/compute/models/libvirt/templates/guest.xml.erb +++ /dev/null @@ -1,37 +0,0 @@ - - <%= name %> - <%= memory_size %> - <%= cpus %> - - <%= os %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/server.xml.erb b/lib/fog/compute/models/libvirt/templates/server.xml.erb new file mode 100644 index 000000000..f24a8370b --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/server.xml.erb @@ -0,0 +1,57 @@ + + <%= name %> + <%= memory_size %> + <%= cpus %> + + <%= os %> + + <% if !iso_file.nil? %> + + + <% end %> + + + + + + + + + + + + + + <% if !iso_file.nil? %> + + + + + +
+ + <% end %> + <% if interface_type=="bridge" %> + + + + <% end %> + <% if interface_type=="nat" %> + + + + <% end %> + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/volume.xml b/lib/fog/compute/models/libvirt/templates/volume.xml deleted file mode 100644 index ca94c34c5..000000000 --- a/lib/fog/compute/models/libvirt/templates/volume.xml +++ /dev/null @@ -1,14 +0,0 @@ - - ubuntu.qcow2 - 10 - 10 - - - - 0 - 0 - 0744 - - - - \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/volume.xml.erb b/lib/fog/compute/models/libvirt/templates/volume.xml.erb new file mode 100644 index 000000000..f47689245 --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/volume.xml.erb @@ -0,0 +1,14 @@ + + <%= "#{name}.#{extension}" %> + <%= allocate %> + <%= size %> + + + + 0 + 0 + 0744 + + + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/util.rb b/lib/fog/compute/models/libvirt/util.rb index b3c19289a..1f1ba228a 100644 --- a/lib/fog/compute/models/libvirt/util.rb +++ b/lib/fog/compute/models/libvirt/util.rb @@ -1,21 +1,10 @@ require "rexml/document" require 'erb' +require 'ostruct' module Fog module Compute module LibvirtUtil - # return templated xml to be used by libvirt - def template_xml - ERB.new(template, nil, '-').result(binding) - end - - private - # template file that contain our xml template - def template - File.read("#{File.dirname(__FILE__)}/templates/#{template_path}") - rescue => e - warn "failed to read template #{template_path}: #{e}" - end # finds a value from xml def document path, attribute=nil @@ -23,6 +12,14 @@ module Fog xml = REXML::Document.new(@xml_desc) attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute] end + + class ErbBinding < OpenStruct + def get_binding + return binding() + end + end + + end end end \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/volume.rb b/lib/fog/compute/models/libvirt/volume.rb index 5119a09a0..7a523ecc5 100644 --- a/lib/fog/compute/models/libvirt/volume.rb +++ b/lib/fog/compute/models/libvirt/volume.rb @@ -1,4 +1,8 @@ require 'fog/core/model' +require 'fog/compute/models/libvirt/util' +require 'rexml/document' +require 'erb' +require 'securerandom' module Fog module Compute @@ -6,130 +10,201 @@ module Fog class Volume < Fog::Model - identity :id + include Fog::Compute::LibvirtUtil + + identity :key attribute :poolname - attribute :xml - attribute :create_persistent + attribute :xml + attribute :template_options + +# attribute :key + attribute :path + attribute :name + attribute :capacity + attribute :allocation + attribute :type + # Can be created by passing in :xml => "" - # A volume always belongs to a pool, :pool => "" + # A volume always belongs to a pool, :poolname => "" # # @returns volume created def initialize(attributes={} ) self.xml ||= nil unless attributes[:xml] - self.poolname ||= nil unless attributes[:poolname] - self.create_persistent ||=true unless attribues[:create_persistent] + super + + # Try to guess the default/first pool of no poolname was specificed + default_pool_name="default" + default_pool=connection.pools.all(:name => "default") + if default_pool.nil? + first_pool=connection.pools.first + if first_pool.nil? + raise Fog::Errors::Error.new('We could not find a pool called "default" and there was no other pool defined') + else + default_pool_name=first_pool.name + end + + end + + self.poolname ||= default_pool_name unless attributes[:poolname] + end - # Takes a pool and xml to create the volume + # Takes a pool and either uses :xml or :template_options->xml to create the volume def save - requires :xml - requires :poolname - - unless xml.nil? - volume=nil - unless poolname.nil? - pool=connection.lookup_storage_pool_by_name(poolname) - if create_persistent - volume=pool.define_volume_xml(xml) - else +# requires :xml +# requires :poolname + + if poolname + # :disk_type => "raw", + # :disk_extension => "img", + # :disk_size => "10000", + # We have a template, let's generate some xml for it + if !template_options.nil? + + template_defaults={ + :type => "raw", + :extension => "img", + :name => "fog-#{SecureRandom.random_number*10E14.to_i.round}", + :size => 10, + :allocate_unit => "G", + :size_unit => "G", + :allocate => 1, + } + template_options2=template_defaults.merge(template_options) + template_options={ :path => "#{template_options2[:name]}.#{template_options2[:extension]}"}.merge(template_options2) + + validate_template_options(template_options) + + xml=xml_from_template(template_options) + # require 'pp' + # pp xml + # exit + + end + + unless xml.nil? + volume=nil + unless poolname.nil? + pool=connection.lookup_storage_pool_by_name(poolname) volume=pool.create_volume_xml(xml) + self.raw=volume + true + else + raise Fog::Errors::Error.new('Creating a new volume requires a pool name or uuid') + false end - self.raw=volume - true else - raise Fog::Errors::Error.new('Creating a new volume requires a pool name or uuid') + raise Fog::Errors::Error.new('Creating a new volume requires non empty xml') false end - else - raise Fog::Errors::Error.new('Creating a new volume requires non empty xml') - false end + end + + def validate_template_options(template_options) + # Here we can validate the template_options + end + + def xml_from_template(template_options) + + # We only want specific variables for ERB + vars = ErbBinding.new(template_options) + template_path=File.join(File.dirname(__FILE__),"templates","volume.xml.erb") + template=File.open(template_path).readlines.join + erb = ERB.new(template) + vars_binding = vars.send(:get_binding) + result=erb.result(vars_binding) + return result + end + + # Destroy a volume + def destroy + requires :raw + raw.delete + true + end + + # Wipes a volume , zeroes disk + def wipe + requires :raw + raw.wipe + true + end + + # Clones this volume to the name provided + def clone(name) + pool=@raw.pool + xml = REXML::Document.new(xml_desc) + xml.root.elements['/volume/name'].text=name + xml.root.elements['/volume/key'].text=name + xml.delete_element('/volume/target/path') + pool.create_volume_xml_from(xml.to_s,@raw) + return connection.volumes.all(:name => name) + end + + def key + requires :raw + raw.key + end + + def path + requires :raw + raw.path + end + + def name + requires :raw + raw.name + end + + def xml_desc + requires :raw + raw.xml_desc + end - # Destroy a volume - def destroy - requires :raw - raw.delete - true + def allocation + requires :raw + raw.info.allocation + end + + def capacity + requires :raw + @capacity=raw.info.capacity + end + + def type + requires :raw + @type=raw.info.type + end + + + private + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :key => new_raw.key, + :path => new_raw.path, + :name => new_raw.name, + :allocation => new_raw.info.allocation, + :capacity => new_raw.info.capacity, + :type => new_raw.info.type + } + + merge_attributes(raw_attributes) + end + end - # Wipes a volume , zeroes disk - def wipe - requires :raw - raw.wipe - true - end - - # Clones this volume to the name provided - def clone(name) - pool=@raw.pool - xml = REXML::Document.new(xml_desc) - xml.root.elements['/volume/name'].text=name - xml.root.elements['/volume/key'].text=name - xml.delete_element('/volume/target/path') - pool.create_volume_xml_from(xml.to_s,@raw) - return connection.volumes.get(:name => name) - end - - def key - requires :raw - raw.key - end - - def path - requires :raw - raw.path - end - - def name - requires :raw - raw.name - end - - def xml_desc - requires :raw - raw.xml_desc - end - - - def allocation - requires :raw - raw.info.allocation - end - - def capacity - requires :raw - raw.info.capacity - end - - def type - requires :raw - raw.info.type - end - - - private - def raw - @raw - end - - def raw=(new_raw) - @raw = new_raw - - raw_attributes = { - :id => new_raw.key, - } - - merge_attributes(raw_attributes) - end - end - end - end -end + end diff --git a/lib/fog/compute/models/libvirt/volumes.rb b/lib/fog/compute/models/libvirt/volumes.rb index c77850d50..15f288f43 100644 --- a/lib/fog/compute/models/libvirt/volumes.rb +++ b/lib/fog/compute/models/libvirt/volumes.rb @@ -9,36 +9,45 @@ module Fog model Fog::Compute::Libvirt::Volume - def all + def all(filter=nil) data=[] - connection.list_storage_pools.each do |poolname| - pool=connection.lookup_storage_pool_by_name(poolname) - pool.list_volumes.each do |volumename| - data << { :raw => pool.lookup_volume_by_name(volumename) } + if filter.nil? + connection.list_storage_pools.each do |poolname| + pool=connection.lookup_storage_pool_by_name(poolname) + pool.list_volumes.each do |volumename| + data << { :raw => pool.lookup_volume_by_name(volumename) } + end end - end + else + volume=nil + begin + volume=self.get_by_name(filter[:name]) if filter.has_key?(:name) + volume=self.get_by_key(filter[:key]) if filter.has_key?(:key) + volume=self.get_by_path(filter[:path]) if filter.has_key?(:path) + rescue ::Libvirt::RetrieveError + return nil + end + data << { :raw => volume} + end + load(data) end - # Retrieve the volume by type - def get(param) - volume=nil - volume=get_by_key(param[:key]) if param.has_key?(:key) - volume=get_by_path(param[:path]) if param.has_key?(:path) - volume=get_by_name(param[:name]) if param.has_key?(:name) - return volume + def get(key) + self.all(:key => key).first end + # Retrieve the volume by name def get_by_name(name) connection.list_storage_pools.each do |poolname| pool=connection.lookup_storage_pool_by_name(poolname) - volume=pool.lookup_volume_by_name(name) - unless volume.nil? - return new(:raw => volume) + volume=pool.lookup_volume_by_name(name) + unless volume.nil? + return volume end end - + return nil end @@ -46,12 +55,12 @@ module Fog def get_by_key(key) connection.list_storage_pools.each do |poolname| pool=connection.lookup_storage_pool_by_name(poolname) - volume=pool.lookup_volume_by_key(key) - unless volume.nil? - return new(:raw => volume) + volume=pool.lookup_volume_by_key(key) + unless volume.nil? + return volume end end - + return nil end @@ -59,15 +68,15 @@ module Fog def get_by_path(path) connection.list_storage_pools.each do |poolname| pool=connection.lookup_storage_pool_by_name(poolname) - volume=pool.lookup_volume_by_key(path) - unless volume.nil? - return new(:raw => volume) + volume=pool.lookup_volume_by_key(path) + unless volume.nil? + return volume end end - + return nil end - + end