. * * @author Julien Fontanet * @license http://www.gnu.org/licenses/gpl-3.0-standalone.html GPLv3 * * @package Xen Orchestra Server */ use Bean\User; /** * */ final class Application extends Base { /** * */ function __construct(DI $di) { parent::__construct(); $this->_di = $di; } /** * */ function api_session_signInWithPassword($id, array $params, Client $c) { // Checks parameters. if (!isset($params[0], $params[1])) { return -32602; // Invalid params. } list($name, $password) = $params; // Checks the client is not already authenticated. if ($c->isAuthenticated()) { return array(0, 'already authenticated'); } $users = $this->_di->get('users'); // Checks the user exists. $user = $users->first(array('name' => $name), false); if (!$user) { return array(1, 'invalid credential'); } // Checks the password matches. if (!password_verify($password, $user->password)) { return array(1, 'invalid credential'); } // Checks whether the hash needs to be updated. if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) { $user->password = password_hash($password, PASSWORD_DEFAULT); $users->save($user); } // Marks the client as authenticated. $c->uid = $user->id; // Returns success. $c->respond($id, true); } /** * */ function api_session_signInWithToken($id, array $params, Client $c) { // Checks parameters. if (!isset($params[0])) { return -32602; // Invalid params. } $token = $params[0]; // Checks the client is not already authenticated. if ($c->isAuthenticated()) { return array(0, 'already authenticated'); } $tokens = $this->_di->get('tokens'); // Checks the token exists. $token = $tokens->first($token, false); if (!$token) { return array(1, 'invalid token'); } // Checks the token is valid. if ($token->expiration < time()) { $tokens->delete($token->id); return array(1, 'invalid token'); } // Marks the client as authenticated. $c->uid = $token->user_id; // Returns success. $c->respond($id, true); } /** * */ function api_session_getUser($id, array $params, Client $c) { if (!$c->isAuthenticated()) { return array(0, 'not authenticated'); } $user = $this->_di->get('users')->first($c->uid); $c->respond($id, array( 'id' => $user->id, 'name' => $user->name, 'permission' => $user->permission, )); } /** * */ function api_session_createToken($id, array $params, Client $c) { // Checks the client is authenticated. if (!$c->isAuthenticated()) { return array(0, 'not authenticated'); } $tokens = $this->_di->get('tokens'); // Generates the token and makes sure it is unique. do { /* If available, we use OpenSSL to create more secure tokens. * * @todo Move the “if” outside of this function and furthermore of * this loop for performance concerns. */ if (function_exists('openssl_random_pseudo_bytes')) { $token = bin2hex(openssl_random_pseudo_bytes(32)); } else { $token = uniqid('', true); } } while ($tokens->exists($token)); // Registers it. $tokens->create(array( 'id' => $token, 'expiration' => time() + 604800, // One week 'user_id' => $c->uid, )); // Returns it. $c->respond($id, $token); } /** * */ function api_session_destroyToken($id, array $params, Client $c) { // Checks parameters. if (!isset($params[0])) { return -32602; // Invalid params. } $token_id = $params[0]; $tokens = $this->_di->get('tokens'); // Checks the token exists. if (!$tokens->exists($token_id)) { return array(0, 'invalid token'); } // Deletes it. $tokens->delete($token_id); // Returns success. $c->respond($id, true); } /** * */ function api_user_create($id, array $params, Client $c) { // Checks parameters. if (!isset($params[0], $params[1])) { return -32602; // Invalid params. } list($name, $password) = $params; // Checks credentials. if (!$c->isAuthenticated() || !$this->_checkPermission($c->uid, User::ADMIN)) { return array(0, 'not authorized'); } // Checks the provided user name. if (!User::check('name', $name)) { return array(1, 'invalid user name'); } // Checks the provided password. if (!User::check('password', $password)) { return array(2, 'invalid password'); } // Checks provided permission. if (isset($params[2])) { $permission = $params[2]; if (!User::check('permission', $permission)) { return array(3, 'invalid permission'); } } else { $permission = User::NONE; } $users = $this->_di->get('users'); // Checks if the user name is already used. if ($users->exists(array('name' => $name))) { return array(4, 'user name already taken'); } // Creates the user. $user = $users->create(array( 'name' => $name, 'password' => $password, 'permission' => $permission, )); // Returns the identifier. $c->respond($id, $user->id); } /** * */ function api_user_delete($id, array $params, Client $c) { // Checks parameter. if (!isset($params[0])) { return -32602; // Invalid params. } $uid = $params[0]; // Checks credentials. if (!$c->isAuthenticated() || !$this->_checkPermission($c->uid, User::ADMIN)) { return array(0, 'not authorized'); } $users = $this->_di->get('users'); // Checks user exists and is not the current user. if (($uid === $c->uid) || !$users->exists($uid)) { return array(1, 'invalid user'); } // Deletes the user. $users->delete($uid); // Returns success. $c->respond($id, true); } /** * */ function api_user_changePassword($id, array $params, Client $c) { // Checks parameters. if (!isset($params[0], $params[1])) { return -32602; // Invalid params. } list($old, $new) = $params; // Checks the client is authenticated. if (!$c->isAuthenticated()) { return array(0, 'not authenticated'); } $users = $this->_di->get('users'); $user = $users->first($c->uid); // Checks the old password matches. if (!password_verify($old, $user->password)) { return array(1, 'invalid credential'); } // Checks the new password is valid. if (($new === $old) || !$user->checkAndSet('password', $new)) { return array(2, 'invalid password'); } $users->save($user); // Returns success. $c->respond($id, true); } /** * */ function api_user_getAll($id, array $params, Client $c) { // Checks credentials. if (!$c->isAuthenticated() || !$this->_checkPermission($c->uid, User::ADMIN)) { return array(0, 'not authorized'); } $users = $this->_di->get('users')->getArray( null, array('id', 'name', 'permission') ); foreach ($users as &$user) { $user['permission'] = User::permissionToString($user['permission']); } $c->respond($id, $users); } /** * */ function api_user_set($id, array $params, Client $c) { // Checks parameter. if (!isset($params[0], $params[1])) { return -32602; // Invalid params. } list($uid, $properties) = $params; if (!$c->isAuthenticated() || !$this->_checkPermission($c->uid, User::ADMIN)) { return array(0, 'not authorized'); } $users = $this->_di->get('users'); // Checks user exists and is not the current user. if (($uid === $c->uid) || !($user = $users->first($uid, false))) { return array(1, 'invalid user'); } foreach ($properties as $field => $value) { if ('name' === $field) { if (!$user->checkAndSet('name', $value)) { return array(3, 'invalid user name'); } } elseif ('password' === $field) { if (!$user->checkAndSet('password', $value)) { return array(4, 'invalid password'); } } elseif ('permission' === $field) { if (!$user->checkAndSet('permission', $value)) { return array(5, 'invalid permission '.$value); } } else { return array(2, 'invalid property'); } } $users->save($user); $c->respond($id, true); } /** * */ function api_vm_get($id, array $params, Client $c) { // Checks parameter. if (!isset($params[0])) { return -32602; // Invalid params. } $di = $this->_di; $vm = $di->get('vms')->first(array('uuid' => $params[0]), false); if (!$vm) { return array(0, 'invalid VM reference'); } $mgr_guest_metrics = $di->get('vms_guest_metrics'); $mgr_hosts = $di->get('hosts'); $mgr_metrics = $di->get('vms_metrics'); $mgr_vbds = $di->get('vbds'); $mgr_vifs = $di->get('vifs'); $mgr_srs = $di->get('srs'); $guest_metrics = $mgr_guest_metrics->first($vm->guest_metrics, false); $host = $mgr_hosts->first($vm->resident_on, false); $metrics = $mgr_metrics->first($vm->metrics); if ($guest_metrics && ($_ = $guest_metrics->memory)) { $used_memory = $_['used']; $total_memory = $_['total']; } else { $used_memory = null; $total_memory = $metrics->memory_actual; } $networks = $guest_metrics ? $guest_metrics->networks : null; $start_time = (0 === $metrics->start_time['timestamp']) ? null : $metrics->start_time['timestamp']; $os_version = $guest_metrics ? $guest_metrics->os_version : null; $pv_drivers_up_to_date = $guest_metrics ? $guest_metrics->PV_drivers_up_to_date : false; $vbds = array(); foreach ($vm->VBDs as $vbd_ref) { $vbd = $mgr_vbds->first($vbd_ref); $vbds[] = $vbd->getProperties(); } $vifs = array(); foreach ($vm->VIFs as $vif_ref) { $vif = $mgr_vifs->first($vif_ref); $vifs[] = $vif->getProperties(); } $entry = array( 'bios' => $vm->bios_strings, 'host_name' => $host ? $host->name_label : null, 'host_uuid' => $host ? $host->uuid : null, 'HVM_boot_params' => $vm->HVM_boot_params, 'memory_dynamic_max' => $vm->memory_dynamic_max, 'memory_dynamic_min' => $vm->memory_dynamic_min, 'name_description' => $vm->name_description, 'name_label' => $vm->name_label, 'networks' => $networks, 'os_version' => $os_version, 'power_state' => $vm->power_state, 'PV_drivers_up_to_date' => $pv_drivers_up_to_date, 'start_time' => $start_time, 'tags' => $vm->tags, 'total_memory' => $total_memory, 'used_memory' => $used_memory, 'uuid' => $vm->uuid, 'VBDs' => $vbds, 'VCPUs_number' => $metrics->VCPUs_number, 'VCPUs_utilisation' => $metrics->VCPUs_utilisation, 'VIFs' => $vifs, ); $c->respond($id, $entry); } /** * */ function api_vm_getAll($id, array $params, Client $c) { // @todo Handles parameter. $di = $this->_di; $mgr_guest_metrics = $di->get('vms_guest_metrics'); $mgr_hosts = $di->get('hosts'); $mgr_metrics = $di->get('vms_metrics'); $mgr_vms = $di->get('vms'); $vms = $mgr_vms->get(array( 'is_a_snapshot' => false, 'is_a_template' => false, 'is_control_domain' => false, )); $entries = array(); foreach ($vms as $vm) { $guest_metrics = $mgr_guest_metrics->first($vm->guest_metrics, false); $host = $mgr_hosts->first($vm->resident_on, false); $metrics = $mgr_metrics->first($vm->metrics); if ($guest_metrics && ($_ = $guest_metrics->memory)) { $used_memory = $_['used']; $total_memory = $_['total']; } else { $used_memory = null; $total_memory = $metrics->memory_actual; } $networks = $guest_metrics ? $guest_metrics->networks : null; $start_time = (0 === $metrics->start_time['timestamp']) ? null : $metrics->start_time['timestamp']; $entries[] = array( 'host_name' => $host ? $host->name_label : null, 'host_uuid' => $host ? $host->uuid : null, 'name_description' => $vm->name_description, 'name_label' => $vm->name_label, 'networks' => $networks, 'power_state' => $vm->power_state, 'start_time' => $start_time, 'total_memory' => $total_memory, 'used_memory' => $used_memory, 'uuid' => $vm->uuid, 'VBDs' => count($vm->VBDs), 'VCPUs_utilisation' => $metrics->VCPUs_utilisation, 'VIFs' => count($vm->VIFs), ); // var_export(array( // 'vm' => $vm->getProperties(), // 'metrics' => $metrics->getProperties(), // 'guest_metrics' => $guest_metrics->getProperties(), // )); } $c->respond($id, $entries); } /** * */ function api_xo_getStats($id, array $params, Client $c) { $mgr_vms = $this->_di->get('vms'); $mgr_metrics = $this->_di->get('vms_metrics'); $memory = 0; $n_vcpus = 0; $n_vifs = 0; $running_vms = $mgr_vms->get(array( 'power_state' => 'Running', 'is_control_domain' => false, )); foreach ($running_vms as $vm) { $metrics = $mgr_metrics->first($vm->metrics); $memory += $metrics->memory_actual; $n_vcpus += $metrics->VCPUs_number; $n_vifs += count($vm->VIFs); } // @todo Replace with inequality filter when Rekodi implements it. $srs = $this->_di->get('srs')->getArray(array( 'shared' => true, )); $n_srs = 0; foreach ($srs as $sr) { if (-1 != $sr['physical_size']) { ++$n_srs; } } $stats = array( 'hosts' => $mgr_vms->count(array( 'is_control_domain' => true, )), 'vms' => $mgr_vms->count(array( 'is_a_snapshot' => false, 'is_a_template' => false, 'is_control_domain' => false, )), 'running_vms' => count($running_vms), 'memory' => $memory, 'vcpus' => $n_vcpus, 'vifs' => $n_vifs, 'srs' => $n_srs, ); $c->respond($id, $stats); } /** * */ function handleServer($handle, $data) { if (feof($handle)) { // Stops listening to this socket. return false; } $handle = @stream_socket_accept($handle, 10); if (!$handle) { trigger_error( 'error while handling an incoming connection', E_USER_ERROR ); } /* Here we build a map for all available methods. * * This technic provides fast case sensitive matching (compare to * “is_callable()”). */ static $methods; if ($methods === null) { $methods = array(); foreach (get_class_methods($this) as $method) { if (!substr_compare($method, 'api_', 0, 4)) { $_ = strtr(substr($method, 4), '_', '.'); $methods[$_] = array($this, $method); } } } new Client( $data['loop'], $handle, $methods ); echo "new client connected\n"; } /** * */ function handleXenEvents(array $events) { static $keys; $objects = array(); foreach ($events as $event) { $class = $event['class']; $ref = $event['ref']; $snapshot = $event['snapshot']; // Not present in the documentation. $objects[$class][$ref] = $snapshot; echo "Event: $class ($ref)\n"; } isset($objects['message']) and $this->_di->get('messages')->batchImport($objects['message']); isset($objects['network']) and $this->_di->get('networks')->batchImport($objects['network']); isset($objects['pool']) and $this->_di->get('pools')->batchImport($objects['pool']); isset($objects['sr']) and $this->_di->get('srs')->batchImport($objects['sr']); isset($objects['vbd']) and $this->_di->get('vbds')->batchImport($objects['vbds']); isset($objects['vif']) and $this->_di->get('vifs')->batchImport($objects['vifs']); isset($objects['vm']) and $this->_di->get('vms')->batchImport($objects['vm']); isset($objects['vm_guest_metrics']) and $this->_di->get('vms_guest_metrics')->batchImport($objects['vm_guest_metrics']); isset($objects['vm_metrics']) and $this->_di->get('vms_metrics')->batchImport($objects['vm_metrics']); // Requeue this request. return true; } /** * */ function run() { $config = $this->_di->get('config'); $loop = $this->_di->get('loop'); //-------------------------------------- // Creates master sockets. foreach ($config['listen'] as $uri) { $handle = self::_createServer($uri); $loop->addRead($handle, array($this, 'handleServer')); } //-------------------------------------- // map(XCP class: manager) $classes = array( 'host' => 'hosts', 'message' => 'messages', 'network' => 'networks', 'pool' => 'pools', 'SR' => 'srs', 'VBD' => 'vbds', 'VIF' => 'vifs', 'VM' => 'vms', 'VM_guest_metrics' => 'vms_guest_metrics', 'VM_metrics' => 'vms_metrics', ); foreach ($config['xcp'] as $_) { $xcp = new XCP($loop, $_['url'], $_['username'], $_['password']); foreach ($classes as $class => $manager) { $xcp->queue( $class.'.get_all_records', null, array($this->_di->get($manager), 'batchImport') ); } $xcp->queue( 'event.register', array(array('host', 'pool', 'vm')) ); $xcp->queue( 'event.next', null, array($this, 'handleXenEvents') ); } //-------------------------------------- $loop->run(array( 'loop' => $loop, 'server' => $this )); } /** * */ private static function _createServer($uri) { list($transport, $target) = explode('://', $uri, 2); if (($transport === 'unix') || ($transport === 'udg')) { @unlink($target); } $handle = @stream_socket_server( $uri, /* out */ $errno, /* out */ $errstr ); if (!$handle) { trigger_error( "could not create the server socket $uri: $errno - $errstr", E_USER_ERROR ); } return $handle; } /** * Dependency injector. * * @var DI */ private $_di; /** * @var array */ private $_xenPools = array(); /** * @var array */ private $_xenHosts = array(); /** * */ private static function _tS($val) { if (is_scalar($val)) { return (string) $val; } return gettype($val); } /** * */ private function _update(&$old, $new) { // There was no previous record. if ($old === null) { echo "new record\n"; $old = $new; return; } // The record has been deleted. if ($new === null) { echo "record deleted\n"; $old = null; return; } $old_keys = array_keys($old); $new_keys = array_keys($new); foreach (array_diff($old_keys, $new_keys) as $key) { $_ = self::_tS($old[$key]); echo "field removed: $key => $_\n"; } foreach (array_diff($new_keys, $old_keys) as $key) { $_ = self::_tS($new[$key]); echo "field added: $key => $_\n"; } foreach (array_intersect($new_keys, $old_keys) as $key) { if ($new[$key] === $old[$key]) { continue; } $_1 = self::_tS($old[$key]); $_2 = self::_tS($new[$key]); echo "field changed: $key => $_1 → $_2\n"; } $old = $new; } /** * */ private function _checkPermission($uid, $permission, $object = null) { $user = $this->_di->get('users')->first($uid); return ($user->permission >= $permission); } }