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

First cut at libvirt integration. Lots of features missing, but it proves the point

This commit is contained in:
Patrick Debois 2011-06-28 15:47:16 +02:00
parent f3d92316b5
commit 1e31e1e41d
21 changed files with 914 additions and 1 deletions

View file

@ -61,6 +61,7 @@ require 'fog/bin/dnsmadeeasy'
require 'fog/bin/ecloud'
require 'fog/bin/go_grid'
require 'fog/bin/google'
require 'fog/bin/libvirt'
require 'fog/bin/linode'
require 'fog/bin/local'
require 'fog/bin/new_servers'

53
lib/fog/bin/libvirt.rb Normal file
View file

@ -0,0 +1,53 @@
module Libvirt # deviates from other bin stuff to accomodate gem
class << self
def class_for(key)
case key
when :compute
Fog::Compute::Libvirt
else
raise ArgumentError, "Unrecognized service: #{key}"
end
end
def [](service)
@@connections ||= Hash.new do |hash, key|
hash[key] = case key
when :compute
Formatador.display_line("[yellow][WARN] Libvirt[:compute] is deprecated, use Compute[:libvirt] instead[/]")
Fog::Compute.new(:provider => 'Libvirt')
else
raise ArgumentError, "Unrecognized service: #{key.inspect}"
end
end
@@connections[service]
end
def available?
availability = !Gem.source_index.find_name('ruby-libvirt').empty?
if availability
for service in services
for collection in self.class_for(service).collections
unless self.respond_to?(collection)
self.class_eval <<-EOS, __FILE__, __LINE__
def self.#{collection}
self[:#{service}].#{collection}
end
EOS
end
end
end
end
availability
end
def collections
services.map {|service| self[service].collections}.flatten.sort_by {|service| service.to_s}
end
def services
Fog::Libvirt.services
end
end
end

View file

@ -47,6 +47,9 @@ module Fog
when 'VirtualBox'
require 'fog/compute/virtual_box'
Fog::Compute::VirtualBox.new(attributes)
when :libvirt
require 'fog/compute/libvirt'
Fog::Compute::Libvirt.new(attributes)
when :voxel
require 'fog/compute/voxel'
Fog::Compute::Voxel.new(attributes)
@ -57,7 +60,7 @@ module Fog
def self.servers
servers = []
for provider in [:aws, :bluebox, :brightbox, :ecloud, :gogrid, :linode, :newservers, :ninefold, :rackspace, :slicehost, :stormondemand, :virtualbox, :voxel]
for provider in [:aws, :bluebox, :brightbox, :ecloud, :gogrid, :libvirt, :linode, :newservers, :ninefold, :rackspace, :slicehost, :stormondemand, :virtualbox, :voxel]
begin
servers.concat(self[provider].servers)
rescue # ignore any missing credentials/etc

View file

@ -0,0 +1,49 @@
module Fog
module Compute
class Libvirt < Fog::Service
requires :libvirt_uri
model_path 'fog/compute/models/libvirt'
model :server
collection :servers
model :network
collection :networks
model :interface
collection :interfaces
model :volume
collection :volumes
model :pool
collection :pools
class Mock
def initialize(options={})
Fog::Mock.not_implemented
end
end
class Real
def initialize(options={})
@uri = options[:libvirt_uri]
#libvirt is part of the gem => ruby-libvirt
require 'libvirt'
@connection = ::Libvirt::open(@uri)
end
# hack to provide 'requests'
def method_missing(method_sym, *arguments, &block)
if @connection.respond_to?(method_sym)
@connection.send(method_sym, *arguments)
else
super
end
end
end
end
end
end

View file

@ -0,0 +1,46 @@
require 'fog/core/model'
module Fog
module Compute
class Libvirt
class Interface < Fog::Model
identity :id
attribute :mac
attribute :name
attribute :xml_desc
def destroy
requires :raw
raw.delete
true
end
private
def raw
@raw
end
def raw=(new_raw)
@raw = new_raw
raw_attributes = {
:id => new_raw.name,
:name => new_raw.name,
:mac => new_raw.mac,
:xml_desc => new_raw.xml_desc,
}
merge_attributes(raw_attributes)
end
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'fog/core/collection'
require 'fog/compute/models/libvirt/interface'
module Fog
module Compute
class Libvirt
class Interfaces < Fog::Collection
model Fog::Compute::Libvirt::Interface
def all
data=[]
connection.list_interfaces.each do |ifname|
interface=connection.lookup_interface_by_name(ifname)
data << { :raw => interface }
end
load(data)
end
# Retrieve the interface by name
def get(name)
interface=connection.lookup_interface_by_name(name)
new(:raw => interface)
end
end #class
end #Class
end #module
end #module

View file

@ -0,0 +1,70 @@
require 'fog/core/model'
require 'fog/compute/models/libvirt/util'
module Fog
module Compute
class Libvirt
class Network < Fog::Model
include Fog::Compute::LibvirtUtil
identity :id
attribute :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
# https://www.redhat.com/archives/libvirt-users/2011-April/msg00006.html
# Routed network without IP
# 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
end
def destroy()
requires :raw
raw.destroy
# raw.undefine
true
end
private
def raw
@raw
end
def raw=(new_raw)
@raw = new_raw
raw_attributes = {
:id => new_raw.uuid,
:uuid => new_raw.uuid,
:name => new_raw.name,
:bridge_name => new_raw.bridge_name,
:xml_desc => new_raw.xml_desc,
}
merge_attributes(raw_attributes)
end
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'fog/core/collection'
require 'fog/compute/models/libvirt/network'
module Fog
module Compute
class Libvirt
class Networks < Fog::Collection
model Fog::Compute::Libvirt::Network
def all
data=[]
connection.list_networks.each do |networkname|
network=connection.lookup_network_by_name(networkname)
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)
end
end #class
end #Class
end #module
end #module

