1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Again major breakthrough

- restructured all command to allow filters
- get only uses the identity to get it
- ssh/scp proxied now work correctly on server
- extended networks and interfaces
This commit is contained in:
Patrick Debois 2011-08-03 12:48:44 +02:00
parent b2d5207fba
commit 1ed3ee3379
16 changed files with 883 additions and 463 deletions

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 => "<xml to create domain/server>"
# 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

View file

@ -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

View file

@ -1,37 +0,0 @@
<domain type='kvm'>
<name><%= name %></name>
<memory><%= memory_size %></memory>
<vcpu><%= cpus %></vcpu>
<os>
<type arch='<%= arch %>'><%= os %></type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='<%= volume_path %>'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='bridge'>
<source bridge='<%= @bridge_name %>'/>
<model type='virtio'/>
</interface>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target port='0'/>
</console>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' keymap='en-us'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
</devices>
</domain>

View file

@ -0,0 +1,57 @@
<domain type='<%= domain_type %>'>
<name><%= name %></name>
<memory><%= memory_size %></memory>
<vcpu><%= cpus %></vcpu>
<os>
<type arch='<%= arch %>'><%= os %></type>
<boot dev='hd'/>
<% if !iso_file.nil? %>
<boot dev='cdrom'/>
<% end %>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='<%= disk_type %>'/>
<source file='<%= "#{disk_path}" %>'/>
<target dev='vda' bus='virtio'/>
</disk>
<% if !iso_file.nil? %>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='<%= "#{iso_dir}/#{iso_file}" %>'/>
<target dev='hdc' bus='ide'/>
<readonly/>
<address type='drive' controller='0' bus='1' unit='0'/>
</disk>
<% end %>
<% if interface_type=="bridge" %>
<interface type="bridge">
<source bridge='<%= bridge_name %>'/>
<model type='virtio'/>
<% end %>
<% if interface_type=="nat" %>
<interface type='network'>
<source network='<%= nat_network_name %>'/>
<model type='virtio'/>
<% end %>
</interface>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target port='0'/>
</console>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' keymap='en-us'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
</devices>
</domain>

View file

@ -1,14 +0,0 @@
<volume>
<name>ubuntu.qcow2</name>
<allocation unit="G">10</allocation>
<capacity unit="G">10</capacity>
<target>
<format type="qcow2"/>
<permissions>
<owner>0</owner>
<group>0</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>

View file

@ -0,0 +1,14 @@
<volume>
<name><%= "#{name}.#{extension}" %></name>
<allocation unit="<%= allocate_unit %>"><%= allocate %></allocation>
<capacity unit="<%= size_unit %>"><%= size %></capacity>
<target>
<format type="<%= type %>"/>
<permissions>
<owner>0</owner>
<group>0</group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>

View file

@ -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

View file

@ -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 => "<xml to create volume>"
# A volume always belongs to a pool, :pool => "<name of pool>"
# A volume always belongs to a pool, :poolname => "<name of pool>"
#
# @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

View file

@ -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