sibprogrammer/owp

View on GitHub
app/models/hardware_server.rb

Summary

Maintainability
D
1 day
Test Coverage
class HardwareServer < ActiveRecord::Base
  include ApplicationHelper

  attr_accessible :host, :auth_key, :description, :daemon_port, :use_ssl
  validates_uniqueness_of :host
  validates_numericality_of :daemon_port, :only_integer => true, :greater_than => 1023, :less_than => 49152
  has_many :os_templates, :dependent => :destroy
  has_many :server_templates, :dependent => :destroy
  has_many :virtual_servers, :dependent => :destroy

  def connect(root_password = '')
    if !auth_key.blank?
      begin
        if !rpc_client.ping
          self.errors.add :auth_key, :bad_auth
          return false
        end
      rescue SocketError
        self.errors.add :host, :connection
        return false
      end
    else
      self.auth_key = generate_id
      return false if !install_daemon(root_password)
    end

    result = save
    sync if result
    EventLog.info("hardware_server.connect", { :host => self.host })
    result
  end

  def install_daemon(root_password)
    if root_password.blank?
      self.errors.add :root_password, :empty
      return false
    end

    require 'net/ssh'
    require 'net/sftp'

    begin
      Net::SSH.start(host, 'root', :password => root_password, :config => false, :user_known_hosts_file => [], :keys => []) do |ssh|
        ssh.sftp.connect do |sftp|
          if !sftp_file_readable(sftp, '/proc/vz/version')
            self.errors.add :host, :openvz_not_found
            return false
          end

          if !sftp_file_readable(sftp, '/usr/bin/ruby')
            self.errors.add :host, :ruby_not_found
            return false
          end

          daemon_dir = '/opt/ovz-web-panel/utils/hw-daemon'

          sftp_mkdir_recursive(sftp, daemon_dir)
          sftp_mkdir_recursive(sftp, "#{daemon_dir}/certs")
          sftp.upload!(Rails.root + '/utils/hw-daemon/hw-daemon.rb', daemon_dir + '/hw-daemon.rb')
          sftp.upload!(Rails.root + '/utils/hw-daemon/certs/server.crt', daemon_dir + '/certs/server.crt')
          sftp.upload!(Rails.root + '/utils/hw-daemon/certs/server.key', daemon_dir + '/certs/server.key')
          prepare_daemon_config(sftp, daemon_dir + '/hw-daemon.ini')
          ssh.exec!("ruby #{daemon_dir}/hw-daemon.rb restart")
        end
      end
    rescue Net::SSH::AuthenticationFailed
      self.errors.add :root_password, :ssh_bad_auth
      return false
    rescue SocketError
      self.errors.add :host, :ssh_connection
      return false
    end

    true
  end

  def disconnect
    destroy
    EventLog.info("hardware_server.disconnect", { :host => self.host })
  end

  def rpc_client
    HwDaemonClient.new(host, auth_key, daemon_port, AppConfig.hw_daemon.timeout, use_ssl)
  end

  def sync_os_templates
    os_templates_on_server = rpc_client.exec('ls', "--block-size=M -s #{self.templates_dir}/cache")['output'].split("\n")
    # remove totals line
    os_templates_on_server.shift

    os_templates_list = os_templates_on_server.collect { |item| item.split[1] }

    os_templates.each do |template|
      template.destroy unless os_templates_list.include?(template.name + '.tar.gz')
    end

    os_templates_on_server.each do |template_record|
      size, template_name = template_record.split
      template_name.sub!(/\.tar\.gz/, '')

      os_template = OsTemplate.find_or_create_by_name_and_hardware_server_id(template_name, self.id)
      os_template.size = size.to_i
      os_template.save
    end
  end

  def sync_server_templates
    path = '/etc/vz/conf';
    server_templates_on_server = rpc_client.exec('ls', "#{path}/ve-*.conf-sample")['output'].split

    server_templates.each do |template|
      template.destroy unless server_templates_on_server.include?("#{path}/ve-" + template.name + '.conf-sample')
    end

    server_templates_on_server.each do |template_name|
      template_name.sub!(/\/etc\/vz\/conf\/ve\-(.*)\.conf\-sample/, '\1')
      if !ServerTemplate.find_by_name_and_hardware_server_id(template_name, self.id)
        server_template = ServerTemplate.new(:name => template_name)
        server_template.hardware_server = self
        server_template.save
      end
    end
  end

  def sync_virtual_servers
    ves_on_server = rpc_client.exec('vzlist', '-a -H -o veid,hostname,ip,status')['output'].split("\n")
    # skip error lines
    ves_on_server = ves_on_server.find_all{ |item| item =~ /^\s*\d+/ }

    ves_ids_on_server = ves_on_server.map{ |vzlist_entry| vzlist_entry = vzlist_entry.split.first }

    virtual_servers.each do |virtual_server|
      virtual_server.destroy unless ves_ids_on_server.include?(virtual_server.identity.to_s)
    end

    ves_on_server.each do |vzlist_entry|
      ve_id, host_name, ip_address, ve_state = vzlist_entry.split

      virtual_server = virtual_servers.find_by_identity(ve_id)
      virtual_server = VirtualServer.new(:identity => ve_id) unless virtual_server

      virtual_server.state = ve_state

      parser = IniParser.new(rpc_client.exec('cat', "/etc/vz/conf/#{ve_id}.conf")['output'])

      virtual_server.orig_os_template = parser.get('OSTEMPLATE')
      virtual_server.orig_server_template = parser.get('ORIGIN_SAMPLE')
      virtual_server.start_on_boot = ('yes' == parser.get('ONBOOT'))
      virtual_server.host_name = parser.get('HOSTNAME')
      virtual_server.ip_address = parser.get('IP_ADDRESS')
      virtual_server.nameserver = parser.get('NAMESERVER')
      virtual_server.search_domain = parser.get('SEARCHDOMAIN')
      virtual_server.description = parser.get('DESCRIPTION') if ve_descriptions_supported?
      virtual_server.cpu_units = parser.get('CPUUNITS')
      virtual_server.cpus = parser.get('CPUS')
      virtual_server.cpu_limit = parser.get('CPULIMIT')
      virtual_server.hardware_server = self
      virtual_server.diskspace = get_diskspace_mb(parser.get('DISKSPACE'))
      virtual_server.vswap = get_ram_mb(parser.get('SWAPPAGES'))

      if vswap and virtual_server.vswap > 0 and !unlimited_limit?(parser.get('PHYSPAGES'))
        virtual_server.memory = get_ram_mb(parser.get('PHYSPAGES'))
      else
        memory = parser.get('PRIVVMPAGES')
        virtual_server.memory = unlimited_limit?(memory) ? 0 : memory.split(":").last.to_i * 4 / 1024
        virtual_server.vswap = 0
      end

      virtual_server.save(false)
    end
  end

  def sync_backups
    backups_list = rpc_client.exec('ls', "--block-size=M -s #{backups_dir}")['output']
    backups_list = backups_list.split("\n")
    # remove totals line
    backups_list.shift

    backups_list.each do |backup_record|
      size, filename = backup_record.split
      next unless match = filename.match(/^ve-dump\.(\d+)\.\d+.tar$/)

      ve_id = match[1]
      virtual_server = VirtualServer.find_by_identity(ve_id.to_i)
      next unless virtual_server

      backup = Backup.find_by_name(filename)
      if backup
        backup.size = size.to_i
        backup.save
        next
      end

      backup = Backup.new(:name => filename, :size => size.to_i, :virtual_server_id => virtual_server.id)
      backup.save
    end
  end

  def sync_config
    parser = IniParser.new(rpc_client.exec('cat', "/etc/vz/vz.conf")['output'])
    self.default_os_template = parser.get('DEF_OSTEMPLATE')
    self.default_server_template = parser.get('CONFIGFILE')
    self.templates_dir = parser.get('TEMPLATE')
    self.backups_dir = parser.get('DUMPDIR')
    self.ve_private = parser.get('VE_PRIVATE')
    save
  end

  def sync_server_info
    self.vzctl_version = rpc_client.exec('vzctl --version')['output'].split[2]

    begin
      rpc_client.exec('ls /proc/vz/vswap')
      self.vswap = true
    rescue HwDaemonExecException => e
      self.vswap = false
    end

    sync_config
    save
  end

  def sync
    if !rpc_client.ping
      EventLog.error("hardware_server.sync_failed", { :host => self.host })
      return
    end

    sync_server_info
    sync_os_templates
    sync_server_templates
    sync_virtual_servers
    sync_backups

    EventLog.info("hardware_server.sync", { :host => self.host })
  end

  def ve_descriptions_supported?
    AppConfig.vzctl.save_descriptions and ((vzctl_version.split('.').map(&:to_i) <=> "3.0.23".split('.').map(&:to_i)) >= 0)
  end

  def reboot
    EventLog.info("hardware_server.reboot", { :host => self.host })
    rpc_client.exec('reboot &')
  end

  def disk_usage
    Watchdog.get_hw_param('disk_usage', id)
  end

  def cpu_load_average
    Watchdog.get_hw_param('cpu_load_average', id)
  end

  def memory_usage
    Watchdog.get_hw_param('memory_usage', id)
  end

  def os_version
    Watchdog.get_hw_param('os_version', id).to_s
  end

  def free_ips
    list = []
    IpPool.find(:all, :conditions => ["hardware_server_id is null OR hardware_server_id = ?", id]).each do |ip_pool|
      list |= ip_pool.free_list
    end
    list
  end

  def ve_root
    ve_private.sub('/private/', '/root/')
  end

  private

    def generate_id
      symbols = [('0'..'9'),('a'..'f')].map{ |i| i.to_a }.flatten
      (1..32).map{ symbols[rand(symbols.length)] }.join
    end

    def sftp_file_readable(sftp, file)
      sftp.stat!(file) do |response|
        return response.ok?
      end
    end

    def sftp_mkdir_recursive(sftp, directory)
      parts = directory.split('/')
      parts.shift
      current_dir = ''

      parts.each do |part|
        current_dir += "/" + part
        sftp.mkdir!(current_dir) unless sftp_file_readable(sftp, current_dir)
      end
    end

    def prepare_daemon_config(sftp, config_file)
      if !sftp_file_readable(sftp, config_file)
        upload_daemon_config(sftp, config_file)
      else
        sftp.file.open(config_file, "r") do |file|
          while (line = file.gets)
            key, value = line.split('=', 2).each { |v| v.strip! }

            case key
              when 'port' then self.daemon_port = value.to_i
              when 'key' then self.auth_key = value
              when 'ssl' then self.use_ssl = 'on' == value
            end
          end
        end

        upload_daemon_config(sftp, config_file)
      end
    end

    def upload_daemon_config(sftp, config_file)
      sftp.file.open(config_file, "w") do |file|
        file.puts "address = 0.0.0.0"
        file.puts "port = #{daemon_port.to_s}"
        file.puts "key = #{auth_key}"
        file.puts "ssl = #{use_ssl ? 'on' : 'off'}"
      end
    end

    def unlimited_limit?(limit)
      return true if limit.blank? || 'unlimited' == limit
      limit = limit.include?(':') ? limit.split(":").last : limit
      return ('unlimited' == limit || (2 ** 31 - 1) == limit.to_i || (2 ** 63 - 1) == limit.to_i)
    end

end