Ruby Class For Operating OpenVZ
Posted by Tres Mon, 15 Jan 2007 11:33:00 GMT
This class was written as a means to allow a “management node” to automatically change the state of a “hardware node” on which client virtual private servers are running. It’s referenced in the earlier article on using CFEngine to build an OpenVZ node. There are some unfinished methods for monitoring, but does provide all standard vzctl functionality. By design, the management node would be running some kind of billing software. By monitoring state changes in the billing software’s database, you can automatically provision new accounts, disable for late payments, delete accounts, add services, add resources, and do it all while you’re asleep.
By using SSH public keys and sudo, you can have the management node make any changes required to the state of a VPS on a hardware node. Why not just use vzctl via SSH? The problem with vzctl is that you’re implicitly granting privileges for the management node to do anything that vzctl can do. Second, it’s easier and more reliable to extend a class to do other things required things like firewall rules, bandwidth monitoring, rate limiting, etc. than it is to create a server-side (i.e. management node) script that will try to do all these things.
This class allows you to write scripts that only have the ability to do one thing. I’ll try to upload my scripts in a separate article later (they’re very small–if you know some Ruby, it should be easy to write them yourself). This means that you can add these scripts to your sudoers file, and reliably limit the kind of changes that can be made from the hardware node. Remember, the ultimate goal here is to automate all of this stuff; we not only have to make sure that the automated system won’t screw up, we have to make sure that we can also hook into the billing system’s database.
#!/usr/bin/env ruby
#
# Created by Tres Wong-Godfrey on 2006-11-11.
# Copyright (c) 2006. All rights reserved.
class Vps
attr_accessor :veid, :hostname, :nameservers, :ostemplate, :addresses, :plan_id, :password, :net_status, :cpu_usage, :disk_status, :disk_usage, :mem_usage, :sys_status, :net_usage, :batch_mode
require ("/usr/local/lib/vpsadmin/firewall.rb")
require ("optparse")
def initialize( )
@vzDir = "/vz"
@scriptsDir = "#{@vzDir}/scripts/"
@backupDir = "#{@scriptsDir}/backups/"
@disabledDir = "#{@scriptsDir}/disabled/"
@batch_mode = false
end
#create
def create()
# firewall = Firewall.new
addressstring = String.new
addresses = @addresses.to_s
address_list = addresses.split(/\s*\,\s*/)
address_list.each { |address| addressstring.concat(" --ipadd #{address} ") }
system("vzctl create #{@veid} --ostemplate #{@ostemplate} --hostname #{@hostname} #{addressstring} --config #{@plan_id}")
self.set_nameserver()
self.set_password()
self.start()
sleep(60)
self.create_tun_dev()
=begin
firewall = Firewall.new
firewall.veid = @veid
firewall.plan = @plan_id
firewall.addresses = @addresses
firewall.setup
=end
end
#create_tun_dev
def create_tun_dev()
system("vzctl exec #{@veid} mkdir -p /dev/net")
system("vzctl exec #{@veid} mknod /dev/net/tun c 10 200")
system("vzctl exec #{@veid} chmod 600 /dev/net/tun")
end
#set nameserver
def set_nameserver()
nameserverstring = String.new
nameservers = @nameservers.to_s
nameservers.each(',') {|address| nameserverstring.concat(" --nameserver #{address} ")}
system("vzctl set #{@veid} #{nameserverstring} --save")
end
#set password
def set_password()
system("vzctl set #{@veid} --userpasswd root:#{@password} --save")
end
#set hostname
def set_hostname()
system("vzctl set #{@veid} --hostname #{@hostname} --save")
end
#destroy
def destroy()
system("vzctl destroy #{@veid}")
firewall = new Firewall()
firewall.veid = @veid
firewall.plan = @plan_id
firewall.addresses = @addresses
firewall.delete()
end
#add_ip
def add_ip()
@addresses.each {|address| system("vzctl set #{@veid} --ipadd #{address} --save") }
firewall = new Firewall()
firewall.veid = @veid
firewall.plan = @plan_id
firewall.addresses = @addresses
firewall.setup()
end
#remove_ip
def remove_ip()
system("vzctl set #{@veid} --ipdel all --save")
@addresses.each {|address| system("vzctl set #{@veid} --ipadd #{address} --save") }
end
#disable
def disable()
old_location = "#{@scriptsDir}/#{@veid}.conf"
new_location = "#{@backupDir}/#{@veid}.conf"
self.stop()
FileUtils.move( old_location, new_location )
end
#enable
def enable()
new_location = "#{@scriptsDir}/#{@veid}.conf"
old_location = "#{@backupDir}/#{@veid}.conf"
FileUtils.move( old_location, new_location )
self.start()
end
#upgrade
def upgrade_plan()
system("vzctl set #{@veid} --applyconfig @plan_id --save")
firewall = new Firewall()
firewall.veid = @veid
firewall.plan_id = @plan_id
firewall.addresses = @addresses
firewall.setup()
end
#downgrade
def downgrade_plan()
system("vzctl set #{@veid} --applyconfig @plan_id --save")
firewall = new Firewall()
firewall.veid = @veid
firewall.plan_id = @plan_id
firewall.addresses = @addresses
firewall.setup()
end
#start
def start()
system("vzctl start #{@veid}")
end
#stop
def stop()
system("vzctl stop #{@veid}")
end
#migrate
def migrate()
system("vzmigrate #{@hardwareNode} #{@veid}")
end
#check_status
def check_sys_status()
self.sys_status = `vzlist | grep #{@veid} | awk '{ print $3 }`
end
#check_net_status
def check_net_status()
end
#check_net_usage
def check_net_usage()
end
#check_disk_usage
def check_disk_usage()
output = `vzquota -b show #{@veid} | head -1`
output_array = output.scan(/\w+/)
usage = output_array[0]
softlimit = output_array[1]
hardlimit = output_array[2]
self.disk_usage = ( (usage.to_f / softlimit.to_f ) * 100 )
end
#check_cpu_usage
def check_cpu_usage()
end
#check_memory_usage
def check_mem_usage()
usage = `vzmemcheck -A #{@veid} | tail -2 | head -1`
self.mem_usage = usage[2]
end
def add(args)
usage = <<-"EOF"
usage: #{@MYNAME} [-h] [-v VEID] [-H hostname] [-n "comma,separated,nameservers"] [-o ostemplate]
[[-a "comma,separated,ip_addresses"] [-p plan_id] [-P password] ]
EOF
banner = <<-"EOF"
#{@MYNAME} #{@Version} (#{@MYDATE})
#{usage}
EOF
opts = OptionParser.new
opts.on("-h", "--help", "Show this message") {
print opts
exit 0
}
opts.on("-v", "--veid veid", String, "Virtual Environment Identifiation Number") { |veid| self.veid = veid }
opts.on("-a", "--addresses addresses", String, "Comma Separated List of IP Addresses") { |addresses| self.addresses = addresses }
opts.on("-H", "--hostname hostname", String, "Hostname of server") { |hostname| self.hostname = hostname }
opts.on("-n", "--nameservers nameservers", String, "Comma Separated List of Name Servers") { |nameservers| self.nameservers = nameservers }
opts.on("-o", "--ostemplate ostemplate", String, "Operating System Template") { |ostemplate| self.ostemplate = ostemplate }
opts.on("-p", "--plan_id plan_id", String, "Plan Level") { |plan_id| self.plan_id = plan_id }
opts.on("-P", "--password password", String, "root User Password") { |password| self.password = password }
host = opts.parse(args)
error_string = String.new
if (self.veid == nil)
error_string.concat(" ERROR: VEID required (-v)\n" )
end
if (self.addresses == nil)
error_string.concat( " ERROR: IP Addresses required in a comma separated list (-a)\n" )
end
if (self.hostname == nil)
error_string.concat( " ERROR: fully qualified domain name required (-H)\n" )
end
if (self.nameservers == nil)
error_string.concat( " ERROR: comma separated list of name server IP addresses required (-n)\n" )
end
if (self.ostemplate == nil)
error_string.concat( " ERROR: OS template required (-o)\n" )
end
if (self.plan_id == nil)
error_string.concat( " ERROR: VPS plan ID required (-p)\n" )
end
if (self.password == nil)
error_string.concat( " ERROR: VPS root password required (-P)\n" )
end
if (error_string == "")
self.create
else
puts usage
puts error_string
end
end
def check(args)
usage = <<-"EOF"
usage: #{@MYNAME} [-bhmsnudc] [-v VEID]
EOF
banner = <<-"EOF"
#{@MYNAME} #{@Version} (#{@MYDATE})
#{usage}
EOF
opts = OptionParser.new
opts.on("-h", "--help", "Show this message") {
print opts
exit 0
}
opts.on("-v", "--veid veid", String, "Virtual Environment Identifiation Number") { |veid| self.veid = veid }
opts.on("-m", "--memory", "Check Memory Usage") { |memory| self.check_mem_usage }
opts.on("-s", "--status", "Current Status of Server") { |status| self.check_sys_status }
opts.on("-n", "--netstat", "Check Network Status of Server") { |net_stat| self.check_net_status }
opts.on("-u", "--netuse", "Check Network Bandwidth Usage") { |net_use| self.check_net_usage }
opts.on("-d", "--diskuse", "Check Disk Usage") { |disk_use| self.check_disk_usage }
opts.on("-c", "--cpuuse", "Check CPU Usage") { |cpu_use| self.check_cpu_usage }
opts.on("-b", "--batch", "Batch Mode") { self.batch_mode = true }
host = opts.parse(args)
error_string = String.new
output_string = String.new
if (self.veid == nil)
error_string.concat(" ERROR: VEID required (-v)\n" )
end
if ( self.batch_mode != true )
if (self..mem_usage != nil)
output_string.concat( " Current Memory Usage: #{self.mem_usage} MB\n" )
end
if (self.sys_status != nil)
output_string.concat( " Current Virtual System Status: #{self.sys_status} \n" )
end
if (self.net_status != nil)
output_string.concat( " Current Virtual System Network Status: #{self.net_status} \n" )
end
if (self.net_usage != nil)
output_string.concat( " Current Virtual System Network Usage: #{self.net_usage} \n" )
end
if (self.disk_usage != nil)
output_string.concat( " Current Virtual System Disk Usage: #{self.disk_usage} \n" )
end
if (self.cpu_usage != nil)
output_string.concat( " Current CPU Usage: #{self.cpu_usage} \n" )
end
puts output_string
else
if (self.mem_usage != nil)
puts( "#{self.mem_usage}" )
end
if (self.sys_status != nil)
puts( "#{self.sys_status}" )
end
if (self.net_status != nil)
puts( "#{self.net_status}" )
end
if (self.net_usage != nil)
puts( "#{self.net_usage}" )
end
if (self.disk_usage != nil)
puts( "#{self.disk_usage}" )
end
if (self.cpu_usage != nil)
puts( "#{self.cpu_usage}" )
end
end
if (error_string != "")
puts usage
puts error_string
end
end
end