PHP

Удаленное управление питанием сервера через Rack PDU



Когда-то по работе возникла необходимость автоматизированного управления серверами в стойке дата-центра. Вопрос с контролем за питанием был решен через power-свич (Switched Rack PDU), который имел возможность удаленного управления по telnet. Для эмуляции действий пользователя я написал вот такой небольшой класс на PHP. Может быть пригодится кому-нибудь еще.

Комментариев в коде не очень много, так как все рисовалось под свои нужды, но все должно быть понятно и без подробных описаний. Для каждого действия сперва выясняется "ручная" последовательность нажатий клавиш в консоли, а затем она просто воспроизводится через сокеты.
<?
// Определение команд управления питанием. Подробности
// по каждой команде читайте в документации к устройству
 
define ("IMMEDIATE_ON", 1);     // Немедленное включение порта
define ("IMMEDIATE_OFF", 2);    // Немедленное выключение порта
define ("IMMEDIATE_REBOOT", 3); // Немедленная перезагрузка
define ("DELAYED_ON", 4);       // Отложенное включение порта
define ("DELAYED_OFF", 5);      // Отложенное выключение порта
define ("DELAYED_REBOOT", 6);   // Отложенная перезагрузка
 
class telnet {
 
var $host;
var $port=23;
var $timeout=30;
var $login;
var $password;
var $connection;
var $type;
var $error;
var $errormsg;
 
define ("ESC", chr(27));
 
// Получение списка и состояния всех портов power-свича
public function Port_List() {
  $this->Get_Type();
 
  if ($this->telnet_login()) {
    $this->Put_Command("1");
    $this->Put_Command("2");
 
    if ($this->type==1) {
      $this->Put_Command("1");
    }
    $this->Put_Command("");
 
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
 
    if ($this->type==1) {
      $this->Put_Command(ESC,1);
    }
 
    $this->Put_Command("4");
 
    $data=$this->Get_Data();
    if ($data) {
      preg_match_all("/\n([0-9]+)\- (([- A-Za-z0-9\(\)]+ ){1,})".
        " {4,}([ONF]+)/",$data,$matches);
 
      if (count($matches[1])) {
        $tmp=Array();
        for ($i=0; $i<count($matches[1]); $i++) {
          $tmp[$matches[1][$i]]=Array(
            "name"=>trim($matches[2][$i]),
            "state"=>$matches[4][$i]
          );
        }
        return $tmp;
      }
      else {
        $this->error=777;
        $this->errormsg="Invalid data recived";
        return false;
      }
    }
    else {
      return false;
    }
  }
  else {
    return false;
  }
}
 
// Получение состояния отдельного порта свича
public function Get_State($port) {
  $this->Get_Type();
 
  if ($this->telnet_login()) {
    $this->Put_Command("1");
    $this->Put_Command("2");
 
    if ($this->type==1) {
      $this->Put_Command("1");
    }
 
    $this->Put_Command("");
 
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
 
    if ($this->type==1) {
      $this->Put_Command(ESC,1);
    }
 
    $this->Put_Command("4");
 
    $data=$this->Get_Data();
    if ($data) {
      $pocket=Array();
      ereg("\n".$port."- (([A-Za-z0-9]+ ){1,}) +([A-Z]+)",$data,$pocket);
      if ($pocket[3]!="") {
        return $pocket[3];
      }
      else {
        $this->error=777;
        $this->errormsg="Invalid data recived";
        return false;
      }
    }
    else {
      return false;
    }
 
  }
  else {
    return false;
  }
}
 
// Изменения состояния (включение-выключение) порта
public function Turn_Port($port,$action) {
  $this->Get_Type();
 
  if ($this->telnet_login()) {
    $this->Put_Command("1");
    $this->Put_Command("2");
 
    if ($this->type==1) {
      $this->Put_Command("1");
    }
 
    $this->Put_Command("");
 
    $this->Put_Command($port);
    $this->Put_Command($action);
 
    $this->Put_Command("YES\r\n");
 
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
 
    if ($this->type==1) {
      $this->Put_Command(ESC,1);
    }
 
    $this->Put_Command("4");
 
    $data=$this->Get_Data();
    if ($data) {
      return true;
    }
    else {
      return false;
    }
  }
  else {
    return false;
  }
}
 
// Получение времени аптайма свича
public function Get_Uptime() {
  if ($this->telnet_login()) {
    $this->Put_Command("4");
    $data=$this->Get_Data();
 
    $pocket=Array();
    eregi("Up Time   : ([0-9]+) Days? ([0-9]+) Hours?".
      " ([0-9]+) Minutes?",$data,$pocket);
    if (count($pocket)==4) {
      $tmp=Array();
      $tmp['days']=$pocket[1];
      $tmp['hours']=$pocket[2];
      $tmp['minutes']=$pocket[3];
      return $tmp;
    }
    else {
      $this->error=777;
      $this->errormsg="Invalid data recived";
      return false;
    }
  }
  else {
    return false;
  }
}
 
// Подключение по telnet
private function telnet_login() {
  if ($this->host=="") {
    $this->error=777;
    $this->errormsg="Invalid host";
    return false;
  }
  if (intval($this->port)<=1) {
    $this->error=777;
    $this->errormsg="Invalid port";
    return false;
  }
  if (intval($this->timeout)<=1) {
    $this->error=777;
    $this->errormsg="Invalid timeout";
    return false;
  }
  $this->connection=@fsockopen($this->host, $this->port,
         $this->error, $this->errormsg, $this->timeout);
  if (!$this->connection) {
    return false;
  }
  fwrite($this->connection, $this->login."\r\n");
  fwrite($this->connection, $this->password."\r\n");
  return true;
}
 
// Получение данных по telnet
private function Get_Data() {
  $data="";
  while (!feof($this->connection)) {
    $tmp=trim(fgets($this->connection,1000));
    $data.=$tmp."\n";
  }
  fclose($this->connection);
  if (trim($data)=="") {
    $this->error=777;
    $this->errormsg="Empty data recived. Please try again later";
    return false;
  }
  else {
    return $data;
  }
}
 
// Отправка данных по telnet
private function Put_Command($cmd,$no_ret=0) {
  fwrite($this->connection, $cmd.($no_ret==0?"\r\n":""));
}
 
// Вспомогательная функция получения типа свича, т.к. у разных
// моделей отличается меню управления
private function Get_Type() {
  if ($this->telnet_login()) {
    $this->Put_Command("1");
    $this->Put_Command("2");
 
    $this->Put_Command("");
 
    $this->Put_Command(ESC,1);
    $this->Put_Command(ESC,1);
 
    $this->Put_Command("4");
 
    $data=$this->Get_Data();
    if ($data=="") {
      return false;
    }
    if (strpos($data,"Outlet Control/Configuration")) {
      $this->type=1;
    }
    else {
      $this->type=0;
    }
    return true;
  }
  else {
    return false;
  }
}
 
// ---------------------------
}
?>

