File: ApcSwitch.php

Recommend this page to a friend!
  Classes of Igor Dyshlenko  >  Apc Switch  >  ApcSwitch.php  >  Download  
File: ApcSwitch.php
Role: Class source
Content type: text/plain
Description: ApcSwitch class
Class: Apc Switch
Manage an APC Rack PDU over ssh
Author: By
Last change: Change accessible file
Date: 2 years ago
Size: 16,709 bytes
 

Contents

Class file image Download
<?php
/***
 * Copyright 2016 Igor Dyshlenko
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * ApcSwitch manages the power switch of the APC Switched Rack PDU over ssh2
 * connection.
 * The device allows users to assign different access to outlets. Because of
 * this, the device menus vary depending on the access rights of the particular
 * user. Therefore, access to the outlets is carried out not by the number of
 * menu items, but by the string identifiers of the outlets assigned in the
 * administrative panel of the device.
 *
 * @author Igor Dyshlenko
 * @category Console
 * @license https://opensource.org/licenses/MIT MIT
 */
class ApcSwitch {
	protected
		$shell,
		$info = array(),		// Main device info (version, uptime, etc.)
		$ids = array(),			// Outlets string ID's
		$outlets = array(),		// Outlet parameters (associative array)
		$banks = array(),		// Banks info
		$logger,
		$host,
		$currentMenu;

	const
		NMC_AOS = 'Network Management Card AOS',
		RPDU_APP = 'Rack PDU APP',
		ESCAPE = "\x1B",
		COMMAND_PROMPT = '> ';
	const
		DEVICE_MANAGER = 'Device Manager',
		BANK_MONITOR = 'Bank Monitor',
		OUTLET_MANAGAMENT = 'Outlet Management',
		OUTLET_CONTROL = 'Outlet Control/Configuration';
		

	protected static
		$getInfo = array('1', '2', '1'),
		$getIds = array(
			self::DEVICE_MANAGER,
			self::OUTLET_MANAGAMENT,
			self::OUTLET_CONTROL
		),
		$getBanks = array(
			self::DEVICE_MANAGER
		);

	/**
	 * Constructor
	 * @param string $host - host name or IP address
	 * @param string $username - user name for login to host
	 * @param string $password - password for login to host
	 * @param Log $logger - logger (PEAR Log object or null)
	 * @throws RuntimeException - error connect to host or login error
	 */
	public function __construct($host, $username, $password, $logger=null) {
		$this->host = $host;
		$this->logger = new LogWrapper($logger);

		try {
			$this->shell = new Shell(new Ssh2Connector($host, 22, $logger), self::COMMAND_PROMPT, null, $logger);
			$this->shell->login($username, $password);
			$this->shell->eol("\r");
			$this->shell->goAhead();
		} catch (Exception $exc) {
			$msg = 'Error communicate to ' . $host . '.';
			$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
			throw new RuntimeException($msg, null, $exc);
		}

		$this->logger->debug(__METHOD__ . ': Shell connected.');
		$array = $this->filterScreenArray(explode("\n", $this->shell->getResult()));

		$this->prepareVersion(self::NMC_AOS, array_shift($array));
		$this->prepareVersion(self::RPDU_APP, array_shift($array));
		array_shift($array);

		$this->logger->debug(__METHOD__ . ': Versions prepared.');

		while (FALSE === strpos(($str = array_shift($array)), '-----')) {
			$this->prepareParamString($str);
		}

		$this->logger->debug(__METHOD__ . ': Parameters prepared.');
		$this->parseMenu($array);
		$this->logger->debug(__METHOD__ . ': Menu prepared. ' . var_export($this->currentMenu, true));
	}

	protected function prepareVersion($needle, $haystack) {
		if (FALSE !== ($pos = strpos($haystack, $needle))) {
			$this->info[$needle] = trim(substr($haystack, $pos + strlen($needle)));
		}
	}

	protected function prepareParamString($paramString) {
		if (FALSE === ($pos = strpos($paramString, '          '))) {
			$this->prepareParam($paramString);
		} else {
			$this->prepareParam(substr($paramString, 0, $pos));
			$this->prepareParam(substr($paramString, $pos));
		}
	}

