Когда-то по работе возникла необходимость автоматизированного управления серверами в стойке дата-центра. Вопрос с контролем за питанием был решен через 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.