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.
@@ -1,42 +1,80 @@
1
- class Xen::Backup
2
- include Xen::Parentable
1
+ module Xen
2
+ class Backup
3
+ include Xen::Parentable
3
4
 
4
- attr_accessor :name, :version
5
+ attr_accessor :name, :version
5
6
 
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
7
+ def self.create(*args)
8
+ options = args.extract_options!
9
+ name = args.first
13
10
 
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])
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
- }.compact
28
- end
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
- def filename
31
- "#{@name}-#{@version}#{Xen::BACKUP_FILE_EXT}"
32
- end
67
+ def filename
68
+ "#{@name}-#{@version}#{Xen::BACKUP_FILE_EXT}"
69
+ end
33
70
 
34
- def fullpath
35
- File.join(Xen::BACKUP_DIR, filename)
36
- end
71
+ def fullpath
72
+ File.join(Xen::BACKUP_DIR, filename)
73
+ end
37
74
 
38
- def size
39
- File.size(fullpath)
40
- end
75
+ def size
76
+ File.size(fullpath)
77
+ end
41
78
 
79
+ end
42
80
  end
@@ -1,73 +1,107 @@
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
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
- # 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
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
- # 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
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
- 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
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
- 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 }
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
- def self.start_instance(config_file)
53
- `xm create #{config_file}`
54
- end
73
+ def self.start_instance(config_file)
74
+ `xm create #{config_file}`
75
+ end
55
76
 
56
- def self.shutdown_instance(name, blocking=false)
57
- `xm shutdown #{'-w' if blocking} #{name}`
58
- end
77
+ def self.shutdown_instance(name, blocking=false)
78
+ `xm shutdown #{'-w' if blocking} #{name}`
79
+ end
59
80
 
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
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
- 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 }
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
+
@@ -1,9 +1,63 @@
1
- class Xen::Host
2
- attr_reader :host, :machine, :total_memory, :free_memory
1
+ module Xen
2
+ class Host
3
+ attr_reader :host, :machine, :total_memory, :nr_cpus
3
4
 
4
- def initialize
5
- Xen::Command.xm_info.each do |i,j|
6
- instance_variable_set("@#{i}", j)
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