ruby-xen 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +6 -4
- data/README.rdoc +26 -20
- data/lib/ruby-xen.rb +60 -10
- data/lib/templates/domu.cfg.erb +1 -1
- data/lib/templates/exclude_from_backups +2 -0
- data/lib/templates/xen-tools.conf.erb +289 -0
- data/lib/xen/backup.rb +71 -33
- data/lib/xen/command.rb +94 -60
- data/lib/xen/config_file.rb +159 -0
- data/lib/xen/host.rb +59 -5
- data/lib/xen/instance.rb +53 -51
- data/lib/xen/lvm.rb +40 -0
- data/lib/xen/slice.rb +118 -65
- data/lib/xen/xen_tools_conf.rb +56 -0
- data/test/test_ruby-xen.rb +2 -2
- metadata +29 -7
- data/lib/xen/config.rb +0 -146
- data/lib/xen/image.rb +0 -12
data/lib/xen/backup.rb
CHANGED
@@ -1,42 +1,80 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Xen
|
2
|
+
class Backup
|
3
|
+
include Xen::Parentable
|
3
4
|
|
4
|
-
|
5
|
+
attr_accessor :name, :version
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
version = options[:version] || generate_version
|
10
|
-
Xen::Command.backup_slice(name, version)
|
11
|
-
new(name, version)
|
12
|
-
end
|
7
|
+
def self.create(*args)
|
8
|
+
options = args.extract_options!
|
9
|
+
name = args.first
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
+
if slice.running?
|
20
|
+
slice.stop
|
21
|
+
sleep 10
|
22
|
+
restart_slice = true
|
26
23
|
end
|
27
|
-
|
28
|
-
|
24
|
+
|
25
|
+
temp_mount = `mktemp -d -p /mnt #{name}-XXXXX`.chomp # XXX test for failure
|
26
|
+
`mount #{slice.root_disk.path} #{temp_mount}` # XXX test for failure
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
FileUtils.mkdir_p backup_dir
|
31
|
+
|
32
|
+
# Creating archive at backup_dir/archive_name ...
|
33
|
+
excludes_file = File.join(File.dirname(__FILE__),'..','templates','exclude_from_backups')
|
34
|
+
temp_tarball = `mktemp -p #{backup_dir} #{name}-XXXXX`.chomp # XXX test for failure
|
35
|
+
`tar --create --exclude-from=#{excludes_file} --directory #{temp_mount} --file #{temp_tarball} . && mv #{temp_tarball} #{backup_dir}/#{archive_name}`
|
36
|
+
|
37
|
+
# Unmounting image
|
38
|
+
`umount #{temp_mount}`
|
39
|
+
Dir.delete(temp_mount)
|
40
|
+
|
41
|
+
# Creating symlink from new backup to filename without version number
|
42
|
+
last_backup = "#{backup_dir}/#{name}#{backup_file_ext}"
|
43
|
+
File.delete(last_backup) if File.symlink?(last_backup)
|
44
|
+
`ln -sf #{backup_dir}/#{archive_name} #{last_backup}`
|
45
|
+
|
46
|
+
slice.start if restart_slice == true
|
47
|
+
|
48
|
+
new(:name => name, :version => version)
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(*args)
|
52
|
+
options = args.extract_options!
|
53
|
+
@name = options[:name]
|
54
|
+
@version = options[:version]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find(*args)
|
58
|
+
# return all
|
59
|
+
slice = args.first
|
60
|
+
Dir.glob("#{Xen::BACKUP_DIR}/*-*#{Xen::BACKUP_FILE_EXT}").collect { |file|
|
61
|
+
if match = File.basename(file, Xen::BACKUP_FILE_EXT).match(/(#{ slice || '.*' })-(.*)/)
|
62
|
+
new(:name => match[1], :version => match[2])
|
63
|
+
end
|
64
|
+
}.compact
|
65
|
+
end
|
29
66
|
|
30
|
-
|
31
|
-
|
32
|
-
|
67
|
+
def filename
|
68
|
+
"#{@name}-#{@version}#{Xen::BACKUP_FILE_EXT}"
|
69
|
+
end
|
33
70
|
|
34
|
-
|
35
|
-
|
36
|
-
|
71
|
+
def fullpath
|
72
|
+
File.join(Xen::BACKUP_DIR, filename)
|
73
|
+
end
|
37
74
|
|
38
|
-
|
39
|
-
|
40
|
-
|
75
|
+
def size
|
76
|
+
File.size(fullpath)
|
77
|
+
end
|
41
78
|
|
79
|
+
end
|
42
80
|
end
|
data/lib/xen/command.rb
CHANGED
@@ -1,73 +1,107 @@
|
|
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
|
-
|
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
|
35
|
+
end
|
15
36
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
+
}
|
25
47
|
}
|
26
|
-
|
27
|
-
end
|
48
|
+
end
|
28
49
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
+
}
|
39
61
|
}
|
40
|
-
|
41
|
-
end
|
62
|
+
end
|
42
63
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
49
71
|
end
|
50
|
-
end
|
51
72
|
|
52
|
-
|
53
|
-
|
54
|
-
|
73
|
+
def self.start_instance(config_file)
|
74
|
+
`xm create #{config_file}`
|
75
|
+
end
|
55
76
|
|
56
|
-
|
57
|
-
|
58
|
-
|
77
|
+
def self.shutdown_instance(name, blocking=false)
|
78
|
+
`xm shutdown #{'-w' if blocking} #{name}`
|
79
|
+
end
|
59
80
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
95
|
+
|
96
|
+
def self.create_backup(*args)
|
97
|
+
name = args.shift
|
98
|
+
slice = Xen::Slice.find(name)
|
99
|
+
slice.create_backup
|
100
|
+
end
|
68
101
|
|
69
|
-
|
70
|
-
|
71
|
-
|
102
|
+
def self.xm_info
|
103
|
+
result = `/usr/sbin/xm info`
|
104
|
+
result.scan(/(\S+)\s*:\s*([^\n]+)/).inject({}){ |m, (i,j)| m[i.to_sym] = j; m }
|
105
|
+
end
|
72
106
|
end
|
73
107
|
end
|
@@ -0,0 +1,159 @@
|
|
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)}" unless instance_variable_get('@'+key) == ''
|
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
|
+
def path
|
150
|
+
"/dev/#{volume_group}/#{name}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_str
|
154
|
+
"phy:#{volume_group}/#{name},#{domu},#{mode}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
data/lib/xen/host.rb
CHANGED
@@ -1,9 +1,63 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Xen
|
2
|
+
class Host
|
3
|
+
attr_reader :host, :machine, :total_memory, :nr_cpus
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
def initialize(detail_hash={})
|
6
|
+
detail_hash.each { |i,j| instance_variable_set("@#{i}", j) }
|
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
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# XXX Move this somewhere else!
|
29
|
+
|
30
|
+
# Network Bridge Script looks like this.
|
31
|
+
#
|
32
|
+
# #!/bin/sh
|
33
|
+
# /etc/xen/scripts/network-bridge $1 netdev=eth0 bridge=xenbr0 vifnum=0 antispoof=no
|
34
|
+
# /etc/xen/scripts/network-bridge $1 netdev=eth1 bridge=xenbr1 vifnum=1 antispoof=no
|
35
|
+
|
36
|
+
class Bridges
|
37
|
+
NETWORK_BRIDGE_WRAPPER = '/etc/xen/scripts/network-bridge-wrapper'
|
38
|
+
|
39
|
+
def self.find
|
40
|
+
f = File.readlines(NETWORK_BRIDGE_WRAPPER).collect { |line|
|
41
|
+
if (m = line.match /netdev=(.*) bridge=(.*) vifnum=(.*) antispoof=(.*)/)
|
42
|
+
Xen::Bridge.new :netdev => m[1], :bridge => m[2], :vifnum => m[3], :antispoof => m[4]
|
43
|
+
end
|
44
|
+
}.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.save
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class Bridge
|
53
|
+
attr_accessor :netdev, :bridge, :vifnum, :antispoof
|
54
|
+
|
55
|
+
def initialize(*args)
|
56
|
+
options = args.extract_options!
|
57
|
+
@netdev = options[:netdev]
|
58
|
+
@bridge = options[:bridge]
|
59
|
+
@vifnum = options[:vifnum]
|
60
|
+
@antispoof = options[:antispoof]
|
7
61
|
end
|
8
62
|
end
|
9
63
|
|