Доступные функции: Port_List - получение списка и состояния всех портов свича, Get_State - получение состояния отдельного порта свича, Turn_Port - изменение состояния (включение-выключение) порта, Get_Uptime - получение времени аптайма свича.

Пример использования скрипта. В нем удаленный сервер будет перезагружен путем отключения питания на свиче и включения его обратно через 5 секунд.
<?
$host='1.2.3.4'; // IP-адрес свича
$login='login';  // Логин для подключения по telnet
$password='password';  // Пароль для подключения по telnet
$port=5;         // Номер порта, который надо перезагрузить
 
// Инициализация класса
$telnet = new telnet;
$telnet->host=$host;
$telnet->login=$login;
$telnet->password=$password;
 
// Получить список и состояние портов свича
if ($tmp=$telnet->Port_List()) {
    // Если запрошенный порт есть, то перезагрузить сервер по питанию
    if ($tmp[$port]['state']!="") {
        // Выключить питание
        if ($telnet->Turn_Port($port, IMMEDIATE_OFF)) {
            // Пауза 5 секунд
            sleep(5);
            // Включить питание
            if (!$telnet->Turn_Port($port, IMMEDIATE_ON)) {
                $error=$telnet->errormsg;
            }
        }
        else {
            $error=$telnet->errormsg;
        }
    }
    else {
        // Возможно, такого порта нет
        $error=$telnet->errormsg;
    }
}
else {
    // Невозможно проверить состояние портов
    $error=$telnet->errormsg;
}
?>

Как видите, ничего сложного. На основе этого класса вы можете написать другой, для управления какими-нибудь другим устройствами, которые поддерживают протокол telnet.
Теги:
питание, сервер, Rack PDU
Добавлено: 22 Сентября 2013 02:14:11 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...