virt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,82 @@
1
+ = virt
2
+
3
+ Simple to use ruby interface to libvirt.
4
+
5
+ Libvirt is located at http://libvirt.org/ruby
6
+
7
+ == Contributing
8
+
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == RDOC
16
+
17
+ for full API please visit http://rdoc.info/github/ohadlevy/virt
18
+
19
+ == Examples
20
+
21
+ === Connectivity Options
22
+ uri = "qemu:///system" # libvirt running locally
23
+ uri = "qemu:/fqdn//system" # used for QEMU+TLS connection
24
+ uri = "qemu+ssh:/user@fqdn//system" # used for SSH connection
25
+ uri = "qemu+tcp:/fqdn//system" # used for non secured tcp connection
26
+
27
+ ==== Establish the connection
28
+ conn = Virt.connect(uri)
29
+
30
+ ==== get the hypervisor
31
+ host = conn.host
32
+
33
+ ==== get the list of running guests
34
+ host.running_guests
35
+
36
+ ==== get the list of stopped guests
37
+ host.defined_guests
38
+
39
+ ====or a simple hash of both
40
+ host.guests
41
+
42
+ ==== create a new guest
43
+
44
+ guest=Virt::Guest.new({:name => "test123"})
45
+ guest.vcpu = 2
46
+ guest.volume.size = 10 # in GB
47
+
48
+ guest.save
49
+ guest.start
50
+
51
+ puts guest.mac
52
+
53
+ guest.stop
54
+ Many more options exists, make sure checkout the api/tests
55
+
56
+ ==== list of host bridges
57
+ host.interfaces # physical interfaces
58
+ host.networks # virtual / NAT interfaces
59
+
60
+ ==== Storage
61
+
62
+ host.storage_pools
63
+
64
+ host.volumes
65
+
66
+
67
+
68
+ == Copyright
69
+ Copyright (c) 2011 Ohad Levy
70
+
71
+ This program and entire repository is free software: you can redistribute it and/or modify
72
+ it under the terms of the GNU General Public License as published by
73
+ the Free Software Foundation, either version 3 of the License, or
74
+ any later version.
75
+
76
+ This program is distributed in the hope that it will be useful,
77
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
78
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
79
+ GNU General Public License for more details.
80
+
81
+ You should have received a copy of the GNU General Public License
82
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gem|
13
+ # Gem::Specification... see http://docs.rubygems.org/read/chapter/20
14
+ gem.name = "virt"
15
+ gem.homepage = "https://github.com/ohadlevy/virt"
16
+ gem.license = "GPLv3"
17
+ gem.summary = %Q{Simple to use ruby interface to libvirt}
18
+ gem.description = %Q{Simplied interface to use ruby the libvirt ruby library}
19
+ gem.email = "ohadlevy@gmail.com"
20
+ gem.authors = ["Ohad Levy"]
21
+
22
+ #gem.add_runtime_dependency 'libvirt', '> 0.2.0'
23
+ #gem.add_development_dependency 'rspec', '> 1.2.3'
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/*test*.rb'
31
+ test.verbose = true
32
+ test.warning = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "hello-gem #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
53
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,32 @@
1
+ require 'libvirt'
2
+ module Virt
3
+ class Connection
4
+ attr_reader :connection
5
+
6
+ def initialize uri
7
+ raise("Must provide a guest to connect to") unless uri
8
+ @connection = Libvirt::open uri
9
+ end
10
+
11
+ def closed?
12
+ connection.closed?
13
+ end
14
+
15
+ def secure?
16
+ connection.encrypted?
17
+ end
18
+
19
+ def version
20
+ connection.libversion
21
+ end
22
+
23
+ def disconnect
24
+ connection.close
25
+ end
26
+
27
+ def host
28
+ Host.new
29
+ end
30
+
31
+ end
32
+ end
data/lib/virt/guest.rb ADDED
@@ -0,0 +1,102 @@
1
+ module Virt
2
+ class Guest
3
+ include Virt::Util
4
+ attr_reader :name, :xml_desc
5
+ attr_accessor :memory, :vcpu, :arch, :volume, :interface, :template_path
6
+
7
+ def initialize options = {}
8
+ @connection = Virt.connection
9
+ @name = options[:name] || raise("Must provide a name")
10
+ # If our domain exists, we ignore the provided options and defaults
11
+ fetch_guest
12
+ @memory ||= options[:memory] || default_memory_size
13
+ @vcpu ||= options[:vcpu] || default_vcpu_count
14
+ @arch ||= options[:arch] || default_arch
15
+
16
+ @template_path = options[:template_path] || default_template_path
17
+ @volume = Volume.new options
18
+ @interface = Interface.new options.merge({:mac => @mac})
19
+ end
20
+
21
+ def new?
22
+ @domain.nil?
23
+ end
24
+
25
+ def save
26
+ @domain = @connection.connection.define_domain_xml(xml)
27
+ fetch_info
28
+ !new?
29
+ end
30
+
31
+ def start
32
+ raise "Guest not created, can't start" if new?
33
+ @domain.create unless running?
34
+ running?
35
+ end
36
+
37
+ def running?
38
+ return false if new?
39
+ @domain.active?
40
+ rescue
41
+ # some versions of libvirt do not support checking for active state
42
+ @connection.connection.list_domains.each do |did|
43
+ return true if @connection.connection.lookup_domain_by_id(did).name == name
44
+ end
45
+ false
46
+ end
47
+
48
+ def stop
49
+ raise "Guest not created, can't stop" if new?
50
+ @domain.destroy
51
+ !running?
52
+ rescue Libvirt::Error
53
+ # domain is not running
54
+ true
55
+ end
56
+
57
+ def destroy
58
+ return true if new?
59
+ stop if running?
60
+ @domain = @domain.undefine
61
+ new?
62
+ end
63
+
64
+ def uuid
65
+ @domain.uuid unless new?
66
+ end
67
+
68
+ private
69
+
70
+ def fetch_guest
71
+ @domain = @connection.connection.lookup_domain_by_name(name)
72
+ fetch_info
73
+ rescue Libvirt::RetrieveError
74
+ end
75
+
76
+ def fetch_info
77
+ return if @domain.nil?
78
+ @xml_desc = @domain.xml_desc
79
+ @memory = @domain.max_memory
80
+ @vcpu = document("domain/vcpu")
81
+ @arch = document("domain/os/type", "arch")
82
+ @mac = document("domain/devices/interface/mac", "address")
83
+ @interface.mac = @mac if @interface
84
+ end
85
+
86
+ def default_memory_size
87
+ 1048576
88
+ end
89
+
90
+ def default_vcpu_count
91
+ 1
92
+ end
93
+
94
+ def default_arch
95
+ "x86_64"
96
+ end
97
+
98
+ def default_template_path
99
+ "guest.xml.erb"
100
+ end
101
+ end
102
+ end
data/lib/virt/host.rb ADDED
@@ -0,0 +1,80 @@
1
+ module Virt
2
+ class Host < Virt::Connection
3
+ # Represents a Physical host which runs the libvirt daemon
4
+
5
+ attr_reader :connection
6
+ def initialize
7
+ @connection = Virt.connection.connection
8
+ end
9
+
10
+ def name
11
+ connection.hostname
12
+ end
13
+
14
+ def guests
15
+ {:running => running_guests, :defined => defined_guests}
16
+ end
17
+
18
+ def running_guests
19
+ connection.list_domains.map do |domain|
20
+ find_guest_by_id(domain)
21
+ end
22
+ end
23
+
24
+ def defined_guests
25
+ connection.list_defined_domains.map do |domain|
26
+ find_guest_by_name domain
27
+ end
28
+ end
29
+
30
+ # Available libvirt interfaces, excluding lo
31
+ def interfaces
32
+ connection.list_interfaces.delete_if{|i| i == "lo"}.sort
33
+ rescue => e
34
+ raise "This function is not supported by the hypervisor: #{e}"
35
+ end
36
+
37
+ def interface iface
38
+ connection.lookup_interface_by_name(iface)
39
+ end
40
+
41
+ # libvirt internal networks
42
+ def networks
43
+ connection.list_networks.map do |network|
44
+ connection.lookup_network_by_name(network).bridge_name
45
+ end
46
+ end
47
+
48
+ def storage_pools
49
+ connection.list_storage_pools.map {|p| Pool.new({:name => p})}
50
+ end
51
+
52
+ # Returns a Virt::Pool object based on the pool name
53
+ def storage_pool pool
54
+ Pool.new({:name => pool.is_a?(Libvirt::StoragePool) ? pool.name : pool })
55
+ rescue Libvirt::RetrieveError
56
+ end
57
+
58
+ # Returns a hash of pools and their volumes objects.
59
+ def volumes
60
+ pools = {}
61
+ storage_pools.each do |storage|
62
+ pools[storage.name] = storage.volumes
63
+ end
64
+ pools
65
+ end
66
+
67
+ def find_guest_by_name name
68
+ if connection.lookup_domain_by_name name
69
+ return Guest.new({:name => name})
70
+ end
71
+ end
72
+
73
+ def find_guest_by_id id
74
+ id.to_a.map do |did|
75
+ return Guest.new({:name => connection.lookup_domain_by_id(did).name})
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ module Virt
2
+ class Interface
3
+ attr_accessor :mac, :model, :type, :device
4
+
5
+ def initialize options = {}
6
+ @connection = Virt.connection
7
+ @device = options[:device] || default_device
8
+ @type = options[:type] || default_type
9
+ @model = options[:model] || default_model
10
+ @mac = options[:mac]
11
+ end
12
+
13
+ def new?
14
+ mac.nil?
15
+ end
16
+
17
+ private
18
+
19
+ def default_device
20
+ @connection.host.interfaces.first
21
+ rescue
22
+ "br0"
23
+ end
24
+
25
+ def default_type
26
+ "bridge"
27
+ end
28
+
29
+ def default_model
30
+ "virtio"
31
+ end
32
+
33
+ end
34
+ end
data/lib/virt/pool.rb ADDED
@@ -0,0 +1,53 @@
1
+ module Virt
2
+ # implements Libvirt pool, at the moment it is assumed to be a file based pool.
3
+ class Pool
4
+ include Virt::Util
5
+ attr_reader :name
6
+ def initialize options = {}
7
+ @name = options[:name] || raise("Must Provide a pool name")
8
+ @connection = Virt.connection
9
+ fetch_pool
10
+ end
11
+
12
+ alias :to_s :name
13
+
14
+ def save
15
+ raise "not implemented"
16
+ end
17
+
18
+ def new?
19
+ @pool.nil?
20
+ end
21
+
22
+ def volumes
23
+ @pool.list_volumes
24
+ end
25
+
26
+ %w{name key path}.each do |method|
27
+ define_method "find_volume_by_#{method}" do |value|
28
+ begin
29
+ @pool.send("lookup_volume_by_#{method}", value)
30
+ rescue Libvirt::RetrieveError
31
+ end
32
+ end
33
+ end
34
+
35
+ def path
36
+ document "pool/target/path"
37
+ end
38
+
39
+ def create_vol vol
40
+ raise "Must provide a Virt::Volume object" unless vol.is_a?(Virt::Volume)
41
+ raise "Pool not saved, cant create volume" if new?
42
+ @pool.create_vol_xml vol.xml
43
+ end
44
+
45
+ private
46
+
47
+ def fetch_pool
48
+ if @pool = @connection.connection.lookup_storage_pool_by_name(name)
49
+ @xml_desc = @pool.xml_desc
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/virt/util.rb ADDED
@@ -0,0 +1,26 @@
1
+ require "rexml/document"
2
+ require 'erb'
3
+
4
+ module Virt
5
+ module Util
6
+ # return templated xml to be used by libvirt
7
+ def xml
8
+ ERB.new(template, nil, '-').result(binding)
9
+ end
10
+
11
+ private
12
+ # template file that contain our xml template
13
+ def template
14
+ File.read("#{File.dirname(__FILE__)}/../../templates/#{template_path}")
15
+ rescue => e
16
+ warn "failed to read template #{template_path}: #{e}"
17
+ end
18
+
19
+ # finds a value from xml
20
+ def document path, attribute=nil
21
+ return nil if new?
22
+ xml = REXML::Document.new(@xml_desc)
23
+ attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,69 @@
1
+ module Virt
2
+ class Volume
3
+ include Virt::Util
4
+ attr_reader :name, :pool, :path, :type, :allocated_size, :size, :template_path, :key
5
+
6
+ def initialize options = {}
7
+ @connection = Virt.connection
8
+ self.name = options[:name] || raise("Volume requires a name")
9
+ @type = options[:type] || default_type
10
+ @allocated_size = options[:allocated_size] || default_allocated_size
11
+ @template_path = options[:template_path] || default_template_path
12
+ @size = options[:size] || default_size
13
+ @pool = @connection.host.storage_pool(options[:pool] || "default")
14
+ fetch_volume
15
+ end
16
+
17
+ def new?
18
+ @vol.nil?
19
+ end
20
+
21
+ def save
22
+ raise "volume already exists, can't save" unless new?
23
+ #validations
24
+ #update?
25
+ @vol=pool.create_vol(self)
26
+ !new?
27
+ end
28
+
29
+ def destroy
30
+ return true if new?
31
+ @vol.delete
32
+ fetch_volume
33
+ new?
34
+ end
35
+
36
+ def path
37
+ "#{pool.path}/#{name}"
38
+ end
39
+
40
+ private
41
+
42
+ def name= name
43
+ raise "invalid name" if name.nil?
44
+ @name = name
45
+ @name += ".img" unless name.match(/.*\.img$/)
46
+ end
47
+
48
+ def default_type
49
+ "raw"
50
+ end
51
+
52
+ def default_allocated_size
53
+ 0
54
+ end
55
+
56
+ # default image size in GB
57
+ def default_size
58
+ 8
59
+ end
60
+
61
+ def default_template_path
62
+ "volume.xml.erb"
63
+ end
64
+
65
+ def fetch_volume
66
+ @vol = pool.find_volume_by_name(name)
67
+ end
68
+ end
69
+ end
data/lib/virt.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'virt/util'
2
+ require 'virt/connection'
3
+ require 'virt/host'
4
+ require 'virt/guest'
5
+ require 'virt/pool'
6
+ require 'virt/volume'
7
+ require 'virt/interface'
8
+ module Virt
9
+
10
+ class << self
11
+
12
+ def connect uri
13
+ @connection = Virt::Connection.new uri
14
+ end
15
+
16
+ def connection
17
+ return @connection if @connection and !@connection.closed?
18
+ raise "No Connection or connection has been closed"
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,54 @@
1
+ <domain type='kvm'>
2
+ <name><%= name %></name>
3
+ <%- if uuid -%>
4
+ <uuid><%= uuid %></uuid>
5
+ <%- end -%>
6
+ <memory><%= memory %></memory>
7
+ <currentMemory><%= memory %></currentMemory>
8
+ <vcpu><%= vcpu %></vcpu>
9
+ <os>
10
+ <type arch='<%= arch %>'>hvm</type>
11
+ <boot dev='network'/>
12
+ <%- if volume -%>
13
+ <boot dev='hd'/>
14
+ <%- end -%>
15
+ </os>
16
+ <features>
17
+ <acpi/>
18
+ <apic/>
19
+ <pae/>
20
+ </features>
21
+ <clock offset='utc'/>
22
+ <on_poweroff>destroy</on_poweroff>
23
+ <on_reboot>restart</on_reboot>
24
+ <on_crash>restart</on_crash>
25
+ <devices>
26
+ <%- if volume -%>
27
+ <disk type='file' device='disk'>
28
+ <driver name='qemu' type='raw'/>
29
+ <source file='<%= volume.path %>'/>
30
+ <target dev='vda' bus='virtio'/>
31
+ </disk>
32
+ <%- end -%>
33
+ <%- if interface -%>
34
+ <interface type='<%= interface.type %>'>
35
+ <%- if interface.mac -%>
36
+ <mac address='<%= interface.mac %>'/>
37
+ <%- end -%>
38
+ <source <%= interface.type %>='<%= interface.device %>'/>
39
+ <model type='<%= interface.model %>'/>
40
+ </interface>
41
+ <%- end -%>
42
+ <serial type='pty'>
43
+ <target port='0'/>
44
+ </serial>
45
+ <console type='pty'>
46
+ <target port='0'/>
47
+ </console>
48
+ <input type='mouse' bus='ps2'/>
49
+ <graphics type='vnc' port='-1' autoport='yes' keymap='en-us'/>
50
+ <video>
51
+ <model type='cirrus' vram='9216' heads='1'/>
52
+ </video>
53
+ </devices>
54
+ </domain>
@@ -0,0 +1,8 @@
1
+ <volume>
2
+ <name><%= name %></name>
3
+ <allocation><%= allocated_size %></allocation>
4
+ <capacity unit="G"><%= size %></capacity>
5
+ <target>
6
+ <path><%= path %></path>
7
+ </target>
8
+ </volume>
@@ -0,0 +1,24 @@
1
+ require 'test/test_helper'
2
+
3
+ class Virt::ConnectionTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ hostname = "h01.sat.lab"
7
+ uri = "qemu+ssh://root@#{hostname}/system"
8
+ @conn = Virt.connect uri
9
+ end
10
+
11
+ def test_should_be_able_to_connect
12
+ assert !@conn.closed?
13
+ end
14
+
15
+ def test_should_be_able_to_disconnect
16
+ @conn.disconnect
17
+ assert @conn.closed?
18
+ end
19
+
20
+ def test_should_return_a_version
21
+ assert @conn.version
22
+ end
23
+
24
+ end
@@ -0,0 +1,40 @@
1
+ require 'test/test_helper'
2
+
3
+ class Virt::GuestTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ hostname = "h01.sat.lab"
7
+ uri = "qemu+ssh://root@#{hostname}/system"
8
+ Virt.connect(uri)
9
+ @guest = Virt::Guest.new({:name => "test-host-#{Time.now.to_i}"})
10
+ end
11
+
12
+ def test_should_be_new
13
+ assert @guest.new?
14
+ assert @guest.interface.new?
15
+ assert_nil @guest.interface.mac
16
+ end
17
+
18
+ def test_should_be_able_to_save
19
+ assert @guest.save
20
+ assert !@guest.interface.new?
21
+ assert_not_nil @guest.interface.mac
22
+ @guest.destroy
23
+ end
24
+
25
+ def test_should_be_able_to_destroy
26
+ assert @guest.save
27
+ assert @guest.destroy
28
+ end
29
+
30
+ def test_should_be_able_to_save_and_start
31
+ @guest = Virt::Guest.new({:name => "test-host-#{Time.now.to_i}", :device => "br180"})
32
+ assert @guest.volume.save
33
+ assert @guest.save
34
+ assert @guest.start
35
+ assert @guest.stop
36
+ assert @guest.volume.destroy
37
+ assert @guest.destroy
38
+ end
39
+
40
+ end