mbailey-ruby-xen 0.0.2 → 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 +5 -0
- data/Manifest.txt +6 -4
- data/README.rdoc +1 -3
- data/lib/ruby-xen.rb +92 -18
- data/lib/templates/domu.cfg.erb +1 -1
- data/lib/templates/xen-tools.conf.erb +289 -0
- data/lib/xen/backup.rb +71 -2
- data/lib/xen/command.rb +94 -23
- data/lib/xen/config_file.rb +160 -0
- data/lib/xen/host.rb +23 -5
- data/lib/xen/instance.rb +53 -52
- data/lib/xen/lvm.rb +33 -0
- data/lib/xen/slice.rb +117 -43
- data/lib/xen/xen_tools_conf.rb +56 -0
- data/test/test_ruby-xen.rb +2 -2
- metadata +27 -8
- data/lib/xen/config.rb +0 -139
- data/lib/xen/image.rb +0 -12
data/lib/xen/backup.rb
CHANGED
@@ -1,3 +1,72 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Xen
|
2
|
+
class Backup
|
3
|
+
include Xen::Parentable
|
4
|
+
|
5
|
+
attr_accessor :name, :version
|
6
|
+
|
7
|
+
def self.create(*args)
|
8
|
+
options = args.extract_options!
|
9
|
+
name = args.first
|
10
|
+
|
11
|
+
options = {}
|
12
|
+
name = 'foo' # XXX replace with real value
|
13
|
+
version = options[:version] || Time.now.strftime('%Y%m%d')
|
14
|
+
backup_dir = options[:backup_dir] || Xen::BACKUP_DIR
|
15
|
+
backup_file_ext = options[:backup_file_ext] || Xen::BACKUP_FILE_EXT
|
16
|
+
archive_name="#{name}-#{version}.#{backup_file_ext}"
|
17
|
+
|
18
|
+
slice = Xen::Slice.find(name) # XXX test for failure
|
19
|
+
slice.stop
|
20
|
+
sleep 10
|
21
|
+
|
22
|
+
temp_mount = `mktemp -d -p /mnt #{name}-XXXXX`.chomp # XXX test for failure
|
23
|
+
`mount #{slice.root_disk.path} #{temp_mount}` # XXX test for failure
|
24
|
+
|
25
|
+
# Creating archive at $ARCHIVE_DIR/$ARCHIVE_NAME ...
|
26
|
+
`tar --create --exclude=/proc --exclude=/etc/udev/rules.d/70-persistent-net.rules --directory #{temp_mount} --file #{backup_dir}/#{archive_name} .`
|
27
|
+
# XXX test for failure
|
28
|
+
|
29
|
+
# Unmounting image
|
30
|
+
`umount #{temp_mount}`
|
31
|
+
Dir.delete(temp_mount)
|
32
|
+
|
33
|
+
# Creating symlink from new backup to filename without version number
|
34
|
+
last_backup = "#{backup_dir}/#{name}.#{backup_file_ext}"
|
35
|
+
File.delete(last_backup) if File.symlink(last_backup)
|
36
|
+
`ln -sf #{backup_dir}/#{archive_name} #{last_backup}`
|
37
|
+
|
38
|
+
slice.start
|
39
|
+
|
40
|
+
new(:name => name, :version => version)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(*args)
|
44
|
+
options = args.extract_options!
|
45
|
+
@name = options[:name]
|
46
|
+
@version = options[:version]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find(*args)
|
50
|
+
# return all
|
51
|
+
slice = args.first
|
52
|
+
Dir.glob("#{Xen::BACKUP_DIR}/*-*#{Xen::BACKUP_FILE_EXT}").collect { |file|
|
53
|
+
if match = File.basename(file, Xen::BACKUP_FILE_EXT).match(/(#{ slice || '.*' })-(.*)/)
|
54
|
+
new(:name => match[1], :version => match[2])
|
55
|
+
end
|
56
|
+
}.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def filename
|
60
|
+
"#{@name}-#{@version}#{Xen::BACKUP_FILE_EXT}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def fullpath
|
64
|
+
File.join(Xen::BACKUP_DIR, filename)
|
65
|
+
end
|
66
|
+
|
67
|
+
def size
|
68
|
+
File.size(fullpath)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
3
72
|
end
|
data/lib/xen/command.rb
CHANGED
@@ -1,30 +1,101 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module Xen
|
2
|
+
class Command
|
3
|
+
|
4
|
+
class ExternalFailure < RuntimeError; end
|
5
|
+
|
6
|
+
# def self.xm_list
|
7
|
+
# raw = `xm list`.scan(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/)
|
8
|
+
# headers = raw.delete_at(0)
|
9
|
+
# raw.map do |row|
|
10
|
+
# headers.enum_with_index.inject({}) { |m, (head, i)| m[head] = row[i]; m }
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
|
14
|
+
def self.run(cmd)
|
15
|
+
output = []
|
16
|
+
error = nil
|
17
|
+
stat = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
|
18
|
+
while line = stdout.gets
|
19
|
+
output << line.strip
|
20
|
+
end
|
21
|
+
error = stderr.read.strip
|
22
|
+
end
|
23
|
+
# if stat.exited? # Is this needed?
|
24
|
+
if stat.exitstatus > 0
|
25
|
+
raise ExternalFailure, "Fatal error, `#{cmd}` returned #{stat.exitstatus} with '#{error}'"
|
26
|
+
end
|
27
|
+
# end
|
28
|
+
return output
|
29
|
+
end
|
9
30
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attributes.inject({}) { |m, (key, val)| m[key.to_sym] = val; m }
|
31
|
+
# Return the size of a logical volume in gigabytes
|
32
|
+
def self.lv_size(vg_name, lv_name)
|
33
|
+
cmd = "lvs --noheadings --nosuffix --options lv_size --units g #{vg_name}/#{lv_name}"
|
34
|
+
`#{cmd}`.strip
|
15
35
|
end
|
16
|
-
end
|
17
36
|
|
18
|
-
|
19
|
-
|
20
|
-
|
37
|
+
# Return list of logical volumes
|
38
|
+
def self.lv_list(vg_name)
|
39
|
+
cmd = "lvs --noheadings --nosuffix --options vg_name,lv_name,lv_size --units g #{vg_name}"
|
40
|
+
raw = `#{cmd}`
|
41
|
+
raw.scan(/(\S+)\s+(\S+)\s+(\S+)/).collect{ |vg_name, lv_name, size|
|
42
|
+
{
|
43
|
+
:vg => vg_name,
|
44
|
+
:name => lv_name,
|
45
|
+
:size => size
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.vg_list
|
51
|
+
cmd = "vgs --noheadings --units g --nosuffix --options vg_name,vg_size,vg_free,lv_count,max_lv"
|
52
|
+
raw = `#{cmd}`
|
53
|
+
raw.scan(/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/).collect{ |vg_name, vg_size, vg_free, lv_count, max_lv|
|
54
|
+
{
|
55
|
+
:name => vg_name,
|
56
|
+
:size => vg_size,
|
57
|
+
:free => vg_free,
|
58
|
+
:lv_count => lv_count,
|
59
|
+
:max_lv => max_lv
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
21
63
|
|
22
|
-
|
23
|
-
|
24
|
-
|
64
|
+
def self.detailed_instance_list(name='')
|
65
|
+
cmd = "xm list --long #{name}"
|
66
|
+
raw_entries = `#{cmd}`.split(/\n\(domain/)
|
67
|
+
raw_entries.collect do |entry|
|
68
|
+
attributes = entry.scan(/\((name|domid|memory|vcpus|state|cpu_time|start_time) (.*)\)/)
|
69
|
+
attributes.inject({}) { |m, (key, val)| m[key.to_sym] = val; m }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.start_instance(config_file)
|
74
|
+
`xm create #{config_file}`
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.shutdown_instance(name, blocking=false)
|
78
|
+
`xm shutdown #{'-w' if blocking} #{name}`
|
79
|
+
end
|
80
|
+
|
81
|
+
# Xen::Command.create_image('memory=512', :size => '10Gb')
|
82
|
+
# => "xm-create-image memory=512 size=10Gb"
|
83
|
+
#
|
84
|
+
# XXX call with a hash by default
|
85
|
+
#
|
86
|
+
def self.create_image(*args)
|
87
|
+
options = args.extract_options!
|
88
|
+
cmd = "xen-create-image #{args.concat(options.to_args).join(' ')}"
|
89
|
+
puts
|
90
|
+
puts "Running the command:"
|
91
|
+
puts cmd
|
92
|
+
puts
|
93
|
+
system(cmd)
|
94
|
+
end
|
25
95
|
|
26
|
-
|
27
|
-
|
28
|
-
|
96
|
+
def self.xm_info
|
97
|
+
result = `/usr/sbin/xm info`
|
98
|
+
result.scan(/(\S+)\s*:\s*([^\n]+)/).inject({}){ |m, (i,j)| m[i.to_sym] = j; m }
|
99
|
+
end
|
29
100
|
end
|
30
101
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Xen
|
4
|
+
class ConfigFile
|
5
|
+
# The config files for each Xen domU
|
6
|
+
include Xen::Parentable
|
7
|
+
attr_accessor :name, :kernel, :ramdisk, :memory, :root, :vbds, :vifs, :on_poweroff, :on_reboot, :on_crash, :extra
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
options = args.extract_options!
|
11
|
+
@name = options[:name]
|
12
|
+
@kernel = options[:kernel]
|
13
|
+
@ramdisk = options[:ramdisk]
|
14
|
+
@memory = options[:memory].to_i
|
15
|
+
@root = options[:root]
|
16
|
+
@vbds = options[:vbds]
|
17
|
+
@vifs = options[:vifs]
|
18
|
+
@on_poweroff = options[:on_poweroff]
|
19
|
+
@on_reboot = options[:on_reboot]
|
20
|
+
@on_crash = options[:on_crash]
|
21
|
+
@extra = options[:extra]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find(*args)
|
25
|
+
options = args.extract_options!
|
26
|
+
case args.first
|
27
|
+
when :all then all
|
28
|
+
else find_by_name(args.first)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.all
|
33
|
+
config_files = Dir.glob("#{Xen::XEN_DOMU_CONFIG_DIR}/*#{Xen::CONFIG_FILE_EXTENSION}")
|
34
|
+
config_files.collect do |filename|
|
35
|
+
create_from_config_file(File.read(filename))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.find_by_name(name)
|
40
|
+
return new(:name => 'Domain-0') if name == 'Domain-0'
|
41
|
+
filename = "#{Xen::XEN_DOMU_CONFIG_DIR}/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
42
|
+
create_from_config_file(File.read(filename)) if File.exists?(filename)
|
43
|
+
end
|
44
|
+
|
45
|
+
def filename
|
46
|
+
"#{Xen::XEN_DOMU_CONFIG_DIR}/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def auto_file
|
50
|
+
"#{Xen::XEN_DOMU_CONFIG_DIR}/auto/#{name}#{Xen::CONFIG_FILE_EXTENSION}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def updated_at
|
54
|
+
File.mtime(filename)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set to true|false to enable|disable autostart of slice
|
58
|
+
def set_auto(value)
|
59
|
+
if value == true
|
60
|
+
File.symlink("../#{File.basename(filename)}", auto_file) unless auto
|
61
|
+
else
|
62
|
+
File.unlink(auto_file) if auto
|
63
|
+
end
|
64
|
+
auto == value # return true if final state is as requested
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns true|false depending on whether slice is set to start automatically
|
68
|
+
def auto
|
69
|
+
File.symlink?(auto_file) && File.expand_path(File.readlink(auto_file), File.dirname(auto_file)) == filename
|
70
|
+
end
|
71
|
+
|
72
|
+
alias auto? auto
|
73
|
+
|
74
|
+
def self.create_from_config_file(config)
|
75
|
+
name, kernel, ramdisk, memory, root, disk, vif, on_poweroff, on_reboot, on_crash, extra = nil
|
76
|
+
eval(config)
|
77
|
+
vifs = Array(vif).collect { |v| Xen::Vif.from_str(v) }
|
78
|
+
vbds = Array(disk).collect { |d| Xen::Vbd.from_str(d) }
|
79
|
+
new(:name => 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)
|
80
|
+
end
|
81
|
+
|
82
|
+
def save
|
83
|
+
template = ERB.new IO.read(Xen::TEMPLATE_DIR + '/domu.cfg.erb')
|
84
|
+
File.open(filename, 'w'){ |f| f.write template.result(binding) }
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Virtual Network Interface
|
91
|
+
#
|
92
|
+
# http://wiki.xensource.com/xenwiki/XenNetworking
|
93
|
+
#
|
94
|
+
class Xen::Vif
|
95
|
+
attr_accessor :ip, :mac, :bridge, :vifname
|
96
|
+
|
97
|
+
def initialize(*args)
|
98
|
+
options = args.extract_options!
|
99
|
+
|
100
|
+
# Validations - move them out into a validate function
|
101
|
+
raise(ValidationFailed, 'message') if options[:vifname] == 'foos'
|
102
|
+
|
103
|
+
@ip = options[:ip].to_s.gsub /['"]/, ''
|
104
|
+
@mac = options[:mac].to_s.gsub /['"]/, ''
|
105
|
+
@bridge = options[:bridge].to_s.gsub /['"]/, ''
|
106
|
+
@vifname = options[:vifname].to_s.gsub /['"]/, ''
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.from_str(value)
|
110
|
+
options = value.scan(/(\w+)=([^,]+)/).inject({}){ |m, (k, v)| m[k.to_sym] = v; m }
|
111
|
+
new(options)
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_str
|
115
|
+
%w(ip mac bridge vifname).collect { |key|
|
116
|
+
"#{key}=#{instance_variable_get('@' + key)}" if !instance_variable_get('@'+key).nil?
|
117
|
+
}.compact.join(',')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Virtual Block Device
|
123
|
+
#
|
124
|
+
# We're only supporting Logical Volumes. No loopback devices.
|
125
|
+
#
|
126
|
+
# http://wiki.xensource.com/xenwiki/XenStorage
|
127
|
+
#
|
128
|
+
# == Example
|
129
|
+
#
|
130
|
+
# disk = [ 'phy:xendisks/example-disk,sda1,w',
|
131
|
+
# 'phy:xendisks/example-swap,sda2,w',
|
132
|
+
# 'phy:assets/example-assets,sdb1,w' ]
|
133
|
+
class Xen::Vbd
|
134
|
+
attr_accessor :name, :volume_group, :domu, :mode
|
135
|
+
def initialize(name, volume_group, domu, mode='w')
|
136
|
+
@name, @volume_group, @domu, @mode = name, volume_group, domu, mode
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.from_str(value)
|
140
|
+
dom0, domu, mode = value.split(',')
|
141
|
+
volume_group, name = dom0.split(/[\/:]/).slice(-2, 2)
|
142
|
+
new(name, volume_group, domu, mode)
|
143
|
+
end
|
144
|
+
|
145
|
+
def size
|
146
|
+
Xen::Command.lv_size(@volume_group, @name)
|
147
|
+
end
|
148
|
+
|
149
|
+
# XXX Not needed?
|
150
|
+
# def path
|
151
|
+
# "/dev/#{volume_group}/#{name}"
|
152
|
+
# end
|
153
|
+
|
154
|
+
def to_str
|
155
|
+
"phy:#{volume_group}/#{name},#{domu},#{mode}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
data/lib/xen/host.rb
CHANGED
@@ -1,9 +1,27 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Xen
|
2
|
+
class Host
|
3
|
+
attr_reader :host, :machine, :total_memory, :nr_cpus
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
instance_variable_set("@#{i}", j)
|
5
|
+
def initialize(detail_hash={})
|
6
|
+
detail_hash.each { |i,j| instance_variable_set("@#{i}", j) }
|
7
7
|
end
|
8
|
+
|
9
|
+
def self.find
|
10
|
+
new Xen::Command.xm_info
|
11
|
+
end
|
12
|
+
|
13
|
+
def free_memory
|
14
|
+
if f = `free -m`
|
15
|
+
if (m = f.match /buffers\/cache.*\s+(\w+)\n/)
|
16
|
+
m[1].to_i
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def domu_memory
|
22
|
+
Xen::Slice.find(:running).inject(0){|m, slice| m += slice.instance.memory.to_i; m}
|
23
|
+
end
|
24
|
+
|
8
25
|
end
|
26
|
+
|
9
27
|
end
|
data/lib/xen/instance.rb
CHANGED
@@ -1,67 +1,68 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Xen
|
2
|
+
class Instance
|
3
|
+
include Xen::Parentable
|
4
|
+
attr_reader :name, :domid, :memory, :cpu_time, :vcpus, :state, :start_time
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
6
|
+
def initialize(options={})
|
7
|
+
@name = options[:name]
|
8
|
+
@domid = options[:domid]
|
9
|
+
@memory = options[:memory]
|
10
|
+
@cpu_time = options[:cpu_time]
|
11
|
+
@vcpus = options[:vcpus]
|
12
|
+
@state = options[:state]
|
13
|
+
@start_time = Time.at(options[:start_time].to_f) if options[:start_time]
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def self.find(*args)
|
17
|
+
options = args.extract_options!
|
18
|
+
case args.first
|
19
|
+
when :all then all
|
20
|
+
else find_by_name(args.first)
|
21
|
+
end
|
21
22
|
end
|
22
|
-
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def self.all
|
25
|
+
Xen::Command.detailed_instance_list.collect do |instance|
|
26
|
+
new(instance)
|
27
|
+
end
|
27
28
|
end
|
28
|
-
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def self.find_by_name(name)
|
31
|
+
Xen::Command.detailed_instance_list(name).each do |instance|
|
32
|
+
return new(instance)
|
33
|
+
end
|
34
|
+
return false
|
33
35
|
end
|
34
|
-
return false
|
35
|
-
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def self.create(name)
|
38
|
+
output = Xen::Command.start_instance(name.to_s + Xen::CONFIG_FILE_EXTENSION)
|
39
|
+
$? == 0 ? true : false
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
def self.shutdown(name)
|
43
|
+
output = Xen::Command.shutdown_instance(name)
|
44
|
+
$? == 0 ? true : false
|
45
|
+
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
# A convenience wrapper for <tt>find(:dom0)</tt>.</tt>.
|
48
|
+
def self.dom0(*args)
|
49
|
+
find_by_name(:dom0)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
def uptime
|
53
|
+
start_time ? Time.now - start_time : nil
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
def reboot
|
57
|
+
`xm reboot #{name}`
|
58
|
+
$? == 0 ? true : false
|
59
|
+
end
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
def destroy
|
62
|
+
end
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
def pause
|
65
|
+
end
|
66
66
|
|
67
|
-
end
|
67
|
+
end
|
68
|
+
end
|