View file

@ -0,0 +1,96 @@
require 'fog/core/model'
module Fog
module Compute
class Libvirt
class Pool < Fog::Model
identity :id
attribute :name
attribute :uuid
attribute :xml
attr_reader :allocation
attr_reader :available
attr_reader :xml_desc
attr_reader :num_of_volumes
attr_reader :state
# There are two options to initialize a new Pool
# 1. provide :xml as an argument
# 2. provide :name, :path
def initialize(attributes={})
# unless attributes.has_key?(:xml)
# name = attributes[:name] || raise("Must provide a pool name")
# path = attributes[:path] || "/var/lib/libvirt/images"
# else
# @xml = attributes[:xml]
# end
super
end
def save
unless @xml.nil?
#connection.
end
end
def destroy
requires :raw
# Shutdown pool if active
if raw.active?
raw.destroy
end
# Delete corresponding data in this pool
raw.delete
true
end
def shutdown
requires :raw
raw.destroy
true
end
private
def raw
@raw
end
def raw=(new_raw)
@raw = new_raw
raw_attributes = {
:id => new_raw.uuid,
:uuid => new_raw.uuid,
:name => new_raw.name,
:xml_desc => new_raw.xml_desc,
:num_of_volumes => new_raw.num_of_volumes,
:allocation => new_raw.info.allocation,
:available => new_raw.info.available,
:capacity => new_raw.info.capacity,
:state => new_raw.info.state,
}
# State
#INACTIVE = INT2NUM(VIR_STORAGE_POOL_INACTIVE) virStoragePoolState
#BUILDING = INT2NUM(VIR_STORAGE_POOL_BUILDING)
#RUNNING = INT2NUM(VIR_STORAGE_POOL_RUNNING)
#DEGRADED = INT2NUM(VIR_STORAGE_POOL_DEGRADED)
#INACCESSIBLE = INT2NUM(VIR_STORAGE_POOL_INACCESSIBLE)
merge_attributes(raw_attributes)
end
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'fog/core/collection'
require 'fog/compute/models/libvirt/pool'
module Fog
module Compute
class Libvirt
class Pools < Fog::Collection
model Fog::Compute::Libvirt::Pool
def all
data=[]
connection.list_storage_pools.each do |poolname|
pool=connection.lookup_storage_pool_by_name(poolname)
data << { :raw => pool }
end
load(data)
end
# Retrieve the pool by uuid
def get(uuid)
pool=connection.lookup_storage_pool_by_uuid(uuid)
new(:raw => pool)
end
end #class
end #Class
end #module
end #module

View file

