ruby-xen 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +10 -0
- data/Manifest.txt +9 -1
- data/README.rdoc +16 -4
- data/Rakefile +8 -8
- data/lib/ruby-xen.rb +79 -3
- data/lib/templates/domu.cfg.erb +40 -0
- data/lib/xen/backup.rb +42 -0
- data/lib/xen/command.rb +73 -0
- data/lib/xen/config.rb +146 -0
- data/lib/xen/host.rb +10 -0
- data/lib/xen/image.rb +12 -0
- data/lib/xen/instance.rb +66 -0
- data/lib/xen/slice.rb +80 -0
- data/test/test_ruby-xen.rb +3 -0
- metadata +12 -5
- data/lib/ruby-xen/domain.rb +0 -229
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
=== 0.0.3 / 2008-09-21
|
2
|
+
|
3
|
+
* fleshed out :backup class
|
4
|
+
* Xen::Commands are now easily called by backgroundjob (bj)
|
5
|
+
|
6
|
+
=== 0.0.2 / 2008-09-11
|
7
|
+
|
8
|
+
* Broke out classes into separate files
|
9
|
+
* Added rspec outline
|
10
|
+
|
1
11
|
=== 0.0.1 / 2008-09-08
|
2
12
|
|
3
13
|
* Initial import
|
data/Manifest.txt
CHANGED
@@ -4,5 +4,13 @@ README.rdoc
|
|
4
4
|
Rakefile
|
5
5
|
bin/ruby-xen
|
6
6
|
lib/ruby-xen.rb
|
7
|
-
test/
|
7
|
+
test/test_ruby-xen.rb
|
8
8
|
lib/ruby-xen/domain.rb
|
9
|
+
lib/xen/backup.rb
|
10
|
+
lib/xen/command.rb
|
11
|
+
lib/xen/config.rb
|
12
|
+
lib/xen/slice.rb
|
13
|
+
lib/xen/host.rb
|
14
|
+
lib/xen/image.rb
|
15
|
+
lib/xen/instance.rb
|
16
|
+
lib/templates/domu.cfg.erb
|
data/README.rdoc
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
= ruby-xen
|
2
2
|
|
3
|
-
|
3
|
+
# Warning! Not ready yet - project started Sep 2008
|
4
|
+
|
5
|
+
http://github.com/mbailey/ruby-xen
|
6
|
+
|
4
7
|
|
5
8
|
== DESCRIPTION:
|
6
9
|
|
@@ -19,11 +22,20 @@ ruby-xen can also be used by ruby code or from irb.
|
|
19
22
|
|
20
23
|
== SYNOPSIS:
|
21
24
|
|
22
|
-
|
25
|
+
require 'rubygems'
|
26
|
+
require 'ruby-xen'
|
27
|
+
|
28
|
+
slice = Xen::Slice.find(:example)
|
29
|
+
slice.running? # true
|
30
|
+
slice.stop
|
31
|
+
slice.running? # false
|
32
|
+
slice.start
|
33
|
+
slice.running? # true
|
23
34
|
|
24
35
|
== REQUIREMENTS:
|
25
36
|
|
26
|
-
xen
|
37
|
+
ruby-xen must be run as root as it uses Xen's 'xm' command.
|
38
|
+
xen-tools must be installed (http://www.xen-tools.org/software/xen-tools/)
|
27
39
|
|
28
40
|
== INSTALL:
|
29
41
|
|
@@ -36,7 +48,7 @@ or open source applications. More details found here:
|
|
36
48
|
http://www.gnu.org/licenses/gpl.html
|
37
49
|
|
38
50
|
ruby-xen
|
39
|
-
Copyright (C) 2008 Mike Bailey and Nick
|
51
|
+
Copyright (C) 2008 Mike Bailey and Nick Marfleet
|
40
52
|
|
41
53
|
This program is free software; you can redistribute it and/or
|
42
54
|
modify it under the terms of the GNU General Public License
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
|
-
require 'hoe'
|
5
|
-
require './lib/
|
6
|
-
|
7
|
-
Hoe.new('ruby-xen',
|
8
|
-
|
9
|
-
|
10
|
-
end
|
3
|
+
# require 'rubygems'
|
4
|
+
# require 'hoe'
|
5
|
+
# require './lib/ruby-xen.rb'
|
6
|
+
#
|
7
|
+
# Hoe.new('ruby-xen', Xen::VERSION) do |p|
|
8
|
+
# # p.rubyforge_name = 'ruby-xenx' # if different than lowercase project name
|
9
|
+
# p.developer('Mike Bailey', 'mike@bailey.net.au')
|
10
|
+
# end
|
11
11
|
|
12
12
|
# vim: syntax=Ruby
|
data/lib/ruby-xen.rb
CHANGED
@@ -1,4 +1,80 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Xen
|
2
|
+
# General configuration for ruby-xen
|
3
|
+
|
4
|
+
# Location of Xen config files
|
5
|
+
XEN_DOMU_CONFIG_DIR = '/etc/xen'
|
6
|
+
# XEN_DOMU_CONFIG_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '/spec/fixtures/xen_domu_configs'))
|
7
|
+
|
8
|
+
# We don't want out library to hit Xen too often (premature optimization perhaps?)
|
9
|
+
# so we keep information about Xen instances in an object. Specify how long before
|
10
|
+
# the object expires.
|
11
|
+
INSTANCE_OBJECT_LIFETIME = 5 # seconds
|
12
|
+
|
13
|
+
# General location for config file templates
|
14
|
+
TEMPLATE_DIR = File.expand_path(File.dirname(__FILE__) + '/../lib/templates')
|
15
|
+
|
16
|
+
# Extension for Xen domU config files
|
17
|
+
CONFIG_FILE_EXTENSION = '.cfg'
|
18
|
+
|
19
|
+
# Directory for backups of system images
|
20
|
+
BACKUP_DIR='/var/xen_images'
|
21
|
+
|
22
|
+
# FIle extension for backups
|
23
|
+
BACKUP_FILE_EXT = '.tar'
|
3
24
|
end
|
4
|
-
|
25
|
+
|
26
|
+
class Array #:nodoc:
|
27
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
28
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
29
|
+
#
|
30
|
+
# def options(*args)
|
31
|
+
# args.extract_options!
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# options(1, 2) # => {}
|
35
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
36
|
+
def extract_options!
|
37
|
+
last.is_a?(::Hash) ? pop : {}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Hash #:nodoc:
|
42
|
+
# Converts a Hash into an array of key=val formatted strings
|
43
|
+
#
|
44
|
+
# puts { :nics => 2, :vcpus => 1, :memory => 64 }.to_args
|
45
|
+
#
|
46
|
+
# produces:
|
47
|
+
#
|
48
|
+
# ["memory=64", "nics=2", "vcpus=1"]
|
49
|
+
def to_args
|
50
|
+
collect{|k,v| "#{k}=#{v}"}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Xen
|
55
|
+
# DRY up some classes (children of Slice) with some module funkiness.
|
56
|
+
module Parentable
|
57
|
+
# Returns the parent Slice object (d) for a sub-object.
|
58
|
+
# We ensure d.instance.object_id == self.object_id
|
59
|
+
#
|
60
|
+
# ==== Example
|
61
|
+
# i = Xen::Instance.all[2]
|
62
|
+
# s = i.slice
|
63
|
+
# i.object_id == s.instance.object_id # true
|
64
|
+
#
|
65
|
+
def slice
|
66
|
+
d = Xen::Slice.new(name)
|
67
|
+
# Insert the current object into the newly created Slice's attributes
|
68
|
+
d.instance_variable_set("@#{self.class.to_s.sub('Xen::','').downcase}", self)
|
69
|
+
d
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
require "#{File.dirname(__FILE__)}/xen/backup"
|
75
|
+
require "#{File.dirname(__FILE__)}/xen/command"
|
76
|
+
require "#{File.dirname(__FILE__)}/xen/config"
|
77
|
+
require "#{File.dirname(__FILE__)}/xen/slice"
|
78
|
+
require "#{File.dirname(__FILE__)}/xen/host"
|
79
|
+
require "#{File.dirname(__FILE__)}/xen/image"
|
80
|
+
require "#{File.dirname(__FILE__)}/xen/instance"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# Configuration file for the Xen instance <%= name %>, created
|
3
|
+
# by ruby-xen on <%= Time.now %>.
|
4
|
+
#
|
5
|
+
|
6
|
+
#
|
7
|
+
# Kernel + memory size
|
8
|
+
#
|
9
|
+
kernel = '<%= kernel %>'
|
10
|
+
ramdisk = '<%= ramdisk %>'
|
11
|
+
memory = '<%= memory %>'
|
12
|
+
|
13
|
+
#
|
14
|
+
# Disk device(s).
|
15
|
+
#
|
16
|
+
root = '<%= root %>'
|
17
|
+
disk = [
|
18
|
+
'<%= Array(vbds).collect{|d| d.to_str}.join("',\n\t'") %>'
|
19
|
+
]
|
20
|
+
|
21
|
+
#
|
22
|
+
# Hostname
|
23
|
+
#
|
24
|
+
name = '<%= name %>'
|
25
|
+
|
26
|
+
#
|
27
|
+
# Networking
|
28
|
+
#
|
29
|
+
vif = [
|
30
|
+
'<%= Array(vifs).collect{|v| v.to_str}.join("',\n\t'") %>'
|
31
|
+
]
|
32
|
+
|
33
|
+
#
|
34
|
+
# Behaviour
|
35
|
+
#
|
36
|
+
on_poweroff = '<%= on_poweroff %>'
|
37
|
+
on_reboot = '<%= on_reboot %>'
|
38
|
+
on_crash = '<%= on_crash %>'
|
39
|
+
|
40
|
+
extra = '<%= extra %>'
|
data/lib/xen/backup.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class Xen::Backup
|
2
|
+
include Xen::Parentable
|
3
|
+
|
4
|
+
attr_accessor :name, :version
|
5
|
+
|
6
|
+
def self.create(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
name = args.first
|
9
|
+
version = options[:version] || generate_version
|
10
|
+
Xen::Command.backup_slice(name, version)
|
11
|
+
new(name, version)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
options = args.extract_options!
|
16
|
+
@name = args.first
|
17
|
+
@version = options[:version]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.find(*args)
|
21
|
+
# return all
|
22
|
+
slice = args.first
|
23
|
+
Dir.glob("#{Xen::BACKUP_DIR}/*-*#{Xen::BACKUP_FILE_EXT}").collect { |file|
|
24
|
+
if match = File.basename(file, Xen::BACKUP_FILE_EXT).match(/(#{ slice || '.*' })-(.*)/)
|
25
|
+
new(match[1], :version => match[2])
|
26
|
+
end
|
27
|
+
}.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def filename
|
31
|
+
"#{@name}-#{@version}#{Xen::BACKUP_FILE_EXT}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def fullpath
|
35
|
+
File.join(Xen::BACKUP_DIR, filename)
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
File.size(fullpath)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/xen/command.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
class Xen::Command
|
2
|
+
# def self.xm_list
|
3
|
+
# raw = `xm list`.scan(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/)
|
4
|
+
# headers = raw.delete_at(0)
|
5
|
+
# raw.map do |row|
|
6
|
+
# headers.enum_with_index.inject({}) { |m, (head, i)| m[head] = row[i]; m }
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
|
10
|
+
# Return the size of a logical volume in gigabytes
|
11
|
+
def self.lv_size(vg_name, lv_name)
|
12
|
+
cmd = "lvs --noheadings --nosuffix --options lv_size --units g #{vg_name}/#{lv_name}"
|
13
|
+
`#{cmd}`.strip
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return list of logical volumes
|
17
|
+
def self.lv_list(vg_name)
|
18
|
+
cmd = "lvs --noheadings --nosuffix --options vg_name,lv_name,lv_size --units g #{vg_name}"
|
19
|
+
raw = `#{cmd}`
|
20
|
+
raw.scan(/(\S+)\s+(\S+)\s+(\S+)/).collect{ |vg_name, lv_name, size|
|
21
|
+
{
|
22
|
+
:vg => vg_name,
|
23
|
+
:name => lv_name,
|
24
|
+
:size => size
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.vg_list
|
30
|
+
cmd = "vgs --noheadings --units g --nosuffix --options vg_name,vg_size,vg_free,lv_count,max_lv"
|
31
|
+
raw = `#{cmd}`
|
32
|
+
raw.scan(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/).collect{ |vg_name, vg_size, vg_free, lv_count, max_lv|
|
33
|
+
{
|
34
|
+
:name => vg_name,
|
35
|
+
:size => vg_size,
|
36
|
+
:free => vg_free,
|
37
|
+
:lv_count => lv_count,
|
38
|
+
:max_lv => max_lv
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.detailed_instance_list(name='')
|
44
|
+
cmd = "xm list --long #{name}"
|
45
|
+
raw_entries = `#{cmd}`.split(/\n\(domain/)
|
46
|
+
raw_entries.collect do |entry|
|
47
|
+
attributes = entry.scan(/\((name|domid|memory|vcpus|state|cpu_time|start_time) (.*)\)/)
|
48
|
+
attributes.inject({}) { |m, (key, val)| m[key.to_sym] = val; m }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.start_instance(config_file)
|
53
|
+
`xm create #{config_file}`
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.shutdown_instance(name, blocking=false)
|
57
|
+
`xm shutdown #{'-w' if blocking} #{name}`
|
58
|
+
end
|
59
|
+
|
60
|
+
# Xen::Command.create_image('memory=512', :size => '10Gb')
|
61
|
+
# => "xm-create-image memory=512 size=10Gb"
|
62
|
+
#
|
63
|
+
def self.create_image(*args)
|
64
|
+
options = args.extract_options!
|
65
|
+
cmd = "xm-create-image #{args.concat(options.to_args).join(' ')}"
|
66
|
+
`cmd`
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.xm_info
|
70
|
+
result = `xm info`
|
71
|
+
result.scan(/(\S+)\s*:\s*([^\n]+)/).inject({}){ |m, (i,j)| m[i.to_sym] = j; m }
|
72
|
+
end
|
73
|
+
end
|
data/lib/xen/config.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class Xen::Config
|
4
|
+
# The config files for each Xen domU
|
5
|
+
include Xen::Parentable
|
6
|
+
attr_accessor :name, :kernel, :ramdisk, :memory, :root, :vbds, :vifs, :on_poweroff, :on_reboot, :on_crash, :extra
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
options = args.extract_options!
|
10
|
+
@name = args.first
|
11
|
+
@kernel = options[:kernel]
|
12
|
+
@ramdisk = options[:ramdisk]
|
13
|
+
@memory = options[:memory]
|
14
|
+
@root = options[:root]
|
15
|
+
@vbds = options[:vbds]
|
16
|
+
@vifs = options[:vifs]
|
17
|
+
@on_poweroff = options[:on_poweroff]
|
18
|
+
@on_reboot = options[:on_reboot]
|
19
|
+
@on_crash = options[:on_crash]
|
20
|
+
@extra = options[:extra]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.find(*args)
|
24
|
+
options = args.extract_options!
|
25
|
+
case args.first
|
26
|
+
when :all then all
|
27
|
+
else find_by_name(args.first)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.all
|
32
|
+
config_files = Dir.glob("#{Xen::XEN_DOMU_CONFIG_DIR}/*#{Xen::CONFIG_FILE_EXTENSION}")
|
33
|
+
config_files.collect do |filename|
|
34
|
+
create_from_config_file(File.read(filename))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_by_name(name)
|
39
|
+
return new('Domain-0') if name == 'Domain-0'
|
40
|
+
filename = "#{Xen::XEN_DOMU_CONFIG_DIR}/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
41
|
+
create_from_config_file(File.read(filename)) if File.exists?(filename)
|
42
|
+
end
|
43
|
+
|
44
|
+
def config_file
|
45
|
+
"#{Xen::XEN_DOMU_CONFIG_DIR}/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def auto_file
|
49
|
+
"#{Xen::XEN_DOMU_CONFIG_DIR}/auto/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def updated_at
|
53
|
+
File.mtime(config_file)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set to true|false to enable|disable autostart of slice
|
57
|
+
def auto=(value)
|
58
|
+
filename = File.basename(config_file)
|
59
|
+
if value == true
|
60
|
+
File.symlink("../#{filename}", auto_file)
|
61
|
+
else
|
62
|
+
File.unlink(auto_file)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true|false depending on whether slice is set to start automatically
|
67
|
+
def auto
|
68
|
+
File.symlink?(auto_file) && File.expand_path(File.readlink(auto_file), File.dirname(auto_file)) == config_file
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create_from_config_file(config)
|
72
|
+
name, kernel, ramdisk, memory, root, disk, vif, on_poweroff, on_reboot, on_crash, extra = nil
|
73
|
+
eval(config)
|
74
|
+
vifs = Array(vif).collect { |v| Xen::Vif.from_str(v) }
|
75
|
+
vbds = Array(disk).collect { |d| Xen::Vbd.from_str(d) }
|
76
|
+
new(name, :disk => disk, :kernel => kernel, :ramdisk => ramdisk, :memory => memory, :root => root, :vbds => vbds, :vifs => vifs, :on_poweroff => on_poweroff, :on_reboot => on_reboot, :on_crash => on_crash, :extra => extra)
|
77
|
+
end
|
78
|
+
|
79
|
+
def save
|
80
|
+
template = ERB.new IO.read(Xen::TEMPLATE_DIR + '/domu.cfg.erb')
|
81
|
+
File.open(config_file, 'w'){ |f| f.write template.result(binding) }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Virtual Network Interface
|
88
|
+
#
|
89
|
+
# http://wiki.xensource.com/xenwiki/XenNetworking
|
90
|
+
#
|
91
|
+
class Xen::Vif
|
92
|
+
attr_accessor :ip, :mac, :bridge, :vifname
|
93
|
+
def initialize(*args)
|
94
|
+
options = args.extract_options!
|
95
|
+
@ip = options[:ip]
|
96
|
+
@mac = options[:mac]
|
97
|
+
@bridge = options[:bridge]
|
98
|
+
@vifname = options[:vifname]
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.from_str(value)
|
102
|
+
options = value.scan(/(\w+)=([^,]+)/).inject({}){ |m, (k, v)| m[k.to_sym] = v; m }
|
103
|
+
new(options)
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_str
|
107
|
+
%w(ip mac bridge vifname).collect { |key|
|
108
|
+
"#{key}=#{instance_variable_get('@' + key)}" if !instance_variable_get('@'+key).nil?
|
109
|
+
}.compact.join(',')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Virtual Block Device
|
115
|
+
#
|
116
|
+
# We're only supporting Logical Volumes. No loopback devices.
|
117
|
+
#
|
118
|
+
# http://wiki.xensource.com/xenwiki/XenStorage
|
119
|
+
#
|
120
|
+
# == Example
|
121
|
+
#
|
122
|
+
# disk = [ 'phy:xendisks/example-disk,sda1,w',
|
123
|
+
# 'phy:xendisks/example-swap,sda2,w',
|
124
|
+
# 'phy:assets/example-assets,sdb1,w' ]
|
125
|
+
class Xen::Vbd
|
126
|
+
attr_accessor :name, :vg, :domu, :mode
|
127
|
+
def initialize(name, vg, domu, mode='w')
|
128
|
+
@name, @vg, @domu, @mode = name, vg, domu, mode
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.from_str(value)
|
132
|
+
dom0, domu, mode = value.split(',')
|
133
|
+
vg, name = dom0.split(/[\/:]/).slice(-2, 2)
|
134
|
+
new(name, vg, domu, mode)
|
135
|
+
end
|
136
|
+
|
137
|
+
def size
|
138
|
+
Xen::Command.lv_size(@vg, @name)
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_str
|
142
|
+
"phy:#{vg}/#{lv},#{domu},#{mode}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
data/lib/xen/host.rb
ADDED
data/lib/xen/image.rb
ADDED
data/lib/xen/instance.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
class Xen::Instance
|
2
|
+
include Xen::Parentable
|
3
|
+
attr_reader :name, :domid, :memory, :cpu_time, :vcpus, :state, :start_time
|
4
|
+
|
5
|
+
def initialize(name, options={})
|
6
|
+
@name = name
|
7
|
+
@domid = options[:domid]
|
8
|
+
@memory = options[:memory]
|
9
|
+
@cpu_time = options[:cpu_time]
|
10
|
+
@vcpus = options[:vcpus]
|
11
|
+
@state = options[:state]
|
12
|
+
@start_time = Time.at(options[:start_time].to_f) if options[:start_time]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find(*args)
|
16
|
+
options = args.extract_options!
|
17
|
+
case args.first
|
18
|
+
when :all then all
|
19
|
+
else find_by_name(args.first)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.all
|
24
|
+
Xen::Command.detailed_instance_list.collect do |instance|
|
25
|
+
new(name, instance)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_by_name(name)
|
30
|
+
Xen::Command.detailed_instance_list(name).each do |instance|
|
31
|
+
return new(name, instance)
|
32
|
+
end
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.create(name)
|
37
|
+
output = Xen::Command.start_instance(name.to_s + Xen::CONFIG_FILE_EXTENSION)
|
38
|
+
$? == 0 ? true : false
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.shutdown(name)
|
42
|
+
output = Xen::Command.shutdown_instance(name)
|
43
|
+
$? == 0 ? true : false
|
44
|
+
end
|
45
|
+
|
46
|
+
# A convenience wrapper for <tt>find(:dom0)</tt>.</tt>.
|
47
|
+
def self.dom0(*args)
|
48
|
+
find_by_name(:dom0)
|
49
|
+
end
|
50
|
+
|
51
|
+
def uptime
|
52
|
+
start_time ? Time.now - start_time : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def reboot
|
56
|
+
`xm reboot #{name}`
|
57
|
+
$? == 0 ? true : false
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy
|
61
|
+
end
|
62
|
+
|
63
|
+
def pause
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/xen/slice.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
class Xen::Slice
|
2
|
+
attr_accessor :name, :image, :config, :backups
|
3
|
+
|
4
|
+
def self.find(*args)
|
5
|
+
options = args.extract_options!
|
6
|
+
case args.first
|
7
|
+
when :all then Xen::Config.find(:all, options).collect { |config| config.slice }
|
8
|
+
when :running then Xen::Instance.find(:all, options).collect { |instance| instance.slice }
|
9
|
+
# Retrieve a Slice by name
|
10
|
+
else Xen::Config.find_by_name(args.first) && self.new(args.first)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.all(options={})
|
15
|
+
self.find(:all, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*args)
|
19
|
+
options = args.extract_options! # remove trailing hash (not used)
|
20
|
+
@name = args.first
|
21
|
+
@config = options[:config]
|
22
|
+
@instance = options[:instance]
|
23
|
+
@instance_cache_expires = Time.now
|
24
|
+
@backups = Array(options[:backups])
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_image(args)
|
28
|
+
args = hash.collect{|k,v| "#{k}=#{v}"}
|
29
|
+
Xen::Command.create_image
|
30
|
+
end
|
31
|
+
|
32
|
+
# Cache Xen instance info to reduce system calls to xm command.
|
33
|
+
# It still needs to be checked regularly as operations like shutdown
|
34
|
+
# and create can take a while.
|
35
|
+
def instance
|
36
|
+
if @instance_cache_expires > Time.now
|
37
|
+
@instance
|
38
|
+
else
|
39
|
+
@instance_cache_expires = Time.now + Xen::INSTANCE_OBJECT_LIFETIME
|
40
|
+
@instance = Xen::Instance.find(@name) if @name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# XXX We're assuming other processes aren't going to edit configs
|
45
|
+
# This is reasonable in simple cases.
|
46
|
+
def config
|
47
|
+
@config ||= Xen::Config.find(name) if @name
|
48
|
+
end
|
49
|
+
|
50
|
+
def backups
|
51
|
+
Xen::Backup.find(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def state
|
55
|
+
self.instance ? :running : :stopped
|
56
|
+
end
|
57
|
+
|
58
|
+
def running?
|
59
|
+
self.instance ? true : false
|
60
|
+
end
|
61
|
+
|
62
|
+
def start
|
63
|
+
Xen::Instance.create(@name)
|
64
|
+
@instance = Xen::Instance.find(@name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def stop
|
68
|
+
Xen::Instance.shutdown(@name)
|
69
|
+
@instance = Xen::Instance.find(@name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def config_newer_than_instance?
|
73
|
+
instance && config.updated_at > instance.start_time
|
74
|
+
end
|
75
|
+
|
76
|
+
def save
|
77
|
+
@config.save
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/test/test_ruby-xen.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-xen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bailey
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2008-09-
|
13
|
+
date: 2008-09-21 00:00:00 +10:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -32,13 +32,20 @@ files:
|
|
32
32
|
- bin/ruby-xen
|
33
33
|
- lib/ruby-xen.rb
|
34
34
|
- test/test_ruby-xen.rb
|
35
|
-
- lib/
|
35
|
+
- lib/xen/backup.rb
|
36
|
+
- lib/xen/command.rb
|
37
|
+
- lib/xen/config.rb
|
38
|
+
- lib/xen/host.rb
|
39
|
+
- lib/xen/image.rb
|
40
|
+
- lib/xen/instance.rb
|
41
|
+
- lib/xen/slice.rb
|
42
|
+
- lib/templates/domu.cfg.erb
|
36
43
|
has_rdoc: true
|
37
|
-
homepage: http://github.com/
|
44
|
+
homepage: http://github.com/mbailey/ruby-xen
|
38
45
|
post_install_message:
|
39
46
|
rdoc_options:
|
40
47
|
- --main
|
41
|
-
- README.
|
48
|
+
- README.rdoc
|
42
49
|
require_paths:
|
43
50
|
- lib
|
44
51
|
required_ruby_version: !ruby/object:Gem::Requirement
|
data/lib/ruby-xen/domain.rb
DELETED
@@ -1,229 +0,0 @@
|
|
1
|
-
class Array #:nodoc:
|
2
|
-
# Extracts options from a set of arguments. Removes and returns the last
|
3
|
-
# element in the array if it's a hash, otherwise returns a blank hash.
|
4
|
-
#
|
5
|
-
# def options(*args)
|
6
|
-
# args.extract_options!
|
7
|
-
# end
|
8
|
-
#
|
9
|
-
# options(1, 2) # => {}
|
10
|
-
# options(1, 2, :a => :b) # => {:a=>:b}
|
11
|
-
def extract_options!
|
12
|
-
last.is_a?(::Hash) ? pop : {}
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
module Xen
|
17
|
-
class Host
|
18
|
-
|
19
|
-
attr_reader :host, :machine, :total_memory, :free_memory
|
20
|
-
def initialize
|
21
|
-
result = `xm info`
|
22
|
-
result.scan(/(\S+)\s*:\s*([^\n]+)/).each do |i,j|
|
23
|
-
instance_variable_set("@#{i}", j)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
class Domain
|
31
|
-
|
32
|
-
attr_accessor :name, :image, :config, :instance
|
33
|
-
|
34
|
-
def initialize(name)
|
35
|
-
@name = name
|
36
|
-
@config = Xen::Config.find(name)
|
37
|
-
@instance = Xen::Instance.find(name)
|
38
|
-
@image = Xen::Image.find(name)
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.find(*args)
|
42
|
-
options = args.extract_options!
|
43
|
-
case args.first
|
44
|
-
when :all then Xen::Config.find(:all, options).collect { |config| Xen::Domain.new(config.name) }
|
45
|
-
when :running then Xen::Instance.find(:all, options).collect { |instance| Xen::Domain.new(instance.name) }
|
46
|
-
# Retrieve a Domain by name
|
47
|
-
else Xen::Config.find_by_name(args.first) && self.new(args.first)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def running?
|
52
|
-
@instance
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
class Config
|
59
|
-
|
60
|
-
attr_reader :name, :memory, :ip
|
61
|
-
|
62
|
-
def initialize(*args)
|
63
|
-
options = args.extract_options!
|
64
|
-
@name = args.first
|
65
|
-
@memory = options[:memory] || nil
|
66
|
-
@ip = options[:ip] || nil
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.find(*args)
|
70
|
-
options = args.extract_options!
|
71
|
-
case args.first
|
72
|
-
when :all then all
|
73
|
-
else find_by_name(args.first)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def self.all
|
78
|
-
result = `xen-list-images`
|
79
|
-
configs = result.scan(/Name: (\w+)\nMemory: (\w+)\nIP: (\S+)/)
|
80
|
-
configs.collect do |config|
|
81
|
-
name, memory, ip = config
|
82
|
-
new(name, :memory => memory, :ip => ip)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.find_by_name(name)
|
87
|
-
return new('Domain-0') if name == 'Domain-0'
|
88
|
-
all.detect {|config| puts config; config.name == name.to_s}
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
class Image
|
95
|
-
|
96
|
-
attr_accessor :name
|
97
|
-
|
98
|
-
def initialize(name)
|
99
|
-
@name = name
|
100
|
-
end
|
101
|
-
|
102
|
-
def self.find(name)
|
103
|
-
new name
|
104
|
-
end
|
105
|
-
|
106
|
-
def find_one(name, options)
|
107
|
-
if result = find_every(options).first
|
108
|
-
result
|
109
|
-
else
|
110
|
-
raise RecordNotFound, "Couldn't find domain with name=#{name}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
class Instance
|
117
|
-
|
118
|
-
attr_reader :name, :domid, :memory, :cpu_time, :vcpus, :state, :start_time
|
119
|
-
|
120
|
-
def initialize(name, options={})
|
121
|
-
@name = name
|
122
|
-
@domid = options[:domid] || nil
|
123
|
-
@memory = options[:memory] || nil
|
124
|
-
@cpu_time = options[:cpu_time] || nil
|
125
|
-
@vcpus = options[:vcpus] || nil
|
126
|
-
@state = options[:state] || nil
|
127
|
-
@start_time = options[:start_time] || nil
|
128
|
-
end
|
129
|
-
|
130
|
-
def self.find(*args)
|
131
|
-
options = args.extract_options!
|
132
|
-
case args.first
|
133
|
-
when :all then all
|
134
|
-
else find_by_name(args.first)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.all
|
139
|
-
result = `xm list`
|
140
|
-
# XXX check for failed command
|
141
|
-
result_array = result.split("\n")
|
142
|
-
result_array.shift
|
143
|
-
result_array.collect do |domain|
|
144
|
-
name, domid, memory, vcpus, state, cpu_time = domain.scan(/[^ ,]+/)
|
145
|
-
new(name, :domid => domid, :memory => memory, :cpu_time => cpu_time)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def self.find_by_name(name)
|
150
|
-
all.detect{|domain| domain.name == name.to_s }
|
151
|
-
end
|
152
|
-
|
153
|
-
# XXX Rails version - we need some error checking!
|
154
|
-
#
|
155
|
-
# def self.find_by_name(name, options)
|
156
|
-
# if result = find_every(options)
|
157
|
-
# result.detect{ |domain| domain.name == name }
|
158
|
-
# else
|
159
|
-
# raise RecordNotFound, "Couldn't find domain with name=#{name}"
|
160
|
-
# end
|
161
|
-
# end
|
162
|
-
|
163
|
-
# A convenience wrapper for <tt>find(:dom0)</tt>.</tt>.
|
164
|
-
def dom0(*args)
|
165
|
-
find_by_name(:dom0)
|
166
|
-
end
|
167
|
-
|
168
|
-
# This is an alias for find(:all). You can pass in all the same arguments to this method as you can
|
169
|
-
# to find(:all)
|
170
|
-
def all(*args)
|
171
|
-
find(:all, *args)
|
172
|
-
end
|
173
|
-
|
174
|
-
def uptime
|
175
|
-
start_time ? Time.now - start_time : nil
|
176
|
-
end
|
177
|
-
|
178
|
-
def running?
|
179
|
-
output = `xm list #{name}`
|
180
|
-
$? == 0 ? true : false
|
181
|
-
end
|
182
|
-
|
183
|
-
def start
|
184
|
-
output = `xm create #{name}.cfg`
|
185
|
-
$? == 0 ? true : false
|
186
|
-
end
|
187
|
-
|
188
|
-
def shutdown
|
189
|
-
output = `xm shutdown #{name}`
|
190
|
-
$? == 0 ? true : false
|
191
|
-
end
|
192
|
-
|
193
|
-
def reboot
|
194
|
-
`xm reboot #{name}`
|
195
|
-
$? == 0 ? true : false
|
196
|
-
end
|
197
|
-
|
198
|
-
def destroy
|
199
|
-
end
|
200
|
-
|
201
|
-
def pause
|
202
|
-
end
|
203
|
-
|
204
|
-
end
|
205
|
-
|
206
|
-
class Backup
|
207
|
-
end
|
208
|
-
|
209
|
-
# class XenTools
|
210
|
-
#
|
211
|
-
# def self.xen_list_images
|
212
|
-
# puts "returning list of images"
|
213
|
-
# end
|
214
|
-
#
|
215
|
-
# def xen_create_image
|
216
|
-
# puts "creating image"
|
217
|
-
# end
|
218
|
-
#
|
219
|
-
# def xen_delete_image
|
220
|
-
# puts "creating image"
|
221
|
-
# end
|
222
|
-
#
|
223
|
-
# def xen_archive_image
|
224
|
-
# puts "archiving image"
|
225
|
-
# end
|
226
|
-
#
|
227
|
-
# end
|
228
|
-
|
229
|
-
end
|