	protected function prepareParam($str) {
		$arr = explode(': ', $str);
		if (isset($arr[0]) && isset($arr[1])) {
			$this->info[trim($arr[0])] = trim($arr[1]);
		}
	}

	/**
	 * Get main device info.
	 * @return array - associative array with main information of device.
	 */
	public function getInfo() {
		return $this->info;
	}

	/**
	 * Get outlets ID list.
	 * @return array Outlets ID list.
	 * @throws RuntimeException - communicate error to host
	 */
	public function getIds() {
		if (empty($this->ids)){
			$this->gotoPage(self::$getIds);

			foreach ($this->currentMenu as $key => $value) {
				$this->logger->debug(__METHOD__ . ': pair ' . var_export($key, true) . ' => ' . var_export($value, true));
				$state = strtoupper(trim(strrchr($key, ' ')));
				if ($state === 'ON' || $state === 'OFF') {
					$id = trim(substr($key, 0, strlen($key) - strlen($state)));
				} else {
					$id = trim($key);
					$state = null;
				}
				$this->logger->debug(__METHOD__ . ': ID ' . var_export($id, true) . ' => state ' . var_export($state, true));
				$this->ids[$value] = $id;
				if (isset($this->outlets[$id]) && is_array($this->outlets[$id])) {
					$this->outlets[$id]['State'] = $state;
				} else {
					$this->outlets[$id] = array('State' => $state, 'Name' => $id);
				}
			}

			$this->returnToFirstPage(self::$getIds);
		}

		return $this->ids;
	}

	/**
	 * Get power banks main info.
	 * @return array - associative array with banks main info.
	 * @throws RuntimeException - communicate error to host
	 */
	public function getBanksInfo() {
		if (empty($this->banks)) {
			$this->gotoPage(self::$getBanks);

			try {
				$result = $this->shell->exec($this->currentMenu[self::BANK_MONITOR]);
			} catch (Exception $exc) {
				$msg = 'Error communicate to ' . $this->host . '.';
				$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
				throw new RuntimeException($msg, null, $exc);
			}
			$array = explode("\n", $result);
			$this->parseMenu($array);
			$this->prepareBanksParams($array);

			$arr = array_fill(0, count(self::$getBanks) + 1, null);
			$this->returnToFirstPage($arr);
			$this->logger->debug(__METHOD__ . ': ' . self::BANK_MONITOR . ' data loaded successfully.');
		}

		return $this->banks;
	}

	protected function prepareBanksParams($array) {
		foreach ($array as $key => $string) {
			if (FALSE !== stripos($string, self::BANK_MONITOR)) {
				unset($array[$key]);
				break;
			}
			unset($array[$key]);
		}
		foreach ($array as $key => $string) {
			if (FALSE !== strpos($string, '----')) {
				unset($array[$key]);
				break;
			}
			unset($array[$key]);
		}
		foreach ($array as $string) {
			$length = strlen($str = str_replace('  ', ' ', trim($string)));
			while ($length > ($l = strlen($str = str_replace('  ', ' ', $str)))) {$length = $l;}
			$values = explode(' ', $str);
			if (count($values) < 7){
				break;
			}
			$this->banks[$values[0]] = array(
				'Restrictions'	=>	$values[1],
				'Load'			=>	$values[2],
				'Low'			=>	$values[3],
				'NearOver'		=>	$values[4],
				'Over'			=>	$values[5],
				'State'			=>	implode(' ', array_slice($values, 6))
			);
		}
	}

	protected function gotoPage(&$path) {
		foreach ($path as $command) {
			try {
				$result = $this->shell->exec($this->currentMenu[$command]);
			} catch (Exception $exc) {
				$msg = 'Error communicate to ' . $this->host . '.';
				$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
				throw new RuntimeException($msg, null, $exc);
			}
			$this->parseMenu(explode("\n", $result));
//			$this->logger->debug(var_export($this->currentMenu, true));
		}
	}