@ -0,0 +1,237 @@
require 'fog/core/model'
require 'fog/compute/models/libvirt/util'
module Fog
module Compute
class Libvirt
class Server < Fog::Model
include Fog::Compute::LibvirtUtil
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
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)
end
def start
requires :raw
unless @raw.active?
begin
@raw.create
rescue
print "An error occured :",$!,"\n"
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 ready?
status == :running
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 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
end
return state
end
def save()
raise Fog::Errors::Error.new('Updating an existing server is not yet implemented. Contributions welcome!')
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
end
return { 'public' => [ipaddress], 'private' => [ipaddress]}
end
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
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

View file

@ -0,0 +1,52 @@
require 'fog/core/collection'
require 'fog/compute/models/libvirt/server'
module Fog
module Compute
class Libvirt
class Servers < Fog::Collection
model Fog::Compute::Libvirt::Server
def all
data = connection.list_defined_domains.map do |machine|
{
:raw => connection.lookup_domain_by_name(machine)
}
end
connection.list_domains.each do |machine|
data << {
:raw => connection.lookup_domain_by_id(machine)
}
end
load(data)
end
def bootstrap(new_attributes = {})
raise 'Not Implemented'
# server = create(new_attributes)
# server.start
# server.wait_for { ready? }
# server.setup(:password => server.password)
# server
end
# Retrieve the server by uuid
def get(server_id)
machine=connection.lookup_domain_by_uuid(server_id)
new(:raw => machine)
end
# Retrieve the server by name
def get_by_name(name)
machine=connection.lookup_domain_by_name(name)
new(:raw => machine)
end
end #class
end #Class
end #module
end #Module

View file

@ -0,0 +1,37 @@
<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,6 @@
<network>
<name><%= name %></name>
<forward mode='<%= network_mode %>'/>
<bridge name='<%= bridge_name %>' stp='on' delay='0' />
</ip>
</network>

View file

@ -0,0 +1,6 @@
<pool type="dir">
<name><%= name %></name>
<target>
<path><%= path %></path>
</target>
</pool>

View file

@ -0,0 +1,14 @@
<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,28 @@
require "rexml/document"
require 'erb'
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
return nil if new?
xml = REXML::Document.new(@xml_desc)
attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute]
end
end
end
end

View file

@ -0,0 +1,70 @@
require 'fog/core/model'
module Fog
module Compute
class Libvirt
class Volume < Fog::Model
identity :id
attribute :key
attribute :path
attribute :name
attribute :type
attribute :allocation
attribute :capacity
attribute :xml_desc
def destroy
requires :raw
raw.delete
true
end
def wipe
requires :raw
raw.wipe
true
end
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')
puts "cloning this might take a while"
pool.create_volume_xml_from(xml.to_s,@raw)
return connection.volumes.get(name)
end
private
def raw
@raw
end
def raw=(new_raw)
@raw = new_raw
raw_attributes = {
:id => new_raw.key,
:key => new_raw.key,
:path => new_raw.path ,
:name => new_raw.name,
:xml_desc => new_raw.xml_desc,
:allocation => new_raw.info.allocation,
:capacity => new_raw.info.capacity,
:type => new_raw.info.type
}
merge_attributes(raw_attributes)
end
end
end
end
end

View file

@ -0,0 +1,40 @@
require 'fog/core/collection'
require 'fog/compute/models/libvirt/volume'
module Fog
module Compute
class Libvirt
class Volumes < Fog::Collection
model Fog::Compute::Libvirt::Volume
def all
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) }
end
end
load(data)
end
# Retrieve the volume by uuid
def get(key)
connection.list_storage_pools.each do |poolname|
pool=connection.lookup_storage_pool_by_name(poolname)
volume=pool.lookup_volume_by_name(key)
unless volume.nil?
return new(:raw => volume)
end
end
return nil
end
end
end
end
end

View file

@ -14,6 +14,7 @@ require 'fog/providers/dnsmadeeasy'
require 'fog/providers/ecloud'
require 'fog/providers/go_grid'
require 'fog/providers/google'
require 'fog/providers/libvirt'
require 'fog/providers/linode'
require 'fog/providers/local'
require 'fog/providers/new_servers'

View file

@ -0,0 +1,11 @@
require 'fog/core'
module Fog
module Libvirt
extend Fog::Provider
service(:compute, 'compute/libvirt')
end
end