ruby-xen 0.0.3 → 0.1.0

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.
@@ -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