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

Posted in , ,  | Tags , ,