	protected function returnToFirstPage(&$path) {
		foreach ($path as $nothing) {
			try {
				$this->shell->write(self::ESCAPE);
				$this->shell->goAhead();
			} catch (Exception $exc) {
				$msg = 'Error communicate to ' . $this->host . '.';
				$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
				throw new RuntimeException($msg, null, $exc);
			}
			$this->parseMenu(explode("\n", $this->shell->getResult()));
//			$this->logger->debug(var_export($this->currentMenu, true));
		}
	}

	protected function parseMenu($stringsArray) {
		$this->currentMenu = array();
		foreach ($stringsArray as $string) {
			$pair = explode('- ', $string);
			if (isset($pair[0]) && isset($pair[1]) && (intval(trim($pair[0])) > 0)) {
				$this->currentMenu[trim($pair[1])] = intval(trim($pair[0]));
			}
		}
	}

	/**
	 * Get detail outlet info.
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 * @return associative array with detail information of outlet.
	 * @throws RuntimeException - communicate error to host
	 */
	public function getOutletInfo($outletId) {
		if ($this->isOutletIdOk($outletId)) {
			$this->outletOperation($outletId);
			return $this->outlets[$outletId];
		}

		return null;
	}

	/**
	 * Turn outlet to "ON" or "OFF".
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 * @param mixed $newState - string "on" or "yes", int 1 or bool true for turn
	 *				"ON"; string "off", "no" or "" (empty string), int 0 or bool
	 *				false for turn "OFF"; otherwise do nothing.
	 * @param mixed $delayed - use delayed operation if string "on" or "yes",
	 *				int 1 or bool true (default FALSE).
	 * @return mixed new state: boolean true if turned ON, false if turned OFF,
	 *				NULL if operation incomplette or outlet ID is incorrect.
	 * @throws RuntimeException - communicate error to host
	 */
	public function turn($outletId, $newState, $delayed=false) {
		$stateTo = filter_var($newState, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

		if (is_bool($stateTo) && $this->isOutletIdOk($outletId)) {
			$useDelayed = filter_var($delayed, FILTER_VALIDATE_BOOLEAN);
			return $this->outletOperation($outletId, $stateTo ? 'On' : 'Off', $useDelayed);
		}

		return null;
	}

	/**
	 * Turn outlet to "ON".
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 *				returned $this->getIds().
	 * @param mixed $delayed - use delayed operation if string "on" or "yes",
	 *				int 1 or bool true (default FALSE).
	 * @return mixed new state: boolean true if turned ON, false if turned OFF,
	 *				NULL if operation incomplette or outlet ID is incorrect.
	 * @throws RuntimeException - communicate error to host
	 */
	public function turnOn($outletId, $delayed=false) {
		return $this->turn($outletId, true, $delayed);
	}

	/**
	 * Turn outlet to "OFF".
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 * @param mixed $delayed - use delayed operation if string "on" or "yes",
	 *				int 1 or bool true (default FALSE).
	 * @return mixed new state: boolean true if turned ON, false if turned OFF,
	 *				NULL if operation incomplette or outlet ID is incorrect.
	 * @throws RuntimeException - communicate error to host
	 */
	public function turnOff($outletId, $delayed=false) {
		return $this->turn($outletId, false, $delayed);
	}

	/**
	 * Reboot operation. Turn "OFF" the outlet, then after pause - turn it "ON".
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 * @param mixed $delayed - use delayed operation if string "on" or "yes",
	 *				int 1 or bool true (default FALSE).
	 * @return mixed new state: boolean true if turned ON, false if turned OFF,
	 *				NULL if operation incomplette or outlet ID is incorrect.
	 * @throws RuntimeException - communicate error to host
	 */
	public function reboot($outletId, $delayed=false) {
		if ($this->isOutletIdOk($outletId)) {
			$useDelayed = filter_var($delayed, FILTER_VALIDATE_BOOLEAN);
			return $this->outletOperation($outletId, 'Reboot', $useDelayed);
		}

		return null;
	}

	/**
	 * Cancel all pending (delayed) commands for outlet.
	 * @param string $outletId - string outlet ID returned $this->getIds().
	 * @throws RuntimeException - communicate error to host
	 */
	public function cancelDelayed($outletId) {
		if ($this->isOutletIdOk($outletId)) {
			return $this->outletOperation($outletId, 'Cancel');
		}

		return null;
	}

	/**
	 * Get APC Switch host name or IP.
	 * @return string host name or IP.
	 */
	public function getHost() {
		return $this->host;
	}

	protected function isOutletIdOk($outletId) {
		$this->getIds();
		return	is_int($outletId) && array_key_exists($outletId, $this->ids) ||
				is_string($outletId) && in_array($outletId, $this->ids);
	}

	protected function outletOperation($outletId, $operation=null, $delayed=false) {
		$complette = false;
		$command = is_int($outletId) ? $outletId : array_search($outletId, $this->ids);
		$this->gotoPage(self::$getIds);

		try {
			$result = $this->shell->exec($command);
		} catch (Exception $exc) {
			$msg = 'Error communicate to ' . $this->host . '.';
			$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
			throw new RuntimeException($msg, null, $exc);
		}

		$this->prepareOutletOperationScreenArray($this->ids[$command], explode("\n", $result));
		$menuItem = $this->switchOutletOperationName($operation, $delayed, $command);

		$this->logger->debug(__METHOD__ . ': Operation "' . (is_null($menuItem) ? 'nothing' : $menuItem) . '".');
		if (isset($this->currentMenu[$menuItem])) {
			try {
				$this->shell->write($this->currentMenu[$menuItem] . $this->shell->eol() . 'yes' . $this->shell->eol());
				$this->shell->read('Press <ENTER> to continue...');
				$this->shell->getResult();
				$this->shell->write($this->shell->eol());
				$this->shell->goAhead();
			} catch (Exception $exc) {
				$msg = 'Error communicate to ' . $this->host . '.';
				$this->logger->err(__METHOD__ . ': ' . $msg .  "\n" . $exc->getTraceAsString());
				throw new RuntimeException($msg, null, $exc);
			}
			$this->prepareOutletOperationScreenArray($this->ids[$command], explode("\n", $this->shell->getResult()));
			$complette = true;
			$this->logger->debug(__METHOD__ . ': Operation "' . $menuItem . '" complette.');
			
		} elseif (!is_null($menuItem)) {
			$msg = 'Incorrect operation "' . var_export($menuItem, true) . '".';
			$this->logger->err(__METHOD__ . ': ' . $msg);
			throw new RuntimeException($msg);
		}

		$arr = array_fill(0, count(self::$getIds) + 1, null);
		$this->returnToFirstPage($arr);

		return $complette;
	}

	protected function prepareOutletParam($outletId, $str) {
		$arr = explode(': ', $str);
		if (isset($arr[0]) && isset($arr[1])) {
			$this->outlets[$outletId][trim($arr[0])] = trim($arr[1]);
			return true;
		}
		return false;
	}

	protected function filterScreenArray($screenArray) {
		unset($screenArray[0]);
		$prompt = trim($this->shell->prompt());
		foreach ($screenArray as $i => $str) {
			$str = trim($str);
			if (empty($str) || ($prompt === $str)) {
				unset($screenArray[$i]);
			}
		}
		return $screenArray;
	}

	protected function prepareOutletOperationScreenArray($strOutletId, $screenArray) {
		$array = $this->filterScreenArray($screenArray);

		array_shift($array);
		while ($this->prepareOutletParam($strOutletId, ($str = array_shift($array)))) {}
		array_unshift($array, $str);

		$this->parseMenu($array);
	}

	protected function switchOutletOperationName($operationName, $delayed, $intOutletId) {
		switch (strtoupper($operationName)) {
			case 'ON':
				return ($delayed ? 'Delayed' : 'Immediate') . ' On';

			case 'OFF':
				return ($delayed ? 'Delayed' : 'Immediate') . ' Off';

			case 'REBOOT':
				return ($delayed ? 'Delayed' : 'Immediate') . ' Reboot';

			case 'CANCEL':
				$menuItem = 'Cancel';
				if ($this->ids[$intOutletId] === 'ALL Accessible Outlets') {
					$menuItem .= ' Pending Commands';
				}
				return $menuItem;
		}

		return null;
	}

	public function disconnect() {
		try {
			$this->shell->logout();
		} catch (Exception $ex) {}
	}
}

For more information send a message to info at phpclasses dot org.