Citrix XenServerのAPI「XenAPI.py」をPHPに移植しようとして諦めた

とりあえず晒しとく。
PHP5.3ならもっとPython版に近い感じで書けただろうけど、自分の所の環境にあわせてPHP5.2で。
 
なんで諦めたかというと、PearXML_RPCの返り値(XML_RPC_Message)が汚すぎて使う気が起きなかった。
他にもっといいパーサがあるならそっちを使いたいけど、PearXML_RPCじゃ無理。
この手の型だと、PHPに無理があるのかもしれないけど。
それにしてもXML_RPC_Messageをprint_rするとやたらと時間がかかる。
 
PHPオブジェクト指向じゃないのと命名規則に統一性がないから、よく引数の順番とか忘れる。
strposを使おうとする度にリファレンスを見に行ってる気がする…。
 

XenAPI.php

PearXML_RPCが必須。

<?php
require_once 'XML/RPC.php';

define('_RECONNECT_AND_RETRY', '_RECONNECT_AND_RETRY');

class Session
{
	private $_host = null;
	private $_port = 0;

	public $session = null;
	public $last_login_method = null;
	public $last_login_params = null;

	public function __construct($uri)
	{
		if (strpos($uri, '://')===false) {
			$uri = 'http://'.$uri;
		}
		$hash = parse_url($uri);
		$this->_host = $hash['scheme'].'://'.$hash['host'];
		if (isset($hash['port'])) {
			$this->_port = $hash['port'];
		}
	}

	public function xenapi_request($methodname, $params)
	{
		if (strpos($methodname, 'login')) {
			$this->_login($methodname, $params);
			return null;
		} else {
			$retry_count = 0;
			while ($retry_count < 3) {
				$full_params = array_merge(array($this->session), $params);
				$result = _parse_result(call_user_func_array(array($this, $methodname), $full_params));
				if ($result===_RECONNECT_AND_RETRY) {
					$retry_count++;
					if ($this->last_login_method) {
						$this->_login($this->last_login_method, $this->last_login_params);
					} else {
						throw new Exception('You must log in', 401);
					}
				} else {
					return $result;
				}
			}
			throw new Exception('Tried 3 times to get a valid session, but failed', 500);
		}
	}

	public function _login($method, $params)
	{
		$result = _parse_result(call_user_func_array(array($this, 'session.'.$method), $params));
		if ($result===_RECONNECT_AND_RETRY) {
			throw new Exception('Received SESSION_INVALID when logging in', 500);
		}
		$this->session = $result->me['string'];
		$this->last_login_method = $method;
		$this->last_login_params = $params;
	}

	public function __get($name)
	{
		if ($name=='xenapi') {
			return new _Dispatcher($this, null);
		}
		return null;
	}

	public function __call($name, $args)
	{
		if (strpos($name, 'login')===0) {
			return $this->_login($name, $args);
		}
		return $this->_serverProxy($name, $args);
	}

	private function _serverProxy($name, $args)
	{
		$msg = new XML_RPC_Message($name);
		foreach ($args as $item) {
			$msg->addParam(new XML_RPC_Value($item));
		}
		$cli = new XML_RPC_client('/', $this->_host, $this->_port);
		$resp = $cli->send($msg);
		return $resp;
	}
}

function _parse_result($result)
{
	if (!isset($result->xv->me['struct']['Status']->me['string'])) {
		throw new Exception('Missing Status in response from server', 500);
	}
	if ($result->xv->me['struct']['Status']->me['string']=='Success') {
		if (isset($result->xv->me['struct']['Value'])) {
			return $result->xv->me['struct']['Value'];
		} else {
			throw new Exception('Missing Value in response from server', 500);
		}
	} else {
		if (isset($result->xv->me['struct']['ErrorDescription'])) {
			if ($result->xv->me['struct']['ErrorDescription']->me['array'][0]->me['string']=='SESSION_INVALID') {
				return _RECONNECT_AND_RETRY;
			} else {
				throw new Exception($result->xv->me['struct']['ErrorDescription']->me['array'][0]->me['string']);
			}
		} else {
			throw new Exception('Missing ErrorDescription in response from server', 500);
		}
	}
}

class _Dispatcher
{
	private $_api = null;
	private $_name = null;

	public function __construct($api, $name)
	{
		$this->_api = $api;
		$this->_setName($name);
	}

	public function __toString()
	{
		if ($this->_name) {
			return sprintf("<XenAPI._Dispatcher for %s>\n", $this->_name);
		}
		return "<XenAPI._Dispatcher>\n";
	}

	public function __get($name)
	{
		$this->_setName($name);
		return new _Dispatcher($this->_api, $this->_name);
	}

	public function __call($name, $args)
	{
		$this->_setName($name);
		return $this->_api->xenapi_request($this->_name, $args);
	}

	private function _setName($name)
	{
		if (is_null($this->_name)) {
			$this->_name = $name;
		} else {
			$this->_name .= '.'.$name;
		}
	}
}

 

使い方

これ以外試してない。

<?php
require_once 'XenAPI.php';

try {
	$session = new Session('http://192.168.0.30/');
	$session->login_with_password('root', 'pass');
	$vm_list = $session->xenapi->VM->get_all_records();
	print_r($vm_list);
} catch(Exception $e) {
	echo "### Exception ###\n";
	echo $e->getMessage()."\n";
}