Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15459050
D17380.id41789.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D17380.id41789.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -78,10 +78,15 @@
'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php',
'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php',
'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php',
+ 'PhageAction' => 'phage/action/PhageAction.php',
+ 'PhageAgentAction' => 'phage/action/PhageAgentAction.php',
'PhageAgentBootloader' => 'phage/bootloader/PhageAgentBootloader.php',
'PhageAgentTestCase' => 'phage/__tests__/PhageAgentTestCase.php',
+ 'PhageExecuteAction' => 'phage/action/PhageExecuteAction.php',
+ 'PhageLocalAction' => 'phage/action/PhageLocalAction.php',
'PhagePHPAgent' => 'phage/agent/PhagePHPAgent.php',
'PhagePHPAgentBootloader' => 'phage/bootloader/PhagePHPAgentBootloader.php',
+ 'PhagePlanAction' => 'phage/action/PhagePlanAction.php',
'Phobject' => 'object/Phobject.php',
'PhobjectTestCase' => 'object/__tests__/PhobjectTestCase.php',
'PhutilAPCKeyValueCache' => 'cache/PhutilAPCKeyValueCache.php',
@@ -669,10 +674,15 @@
'LinesOfALargeFileTestCase' => 'PhutilTestCase',
'MFilterTestHelper' => 'Phobject',
'PHPASTParserTestCase' => 'PhutilTestCase',
+ 'PhageAction' => 'Phobject',
+ 'PhageAgentAction' => 'PhageAction',
'PhageAgentBootloader' => 'Phobject',
'PhageAgentTestCase' => 'PhutilTestCase',
+ 'PhageExecuteAction' => 'PhageAction',
+ 'PhageLocalAction' => 'PhageAgentAction',
'PhagePHPAgent' => 'Phobject',
'PhagePHPAgentBootloader' => 'PhageAgentBootloader',
+ 'PhagePlanAction' => 'PhageAction',
'Phobject' => 'Iterator',
'PhobjectTestCase' => 'PhutilTestCase',
'PhutilAPCKeyValueCache' => 'PhutilKeyValueCache',
diff --git a/src/phage/action/PhageAction.php b/src/phage/action/PhageAction.php
new file mode 100644
--- /dev/null
+++ b/src/phage/action/PhageAction.php
@@ -0,0 +1,49 @@
+<?php
+
+abstract class PhageAction
+ extends Phobject {
+
+ private $actions = array();
+
+ abstract public function isContainerAction();
+
+ protected function willAddAction(PhageAction $action) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ final protected function getActions() {
+ $this->requireContainerAction();
+
+ return $this->actions;
+ }
+
+ final public function addAction(PhageAction $action) {
+ $this->requireContainerAction();
+
+ $this->willAddAction($action);
+
+ $this->actions[] = $action;
+ }
+
+ protected function getAllWaitingChannels() {
+ if (!$this->isContainerAction()) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ $channels = array();
+ foreach ($this->getActions() as $action) {
+ foreach ($action->getAllWaitingChannels() as $channel) {
+ $channels[] = $channel;
+ }
+ }
+
+ return $channels;
+ }
+
+ private function requireContainerAction() {
+ if (!$this->isContainerAction()) {
+ throw new Exception(pht('This is not a container action.'));
+ }
+ }
+
+}
diff --git a/src/phage/action/PhageAgentAction.php b/src/phage/action/PhageAgentAction.php
new file mode 100644
--- /dev/null
+++ b/src/phage/action/PhageAgentAction.php
@@ -0,0 +1,186 @@
+<?php
+
+abstract class PhageAgentAction
+ extends PhageAction {
+
+ private $future;
+ private $channel;
+ private $commands = array();
+ private $commandKey = 0;
+
+ private $isExiting = false;
+ private $isActive = false;
+
+ final public function isContainerAction() {
+ return true;
+ }
+
+ protected function willAddAction(PhageAction $action) {
+ // TODO: You should probably be able to add agents to an agent, is that
+ // explicit or by making agents execute actions?
+
+ if (!($action instanceof PhageExecuteAction)) {
+ throw new Exception(pht('Can only add execute actions to an agent.'));
+ }
+ }
+
+ public function isActiveAgent() {
+ return $this->isActive;
+ }
+
+ abstract protected function newAgentFuture(PhutilCommandString $command);
+
+ protected function getAllWaitingChannels() {
+ $channels = array();
+
+ if ($this->isActiveAgent()) {
+ $channels[] = $this->channel;
+ }
+
+ return $channels;
+ }
+
+ public function startAgent() {
+ $bootloader = new PhagePHPAgentBootloader();
+
+ $future = $this->newAgentFuture($bootloader->getBootCommand());
+
+ $future->write($bootloader->getBootSequence(), $keep_open = true);
+
+ $channel = new PhutilExecChannel($future);
+ $channel->setStderrHandler(array($this, 'didReadAgentStderr'));
+
+ $channel = id(new PhutilLogFileChannel($channel))
+ ->setLogfile('proto.log');
+
+ $channel = new PhutilJSONProtocolChannel($channel);
+
+ foreach ($this->getActions() as $command) {
+ $key = 'command/'.$this->commandKey++;
+
+ $this->commands[$key] = array(
+ 'key' => $key,
+ 'command' => $command,
+ );
+
+ $channel->write(
+ array(
+ 'type' => 'EXEC',
+ 'key' => $key,
+ 'command' => $command->getCommand()->getUnmaskedString(),
+ ));
+ }
+
+ $this->future = $future;
+ $this->channel = $channel;
+ $this->isActive = true;
+ }
+
+ public function updateAgent() {
+ if (!$this->isActiveAgent()) {
+ return;
+ }
+
+ $channel = $this->channel;
+
+ while (true) {
+ $is_open = $channel->update();
+
+ $message = $channel->read();
+ if ($message !== null) {
+ switch ($message['type']) {
+ case 'TEXT':
+ $key = $message['key'];
+ $this->writeOutput($key, $message['kind'], $message['text']);
+ break;
+ case 'RSLV':
+ $key = $message['key'];
+ $command = $this->commands[$key]['command'];
+
+ $this->writeOutput($key, 'stdout', $message['stdout']);
+ $this->writeOutput($key, 'stderr', $message['stderr']);
+
+ $exit_code = $message['err'];
+
+ if ($exit_code != 0) {
+ $exit_code = $this->formatOutput(
+ pht(
+ 'Command ("%s") exited nonzero ("%s")!',
+ $command->getCommand(),
+ $exit_code),
+ $key.'/exit');
+
+ fprintf(STDOUT, $exit_code);
+ }
+
+ unset($this->commands[$key]);
+
+ if (!$this->commands) {
+ $channel->write(
+ array(
+ 'type' => 'EXIT',
+ 'key' => 'exit',
+ ));
+
+ $this->isExiting = true;
+ break;
+ }
+ }
+ }
+
+ if (!$is_open) {
+ if ($this->isExiting) {
+ $this->isActive = false;
+ break;
+ } else {
+ throw new Exception(pht('Channel closed unexpectedly!'));
+ }
+ }
+ }
+ }
+
+ private function writeOutput($key, $kind, $text) {
+ if (!strlen($text)) {
+ return;
+ }
+
+ switch ($kind) {
+ case 'stdout':
+ $target = STDOUT;
+ break;
+ case 'stderr':
+ $target = STDERR;
+ break;
+ default:
+ throw new Exception(pht('Unknown output kind "%s".', $kind));
+ }
+
+ $command = $this->commands[$key]['command'];
+
+ $label = $command->getLabel();
+ if (!strlen($label)) {
+ $label = pht('Unknown Command');
+ }
+
+ $text = $this->formatOutput($text, $label);
+ fprintf($target, $text);
+ }
+
+ private function formatOutput($output, $context) {
+ $output = phutil_split_lines($output, false);
+ foreach ($output as $key => $line) {
+ $output[$key] = tsprintf("[%s] %R\n", $context, $line);
+ }
+ $output = implode('', $output);
+
+ return $output;
+ }
+
+ public function didReadAgentStderr($channel, $stderr) {
+ throw new Exception(
+ pht(
+ 'Unexpected output on agent stderr: %s.',
+ $stderr));
+ }
+
+}
diff --git a/src/phage/action/PhageExecuteAction.php b/src/phage/action/PhageExecuteAction.php
new file mode 100644
--- /dev/null
+++ b/src/phage/action/PhageExecuteAction.php
@@ -0,0 +1,31 @@
+<?php
+
+final class PhageExecuteAction
+ extends PhageAction {
+
+ private $command;
+ private $label;
+
+ public function isContainerAction() {
+ return false;
+ }
+
+ public function setCommand(PhutilCommandString $command) {
+ $this->command = $command;
+ return $this;
+ }
+
+ public function getCommand() {
+ return $this->command;
+ }
+
+ public function setLabel($label) {
+ $this->label = $label;
+ return $this;
+ }
+
+ public function getLabel() {
+ return $this->label;
+ }
+
+}
diff --git a/src/phage/action/PhageLocalAction.php b/src/phage/action/PhageLocalAction.php
new file mode 100644
--- /dev/null
+++ b/src/phage/action/PhageLocalAction.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhageLocalAction
+ extends PhageAgentAction {
+
+ protected function newAgentFuture(PhutilCommandString $command) {
+ return new ExecFuture('sh -c %s', $command);
+ }
+
+}
diff --git a/src/phage/action/PhagePlanAction.php b/src/phage/action/PhagePlanAction.php
new file mode 100644
--- /dev/null
+++ b/src/phage/action/PhagePlanAction.php
@@ -0,0 +1,55 @@
+<?php
+
+final class PhagePlanAction
+ extends PhageAction {
+
+ public function isContainerAction() {
+ return true;
+ }
+
+ protected function willAddAction(PhageAction $action) {
+ if (!($action instanceof PhageAgentAction)) {
+ throw new Exception(
+ pht('Only agent actions may be added to a plan.'));
+ }
+ }
+
+ public function executePlan() {
+ $agents = $this->getAgents();
+ foreach ($agents as $agent) {
+ $agent->startAgent();
+ }
+
+ while (true) {
+ $channels = $this->getAllWaitingChannels();
+ PhutilChannel::waitForAny($channels);
+
+ $agents = $this->getActiveAgents();
+ if (!$agents) {
+ break;
+ }
+
+ foreach ($agents as $agent) {
+ $agent->updateAgent();
+ }
+ }
+ }
+
+ protected function getAgents() {
+ return $this->getActions();
+ }
+
+ protected function getActiveAgents() {
+ $agents = $this->getAgents();
+
+ foreach ($agents as $key => $agent) {
+ if (!$agent->isActiveAgent()) {
+ unset($agents[$key]);
+ }
+ }
+
+ return $agents;
+ }
+
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Apr 1, 9:23 AM (4 d, 9 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7389389
Default Alt Text
D17380.id41789.diff (10 KB)
Attached To
Mode
D17380: Add PhageActions to libphutil
Attached
Detach File
Event Timeline
Log In to Comment