/** * Constructor. * * @param AMQPMessage $message Message with serialized event. */ public function __construct(AMQPMessage $message) { $this->message = $message; $body = Util::jsonDecode($message->body); $this->firedAt = Util::datetimeFromString($body->fired_at); $this->name = $body->name; $this->eventId = $body->event_id; $ref = new \ReflectionClass(EventName::payloadClassForName($body->name)); $this->payload = $ref->getMethod('newFromResponse')->invoke(null, $body->payload); }
/** * Constructor. * - translates 'under_score' names into 'camelCase' * - converts datetime fields value into \Datetime * * @param \stdClass $object Raw object returned by Fatmouse */ public function __construct($object) { foreach (get_object_vars($object) as $key => $value) { if (substr($key, -4) === 'date') { $value = new \Datetime($value); } $key = Util::camelize($key); $this->{$key} = $value; } }
/** * Convert object into Fatmouse request data. * * - replace camelCase with under_score * - format Datetime values as ISO8601 string * * @return \stdClass Fatmouse request part. */ public function toRequest() { $retval = new \stdClass(); $ref = new \ReflectionObject($this); foreach ($ref->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { $key = Util::underscorize($prop->getName()); $value = $this->{$prop->getName()}; if ($value instanceof self) { $value = $value->toRequest(); } elseif ($value instanceof \DateTime) { $value = Util::datetimeToString($value); } $retval->{$key} = $value; } return $retval; }
/** * Constucts object. * * @param callable $callback Callable to call for each consumed event. * @param AMQPStreamConnection $connection AMQP connection object. * @param string $queue Queue to bind. * @param string $exchange An events exchange. * @param string $bindingKey A binding key. * * @throws ErrorException On network\transport errors. */ public function __construct(callable $callback, AMQPStreamConnection $connection, $queue, $exchange, $bindingKey) { // TODO: consumer shoud has own connection with heartbeat. // The heartbeat is checked only during IO operations, so when AMQP connection // not used for a long time, RabbitMQ terminates connection $this->queue = $queue; $this->bindingKey = $bindingKey; $this->channel = $connection->channel(); Util::declareQueue($connection, $queue, ['durable' => true, 'exclusive' => false]); Util::declareExchange($connection, $exchange, 'direct', ['auto_delete' => false]); $ch->queue_bind($queue, $exchange, $bindingKey); $wrapper = function ($msg) use($callback) { $event = new Event($msg); return $callback($event); }; $ch->basic_consume($this->queue, '', false, false, false, false, $wrapper); }
/** * Translate result message value into Task object. * * @return void * @throws Errors\ClientException When result message has not yet been received. */ private function translate() { if ($this->task) { return; } if (!isset($this->message)) { throw Errors\ClientException('Result has not yet been received'); } $celeryMsg = Util::jsonDecode($this->message->body); $this->task = Fatmouse::newTaskFromCeleryMessage($celeryMsg, $this->taskName); }
/** * Generates Celery task-id and serializes task call into a message. * * @param string $taskName Celery task name. * @param array $kwargs Optional Celery task parameters. * * @return [taskId, messageBody] A pair of task-id and call message body. */ private function prepareCeleryTask($taskName, array $kwargs = null) { $taskId = str_replace('-', '', self::newUUID4()); // Celery replaces '-' in task id $task = ['id' => $taskId, 'task' => $taskName, 'args' => [], 'kwargs' => (object) $kwargs ?: new \stdClass()]; return [$taskId, Util::jsonEncode($task)]; }
/** * Generic method to synchronously call Fatmouse Agent tasks. * * Create result queue for the task and bind it to results exchange. * Scalr uses agent tasks directly in synchronous manner (mostly for * retrieving server stats), and due to very high request rate we need * to clean up per-task result queues ASAP. * * `auto-delete` argument makes sure that queue will be deleted after last queue * consumer disconnected. (Won't be deleted if there weren't any consumers) * * `x-expires` queue option means that queue will be deleted if it's unused for the * specified time in milliseconds. Unused means that queue has no consumers, * the queue has not been redeclared, and basic.get has not been invoked. * * Example: Call Fatmouse Agent task * <code> * <?php * $serverId = "cf3a320b-7ac6-4b88-810a-c760a94e1875"; * $taskName = "sys.set_hostname"; * $params = ["hostname" => "myexample.com"]; * $timeout = 5; * $result = $fatmouse->callAgentSync($serverId, $taskName, $params, $timeout); * ?> * </code> * * @param string $serverId Scalr server-id to call task at * @param string $taskName Agent task name * @param array $kwargs optional Task parameters * @param integer $timeout optional Result timeout in seconds * @return \stdClass|mixed Task result */ public function callAgentSync($serverId, $taskName, $kwargs = null, $timeout = null) { list($taskId, $serializedTask) = $this->prepareCeleryTask($taskName, $kwargs); $serverExchange = 'server.' . $serverId . '.celery'; $connection = $this->getConnection(); $channel = $connection->channel(); $timeout = $timeout === null ? $this->defaultCallAgentTimeout : $timeout; try { Util::declareQueue($connection, $taskId, ['durable' => false, 'auto_delete' => true, 'arguments' => $timeout ? ['x-expires' => $timeout * 1000] : null]); Util::declareExchange($connection, 'celeryresults', 'direct'); $channel->queue_bind($taskId, 'celeryresults', $taskId); // Sending task to server's queue $msg = new AMQPMessage($serializedTask, $this->publishParams); $channel->basic_publish($msg, $serverExchange); $result = null; $setResult = function ($msg) use(&$result) { $result = Util::jsonDecode($msg->body); }; // Setting callback for message $channel->basic_consume($taskId, '', false, true, false, false, $setResult); try { $channel->wait(null, false, $timeout); } catch (AMQPTimeoutException $e) { throw new Errors\ClientException(sprintf("Timeout %d seconds exceeded while waiting for task '%s' to complete on server '%s'", $timeout, $taskName, $server_id)); } return $result; } finally { $channel->close(); } }