From c99508cfe26f5d3fad1b632d01549250a238d385 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Sep 2015 13:10:44 -0700 Subject: [PATCH 01/43] Explain upstream attitudes toward CLI exit codes Summary: Ref T5991. See D14116. We are consistent but nonstandard in our use of exit codes. This document explains what we use exit codes for and why we do this. Test Plan: Read it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5991 Differential Revision: https://secure.phabricator.com/D14173 --- src/docs/user/field/exit_codes.diviner | 243 +++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/docs/user/field/exit_codes.diviner diff --git a/src/docs/user/field/exit_codes.diviner b/src/docs/user/field/exit_codes.diviner new file mode 100644 index 0000000000..a49083b934 --- /dev/null +++ b/src/docs/user/field/exit_codes.diviner @@ -0,0 +1,243 @@ +@title Command Line Exit Codes +@group fieldmanual + +Explains the use of exit codes in Phabricator command line scripts. + +Overview +======== + +When you run a command from the command line, it exits with an //exit code//. +This code is normally not shown on the CLI, but you can examine the exit code +of the last command you ran by looking at `$?` in your shell: + + $ ls + ... + $ echo $? + 0 + +Programs which run commands can operate on exit codes, and shell constructs +like `cmdx && cmdy` operate on exit codes. + +The code `0` means success. Other codes signal some sort of error or status +condition, depending on the system and command. + +With rare exception, Phabricator uses //all other codes// to signal +**catastrophic failure**. + +This is an explicit architectural decision and one we are unlikely to deviate +from: generally, we will not accept patches which give a command a nonzero exit +code to indicate an expected state, an application status, or a minor abnormal +condition. + +Generally, this decision reflects a philosophical belief that attaching +application semantics to exit codes is a relic of a simpler time, and that +they are not appropriate for communicating application state in a modern +operational environment. This document explains the reasoning behind our use of +exit codes in more detail. + +In particular, this approach is informed by a focus on operating Phabricator +clusters at scale. This is not a common deployment scenario, but we consider it +the most important one. Our use of exit codes makes it easier to deploy and +operate a Phabricator cluster at larger scales. It makes it slightly harder to +deploy and operate a small cluster or single host by gluing together `bash` +scripts. We are willingly trading the small scale away for advantages at larger +scales. + + +Problems With Exit Codes +======================== + +We do not use exit codes to communicate application state because doing so +makes it harder to write correct scripts, and the primary benefit is that it +makes it easier to write incorrect ones. + +This is somewhat at odds with the philosophy of "worse is better", but a modern +operations environment faces different forces than the interactive shell did +in the 1970s, particularly at scale. + +We consider correctness to be very important to modern operations environments. +In particular, we manage a Phabricator cluster (Phacility) and believe that +having reliable, repeatable processes for provisioning, configuration and +deployment is critical to maintaining and scaling our operations. Our use of +exit codes makes it easier to implement processes that are correct and reliable +on top of Phabricator management scripts. + +Exit codes as signals for application state are problematic because they are +ambiguous: you can't use them to distinguish between dissimilar failure states +which should prompt very different operational responses. + +Exit codes primarily make writing things like `bash` scripts easier, but we +think you shouldn't be writing `bash` scripts in a modern operational +environment if you care very much about your software working. + +Software environments which are powerful enough to handle errors properly are +also powerful enough to parse command output to unambiguously read and react to +complex state. Communicating application state through exit codes almost +exclusively makes it easier to handle errors in a haphazard way which is often +incorrect. + + +Exit Codes are Ambiguous +======================== + +In many cases, exit codes carry very little information and many different +conditions can produce the same exit code, including conditions which should +prompt very different responses. + +The command line tool `grep` searches for text. For example, you might run +a command like this: + + $ grep zebra corpus.txt + +This searches for the text `zebra` in the file `corpus.txt`. If the text is +not found, `grep` exits with a nonzero exit code (specifically, `1`). + +Suppose you run `grep zebra corpus.txt` and observe a nonzero exit code. What +does that mean? These are //some// of the possible conditions which are +consistent with your observation: + + - The text `zebra` was not found in `corpus.txt`. + - `corpus.txt` does not exist. + - You do not have permission to read `corpus.txt`. + - `grep` is not installed. + - You do not have permission to run `grep`. + - There is a bug in `grep`. + - Your `grep` binary is corrupt. + - `grep` was killed by a signal. + +If you're running this command interactively on a single machine, it's probably +OK for all of these conditions to be conflated. You aren't going to examine the +exit code anyway (it isn't even visible to you by default), and `grep` likely +printed useful information to `stderr` if you hit one of the less common issues. + +If you're running this command from operational software (like deployment, +configuration or monitoring scripts) and you care about the correctness and +repeatability of your process, we believe conflating these conditions is not +OK. The operational response to text not being present in a file should almost +always differ substantially from the response to the file not being present or +`grep` being broken. + +In a particularly bad case, a broken `grep` might cause a careless deployment +script to continue down an inappropriate path and cascade into a more serious +failure. + +Even in a less severe case, unexpected conditions should be detected and raised +to operations staff. `grep` being broken or a file that is expected to exist +not existing are both detectable, unexpected, and likely severe conditions, but +they can not be differentiated and handled by examining the exit code of +`grep`. It is much better to detect and raise these problems immediately than +discover them after a lengthy root cause analysis. + +Some of these conditions can be differentiated by examining the specific exit +code of the command instead of acting on all nonzero exit codes. However, many +failure conditions produce the same exit codes (particularly code `1`) and +there is no way to guarantee that a particular code signals a particular +condition, especially across systems. + +Realistically, it is also relatively rare for scripts to even make an effort to +distinguish between exit codes, and all nonzero exit codes are often treated +the same way. + + +Bash Scripts are not Robust +============================ + +Exit codes that indicate application status make writing `bash` scripts (or +scripts in other tools which provide a thin layer on top of what is essentially +`bash`) a lot easier and more convenient. + +For example, it is pretty tricky to parse JSON in `bash` or with standard +command-line tools, and much easier to react to exit codes. This is sometimes +used as an argument for communicating application status in exit codes. + +We reject this because we don't think you should be writing `bash` scripts if +you're doing real operations. Funadmentally, `bash` shell scripts are not a +robust building block for creating correct, reliable operational processes. + +Here is one problem with using `bash` scripts to perform operational tasks. +Consider this command: + + $ mysqldump | gzip > backup.sql.gz + +Now, consider this command: + + $ mysqldermp | gzip > backup.sql.gz + +These commands represent a fairly standard way to accomplish a task (dumping +a compressed database backup to disk) in a `bash` script. + +Note that the second command contains a typo (`dermp` instead of `dump`) which +will cause the command to exit abruptly with a nonzero exit code. + +However, both these statements run successfully and exit with exit code `0` +(indicating success). Both will create a `backup.sql.gz` file. One backs up +your data; the other never backs up your data. This second command will never +work and never do what the author intended, but will appear successful under +casual inspection. + +These behaviors are the same under `set -e`. + +This fragile attitude toward error handling is endemic to `bash` scripts. The +default behavior is to continue on errors, and it isn't easy to change this +default. Options like `set -e` are unreliable and it is difficult to detect and +react to errors in fundamental constructs like pipes. The tools that `bash` +scripts employ (like `grep`) emit ambiguous error codes. Scripts can not help +but propagate this ambiguity no matter how careful they are with error handling. + +It is likely //possible// to implement these things safely and correctly in +`bash`, but it is not easy or straightforward. More importantly, it is not the +default: the default behavior of `bash` is to ignore errors and continue. + +Gluing commands together in `bash` or something that sits on top of `bash` +makes it easy and convenient to get a process that works fairly well most of +the time at small scales, but we are not satisfied that it represents a robust +foundation for operations at larger scales. + + +Reacting to State +================= + +Instead of communicating application state through exit codes, we generally +communicate application state through machine-parseable output with a success +(`0`) exit code. All nonzero exit codes indicate catastrophic failure which +requires operational intervention. + +Callers are expected to request machine-parseable output if necessary (for +example, by passing a `--json` flag or other similar flags), verify the command +exits with a `0` exit code, parse the output, then react to the state it +communicates as appropriate. + +In a sufficiently powerful scripting environment (e.g., one with data +structures and a JSON parser), this is straightforward and makes it easy to +react precisely and correctly. It also allows scripts to communicate +arbitrarily complex state. Provided your environment gives you an appropriate +toolset, it is much more powerful and not significantly more complex than using +error codes. + +Most importantly, it allows the calling environment to treat nonzero exit +statuses as catastrophic failure by default. + + +Moving Forward +============== + +Given these concerns, we are generally unwilling to bring changes which use +exit codes to communicate application state (other than catastrophic failure) +into the upstream. There are some exceptions, but these are rare. In +particular, ease of use in a `bash` environment is not a compelling motivation. + +We are broadly willing to make output machine parseable or provide an explicit +machine output mode (often a `--json` flag) if there is a reasonable use case +for it. However, we operate a large production cluster of Phabricator instances +with the tools available in the upstream, so the lack of machine parseable +output is not sufficient to motivate adding such output on its own: we also +need to understand the problem you're facing, and why it isn't a problem we +face. A simpler or cleaner approach to the problem may already exist. + +If you just want to write `bash` scripts on top of Phabricator scripts and you +are unswayed by these concerns, you can often just build a composite command to +get roughly the same effect that you'd get out of an exit code. + +For example, you can pipe things to `grep` to convert output into exit codes. +This should generally have failure rates that are comparable to the background +failure level of relying on `bash` as a scripting environment. From 24845c70b918789be5309f88ed3f6455f5f29748 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Sep 2015 13:11:11 -0700 Subject: [PATCH 02/43] Refine error behavior of `bin/search index` Summary: Fixes T5991. If //all requested documents// failed to index, consider this a catastrophic failure and exit with an error code. Test Plan: - Ran `bin/search index --type TASK`, observed successful exit despite a small number of un-indexable documents. - Ran `bin/search index PHID-TASK-xxx` for an invalid task, observed exception on exit after complete failure. - Ran normal indexing through daemons. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5991 Differential Revision: https://secure.phabricator.com/D14174 --- .../PhabricatorSearchDocumentIndexer.php | 74 +++++++------------ ...abricatorSearchManagementIndexWorkflow.php | 15 +++- .../search/worker/PhabricatorSearchWorker.php | 12 ++- 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php index 5e2140d4e0..a9ecc3e9c0 100644 --- a/src/applications/search/index/PhabricatorSearchDocumentIndexer.php +++ b/src/applications/search/index/PhabricatorSearchDocumentIndexer.php @@ -42,55 +42,37 @@ abstract class PhabricatorSearchDocumentIndexer extends Phobject { } public function indexDocumentByPHID($phid, $context) { - try { - $this->setContext($context); + $this->setContext($context); - $document = $this->buildAbstractDocumentByPHID($phid); - if ($document === null) { - // This indexer doesn't build a document index, so we're done. - return $this; - } - - $object = $this->loadDocumentByPHID($phid); - - // Automatically rebuild CustomField indexes if the object uses custom - // fields. - if ($object instanceof PhabricatorCustomFieldInterface) { - $this->indexCustomFields($document, $object); - } - - // Automatically rebuild subscriber indexes if the object is subscribable. - if ($object instanceof PhabricatorSubscribableInterface) { - $this->indexSubscribers($document); - } - - // Automatically build project relationships - if ($object instanceof PhabricatorProjectInterface) { - $this->indexProjects($document, $object); - } - - $engine = PhabricatorSearchEngine::loadEngine(); - try { - $engine->reindexAbstractDocument($document); - } catch (Exception $ex) { - phlog( - pht( - 'Unable to index document %s with engine %s.', - $document->getPHID(), - get_class($engine))); - phlog($ex); - } - - $this->dispatchDidUpdateIndexEvent($phid, $document); - } catch (Exception $ex) { - phlog( - pht( - 'Unable to build document %s with indexer %s.', - $phid, - get_class($this))); - phlog($ex); + $document = $this->buildAbstractDocumentByPHID($phid); + if ($document === null) { + // This indexer doesn't build a document index, so we're done. + return $this; } + $object = $this->loadDocumentByPHID($phid); + + // Automatically rebuild CustomField indexes if the object uses custom + // fields. + if ($object instanceof PhabricatorCustomFieldInterface) { + $this->indexCustomFields($document, $object); + } + + // Automatically rebuild subscriber indexes if the object is subscribable. + if ($object instanceof PhabricatorSubscribableInterface) { + $this->indexSubscribers($document); + } + + // Automatically build project relationships + if ($object instanceof PhabricatorProjectInterface) { + $this->indexProjects($document, $object); + } + + $engine = PhabricatorSearchEngine::loadEngine(); + $engine->reindexAbstractDocument($document); + + $this->dispatchDidUpdateIndexEvent($phid, $document); + return $this; } diff --git a/src/applications/search/management/PhabricatorSearchManagementIndexWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementIndexWorkflow.php index 853f8c42c1..b1afc3619b 100644 --- a/src/applications/search/management/PhabricatorSearchManagementIndexWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementIndexWorkflow.php @@ -93,13 +93,26 @@ final class PhabricatorSearchManagementIndexWorkflow $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($phids)); + $any_success = false; $indexer = new PhabricatorSearchIndexer(); foreach ($phids as $phid) { - $indexer->queueDocumentForIndexing($phid); + try { + $indexer->queueDocumentForIndexing($phid); + $any_success = true; + } catch (Exception $ex) { + phlog($ex); + } + $bar->update(1); } $bar->done(); + + if (!$any_success) { + throw new Exception( + pht('Failed to rebuild search index for any documents.')); + } + } private function loadPHIDsByNames(array $names) { diff --git a/src/applications/search/worker/PhabricatorSearchWorker.php b/src/applications/search/worker/PhabricatorSearchWorker.php index 873d9ff2dc..689602ab06 100644 --- a/src/applications/search/worker/PhabricatorSearchWorker.php +++ b/src/applications/search/worker/PhabricatorSearchWorker.php @@ -8,8 +8,16 @@ final class PhabricatorSearchWorker extends PhabricatorWorker { $phid = idx($data, 'documentPHID'); $context = idx($data, 'context'); - id(new PhabricatorSearchIndexer()) - ->indexDocumentByPHID($phid, $context); + try { + id(new PhabricatorSearchIndexer()) + ->indexDocumentByPHID($phid, $context); + } catch (Exception $ex) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Failed to update search index for document "%s": %s', + $phid, + $ex->getMessage())); + } } } From ffbcefb62957f6695d9eab3ea8b804dcce311125 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 04:13:36 -0700 Subject: [PATCH 03/43] Fix a doc typo Summary: chack spels Test Plan: typey typey Reviewers: chad, hach-que Reviewed By: hach-que Differential Revision: https://secure.phabricator.com/D14175 --- src/docs/user/field/exit_codes.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/field/exit_codes.diviner b/src/docs/user/field/exit_codes.diviner index a49083b934..7c69b3509b 100644 --- a/src/docs/user/field/exit_codes.diviner +++ b/src/docs/user/field/exit_codes.diviner @@ -151,7 +151,7 @@ command-line tools, and much easier to react to exit codes. This is sometimes used as an argument for communicating application status in exit codes. We reject this because we don't think you should be writing `bash` scripts if -you're doing real operations. Funadmentally, `bash` shell scripts are not a +you're doing real operations. Fundamentally, `bash` shell scripts are not a robust building block for creating correct, reliable operational processes. Here is one problem with using `bash` scripts to perform operational tasks. From a3b49053c01b07735ed888192b4876021583d8e6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 28 Sep 2015 07:55:28 -0700 Subject: [PATCH 04/43] Allow polls to be public Summary: Fixes T9474 Test Plan: Make a poll, log out, still see it. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9474 Differential Revision: https://secure.phabricator.com/D14179 --- .../slowvote/controller/PhabricatorSlowvotePollController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 40980e5f41..97a6a1f84f 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -3,6 +3,10 @@ final class PhabricatorSlowvotePollController extends PhabricatorSlowvoteController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); From ec6d69e74d9295074bbc0f8a3e7c3fdf5d0eda1e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 09:35:14 -0700 Subject: [PATCH 05/43] Give Drydock resources a proper expiry mechanism Summary: Fixes T6569. This implements an expiry mechanism for Drydock resources which parallels the mechanism for leases. A few things are missing that we'll probably need in the future: - An "EXPIRES" command to update the expiration time. This would let resources be permanent while leased, then expire after, say, 24 hours without any leases. - A callback like `shouldActuallyExpireRightNow()` for resources and leases that lets them decide not to expire at the last second. - A callback like `didAcquireLease()` for resource blueprints, to parallel `didReleaseLease()`, letting them clear or extend their timer. However, this stuff would mostly just let us tune behaviors, not really open up new capabilities. Test Plan: Changed host resources to expire after 60 seconds, leased one, saw it vanish 60 seconds later. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T6569 Differential Revision: https://secure.phabricator.com/D14176 --- .../20150928.drydock.rexpire.1.sql | 2 + .../DrydockDefaultViewCapability.php | 4 ++ .../controller/DrydockConsoleController.php | 11 +--- .../DrydockResourceViewController.php | 8 +++ .../drydock/storage/DrydockLease.php | 9 +++ .../drydock/storage/DrydockResource.php | 26 +++++++- .../worker/DrydockLeaseUpdateWorker.php | 32 ++-------- .../worker/DrydockResourceUpdateWorker.php | 21 ++++-- .../drydock/worker/DrydockWorker.php | 64 +++++++++++++++++++ .../PhabricatorWorkerManagementWorkflow.php | 7 ++ 10 files changed, 142 insertions(+), 42 deletions(-) create mode 100644 resources/sql/autopatches/20150928.drydock.rexpire.1.sql diff --git a/resources/sql/autopatches/20150928.drydock.rexpire.1.sql b/resources/sql/autopatches/20150928.drydock.rexpire.1.sql new file mode 100644 index 0000000000..9321b9c0e1 --- /dev/null +++ b/resources/sql/autopatches/20150928.drydock.rexpire.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_resource + ADD until INT UNSIGNED; diff --git a/src/applications/drydock/capability/DrydockDefaultViewCapability.php b/src/applications/drydock/capability/DrydockDefaultViewCapability.php index af51edc38d..f43471766c 100644 --- a/src/applications/drydock/capability/DrydockDefaultViewCapability.php +++ b/src/applications/drydock/capability/DrydockDefaultViewCapability.php @@ -8,4 +8,8 @@ final class DrydockDefaultViewCapability extends PhabricatorPolicyCapability { return pht('Default Blueprint View Policy'); } + public function shouldAllowPublicPolicySetting() { + return true; + } + } diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php index 118d49ea59..1964f508d0 100644 --- a/src/applications/drydock/controller/DrydockConsoleController.php +++ b/src/applications/drydock/controller/DrydockConsoleController.php @@ -15,7 +15,6 @@ final class DrydockConsoleController extends DrydockController { $nav->addFilter('blueprint', pht('Blueprints')); $nav->addFilter('resource', pht('Resources')); $nav->addFilter('lease', pht('Leases')); - $nav->addFilter('log', pht('Logs')); $nav->selectFilter(null); @@ -31,6 +30,7 @@ final class DrydockConsoleController extends DrydockController { $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Blueprints')) + ->setFontIcon('fa-map-o') ->setHref($this->getApplicationURI('blueprint/')) ->addAttribute( pht( @@ -40,6 +40,7 @@ final class DrydockConsoleController extends DrydockController { $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Resources')) + ->setFontIcon('fa-map') ->setHref($this->getApplicationURI('resource/')) ->addAttribute( pht('View and manage resources Drydock has built, like hosts.'))); @@ -47,16 +48,10 @@ final class DrydockConsoleController extends DrydockController { $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Leases')) + ->setFontIcon('fa-link') ->setHref($this->getApplicationURI('lease/')) ->addAttribute(pht('Manage leases on resources.'))); - $menu->addItem( - id(new PHUIObjectItemView()) - ->setHeader(pht('Logs')) - ->setHref($this->getApplicationURI('log/')) - ->addAttribute(pht('View logs.'))); - - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 0641ced96d..d241b00808 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -116,6 +116,14 @@ final class DrydockResourceViewController extends DrydockResourceController { pht('Status'), $status); + $until = $resource->getUntil(); + if ($until) { + $until_display = phabricator_datetime($until, $viewer); + } else { + $until_display = phutil_tag('em', array(), pht('Never')); + } + $view->addProperty(pht('Expires'), $until_display); + $view->addProperty( pht('Resource Type'), $resource->getType()); diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index fe38f54fe0..bb65b982b6 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -295,6 +295,15 @@ final class DrydockLease extends DrydockDAO } } + public function canUpdate() { + switch ($this->getStatus()) { + case DrydockLeaseStatus::STATUS_ACTIVE: + return true; + default: + return false; + } + } + public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index f2be89a6f2..f46383a84c 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -7,6 +7,7 @@ final class DrydockResource extends DrydockDAO protected $phid; protected $blueprintPHID; protected $status; + protected $until; protected $type; protected $name; @@ -32,6 +33,7 @@ final class DrydockResource extends DrydockDAO 'ownerPHID' => 'phid?', 'status' => 'text32', 'type' => 'text64', + 'until' => 'epoch?', ), self::CONFIG_KEY_SCHEMA => array( 'key_type' => array( @@ -126,6 +128,10 @@ final class DrydockResource extends DrydockDAO $this->isAllocated = true; + if ($new_status == DrydockResourceStatus::STATUS_ACTIVE) { + $this->didActivate(); + } + return $this; } @@ -164,6 +170,8 @@ final class DrydockResource extends DrydockDAO $this->isActivated = true; + $this->didActivate(); + return $this; } @@ -181,14 +189,16 @@ final class DrydockResource extends DrydockDAO } } - public function scheduleUpdate() { + public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockResourceUpdateWorker', array( 'resourcePHID' => $this->getPHID(), + 'isExpireTask' => ($epoch !== null), ), array( 'objectPHID' => $this->getPHID(), + 'delayUntil' => $epoch, )); } @@ -209,6 +219,20 @@ final class DrydockResource extends DrydockDAO if ($need_update) { $this->scheduleUpdate(); } + + $expires = $this->getUntil(); + if ($expires) { + $this->scheduleUpdate($expires); + } + } + + public function canUpdate() { + switch ($this->getStatus()) { + case DrydockResourceStatus::STATUS_ACTIVE: + return true; + default: + return false; + } } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 98ecf2b498..8f15201a2c 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -23,31 +23,15 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } private function updateLease(DrydockLease $lease) { - if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { + if (!$lease->canUpdate()) { return; } - $viewer = $this->getViewer(); - $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); - - // Check if the lease has expired. If it is, we're going to send it a - // release command. This command will be handled immediately below, it - // just generates a command log and improves consistency. - $now = PhabricatorTime::getNow(); - $expires = $lease->getUntil(); - if ($expires && ($expires <= $now)) { - $command = DrydockCommand::initializeNewCommand($viewer) - ->setTargetPHID($lease->getPHID()) - ->setAuthorPHID($drydock_phid) - ->setCommand(DrydockCommand::COMMAND_RELEASE) - ->save(); - } + $this->checkLeaseExpiration($lease); $commands = $this->loadCommands($lease->getPHID()); foreach ($commands as $command) { - if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { - // Leases can't receive commands before they activate or after they - // release. + if (!$lease->canUpdate()) { break; } @@ -58,15 +42,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { ->save(); } - // If this is the task which will eventually release the lease after it - // expires but it is still active, reschedule the task to run after the - // lease expires. This can happen if the lease's expiration was pushed - // forward. - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { - if ($this->getTaskDataValue('isExpireTask') && $expires) { - throw new PhabricatorWorkerYieldException($expires - $now); - } - } + $this->yieldIfExpiringLease($lease); } private function processCommand( diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 4afc51bc23..681741b6f1 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -11,18 +11,27 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $lock = PhabricatorGlobalLock::newLock($lock_key) ->lock(1); - $resource = $this->loadResource($resource_phid); - $this->updateResource($resource); + try { + $resource = $this->loadResource($resource_phid); + $this->updateResource($resource); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } $lock->unlock(); } private function updateResource(DrydockResource $resource) { + if (!$resource->canUpdate()) { + return; + } + + $this->checkResourceExpiration($resource); + $commands = $this->loadCommands($resource->getPHID()); foreach ($commands as $command) { - if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) { - // Resources can't receive commands before they activate or after they - // release. + if (!$resource->canUpdate()) { break; } @@ -32,6 +41,8 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { ->setIsConsumed(true) ->save(); } + + $this->yieldIfExpiringResource($resource); } private function processCommand( diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index d41643de47..e64cfdafd7 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -50,4 +50,68 @@ abstract class DrydockWorker extends PhabricatorWorker { return $commands; } + protected function checkLeaseExpiration(DrydockLease $lease) { + $this->checkObjectExpiration($lease); + } + + protected function checkResourceExpiration(DrydockResource $resource) { + $this->checkObjectExpiration($resource); + } + + private function checkObjectExpiration($object) { + // Check if the resource or lease has expired. If it has, we're going to + // send it a release command. + + // This command is sent from within the update worker so it is handled + // immediately, but doing this generates a log and improves consistency. + + $expires = $object->getUntil(); + if (!$expires) { + return; + } + + $now = PhabricatorTime::getNow(); + if ($expires > $now) { + return; + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($object->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + } + + protected function yieldIfExpiringLease(DrydockLease $lease) { + if (!$lease->canUpdate()) { + return; + } + + $this->yieldIfExpiring($lease->getUntil()); + } + + protected function yieldIfExpiringResource(DrydockResource $resource) { + if (!$resource->canUpdate()) { + return; + } + + $this->yieldIfExpiring($resource->getUntil()); + } + + private function yieldIfExpiring($expires) { + if (!$expires) { + return; + } + + if (!$this->getTaskDataValue('isExpireTask')) { + return; + } + + $now = PhabricatorTime::getNow(); + throw new PhabricatorWorkerYieldException($expires - $now); + } + } diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php index 9189489208..6357ebc5d3 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php @@ -39,6 +39,13 @@ abstract class PhabricatorWorkerManagementWorkflow } } + // When we lock tasks properly, this gets populated as a side effect. Just + // fake it when doing manual CLI stuff. This makes sure CLI yields have + // their expires times set properly. + foreach ($tasks as $task) { + $task->setServerTime(PhabricatorTime::getNow()); + } + return $tasks; } From cd2dd2a08f81d053d8d45e045006917bd7d14e98 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 09:35:26 -0700 Subject: [PATCH 06/43] Give visual feedback when a Drydock resource or lease is releasing Summary: Ref T9252. Show the user when a resource or lease has a pending release command in queue. Test Plan: Released a resource and lease from the web UI. In both cases, saw a "releasing" tag and the action disable. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14177 --- .../controller/DrydockLeaseViewController.php | 9 +++++++ .../DrydockResourceViewController.php | 9 +++++++ .../drydock/query/DrydockLeaseQuery.php | 26 ++++++++++++++++++- .../drydock/query/DrydockResourceQuery.php | 25 ++++++++++++++++++ .../drydock/storage/DrydockLease.php | 21 +++++++++++++++ .../drydock/storage/DrydockResource.php | 22 +++++++++++++++- 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index bb748cceee..f37ac1a376 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -9,6 +9,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $lease = id(new DrydockLeaseQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->needUnconsumedCommands(true) ->executeOne(); if (!$lease) { return new Aphront404Response(); @@ -21,6 +22,10 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $header = id(new PHUIHeaderView()) ->setHeader($title); + if ($lease->isReleasing()) { + $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + } + $actions = $this->buildActionListView($lease); $properties = $this->buildPropertyListView($lease, $actions); @@ -78,6 +83,10 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $id = $lease->getID(); $can_release = $lease->canRelease(); + if ($lease->isReleasing()) { + $can_release = false; + } + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $lease, diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index d241b00808..23f81c5225 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -9,6 +9,7 @@ final class DrydockResourceViewController extends DrydockResourceController { $resource = id(new DrydockResourceQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->needUnconsumedCommands(true) ->executeOne(); if (!$resource) { return new Aphront404Response(); @@ -21,6 +22,10 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setPolicyObject($resource) ->setHeader($title); + if ($resource->isReleasing()) { + $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + } + $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource, $actions); @@ -82,6 +87,10 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setObject($resource); $can_release = $resource->canRelease(); + if ($resource->isReleasing()) { + $can_release = false; + } + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $resource, diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index c7c770bbea..c5fb41ff56 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -7,7 +7,7 @@ final class DrydockLeaseQuery extends DrydockQuery { private $resourcePHIDs; private $statuses; private $datasourceQuery; - private $needCommands; + private $needUnconsumedCommands; public function withIDs(array $ids) { $this->ids = $ids; @@ -34,6 +34,11 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } + public function needUnconsumedCommands($need) { + $this->needUnconsumedCommands = $need; + return $this; + } + public function newResultObject() { return new DrydockLease(); } @@ -71,6 +76,25 @@ final class DrydockLeaseQuery extends DrydockQuery { return $leases; } + protected function didFilterPage(array $leases) { + if ($this->needUnconsumedCommands) { + $commands = id(new DrydockCommandQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withTargetPHIDs(mpull($leases, 'getPHID')) + ->withConsumed(false) + ->execute(); + $commands = mgroup($commands, 'getTargetPHID'); + + foreach ($leases as $lease) { + $list = idx($commands, $lease->getPHID(), array()); + $lease->attachUnconsumedCommands($list); + } + } + + return $leases; + } + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); diff --git a/src/applications/drydock/query/DrydockResourceQuery.php b/src/applications/drydock/query/DrydockResourceQuery.php index d15b737141..c477da20b5 100644 --- a/src/applications/drydock/query/DrydockResourceQuery.php +++ b/src/applications/drydock/query/DrydockResourceQuery.php @@ -8,6 +8,7 @@ final class DrydockResourceQuery extends DrydockQuery { private $types; private $blueprintPHIDs; private $datasourceQuery; + private $needUnconsumedCommands; public function withIDs(array $ids) { $this->ids = $ids; @@ -39,6 +40,11 @@ final class DrydockResourceQuery extends DrydockQuery { return $this; } + public function needUnconsumedCommands($need) { + $this->needUnconsumedCommands = $need; + return $this; + } + public function newResultObject() { return new DrydockResource(); } @@ -69,6 +75,25 @@ final class DrydockResourceQuery extends DrydockQuery { return $resources; } + protected function didFilterPage(array $resources) { + if ($this->needUnconsumedCommands) { + $commands = id(new DrydockCommandQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withTargetPHIDs(mpull($resources, 'getPHID')) + ->withConsumed(false) + ->execute(); + $commands = mgroup($commands, 'getTargetPHID'); + + foreach ($resources as $resource) { + $list = idx($commands, $resource->getPHID(), array()); + $resource->attachUnconsumedCommands($list); + } + } + + return $resources; + } + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index bb65b982b6..c5834f0136 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -11,6 +11,8 @@ final class DrydockLease extends DrydockDAO protected $status = DrydockLeaseStatus::STATUS_PENDING; private $resource = self::ATTACHABLE; + private $unconsumedCommands = self::ATTACHABLE; + private $releaseOnDestruction; private $isAcquired = false; private $isActivated = false; @@ -104,6 +106,25 @@ final class DrydockLease extends DrydockDAO return ($this->resource !== null); } + public function getUnconsumedCommands() { + return $this->assertAttached($this->unconsumedCommands); + } + + public function attachUnconsumedCommands(array $commands) { + $this->unconsumedCommands = $commands; + return $this; + } + + public function isReleasing() { + foreach ($this->getUnconsumedCommands() as $command) { + if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) { + return true; + } + } + + return false; + } + public function queueForActivation() { if ($this->getID()) { throw new Exception( diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index f46383a84c..ab968c2249 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -8,7 +8,6 @@ final class DrydockResource extends DrydockDAO protected $blueprintPHID; protected $status; protected $until; - protected $type; protected $name; protected $attributes = array(); @@ -16,6 +15,8 @@ final class DrydockResource extends DrydockDAO protected $ownerPHID; private $blueprint = self::ATTACHABLE; + private $unconsumedCommands = self::ATTACHABLE; + private $isAllocated = false; private $isActivated = false; private $activateWhenAllocated = false; @@ -80,6 +81,25 @@ final class DrydockResource extends DrydockDAO return $this; } + public function getUnconsumedCommands() { + return $this->assertAttached($this->unconsumedCommands); + } + + public function attachUnconsumedCommands(array $commands) { + $this->unconsumedCommands = $commands; + return $this; + } + + public function isReleasing() { + foreach ($this->getUnconsumedCommands() as $command) { + if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) { + return true; + } + } + + return false; + } + public function setActivateWhenAllocated($activate) { $this->activateWhenAllocated = $activate; return $this; From 9b29d46e60f37579cc50f94d3a5b14c0415f1f01 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 09:35:40 -0700 Subject: [PATCH 07/43] Make Drydock lease infrastructure more nimble Summary: Ref T9252. Currently, Harbormaster does this when trying to acquire a working copy: - Ask for a working copy. - Yield for 15 seconds. - Check if we have a working copy yet. That's OK, but Drydock takes ~1s to acquire a working copy lease if a resource is already available, so we end up doing this: - T+0: Ask for a working copy. - T+0: Yield for 15 seconds. - T+1: Working copy lease activates. - T+15: Working copy lease is used. - T+16: Build finishes. So we end up spending about 2 seconds doing work and 14 seconds sleeping. One way to fix this would be to fiddle with the yield duration, so we yield for 1, 2, 4, ... seconds or something. This probably isn't a bad idea for longer leases (i.e., wait for 15, 30, 45 ... seconds or similar) but it implies a lot of churn for short leases. Instead, let tasks "awaken" other tasks when they complete. The "awaken" operation means: if a task is in a yielded state (no failures, no owner, explicitly yielded, future expires time), pretend it only yielded until right now instead of whenever it really yielded to. Basically, this rewrites history so that even though Harbormaster did a `yield(15)`, we pretend it did a `yield(4)` after we activate the lease if lease activation took 4 seconds. If this misses, it's fine: we fall back to the normal yield behavior and things move forward normally a few seconds later. If it hits, we get a more nimble process pretty cleanly. Test Plan: - Restarted a build plan (lease working copy + run `ls`) with this patch no-op'd, took about 16 seconds. - Restarted a build plan with this patch active, took about 1 second. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14178 --- .../drydock/storage/DrydockLease.php | 12 ++- .../drydock/storage/DrydockResource.php | 2 +- .../HarbormasterBuildStepImplementation.php | 10 +++ ...easeWorkingCopyBuildStepImplementation.php | 5 ++ .../worker/HarbormasterTargetWorker.php | 1 + .../daemon/workers/PhabricatorWorker.php | 75 +++++++++++++++++++ .../storage/PhabricatorWorkerActiveTask.php | 3 + 7 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index c5834f0136..af0b322b62 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -334,10 +334,15 @@ final class DrydockLease extends DrydockDAO ), array( 'objectPHID' => $this->getPHID(), - 'delayUntil' => $epoch, + 'delayUntil' => ($epoch ? (int)$epoch : null), )); } + public function setAwakenTaskIDs(array $ids) { + $this->setAttribute('internal.awakenTaskIDs', $ids); + return $this; + } + private function didActivate() { $viewer = PhabricatorUser::getOmnipotentUser(); $need_update = false; @@ -359,6 +364,11 @@ final class DrydockLease extends DrydockDAO if ($expires) { $this->scheduleUpdate($expires); } + + $awaken_ids = $this->getAttribute('internal.awakenTaskIDs'); + if (is_array($awaken_ids) && $awaken_ids) { + PhabricatorWorker::awakenTaskIDs($awaken_ids); + } } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index ab968c2249..ab2230ce32 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -218,7 +218,7 @@ final class DrydockResource extends DrydockDAO ), array( 'objectPHID' => $this->getPHID(), - 'delayUntil' => $epoch, + 'delayUntil' => ($epoch ? (int)$epoch : null), )); } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 744ad2474f..b47f1d00d1 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -6,6 +6,16 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { private $settings; + private $currentWorkerTaskID; + + public function setCurrentWorkerTaskID($id) { + $this->currentWorkerTaskID = $id; + return $this; + } + + public function getCurrentWorkerTaskID() { + return $this->currentWorkerTaskID; + } public static function getImplementations() { return id(new PhutilClassMapQuery()) diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php index 1f5b139008..b01ae26da6 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -54,6 +54,11 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation ->setAttribute('repositoryPHID', $repository_phid) ->setAttribute('commit', $commit); + $task_id = $this->getCurrentWorkerTaskID(); + if ($task_id) { + $lease->setAwakenTaskIDs(array($task_id)); + } + $lease->queueForActivation(); $build_target diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php index db355b145c..0f4d4092fa 100644 --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -59,6 +59,7 @@ final class HarbormasterTargetWorker extends HarbormasterWorker { } $implementation = $target->getImplementation(); + $implementation->setCurrentWorkerTaskID($this->getCurrentWorkerTaskID()); $implementation->execute($build, $target); $next_status = HarbormasterBuildTarget::STATUS_PASSED; diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 4e71440604..c448e7221a 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -8,6 +8,7 @@ abstract class PhabricatorWorker extends Phobject { private $data; private static $runAllTasksInProcess = false; private $queuedTasks = array(); + private $currentWorkerTask; // NOTE: Lower priority numbers execute first. The priority numbers have to // have the same ordering that IDs do (lowest first) so MySQL can use a @@ -18,6 +19,10 @@ abstract class PhabricatorWorker extends Phobject { const PRIORITY_BULK = 3000; const PRIORITY_IMPORT = 4000; + /** + * Special owner indicating that the task has yielded. + */ + const YIELD_OWNER = '(yield)'; /* -( Configuring Retries and Failures )----------------------------------- */ @@ -77,6 +82,23 @@ abstract class PhabricatorWorker extends Phobject { return null; } + public function setCurrentWorkerTask(PhabricatorWorkerTask $task) { + $this->currentWorkerTask = $task; + return $this; + } + + public function getCurrentWorkerTask() { + return $this->currentWorkerTask; + } + + public function getCurrentWorkerTaskID() { + $task = $this->getCurrentWorkerTask(); + if (!$task) { + return null; + } + return $task->getID(); + } + abstract protected function doWork(); final public function __construct($data) { @@ -105,6 +127,14 @@ abstract class PhabricatorWorker extends Phobject { $data, $options = array()) { + PhutilTypeSpec::checkMap( + $options, + array( + 'priority' => 'optional int|null', + 'objectPHID' => 'optional string|null', + 'delayUntil' => 'optional int|null', + )); + $priority = idx($options, 'priority'); if ($priority === null) { $priority = self::PRIORITY_DEFAULT; @@ -208,4 +238,49 @@ abstract class PhabricatorWorker extends Phobject { return $this->queuedTasks; } + + /** + * Awaken tasks that have yielded. + * + * Reschedules the specified tasks if they are currently queued in a yielded, + * unleased, unretried state so they'll execute sooner. This can let the + * queue avoid unnecessary waits. + * + * This method does not provide any assurances about when these tasks will + * execute, or even guarantee that it will have any effect at all. + * + * @param list List of task IDs to try to awaken. + * @return void + */ + final public static function awakenTaskIDs(array $ids) { + if (!$ids) { + return; + } + + $table = new PhabricatorWorkerActiveTask(); + $conn_w = $table->establishConnection('w'); + + // NOTE: At least for now, we're keeping these tasks yielded, just + // pretending that they threw a shorter yield than they really did. + + // Overlap the windows here to handle minor client/server time differences + // and because it's likely correct to push these tasks to the head of their + // respective priorities. There is a good chance they are ready to execute. + $window = phutil_units('1 hour in seconds'); + $epoch_ago = (PhabricatorTime::getNow() - $window); + + queryfx( + $conn_w, + 'UPDATE %T SET leaseExpires = %d + WHERE id IN (%Ld) + AND leaseOwner = %s + AND leaseExpires > %d + AND failureCount = 0', + $table->getTableName(), + $epoch_ago, + $ids, + self::YIELD_OWNER, + $epoch_ago); + } + } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index 0705a27780..99b0ee76ea 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -141,6 +141,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { $worker = null; try { $worker = $this->getWorkerInstance(); + $worker->setCurrentWorkerTask($this); $maximum_failures = $worker->getMaximumRetryCount(); if ($maximum_failures !== null) { @@ -175,6 +176,8 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { } catch (PhabricatorWorkerYieldException $ex) { $this->setExecutionException($ex); + $this->setLeaseOwner(PhabricatorWorker::YIELD_OWNER); + $retry = $ex->getDuration(); $retry = max($retry, 5); From 33be8f719ff394044f5dbaae9c748ca868b2766a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 09:35:58 -0700 Subject: [PATCH 08/43] Allow WorkingCopy resources to have multiple working copies Summary: Ref T9252. For building Phabricator itself, we need to have `libphutil/`, `arcanist/` and `phabricator/` next to one another on disk. Expand the Drydock WorkingCopy resource so that it can have multiple repositories if the caller needs them. I'm not sure if I'm going to put the actual config for this in Harbormaster or Drydock yet, but the WorkingCopy resource itself should work the same way in either case. Test Plan: Restarted a Harbormaster build which leases a working copy, saw it build as expected. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14180 --- ...dockWorkingCopyBlueprintImplementation.php | 179 ++++++++++++------ ...easeWorkingCopyBuildStepImplementation.php | 40 +++- 2 files changed, 155 insertions(+), 64 deletions(-) diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 6ffe90469d..2527a34cdc 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -37,10 +37,37 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { - $have_phid = $resource->getAttribute('repositoryPHID'); - $need_phid = $lease->getAttribute('repositoryPHID'); + $need_map = $lease->getAttribute('repositories.map'); + if (!is_array($need_map)) { + return false; + } - if ($need_phid !== $have_phid) { + $have_map = $resource->getAttribute('repositories.map'); + if (!is_array($have_map)) { + return false; + } + + $have_as = ipull($have_map, 'phid'); + $need_as = ipull($need_map, 'phid'); + + foreach ($need_as as $need_directory => $need_phid) { + if (empty($have_as[$need_directory])) { + // This resource is missing a required working copy. + return false; + } + + if ($have_as[$need_directory] != $need_phid) { + // This resource has a required working copy, but it contains + // the wrong repository. + return false; + } + + unset($have_as[$need_directory]); + } + + if ($have_as && $lease->getAttribute('repositories.strict')) { + // This resource has extra repositories, but the lease is strict about + // which repositories are allowed to exist. return false; } @@ -70,14 +97,9 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockBlueprint $blueprint, DrydockLease $lease) { - $repository_phid = $lease->getAttribute('repositoryPHID'); - $repository = $this->loadRepository($repository_phid); - $resource = $this->newResourceTemplate( $blueprint, - pht( - 'Working Copy (%s)', - $repository->getCallsign())); + pht('Working Copy')); $resource_phid = $resource->getPHID(); @@ -90,8 +112,17 @@ final class DrydockWorkingCopyBlueprintImplementation // TODO: Add some limits to the number of working copies we can have at // once? + $map = $lease->getAttribute('repositories.map'); + foreach ($map as $key => $value) { + $map[$key] = array_select_keys( + $value, + array( + 'phid', + )); + } + return $resource - ->setAttribute('repositoryPHID', $repository->getPHID()) + ->setAttribute('repositories.map', $map) ->setAttribute('host.leasePHID', $host_lease->getPHID()) ->allocateResource(); } @@ -103,26 +134,32 @@ final class DrydockWorkingCopyBlueprintImplementation $lease = $this->loadHostLease($resource); $this->requireActiveLease($lease); - $repository_phid = $resource->getAttribute('repositoryPHID'); - $repository = $this->loadRepository($repository_phid); - $repository_id = $repository->getID(); - $command_type = DrydockCommandInterface::INTERFACE_TYPE; $interface = $lease->getInterface($command_type); // TODO: Make this configurable. $resource_id = $resource->getID(); $root = "/var/drydock/workingcopy-{$resource_id}"; - $path = "{$root}/repo/{$repository_id}/"; - $interface->execx( - 'git clone -- %s %s', - (string)$repository->getCloneURIObject(), - $path); + $map = $resource->getAttribute('repositories.map'); + + $repositories = $this->loadRepositories(ipull($map, 'phid')); + foreach ($map as $directory => $spec) { + // TODO: Validate directory isn't goofy like "/etc" or "../../lol" + // somewhere? + + $repository = $repositories[$spec['phid']]; + $path = "{$root}/repo/{$directory}/"; + + // TODO: Run these in parallel? + $interface->execx( + 'git clone -- %s %s', + (string)$repository->getCloneURIObject(), + $path); + } $resource ->setAttribute('workingcopy.root', $root) - ->setAttribute('workingcopy.path', $path) ->activateResource(); } @@ -151,33 +188,55 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { + $host_lease = $this->loadHostLease($resource); $command_type = DrydockCommandInterface::INTERFACE_TYPE; - $interface = $lease->getInterface($command_type); + $interface = $host_lease->getInterface($command_type); - $cmd = array(); - $arg = array(); + $map = $lease->getAttribute('repositories.map'); + $root = $resource->getAttribute('workingcopy.root'); - $cmd[] = 'git clean -d --force'; - $cmd[] = 'git reset --hard HEAD'; - $cmd[] = 'git fetch'; + $default = null; + foreach ($map as $directory => $spec) { + $cmd = array(); + $arg = array(); - $commit = $lease->getAttribute('commit'); - $branch = $lease->getAttribute('branch'); + $cmd[] = 'cd %s'; + $arg[] = "{$root}/repo/{$directory}/"; - if ($commit !== null) { - $cmd[] = 'git reset --hard %s'; - $arg[] = $commit; - } else if ($branch !== null) { - $cmd[] = 'git reset --hard %s'; - $arg[] = $branch; + $cmd[] = 'git clean -d --force'; + $cmd[] = 'git fetch'; + + $commit = idx($spec, 'commit'); + $branch = idx($spec, 'branch'); + + if ($commit !== null) { + $cmd[] = 'git reset --hard %s'; + $arg[] = $commit; + } else if ($branch !== null) { + $cmd[] = 'git reset --hard %s'; + $arg[] = $branch; + } else { + $cmd[] = 'git reset --hard HEAD'; + } + + $cmd = implode(' && ', $cmd); + $argv = array_merge(array($cmd), $arg); + + $result = call_user_func_array( + array($interface, 'execx'), + $argv); + + if (idx($spec, 'default')) { + $default = $directory; + } } - $cmd = implode(' && ', $cmd); - $argv = array_merge(array($cmd), $arg); + if ($default === null) { + $default = head_key($map); + } - $result = call_user_func_array( - array($interface, 'execx'), - $argv); + // TODO: Use working storage? + $lease->setAttribute('workingcopy.default', "{$root}/repo/{$default}/"); $lease->activateOnResource($resource); } @@ -217,35 +276,41 @@ final class DrydockWorkingCopyBlueprintImplementation $host_lease = $this->loadHostLease($resource); $command_interface = $host_lease->getInterface($type); - $path = $resource->getAttribute('workingcopy.path'); + $path = $lease->getAttribute('workingcopy.default'); $command_interface->setWorkingDirectory($path); return $command_interface; } } - private function loadRepository($repository_phid) { - $repository = id(new PhabricatorRepositoryQuery()) + private function loadRepositories(array $phids) { + $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - // TODO: Permanent failure. - throw new Exception( - pht( - 'Repository PHID "%s" does not exist.', - $repository_phid)); - } + ->withPHIDs($phids) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - break; - default: + foreach ($phids as $phid) { + if (empty($repositories[$phid])) { // TODO: Permanent failure. - throw new Exception(pht('Unsupported VCS!')); + throw new Exception( + pht( + 'Repository PHID "%s" does not exist.', + $phid)); + } } - return $repository; + foreach ($repositories as $repository) { + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + break; + default: + // TODO: Permanent failure. + throw new Exception(pht('Unsupported VCS!')); + } + } + + return $repositories; } private function loadHostLease(DrydockResource $resource) { diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php index b01ae26da6..69f9cab6c1 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -45,14 +45,9 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation ->setResourceType($working_copy_type) ->setOwnerPHID($build_target->getPHID()); - $variables = $build_target->getVariables(); + $map = $this->buildRepositoryMap($build_target); - $repository_phid = idx($variables, 'repository.phid'); - $commit = idx($variables, 'repository.commit'); - - $lease - ->setAttribute('repositoryPHID', $repository_phid) - ->setAttribute('commit', $commit); + $lease->setAttribute('repositories.map', $map); $task_id = $this->getCurrentWorkerTaskID(); if ($task_id) { @@ -108,4 +103,35 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation ); } + private function buildRepositoryMap(HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); + $variables = $build_target->getVariables(); + + $repository_phid = idx($variables, 'repository.phid'); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withPHIDs(array($repository_phid)) + ->executeOne(); + if (!$repository) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load repository with PHID "%s".', + $repository_phid)); + } + + $commit = idx($variables, 'repository.commit'); + + $map = array(); + + $directory = $repository->getCloneName(); + $map[$directory] = array( + 'phid' => $repository->getPHID(), + 'commit' => $commit, + 'default' => true, + ); + + return $map; + } + } From 0438a481e113b9b0121d9fcb6d4071056f694cd7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 11:17:04 -0700 Subject: [PATCH 09/43] Fix issue with "Publish/Notify" handling in repositories Summary: Fixes T8728. As far as I can tell, I simply got this wrong in D11826. This is not the proper name for the preference. That change primarily focused on the "spammy junk during import" issue, and the code did get the importing flag right. It looks like my testing in D11827 focused on "during import" and just missed this case. Test Plan: Grepped for `disable-herald`. Grepped for `herald-disable`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8728 Differential Revision: https://secure.phabricator.com/D14181 --- src/applications/repository/storage/PhabricatorRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index d5b128292a..084f531a10 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -715,7 +715,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return false; } - if ($this->getDetail('disable-herald')) { + if ($this->getDetail('herald-disabled')) { return false; } From bfaa93aa9b2ccea8053a45e8ba9eb098a1986312 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 17:57:41 -0700 Subject: [PATCH 10/43] Allow Harbormaster build plans to request additional working copies Summary: Ref T9123. To run upstream builds in Harbormaster/Drydock, we need to be able to check out `libphutil`, `arcanist` and `phabricator` next to one another. This adds an "Also Clone: ..." field to Harbormaster working copy build steps so I can type all three repos into it and get a proper clone with everything we need. This is somewhat upstream-centric and a bit narrow, but I don't think it's totally unreasonable, and most of the underlying stuff is relatively general. This adds some more typechecking and improves data/type handling for custom fields, too. In particular, it prevents users from entering an invalid/restricted value in a field (for example, you can't "Also Clone" a repository you don't have permission to see). Test Plan: Restarted build, got a Drydock resource with multiple repositories in it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9123 Differential Revision: https://secure.phabricator.com/D14183 --- .../HarbormasterStepEditController.php | 14 +++-- .../HarbormasterBuildStepCoreCustomField.php | 4 ++ .../HarbormasterBuildStepCustomField.php | 6 +- ...easeWorkingCopyBuildStepImplementation.php | 39 ++++++++++--- .../storage/build/HarbormasterBuildTarget.php | 22 ++++++++ .../worker/HarbormasterTargetWorker.php | 1 + .../PhabricatorStandardCustomFieldPHIDs.php | 55 +++++++++++++++++++ .../PhabricatorWorkerManagementWorkflow.php | 4 +- 8 files changed, 129 insertions(+), 16 deletions(-) diff --git a/src/applications/harbormaster/controller/HarbormasterStepEditController.php b/src/applications/harbormaster/controller/HarbormasterStepEditController.php index 9d742740d1..089a801220 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepEditController.php @@ -72,9 +72,9 @@ final class HarbormasterStepEditController extends HarbormasterController { $e_name = true; $v_name = $step->getName(); - $e_description = true; + $e_description = null; $v_description = $step->getDescription(); - $e_depends_on = true; + $e_depends_on = null; $v_depends_on = $step->getDetail('dependsOn', array()); $errors = array(); @@ -82,9 +82,7 @@ final class HarbormasterStepEditController extends HarbormasterController { if ($request->isFormPost()) { $e_name = null; $v_name = $request->getStr('name'); - $e_description = null; $v_description = $request->getStr('description'); - $e_depends_on = null; $v_depends_on = $request->getArr('dependsOn'); $xactions = $field_list->buildFieldTransactionsFromRequest( @@ -139,6 +137,12 @@ final class HarbormasterStepEditController extends HarbormasterController { ->setError($e_name) ->setValue($v_name)); + $form->appendChild(id(new AphrontFormDividerControl())); + + $field_list->appendFieldsToForm($form); + + $form->appendChild(id(new AphrontFormDividerControl())); + $form ->appendControl( id(new AphrontFormTokenizerControl()) @@ -152,8 +156,6 @@ final class HarbormasterStepEditController extends HarbormasterController { ->setError($e_depends_on) ->setValue($v_depends_on)); - $field_list->appendFieldsToForm($form); - $form ->appendChild( id(new PhabricatorRemarkupControl()) diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php index 03703a48f4..0ad8f960cf 100644 --- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php +++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php @@ -72,4 +72,8 @@ final class HarbormasterBuildStepCoreCustomField return; } + public function getBuildTargetFieldValue() { + return $this->getProxy()->getFieldValue(); + } + } diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php index abc442cb20..4ff6ba799d 100644 --- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php +++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php @@ -1,4 +1,8 @@ 'text', 'required' => true, ), + 'repositoryPHIDs' => array( + 'name' => pht('Also Clone'), + 'type' => 'datasource', + 'datasource.class' => 'DiffusionRepositoryDatasource', + ), ); } @@ -108,22 +113,40 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation $variables = $build_target->getVariables(); $repository_phid = idx($variables, 'repository.phid'); + $also_phids = $build_target->getFieldValue('repositoryPHIDs'); - $repository = id(new PhabricatorRepositoryQuery()) + $all_phids = $also_phids; + $all_phids[] = $repository_phid; + + $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Unable to load repository with PHID "%s".', - $repository_phid)); + ->withPHIDs($all_phids) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($all_phids as $phid) { + if (empty($repositories[$phid])) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to load repository with PHID "%s".', + $phid)); + } } $commit = idx($variables, 'repository.commit'); $map = array(); + foreach ($also_phids as $also_phid) { + $also_repo = $repositories[$also_phid]; + $map[$also_repo->getCloneName()] = array( + 'phid' => $also_repo->getPHID(), + 'branch' => 'master', + ); + } + + $repository = $repositories[$repository_phid]; + $directory = $repository->getCloneName(); $map[$directory] = array( 'phid' => $repository->getPHID(), diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index fffa30a883..27655189e6 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -263,6 +263,28 @@ final class HarbormasterBuildTarget extends HarbormasterDAO return $log; } + public function getFieldValue($key) { + $field_list = PhabricatorCustomField::getObjectFields( + $this->getBuildStep(), + PhabricatorCustomField::ROLE_VIEW); + + $fields = $field_list->getFields(); + $full_key = "std:harbormaster:core:{$key}"; + + $field = idx($fields, $full_key); + if (!$field) { + throw new Exception( + pht( + 'Unknown build step field "%s"!', + $key)); + } + + $field = clone $field; + $field->setValueFromStorage($this->getDetail($key)); + return $field->getBuildTargetFieldValue(); + } + + /* -( Status )------------------------------------------------------------- */ diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php index 0f4d4092fa..ac3014dc29 100644 --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -28,6 +28,7 @@ final class HarbormasterTargetWorker extends HarbormasterWorker { $target = id(new HarbormasterBuildTargetQuery()) ->withIDs(array($id)) ->setViewer($this->getViewer()) + ->needBuildSteps(true) ->executeOne(); if (!$target) { diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index d900d41eb3..491c780667 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -152,6 +152,61 @@ abstract class PhabricatorStandardCustomFieldPHIDs } } + public function validateApplicationTransactions( + PhabricatorApplicationTransactionEditor $editor, + $type, + array $xactions) { + + $errors = parent::validateApplicationTransactions( + $editor, + $type, + $xactions); + + // If the user is adding PHIDs, make sure the new PHIDs are valid and + // visible to the actor. It's OK for a user to edit a field which includes + // some invalid or restricted values, but they can't add new ones. + + foreach ($xactions as $xaction) { + $old = phutil_json_decode($xaction->getOldValue()); + $new = phutil_json_decode($xaction->getNewValue()); + + $add = array_diff($new, $old); + + if (!$add) { + continue; + } + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($editor->getActor()) + ->withPHIDs($add) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + + $invalid = array(); + foreach ($add as $phid) { + if (empty($objects[$phid])) { + $invalid[] = $phid; + } + } + + if ($invalid) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Some of the selected PHIDs in field "%s" are invalid or '. + 'restricted: %s.', + $this->getFieldName(), + implode(', ', $invalid)), + $xaction); + $errors[] = $error; + $this->setFieldError(pht('Invalid')); + } + } + + return $errors; + } + public function shouldAppearInHerald() { return true; } diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php index 6357ebc5d3..be2408e54a 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php @@ -43,7 +43,9 @@ abstract class PhabricatorWorkerManagementWorkflow // fake it when doing manual CLI stuff. This makes sure CLI yields have // their expires times set properly. foreach ($tasks as $task) { - $task->setServerTime(PhabricatorTime::getNow()); + if ($task instanceof PhabricatorWorkerActiveTask) { + $task->setServerTime(PhabricatorTime::getNow()); + } } return $tasks; From efaa8170c35348ab22c7e95313a193f964d8e215 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 18:44:40 -0700 Subject: [PATCH 11/43] Simplify value decoding for PHID custom fields Summary: Ref T9123. The handling in D14183 didn't deal with new field values properly. Make all this handling more consistent. Test Plan: Created a new WorkignCopy build plan with some repos. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9123 Differential Revision: https://secure.phabricator.com/D14184 --- .../PhabricatorStandardCustomFieldPHIDs.php | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index 491c780667..ecaf67caa9 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -93,15 +93,8 @@ abstract class PhabricatorStandardCustomFieldPHIDs public function getApplicationTransactionRequiredHandlePHIDs( PhabricatorApplicationTransaction $xaction) { - $old = json_decode($xaction->getOldValue()); - if (!is_array($old)) { - $old = array(); - } - - $new = json_decode($xaction->getNewValue()); - if (!is_array($new)) { - $new = array(); - } + $old = $this->decodeValue($xaction->getOldValue()); + $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -113,15 +106,8 @@ abstract class PhabricatorStandardCustomFieldPHIDs PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); - $old = json_decode($xaction->getOldValue()); - if (!is_array($old)) { - $old = array(); - } - - $new = json_decode($xaction->getNewValue()); - if (!is_array($new)) { - $new = array(); - } + $old = $this->decodeValue($xaction->getOldValue()); + $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); $rem = array_diff($old, $new); @@ -167,8 +153,8 @@ abstract class PhabricatorStandardCustomFieldPHIDs // some invalid or restricted values, but they can't add new ones. foreach ($xactions as $xaction) { - $old = phutil_json_decode($xaction->getOldValue()); - $new = phutil_json_decode($xaction->getNewValue()); + $old = $this->decodeValue($xaction->getOldValue()); + $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); @@ -231,4 +217,13 @@ abstract class PhabricatorStandardCustomFieldPHIDs return array(); } + private function decodeValue($value) { + $value = json_decode($value); + if (!is_array($value)) { + $value = array(); + } + + return $value; + } + } From a5c41771605661a6c2912fd3ecd8f2657e549036 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 19:11:51 -0700 Subject: [PATCH 12/43] Fix an issue with BuildLogs and web UI during builds Summary: Ref T9123. After recent changes, viewing a live build log from the web UI throws a CSRF exception. Check `start` ("this object is an active log"), not `live` ("this log is open somewhere"). Test Plan: Will push / etc. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9123 Differential Revision: https://secure.phabricator.com/D14185 --- .../harbormaster/storage/build/HarbormasterBuildLog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 2e964e8931..761f445e4e 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -20,7 +20,7 @@ final class HarbormasterBuildLog extends HarbormasterDAO const ENCODING_TEXT = 'text'; public function __destruct() { - if ($this->live) { + if ($this->start) { $this->finalize($this->start); } } From 55767aac0f86208509319c4698bcc12b2692b7f3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 19:46:41 -0700 Subject: [PATCH 13/43] Fix an issue where followup tasks could fail to queue with string priorities Auditors: chad --- .../daemon/workers/storage/PhabricatorWorkerActiveTask.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index 99b0ee76ea..069b237e4d 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -214,7 +214,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { $class, $data, array( - 'priority' => $this->getPriority(), + 'priority' => (int)$this->getPriority(), )); } } From 21021b55c4d26d792d9aea5ceda00b98d40649a0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Sep 2015 03:35:59 -0700 Subject: [PATCH 14/43] Update configuring_file_domain.diviner's cloudflare link Summary: [[ https://cloudflare.net | https://cloudflare.net ]] generates SSL certification error, and even if I click "ignore it", the page is 403 forbidden afterwards. Their domain has moved to .com, therefore update it to reflect this. Test Plan: Go to [[ https://secure.phabricator.com/book/phabricator/article/configuring_file_domain/ | Configuring a File Domain ]], click [[ https://cloudflare.net | CloudFlare]]. Then, apply the patch. Refresh the page, and the new link works. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: revi, epriestley Differential Revision: https://secure.phabricator.com/D14172 --- src/docs/user/configuration/configuring_file_domain.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/configuration/configuring_file_domain.diviner b/src/docs/user/configuration/configuring_file_domain.diviner index 98576e5ae3..d6e81bb6b1 100644 --- a/src/docs/user/configuration/configuring_file_domain.diviner +++ b/src/docs/user/configuration/configuring_file_domain.diviner @@ -65,7 +65,7 @@ Continue to "Configuring Phabricator", below. Approach: CloudFlare ======== -[[ https://cloudflare.net | CloudFlare ]] is a general-purpose CDN service. +[[ https://cloudflare.com | CloudFlare ]] is a general-purpose CDN service. To set up CloudFlare, you'll need to register a second domain and go through their enrollment process to host the alternate domain on their servers. Use a From fa943f744bc4e7bbe36c03045843ad99e73b1388 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Sep 2015 06:43:49 -0700 Subject: [PATCH 15/43] Stop all object mentions from matching after "@" Summary: Fixes T9479. Currently, `@aaaaaaaa` may try to match as a commit hash, and `@C123456` may try to match as a Countdown reference. These should only match as user mentions. Prevent object mention rules from matching after `@`. We already prevent them after `-` and `#`, and already prevented the username rule after `@` (i.e., preventing `@@user`). Test Plan: Created some "interesting" users locally and `@mentioned` them: {F850779} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9479 Differential Revision: https://secure.phabricator.com/D14186 --- .../DiffusionCommitRemarkupRuleTestCase.php | 19 +++++++++++++++++++ .../rule/PhabricatorObjectRemarkupRule.php | 7 ++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php b/src/applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php index 450727bbe1..d2dcda0a23 100644 --- a/src/applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php +++ b/src/applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php @@ -121,6 +121,25 @@ final class DiffusionCommitRemarkupRuleTestCase extends PhabricatorTestCase { ), ), ), + + // After an "@", we should not be recognizing references because these + // are username mentions. + 'deadbeef' => array( + 'embed' => array( + ), + 'ref' => array( + array( + 'offset' => 0, + 'id' => 'deadbeef', + ), + ), + ), + '@deadbeef' => array( + 'embed' => array( + ), + 'ref' => array( + ), + ), ); foreach ($cases as $input => $expect) { diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index 896c52276f..d5addbc556 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -209,13 +209,14 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule { $boundary = '\\B'; } - // The "(? Date: Tue, 29 Sep 2015 06:43:55 -0700 Subject: [PATCH 16/43] Fix button for "All Problem Commits" on Owners packages Summary: Ref T9482. This button goes to the wrong place and this table conditionally hides itself so I missed it. Instead: - Always show the table, with an empty string if there are no relevant commits. - Link to a working UI. Test Plan: Saw table. Clicked button. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9482 Differential Revision: https://secure.phabricator.com/D14189 --- .../PhabricatorOwnersDetailController.php | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index bb178cfbcb..80fd67bcdb 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -76,27 +76,28 @@ final class PhabricatorOwnersDetailController 'auditorPHIDs' => $package->getPHID(), )); + $status_concern = DiffusionCommitQuery::AUDIT_STATUS_CONCERN; + $attention_commits = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withAuditorPHIDs(array($package->getPHID())) - ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) + ->withAuditStatus($status_concern) ->needCommitData(true) ->setLimit(10) ->execute(); - if ($attention_commits) { - $view = id(new PhabricatorAuditListView()) - ->setUser($viewer) - ->setCommits($attention_commits); + $view = id(new PhabricatorAuditListView()) + ->setUser($viewer) + ->setNoDataString(pht('This package has no open problem commits.')) + ->setCommits($attention_commits); - $commit_views[] = array( - 'view' => $view, - 'header' => pht('Commits in this Package that Need Attention'), - 'button' => id(new PHUIButtonView()) - ->setTag('a') - ->setHref($commit_uri->alter('status', 'open')) - ->setText(pht('View All Problem Commits')), - ); - } + $commit_views[] = array( + 'view' => $view, + 'header' => pht('Commits in this Package that Need Attention'), + 'button' => id(new PHUIButtonView()) + ->setTag('a') + ->setHref($commit_uri->alter('status', $status_concern)) + ->setText(pht('View All Problem Commits')), + ); $all_commits = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) From 6d5c9e897d31dd3d4508cddb1ff4990911de5f61 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Sep 2015 07:09:12 -0700 Subject: [PATCH 17/43] Hide "Revision" column in Diffusion history view if Differential is uninstalled Summary: Fixes T9481. If the viewer does not have access to Differential (for example, because it is not installed), hide the "Revision" column in Diffusion. Test Plan: - Viewed history, saw "Revision" column. - Uninstalled Differential, reloaded, no "Revision" column. Reviewers: chad Reviewed By: chad Subscribers: revi Maniphest Tasks: T9481 Differential Revision: https://secure.phabricator.com/D14188 --- .../diffusion/view/DiffusionHistoryTableView.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index fd5f463b34..80989efdf2 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -88,6 +88,11 @@ final class DiffusionHistoryTableView extends DiffusionView { $drequest = $this->getDiffusionRequest(); $viewer = $this->getUser(); + + $show_revisions = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDifferentialApplication', + $viewer); + $handles = $viewer->loadHandles($this->getRequiredHandlePHIDs()); $graph = null; @@ -242,6 +247,10 @@ final class DiffusionHistoryTableView extends DiffusionView { $view->setColumnVisibility( array( $graph ? true : false, + true, + true, + true, + $show_revisions, )); $view->setDeviceVisibility( array( From 45a5ea7bf504ca261b5d7c6e76dcb0712c7397c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Sep 2015 09:51:06 -0700 Subject: [PATCH 18/43] Show lease owner in Drydock UI Summary: Replaces D13687. Leases track an owner but don't currently show it. Test Plan: Looked at a lease. {F851223} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14191 --- .../drydock/controller/DrydockLeaseViewController.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index f37ac1a376..af893ca49b 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -119,6 +119,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController { pht('Resource Type'), $lease->getResourceType()); + $owner_phid = $lease->getOwnerPHID(); + if ($owner_phid) { + $owner_display = $viewer->renderHandle($owner_phid); + } else { + $owner_display = phutil_tag('em', array(), pht('No Owner')); + } + $view->addProperty(pht('Owner'), $owner_display); + $resource_phid = $lease->getResourcePHID(); if ($resource_phid) { $resource_display = $viewer->renderHandle($resource_phid); From 52040bc9e4b9190b86f140d7e1fc1319db161f1a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Sep 2015 13:27:33 -0700 Subject: [PATCH 19/43] Update `quickstart.sql` Summary: I haven't regenerated this for a while and it makes instances and unit tests a little faster. Test Plan: - Manually reviewed changes for sanity. - Ran `arc unit --everything`. - Observed runtime drop from ~15-16 seconds to ~12-13 seconds. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14192 --- resources/sql/quickstart.sql | 885 ++++++++++++++++++++++++++++++++--- 1 file changed, 809 insertions(+), 76 deletions(-) diff --git a/resources/sql/quickstart.sql b/resources/sql/quickstart.sql index cc07bb1e61..fdc1e673c7 100644 --- a/resources/sql/quickstart.sql +++ b/resources/sql/quickstart.sql @@ -64,13 +64,79 @@ CREATE TABLE `calendar_event` ( `userPHID` varbinary(64) NOT NULL, `dateFrom` int(10) unsigned NOT NULL, `dateTo` int(10) unsigned NOT NULL, - `status` int(10) unsigned NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isCancelled` tinyint(1) NOT NULL, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `mailKey` binary(20) NOT NULL, + `isAllDay` tinyint(1) NOT NULL, + `icon` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `isRecurring` tinyint(1) NOT NULL, + `recurrenceFrequency` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `recurrenceEndDate` int(10) unsigned DEFAULT NULL, + `instanceOfEventPHID` varbinary(64) DEFAULT NULL, + `sequenceIndex` int(10) unsigned DEFAULT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), - KEY `userPHID_dateFrom` (`userPHID`,`dateTo`) + UNIQUE KEY `key_instance` (`instanceOfEventPHID`,`sequenceIndex`), + KEY `userPHID_dateFrom` (`userPHID`,`dateTo`), + KEY `key_space` (`spacePHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `calendar_eventinvitee` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `eventPHID` varbinary(64) NOT NULL, + `inviteePHID` varbinary(64) NOT NULL, + `inviterPHID` varbinary(64) NOT NULL, + `status` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_event` (`eventPHID`,`inviteePHID`), + KEY `key_invitee` (`inviteePHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `calendar_eventtransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `calendar_eventtransaction_comment` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `transactionPHID` varbinary(64) DEFAULT NULL, + `authorPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isDeleted` tinyint(1) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `calendar_holiday` ( @@ -81,6 +147,24 @@ CREATE TABLE `calendar_holiday` ( UNIQUE KEY `day` (`day`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_chatlog` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; USE `{$NAMESPACE}_chatlog`; @@ -179,8 +263,70 @@ CREATE TABLE `countdown` ( `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `viewPolicy` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, + `editPolicy` varbinary(64) NOT NULL, + `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `mailKey` binary(20) NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `key_phid` (`phid`) + UNIQUE KEY `key_phid` (`phid`), + KEY `key_space` (`spacePHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `countdown_transaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `countdown_transaction_comment` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `transactionPHID` varbinary(64) DEFAULT NULL, + `authorPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isDeleted` tinyint(1) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_daemon` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; @@ -309,7 +455,6 @@ CREATE TABLE `differential_diff` ( `lineCount` int(10) unsigned NOT NULL, `branch` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `bookmark` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, - `arcanistProjectPHID` varbinary(64) DEFAULT NULL, `creationMethod` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, @@ -364,6 +509,15 @@ CREATE TABLE `differential_draft` ( UNIQUE KEY `key_unique` (`objectPHID`,`authorPHID`,`draftKey`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `differential_hiddencomment` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `userPHID` varbinary(64) NOT NULL, + `commentID` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_user` (`userPHID`,`commentID`), + KEY `key_comment` (`commentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `differential_hunk` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `changesetID` int(10) unsigned NOT NULL, @@ -412,14 +566,14 @@ CREATE TABLE `differential_revision` ( `attached` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `mailKey` binary(40) NOT NULL, `branchName` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, - `arcanistProjectPHID` varbinary(64) DEFAULT NULL, `viewPolicy` varbinary(64) NOT NULL, `editPolicy` varbinary(64) NOT NULL, `repositoryPHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), KEY `authorPHID` (`authorPHID`,`status`), - KEY `repositoryPHID` (`repositoryPHID`) + KEY `repositoryPHID` (`repositoryPHID`), + KEY `key_status` (`status`,`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `differential_revisionhash` ( @@ -522,12 +676,13 @@ CREATE TABLE `drydock_blueprint` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, `className` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, - `blueprintName` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `blueprintName` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, `viewPolicy` varbinary(64) NOT NULL, `editPolicy` varbinary(64) NOT NULL, `details` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, + `isDisabled` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -553,20 +708,32 @@ CREATE TABLE `drydock_blueprinttransaction` ( KEY `key_object` (`objectPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `drydock_command` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `authorPHID` varbinary(64) NOT NULL, + `targetPHID` varbinary(64) NOT NULL, + `command` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `isConsumed` tinyint(1) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `key_target` (`targetPHID`,`isConsumed`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `drydock_lease` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, - `resourceID` int(10) unsigned DEFAULT NULL, - `status` int(10) unsigned NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `until` int(10) unsigned DEFAULT NULL, `ownerPHID` varbinary(64) DEFAULT NULL, `attributes` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, - `taskID` int(10) unsigned DEFAULT NULL, `resourceType` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, + `resourcePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `phid` (`phid`) + UNIQUE KEY `key_phid` (`phid`), + KEY `key_resource` (`resourcePHID`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `drydock_log` ( @@ -586,15 +753,28 @@ CREATE TABLE `drydock_resource` ( `phid` varbinary(64) NOT NULL, `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, `ownerPHID` varbinary(64) DEFAULT NULL, - `status` int(10) unsigned NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `type` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, `attributes` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `capabilities` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `blueprintPHID` varbinary(64) NOT NULL, + `until` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `phid` (`phid`) + UNIQUE KEY `key_phid` (`phid`), + KEY `key_type` (`type`,`status`), + KEY `key_blueprint` (`blueprintPHID`,`status`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `drydock_slotlock` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ownerPHID` varbinary(64) NOT NULL, + `lockIndex` binary(12) NOT NULL, + `lockKey` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_lock` (`lockIndex`), + KEY `key_owner` (`ownerPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_feed` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; @@ -621,7 +801,9 @@ CREATE TABLE `feed_storynotification` ( `chronologicalKey` bigint(20) unsigned NOT NULL, `hasViewed` tinyint(1) NOT NULL, UNIQUE KEY `userPHID` (`userPHID`,`chronologicalKey`), - KEY `userPHID_2` (`userPHID`,`hasViewed`,`primaryObjectPHID`) + KEY `userPHID_2` (`userPHID`,`hasViewed`,`primaryObjectPHID`), + KEY `key_object` (`primaryObjectPHID`), + KEY `key_chronological` (`chronologicalKey`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `feed_storyreference` ( @@ -672,12 +854,25 @@ CREATE TABLE `file` ( `isExplicitUpload` tinyint(1) DEFAULT '1', `mailKey` binary(20) NOT NULL, `viewPolicy` varbinary(64) NOT NULL, + `isPartial` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), KEY `authorPHID` (`authorPHID`), KEY `contentHash` (`contentHash`), KEY `key_ttl` (`ttl`), - KEY `key_dateCreated` (`dateCreated`) + KEY `key_dateCreated` (`dateCreated`), + KEY `key_partial` (`authorPHID`,`isPartial`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `file_chunk` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `chunkHandle` binary(12) NOT NULL, + `byteStart` bigint(20) unsigned NOT NULL, + `byteEnd` bigint(20) unsigned NOT NULL, + `dataFilePHID` varbinary(64) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `key_file` (`chunkHandle`,`byteStart`,`byteEnd`), + KEY `key_data` (`dataFilePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `file_imagemacro` ( @@ -848,8 +1043,10 @@ CREATE TABLE `harbormaster_build` ( `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `buildGeneration` int(10) unsigned NOT NULL DEFAULT '0', + `planAutoKey` varchar(32) COLLATE {$COLLATE_TEXT} DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_planautokey` (`buildablePHID`,`planAutoKey`), KEY `key_buildable` (`buildablePHID`), KEY `key_plan` (`buildPlanPHID`), KEY `key_status` (`buildStatus`) @@ -894,6 +1091,7 @@ CREATE TABLE `harbormaster_buildabletransaction` ( CREATE TABLE `harbormaster_buildartifact` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, `artifactType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `artifactIndex` binary(12) NOT NULL, `artifactKey` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, @@ -903,6 +1101,7 @@ CREATE TABLE `harbormaster_buildartifact` ( `buildTargetPHID` varbinary(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_artifact` (`artifactType`,`artifactIndex`), + UNIQUE KEY `key_phid` (`phid`), KEY `key_garbagecollect` (`artifactType`,`dateCreated`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -917,6 +1116,22 @@ CREATE TABLE `harbormaster_buildcommand` ( KEY `key_target` (`targetPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `harbormaster_buildlintmessage` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `buildTargetPHID` varbinary(64) NOT NULL, + `path` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `line` int(10) unsigned DEFAULT NULL, + `characterOffset` int(10) unsigned DEFAULT NULL, + `code` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, + `severity` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `properties` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `key_target` (`buildTargetPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `harbormaster_buildlog` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -957,13 +1172,16 @@ CREATE TABLE `harbormaster_buildmessage` ( CREATE TABLE `harbormaster_buildplan` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, - `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `name` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, `planStatus` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, + `planAutoKey` varchar(32) COLLATE {$COLLATE_TEXT} DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), - KEY `key_status` (`planStatus`) + UNIQUE KEY `key_planautokey` (`planAutoKey`), + KEY `key_status` (`planStatus`), + KEY `key_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `harbormaster_buildplantransaction` ( @@ -998,8 +1216,10 @@ CREATE TABLE `harbormaster_buildstep` ( `sequence` int(10) unsigned NOT NULL, `name` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `stepAutoKey` varchar(32) COLLATE {$COLLATE_TEXT} DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_stepautokey` (`buildPlanPHID`,`stepAutoKey`), KEY `key_plan` (`buildPlanPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -1065,6 +1285,21 @@ CREATE TABLE `harbormaster_buildtransaction` ( KEY `key_object` (`objectPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `harbormaster_buildunitmessage` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `buildTargetPHID` varbinary(64) NOT NULL, + `engine` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `namespace` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `result` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `duration` double DEFAULT NULL, + `properties` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `key_target` (`buildTargetPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `harbormaster_object` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -1143,18 +1378,6 @@ CREATE TABLE `herald_ruleapplied` ( KEY `phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; -CREATE TABLE `herald_ruleedit` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `ruleID` int(10) unsigned NOT NULL, - `editorPHID` varbinary(64) NOT NULL, - `dateCreated` int(10) unsigned NOT NULL, - `dateModified` int(10) unsigned NOT NULL, - `ruleName` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, - `action` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, - PRIMARY KEY (`id`), - KEY `ruleID` (`ruleID`,`dateCreated`) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; - CREATE TABLE `herald_ruletransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -1299,6 +1522,7 @@ CREATE TABLE `maniphest_task` ( `subpriority` double NOT NULL, `viewPolicy` varbinary(64) NOT NULL, `editPolicy` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), KEY `priority` (`priority`,`status`), @@ -1309,7 +1533,8 @@ CREATE TABLE `maniphest_task` ( KEY `priority_2` (`priority`,`subpriority`), KEY `key_dateCreated` (`dateCreated`), KEY `key_dateModified` (`dateModified`), - KEY `key_title` (`title`(64)) + KEY `key_title` (`title`(64)), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `maniphest_transaction` ( @@ -1361,7 +1586,7 @@ CREATE TABLE `patch_status` ( PRIMARY KEY (`patch`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; -INSERT INTO `patch_status` VALUES ('phabricator:000.project.sql',1425312505),('phabricator:0000.legacy.sql',1425312505),('phabricator:001.maniphest_projects.sql',1425312505),('phabricator:002.oauth.sql',1425312505),('phabricator:003.more_oauth.sql',1425312505),('phabricator:004.daemonrepos.sql',1425312505),('phabricator:005.workers.sql',1425312505),('phabricator:006.repository.sql',1425312505),('phabricator:007.daemonlog.sql',1425312505),('phabricator:008.repoopt.sql',1425312505),('phabricator:009.repo_summary.sql',1425312505),('phabricator:010.herald.sql',1425312505),('phabricator:011.badcommit.sql',1425312505),('phabricator:012.dropphidtype.sql',1425312505),('phabricator:013.commitdetail.sql',1425312505),('phabricator:014.shortcuts.sql',1425312505),('phabricator:015.preferences.sql',1425312505),('phabricator:016.userrealnameindex.sql',1425312505),('phabricator:017.sessionkeys.sql',1425312505),('phabricator:018.owners.sql',1425312505),('phabricator:019.arcprojects.sql',1425312505),('phabricator:020.pathcapital.sql',1425312505),('phabricator:021.xhpastview.sql',1425312505),('phabricator:022.differentialcommit.sql',1425312505),('phabricator:023.dxkeys.sql',1425312505),('phabricator:024.mlistkeys.sql',1425312505),('phabricator:025.commentopt.sql',1425312505),('phabricator:026.diffpropkey.sql',1425312505),('phabricator:027.metamtakeys.sql',1425312505),('phabricator:028.systemagent.sql',1425312505),('phabricator:029.cursors.sql',1425312505),('phabricator:030.imagemacro.sql',1425312505),('phabricator:031.workerrace.sql',1425312505),('phabricator:032.viewtime.sql',1425312505),('phabricator:033.privtest.sql',1425312505),('phabricator:034.savedheader.sql',1425312505),('phabricator:035.proxyimage.sql',1425312505),('phabricator:036.mailkey.sql',1425312505),('phabricator:037.setuptest.sql',1425312505),('phabricator:038.admin.sql',1425312505),('phabricator:039.userlog.sql',1425312505),('phabricator:040.transform.sql',1425312505),('phabricator:041.heraldrepetition.sql',1425312506),('phabricator:042.commentmetadata.sql',1425312506),('phabricator:043.pastebin.sql',1425312506),('phabricator:044.countdown.sql',1425312506),('phabricator:045.timezone.sql',1425312506),('phabricator:046.conduittoken.sql',1425312506),('phabricator:047.projectstatus.sql',1425312506),('phabricator:048.relationshipkeys.sql',1425312506),('phabricator:049.projectowner.sql',1425312506),('phabricator:050.taskdenormal.sql',1425312506),('phabricator:051.projectfilter.sql',1425312506),('phabricator:052.pastelanguage.sql',1425312506),('phabricator:053.feed.sql',1425312506),('phabricator:054.subscribers.sql',1425312506),('phabricator:055.add_author_to_files.sql',1425312506),('phabricator:056.slowvote.sql',1425312506),('phabricator:057.parsecache.sql',1425312506),('phabricator:058.missingkeys.sql',1425312506),('phabricator:059.engines.php',1425312506),('phabricator:060.phriction.sql',1425312506),('phabricator:061.phrictioncontent.sql',1425312506),('phabricator:062.phrictionmenu.sql',1425312506),('phabricator:063.pasteforks.sql',1425312506),('phabricator:064.subprojects.sql',1425312506),('phabricator:065.sshkeys.sql',1425312506),('phabricator:066.phrictioncontent.sql',1425312506),('phabricator:067.preferences.sql',1425312506),('phabricator:068.maniphestauxiliarystorage.sql',1425312506),('phabricator:069.heraldxscript.sql',1425312506),('phabricator:070.differentialaux.sql',1425312506),('phabricator:071.contentsource.sql',1425312506),('phabricator:072.blamerevert.sql',1425312506),('phabricator:073.reposymbols.sql',1425312506),('phabricator:074.affectedpath.sql',1425312506),('phabricator:075.revisionhash.sql',1425312506),('phabricator:076.indexedlanguages.sql',1425312506),('phabricator:077.originalemail.sql',1425312506),('phabricator:078.nametoken.sql',1425312506),('phabricator:079.nametokenindex.php',1425312506),('phabricator:080.filekeys.sql',1425312506),('phabricator:081.filekeys.php',1425312506),('phabricator:082.xactionkey.sql',1425312506),('phabricator:083.dxviewtime.sql',1425312506),('phabricator:084.pasteauthorkey.sql',1425312506),('phabricator:085.packagecommitrelationship.sql',1425312506),('phabricator:086.formeraffil.sql',1425312506),('phabricator:087.phrictiondelete.sql',1425312506),('phabricator:088.audit.sql',1425312506),('phabricator:089.projectwiki.sql',1425312506),('phabricator:090.forceuniqueprojectnames.php',1425312506),('phabricator:091.uniqueslugkey.sql',1425312506),('phabricator:092.dropgithubnotification.sql',1425312506),('phabricator:093.gitremotes.php',1425312506),('phabricator:094.phrictioncolumn.sql',1425312506),('phabricator:095.directory.sql',1425312506),('phabricator:096.filename.sql',1425312506),('phabricator:097.heraldruletypes.sql',1425312506),('phabricator:098.heraldruletypemigration.php',1425312506),('phabricator:099.drydock.sql',1425312506),('phabricator:100.projectxaction.sql',1425312506),('phabricator:101.heraldruleapplied.sql',1425312506),('phabricator:102.heraldcleanup.php',1425312506),('phabricator:103.heraldedithistory.sql',1425312507),('phabricator:104.searchkey.sql',1425312507),('phabricator:105.mimetype.sql',1425312507),('phabricator:106.chatlog.sql',1425312507),('phabricator:107.oauthserver.sql',1425312507),('phabricator:108.oauthscope.sql',1425312507),('phabricator:109.oauthclientphidkey.sql',1425312507),('phabricator:110.commitaudit.sql',1425312507),('phabricator:111.commitauditmigration.php',1425312507),('phabricator:112.oauthaccesscoderedirecturi.sql',1425312507),('phabricator:113.lastreviewer.sql',1425312507),('phabricator:114.auditrequest.sql',1425312507),('phabricator:115.prepareutf8.sql',1425312507),('phabricator:116.utf8-backup-first-expect-wait.sql',1425312508),('phabricator:117.repositorydescription.php',1425312508),('phabricator:118.auditinline.sql',1425312508),('phabricator:119.filehash.sql',1425312508),('phabricator:120.noop.sql',1425312508),('phabricator:121.drydocklog.sql',1425312508),('phabricator:122.flag.sql',1425312508),('phabricator:123.heraldrulelog.sql',1425312508),('phabricator:124.subpriority.sql',1425312508),('phabricator:125.ipv6.sql',1425312508),('phabricator:126.edges.sql',1425312509),('phabricator:127.userkeybody.sql',1425312509),('phabricator:128.phabricatorcom.sql',1425312509),('phabricator:129.savedquery.sql',1425312509),('phabricator:130.denormalrevisionquery.sql',1425312509),('phabricator:131.migraterevisionquery.php',1425312509),('phabricator:132.phame.sql',1425312509),('phabricator:133.imagemacro.sql',1425312509),('phabricator:134.emptysearch.sql',1425312509),('phabricator:135.datecommitted.sql',1425312509),('phabricator:136.sex.sql',1425312509),('phabricator:137.auditmetadata.sql',1425312509),('phabricator:138.notification.sql',1425312509),('phabricator:20121209.pholioxactions.sql',1425312509),('phabricator:20121209.xmacroadd.sql',1425312510),('phabricator:20121209.xmacromigrate.php',1425312510),('phabricator:20121209.xmacromigratekey.sql',1425312510),('phabricator:20121220.generalcache.sql',1425312510),('phabricator:20121226.config.sql',1425312510),('phabricator:20130101.confxaction.sql',1425312510),('phabricator:20130102.metamtareceivedmailmessageidhash.sql',1425312510),('phabricator:20130103.filemetadata.sql',1425312510),('phabricator:20130111.conpherence.sql',1425312510),('phabricator:20130127.altheraldtranscript.sql',1425312510),('phabricator:20130131.conpherencepics.sql',1425312510),('phabricator:20130201.revisionunsubscribed.php',1425312510),('phabricator:20130201.revisionunsubscribed.sql',1425312510),('phabricator:20130214.chatlogchannel.sql',1425312510),('phabricator:20130214.chatlogchannelid.sql',1425312510),('phabricator:20130214.token.sql',1425312510),('phabricator:20130215.phabricatorfileaddttl.sql',1425312510),('phabricator:20130217.cachettl.sql',1425312510),('phabricator:20130218.longdaemon.sql',1425312510),('phabricator:20130218.updatechannelid.php',1425312510),('phabricator:20130219.commitsummary.sql',1425312510),('phabricator:20130219.commitsummarymig.php',1425312510),('phabricator:20130222.dropchannel.sql',1425312510),('phabricator:20130226.commitkey.sql',1425312510),('phabricator:20130304.lintauthor.sql',1425312510),('phabricator:20130310.xactionmeta.sql',1425312510),('phabricator:20130317.phrictionedge.sql',1425312510),('phabricator:20130319.conpherence.sql',1425312510),('phabricator:20130319.phabricatorfileexplicitupload.sql',1425312510),('phabricator:20130320.phlux.sql',1425312510),('phabricator:20130321.token.sql',1425312510),('phabricator:20130322.phortune.sql',1425312510),('phabricator:20130323.phortunepayment.sql',1425312510),('phabricator:20130324.phortuneproduct.sql',1425312510),('phabricator:20130330.phrequent.sql',1425312510),('phabricator:20130403.conpherencecache.sql',1425312510),('phabricator:20130403.conpherencecachemig.php',1425312510),('phabricator:20130409.commitdrev.php',1425312510),('phabricator:20130417.externalaccount.sql',1425312510),('phabricator:20130423.conpherenceindices.sql',1425312510),('phabricator:20130423.phortunepaymentrevised.sql',1425312510),('phabricator:20130423.updateexternalaccount.sql',1425312510),('phabricator:20130426.search_savedquery.sql',1425312510),('phabricator:20130502.countdownrevamp1.sql',1425312510),('phabricator:20130502.countdownrevamp2.php',1425312510),('phabricator:20130502.countdownrevamp3.sql',1425312510),('phabricator:20130507.releephrqmailkey.sql',1425312510),('phabricator:20130507.releephrqmailkeypop.php',1425312510),('phabricator:20130507.releephrqsimplifycols.sql',1425312510),('phabricator:20130508.releephtransactions.sql',1425312510),('phabricator:20130508.releephtransactionsmig.php',1425312510),('phabricator:20130508.search_namedquery.sql',1425312510),('phabricator:20130513.receviedmailstatus.sql',1425312510),('phabricator:20130519.diviner.sql',1425312510),('phabricator:20130521.dropconphimages.sql',1425312510),('phabricator:20130523.maniphest_owners.sql',1425312510),('phabricator:20130524.repoxactions.sql',1425312510),('phabricator:20130529.macroauthor.sql',1425312510),('phabricator:20130529.macroauthormig.php',1425312510),('phabricator:20130530.macrodatekey.sql',1425312510),('phabricator:20130530.pastekeys.sql',1425312510),('phabricator:20130530.sessionhash.php',1425312510),('phabricator:20130531.filekeys.sql',1425312511),('phabricator:20130602.morediviner.sql',1425312511),('phabricator:20130602.namedqueries.sql',1425312511),('phabricator:20130606.userxactions.sql',1425312511),('phabricator:20130607.xaccount.sql',1425312511),('phabricator:20130611.migrateoauth.php',1425312511),('phabricator:20130611.nukeldap.php',1425312511),('phabricator:20130613.authdb.sql',1425312511),('phabricator:20130619.authconf.php',1425312511),('phabricator:20130620.diffxactions.sql',1425312511),('phabricator:20130621.diffcommentphid.sql',1425312511),('phabricator:20130621.diffcommentphidmig.php',1425312511),('phabricator:20130621.diffcommentunphid.sql',1425312511),('phabricator:20130622.doorkeeper.sql',1425312511),('phabricator:20130628.legalpadv0.sql',1425312511),('phabricator:20130701.conduitlog.sql',1425312511),('phabricator:20130703.legalpaddocdenorm.php',1425312511),('phabricator:20130703.legalpaddocdenorm.sql',1425312511),('phabricator:20130709.droptimeline.sql',1425312511),('phabricator:20130709.legalpadsignature.sql',1425312511),('phabricator:20130711.pholioimageobsolete.php',1425312511),('phabricator:20130711.pholioimageobsolete.sql',1425312511),('phabricator:20130711.pholioimageobsolete2.sql',1425312511),('phabricator:20130711.trimrealnames.php',1425312511),('phabricator:20130714.votexactions.sql',1425312511),('phabricator:20130715.votecomments.php',1425312511),('phabricator:20130715.voteedges.sql',1425312511),('phabricator:20130716.archivememberlessprojects.php',1425312511),('phabricator:20130722.pholioreplace.sql',1425312511),('phabricator:20130723.taskstarttime.sql',1425312511),('phabricator:20130726.ponderxactions.sql',1425312511),('phabricator:20130727.ponderquestionstatus.sql',1425312511),('phabricator:20130728.ponderunique.php',1425312511),('phabricator:20130728.ponderuniquekey.sql',1425312511),('phabricator:20130728.ponderxcomment.php',1425312511),('phabricator:20130731.releephcutpointidentifier.sql',1425312511),('phabricator:20130731.releephproject.sql',1425312511),('phabricator:20130731.releephrepoid.sql',1425312511),('phabricator:20130801.pastexactions.php',1425312511),('phabricator:20130801.pastexactions.sql',1425312511),('phabricator:20130802.heraldphid.sql',1425312511),('phabricator:20130802.heraldphids.php',1425312511),('phabricator:20130802.heraldphidukey.sql',1425312511),('phabricator:20130802.heraldxactions.sql',1425312511),('phabricator:20130805.pasteedges.sql',1425312511),('phabricator:20130805.pastemailkey.sql',1425312511),('phabricator:20130805.pastemailkeypop.php',1425312511),('phabricator:20130814.usercustom.sql',1425312512),('phabricator:20130820.file-mailkey-populate.php',1425312512),('phabricator:20130820.filemailkey.sql',1425312512),('phabricator:20130820.filexactions.sql',1425312512),('phabricator:20130820.releephxactions.sql',1425312512),('phabricator:20130826.divinernode.sql',1425312512),('phabricator:20130912.maniphest.1.touch.sql',1425312512),('phabricator:20130912.maniphest.2.created.sql',1425312512),('phabricator:20130912.maniphest.3.nameindex.sql',1425312512),('phabricator:20130912.maniphest.4.fillindex.php',1425312512),('phabricator:20130913.maniphest.1.migratesearch.php',1425312512),('phabricator:20130914.usercustom.sql',1425312512),('phabricator:20130915.maniphestcustom.sql',1425312512),('phabricator:20130915.maniphestmigrate.php',1425312512),('phabricator:20130915.maniphestqdrop.sql',1425312512),('phabricator:20130919.mfieldconf.php',1425312512),('phabricator:20130920.repokeyspolicy.sql',1425312512),('phabricator:20130921.mtransactions.sql',1425312512),('phabricator:20130921.xmigratemaniphest.php',1425312512),('phabricator:20130923.mrename.sql',1425312512),('phabricator:20130924.mdraftkey.sql',1425312512),('phabricator:20130925.mpolicy.sql',1425312512),('phabricator:20130925.xpolicy.sql',1425312512),('phabricator:20130926.dcustom.sql',1425312512),('phabricator:20130926.dinkeys.sql',1425312512),('phabricator:20130926.dinline.php',1425312512),('phabricator:20130927.audiomacro.sql',1425312512),('phabricator:20130929.filepolicy.sql',1425312512),('phabricator:20131004.dxedgekey.sql',1425312512),('phabricator:20131004.dxreviewers.php',1425312512),('phabricator:20131006.hdisable.sql',1425312512),('phabricator:20131010.pstorage.sql',1425312512),('phabricator:20131015.cpolicy.sql',1425312512),('phabricator:20131020.col1.sql',1425312512),('phabricator:20131020.harbormaster.sql',1425312512),('phabricator:20131020.pcustom.sql',1425312512),('phabricator:20131020.pxaction.sql',1425312512),('phabricator:20131020.pxactionmig.php',1425312512),('phabricator:20131025.repopush.sql',1425312512),('phabricator:20131026.commitstatus.sql',1425312512),('phabricator:20131030.repostatusmessage.sql',1425312512),('phabricator:20131031.vcspassword.sql',1425312512),('phabricator:20131105.buildstep.sql',1425312512),('phabricator:20131106.diffphid.1.col.sql',1425312512),('phabricator:20131106.diffphid.2.mig.php',1425312512),('phabricator:20131106.diffphid.3.key.sql',1425312512),('phabricator:20131106.nuance-v0.sql',1425312512),('phabricator:20131107.buildlog.sql',1425312512),('phabricator:20131112.userverified.1.col.sql',1425312512),('phabricator:20131112.userverified.2.mig.php',1425312512),('phabricator:20131118.ownerorder.php',1425312512),('phabricator:20131119.passphrase.sql',1425312512),('phabricator:20131120.nuancesourcetype.sql',1425312513),('phabricator:20131121.passphraseedge.sql',1425312513),('phabricator:20131121.repocredentials.1.col.sql',1425312513),('phabricator:20131121.repocredentials.2.mig.php',1425312513),('phabricator:20131122.repomirror.sql',1425312513),('phabricator:20131123.drydockblueprintpolicy.sql',1425312513),('phabricator:20131129.drydockresourceblueprint.sql',1425312513),('phabricator:20131204.pushlog.sql',1425312513),('phabricator:20131205.buildsteporder.sql',1425312513),('phabricator:20131205.buildstepordermig.php',1425312513),('phabricator:20131205.buildtargets.sql',1425312513),('phabricator:20131206.phragment.sql',1425312513),('phabricator:20131206.phragmentnull.sql',1425312513),('phabricator:20131208.phragmentsnapshot.sql',1425312513),('phabricator:20131211.phragmentedges.sql',1425312513),('phabricator:20131217.pushlogphid.1.col.sql',1425312513),('phabricator:20131217.pushlogphid.2.mig.php',1425312513),('phabricator:20131217.pushlogphid.3.key.sql',1425312513),('phabricator:20131219.pxdrop.sql',1425312513),('phabricator:20131224.harbormanual.sql',1425312513),('phabricator:20131227.heraldobject.sql',1425312513),('phabricator:20131231.dropshortcut.sql',1425312513),('phabricator:20131302.maniphestvalue.sql',1425312510),('phabricator:20140104.harbormastercmd.sql',1425312513),('phabricator:20140106.macromailkey.1.sql',1425312513),('phabricator:20140106.macromailkey.2.php',1425312513),('phabricator:20140108.ddbpname.1.sql',1425312513),('phabricator:20140108.ddbpname.2.php',1425312513),('phabricator:20140109.ddxactions.sql',1425312513),('phabricator:20140109.projectcolumnsdates.sql',1425312513),('phabricator:20140113.legalpadsig.1.sql',1425312513),('phabricator:20140113.legalpadsig.2.php',1425312513),('phabricator:20140115.auth.1.id.sql',1425312513),('phabricator:20140115.auth.2.expires.sql',1425312513),('phabricator:20140115.auth.3.unlimit.php',1425312513),('phabricator:20140115.legalpadsigkey.sql',1425312513),('phabricator:20140116.reporefcursor.sql',1425312513),('phabricator:20140126.diff.1.parentrevisionid.sql',1425312513),('phabricator:20140126.diff.2.repositoryphid.sql',1425312513),('phabricator:20140130.dash.1.board.sql',1425312513),('phabricator:20140130.dash.2.panel.sql',1425312513),('phabricator:20140130.dash.3.boardxaction.sql',1425312513),('phabricator:20140130.dash.4.panelxaction.sql',1425312513),('phabricator:20140130.mail.1.retry.sql',1425312513),('phabricator:20140130.mail.2.next.sql',1425312513),('phabricator:20140201.gc.1.mailsent.sql',1425312513),('phabricator:20140201.gc.2.mailreceived.sql',1425312513),('phabricator:20140205.cal.1.rename.sql',1425312513),('phabricator:20140205.cal.2.phid-col.sql',1425312513),('phabricator:20140205.cal.3.phid-mig.php',1425312513),('phabricator:20140205.cal.4.phid-key.sql',1425312513),('phabricator:20140210.herald.rule-condition-mig.php',1425312513),('phabricator:20140210.projcfield.1.blurb.php',1425312513),('phabricator:20140210.projcfield.2.piccol.sql',1425312513),('phabricator:20140210.projcfield.3.picmig.sql',1425312513),('phabricator:20140210.projcfield.4.memmig.sql',1425312513),('phabricator:20140210.projcfield.5.dropprofile.sql',1425312513),('phabricator:20140211.dx.1.nullablechangesetid.sql',1425312513),('phabricator:20140211.dx.2.migcommenttext.php',1425312513),('phabricator:20140211.dx.3.migsubscriptions.sql',1425312513),('phabricator:20140211.dx.999.drop.relationships.sql',1425312513),('phabricator:20140212.dx.1.armageddon.php',1425312513),('phabricator:20140214.clean.1.legacycommentid.sql',1425312513),('phabricator:20140214.clean.2.dropcomment.sql',1425312513),('phabricator:20140214.clean.3.dropinline.sql',1425312513),('phabricator:20140218.differentialdraft.sql',1425312513),('phabricator:20140218.passwords.1.extend.sql',1425312513),('phabricator:20140218.passwords.2.prefix.sql',1425312513),('phabricator:20140218.passwords.3.vcsextend.sql',1425312513),('phabricator:20140218.passwords.4.vcs.php',1425312513),('phabricator:20140223.bigutf8scratch.sql',1425312513),('phabricator:20140224.dxclean.1.datecommitted.sql',1425312513),('phabricator:20140226.dxcustom.1.fielddata.php',1425312513),('phabricator:20140226.dxcustom.99.drop.sql',1425312513),('phabricator:20140228.dxcomment.1.sql',1425312513),('phabricator:20140305.diviner.1.slugcol.sql',1425312513),('phabricator:20140305.diviner.2.slugkey.sql',1425312514),('phabricator:20140311.mdroplegacy.sql',1425312514),('phabricator:20140314.projectcolumn.1.statuscol.sql',1425312514),('phabricator:20140314.projectcolumn.2.statuskey.sql',1425312514),('phabricator:20140317.mupdatedkey.sql',1425312514),('phabricator:20140321.harbor.1.bxaction.sql',1425312514),('phabricator:20140321.mstatus.1.col.sql',1425312514),('phabricator:20140321.mstatus.2.mig.php',1425312514),('phabricator:20140323.harbor.1.renames.php',1425312514),('phabricator:20140323.harbor.2.message.sql',1425312514),('phabricator:20140325.push.1.event.sql',1425312514),('phabricator:20140325.push.2.eventphid.sql',1425312514),('phabricator:20140325.push.3.groups.php',1425312514),('phabricator:20140325.push.4.prune.sql',1425312514),('phabricator:20140326.project.1.colxaction.sql',1425312514),('phabricator:20140328.releeph.1.productxaction.sql',1425312514),('phabricator:20140330.flagtext.sql',1425312514),('phabricator:20140402.actionlog.sql',1425312514),('phabricator:20140410.accountsecret.1.sql',1425312514),('phabricator:20140410.accountsecret.2.php',1425312514),('phabricator:20140416.harbor.1.sql',1425312514),('phabricator:20140420.rel.1.objectphid.sql',1425312514),('phabricator:20140420.rel.2.objectmig.php',1425312514),('phabricator:20140421.slowvotecolumnsisclosed.sql',1425312514),('phabricator:20140423.session.1.hisec.sql',1425312514),('phabricator:20140427.mfactor.1.sql',1425312514),('phabricator:20140430.auth.1.partial.sql',1425312514),('phabricator:20140430.dash.1.paneltype.sql',1425312514),('phabricator:20140430.dash.2.edge.sql',1425312514),('phabricator:20140501.passphraselockcredential.sql',1425312514),('phabricator:20140501.remove.1.dlog.sql',1425312514),('phabricator:20140507.smstable.sql',1425312514),('phabricator:20140509.coverage.1.sql',1425312514),('phabricator:20140509.dashboardlayoutconfig.sql',1425312514),('phabricator:20140512.dparents.1.sql',1425312514),('phabricator:20140514.harbormasterbuildabletransaction.sql',1425312514),('phabricator:20140514.pholiomockclose.sql',1425312514),('phabricator:20140515.trust-emails.sql',1425312514),('phabricator:20140517.dxbinarycache.sql',1425312514),('phabricator:20140518.dxmorebinarycache.sql',1425312514),('phabricator:20140519.dashboardinstall.sql',1425312514),('phabricator:20140520.authtemptoken.sql',1425312514),('phabricator:20140521.projectslug.1.create.sql',1425312514),('phabricator:20140521.projectslug.2.mig.php',1425312514),('phabricator:20140522.projecticon.sql',1425312514),('phabricator:20140524.auth.mfa.cache.sql',1425312514),('phabricator:20140525.hunkmodern.sql',1425312514),('phabricator:20140615.pholioedit.1.sql',1425312514),('phabricator:20140615.pholioedit.2.sql',1425312514),('phabricator:20140617.daemon.explicit-argv.sql',1425312514),('phabricator:20140617.daemonlog.sql',1425312514),('phabricator:20140624.projcolor.1.sql',1425312514),('phabricator:20140624.projcolor.2.sql',1425312514),('phabricator:20140629.dasharchive.1.sql',1425312514),('phabricator:20140629.legalsig.1.sql',1425312514),('phabricator:20140629.legalsig.2.php',1425312514),('phabricator:20140701.legalexemption.1.sql',1425312514),('phabricator:20140701.legalexemption.2.sql',1425312514),('phabricator:20140703.legalcorp.1.sql',1425312514),('phabricator:20140703.legalcorp.2.sql',1425312514),('phabricator:20140703.legalcorp.3.sql',1425312514),('phabricator:20140703.legalcorp.4.sql',1425312514),('phabricator:20140703.legalcorp.5.sql',1425312514),('phabricator:20140704.harbormasterstep.1.sql',1425312514),('phabricator:20140704.harbormasterstep.2.sql',1425312514),('phabricator:20140704.legalpreamble.1.sql',1425312514),('phabricator:20140706.harbormasterdepend.1.php',1425312514),('phabricator:20140706.pedge.1.sql',1425312514),('phabricator:20140711.pnames.1.sql',1425312514),('phabricator:20140711.pnames.2.php',1425312514),('phabricator:20140711.workerpriority.sql',1425312515),('phabricator:20140712.projcoluniq.sql',1425312515),('phabricator:20140721.phortune.1.cart.sql',1425312515),('phabricator:20140721.phortune.2.purchase.sql',1425312515),('phabricator:20140721.phortune.3.charge.sql',1425312515),('phabricator:20140721.phortune.4.cartstatus.sql',1425312515),('phabricator:20140721.phortune.5.cstatusdefault.sql',1425312515),('phabricator:20140721.phortune.6.onetimecharge.sql',1425312515),('phabricator:20140721.phortune.7.nullmethod.sql',1425312515),('phabricator:20140722.appname.php',1425312515),('phabricator:20140722.audit.1.xactions.sql',1425312515),('phabricator:20140722.audit.2.comments.sql',1425312515),('phabricator:20140722.audit.3.miginlines.php',1425312515),('phabricator:20140722.audit.4.migtext.php',1425312515),('phabricator:20140722.renameauth.php',1425312515),('phabricator:20140723.apprenamexaction.sql',1425312515),('phabricator:20140725.audit.1.migxactions.php',1425312515),('phabricator:20140731.audit.1.subscribers.php',1425312515),('phabricator:20140731.cancdn.php',1425312515),('phabricator:20140731.harbormasterstepdesc.sql',1425312515),('phabricator:20140805.boardcol.1.sql',1425312515),('phabricator:20140805.boardcol.2.php',1425312515),('phabricator:20140807.harbormastertargettime.sql',1425312515),('phabricator:20140808.boardprop.1.sql',1425312515),('phabricator:20140808.boardprop.2.sql',1425312515),('phabricator:20140808.boardprop.3.php',1425312515),('phabricator:20140811.blob.1.sql',1425312515),('phabricator:20140811.blob.2.sql',1425312515),('phabricator:20140812.projkey.1.sql',1425312515),('phabricator:20140812.projkey.2.sql',1425312515),('phabricator:20140814.passphrasecredentialconduit.sql',1425312515),('phabricator:20140815.cancdncase.php',1425312515),('phabricator:20140818.harbormasterindex.1.sql',1425312515),('phabricator:20140821.harbormasterbuildgen.1.sql',1425312515),('phabricator:20140822.daemonenvhash.sql',1425312515),('phabricator:20140902.almanacdevice.1.sql',1425312515),('phabricator:20140904.macroattach.php',1425312515),('phabricator:20140911.fund.1.initiative.sql',1425312515),('phabricator:20140911.fund.2.xaction.sql',1425312515),('phabricator:20140911.fund.3.edge.sql',1425312515),('phabricator:20140911.fund.4.backer.sql',1425312515),('phabricator:20140911.fund.5.backxaction.sql',1425312515),('phabricator:20140914.betaproto.php',1425312515),('phabricator:20140917.project.canlock.sql',1425312515),('phabricator:20140918.schema.1.dropaudit.sql',1425312515),('phabricator:20140918.schema.2.dropauditinline.sql',1425312515),('phabricator:20140918.schema.3.wipecache.sql',1425312515),('phabricator:20140918.schema.4.cachetype.sql',1425312515),('phabricator:20140918.schema.5.slowvote.sql',1425312515),('phabricator:20140919.schema.01.calstatus.sql',1425312515),('phabricator:20140919.schema.02.calname.sql',1425312515),('phabricator:20140919.schema.03.dropaux.sql',1425312515),('phabricator:20140919.schema.04.droptaskproj.sql',1425312515),('phabricator:20140926.schema.01.droprelev.sql',1425312515),('phabricator:20140926.schema.02.droprelreqev.sql',1425312515),('phabricator:20140926.schema.03.dropldapinfo.sql',1425312515),('phabricator:20140926.schema.04.dropoauthinfo.sql',1425312515),('phabricator:20140926.schema.05.dropprojaffil.sql',1425312515),('phabricator:20140926.schema.06.dropsubproject.sql',1425312515),('phabricator:20140926.schema.07.droppondcom.sql',1425312515),('phabricator:20140927.schema.01.dropsearchq.sql',1425312515),('phabricator:20140927.schema.02.pholio1.sql',1425312515),('phabricator:20140927.schema.03.pholio2.sql',1425312515),('phabricator:20140927.schema.04.pholio3.sql',1425312515),('phabricator:20140927.schema.05.phragment1.sql',1425312515),('phabricator:20140927.schema.06.releeph1.sql',1425312515),('phabricator:20141001.schema.01.version.sql',1425312515),('phabricator:20141001.schema.02.taskmail.sql',1425312515),('phabricator:20141002.schema.01.liskcounter.sql',1425312515),('phabricator:20141002.schema.02.draftnull.sql',1425312515),('phabricator:20141004.currency.01.sql',1425312515),('phabricator:20141004.currency.02.sql',1425312515),('phabricator:20141004.currency.03.sql',1425312515),('phabricator:20141004.currency.04.sql',1425312515),('phabricator:20141004.currency.05.sql',1425312515),('phabricator:20141004.currency.06.sql',1425312515),('phabricator:20141004.harborliskcounter.sql',1425312515),('phabricator:20141005.phortuneproduct.sql',1425312515),('phabricator:20141006.phortunecart.sql',1425312515),('phabricator:20141006.phortunemerchant.sql',1425312515),('phabricator:20141006.phortunemerchantx.sql',1425312515),('phabricator:20141007.fundmerchant.sql',1425312515),('phabricator:20141007.fundrisks.sql',1425312515),('phabricator:20141007.fundtotal.sql',1425312515),('phabricator:20141007.phortunecartmerchant.sql',1425312515),('phabricator:20141007.phortunecharge.sql',1425312516),('phabricator:20141007.phortunepayment.sql',1425312516),('phabricator:20141007.phortuneprovider.sql',1425312516),('phabricator:20141007.phortuneproviderx.sql',1425312516),('phabricator:20141008.phortunemerchdesc.sql',1425312516),('phabricator:20141008.phortuneprovdis.sql',1425312516),('phabricator:20141008.phortunerefund.sql',1425312516),('phabricator:20141010.fundmailkey.sql',1425312516),('phabricator:20141011.phortunemerchedit.sql',1425312516),('phabricator:20141012.phortunecartxaction.sql',1425312516),('phabricator:20141013.phortunecartkey.sql',1425312516),('phabricator:20141016.almanac.device.sql',1425312516),('phabricator:20141016.almanac.dxaction.sql',1425312516),('phabricator:20141016.almanac.interface.sql',1425312516),('phabricator:20141016.almanac.network.sql',1425312516),('phabricator:20141016.almanac.nxaction.sql',1425312516),('phabricator:20141016.almanac.service.sql',1425312516),('phabricator:20141016.almanac.sxaction.sql',1425312516),('phabricator:20141017.almanac.binding.sql',1425312516),('phabricator:20141017.almanac.bxaction.sql',1425312516),('phabricator:20141025.phriction.1.xaction.sql',1425312516),('phabricator:20141025.phriction.2.xaction.sql',1425312516),('phabricator:20141025.phriction.mailkey.sql',1425312516),('phabricator:20141103.almanac.1.delprop.sql',1425312516),('phabricator:20141103.almanac.2.addprop.sql',1425312516),('phabricator:20141104.almanac.3.edge.sql',1425312516),('phabricator:20141105.ssh.1.rename.sql',1425312516),('phabricator:20141106.dropold.sql',1425312516),('phabricator:20141106.uniqdrafts.php',1425312516),('phabricator:20141107.phriction.policy.1.sql',1425312516),('phabricator:20141107.phriction.policy.2.php',1425312516),('phabricator:20141107.phriction.popkeys.php',1425312516),('phabricator:20141107.ssh.1.colname.sql',1425312516),('phabricator:20141107.ssh.2.keyhash.sql',1425312516),('phabricator:20141107.ssh.3.keyindex.sql',1425312516),('phabricator:20141107.ssh.4.keymig.php',1425312516),('phabricator:20141107.ssh.5.indexnull.sql',1425312516),('phabricator:20141107.ssh.6.indexkey.sql',1425312516),('phabricator:20141107.ssh.7.colnull.sql',1425312516),('phabricator:20141113.auditdupes.php',1425312516),('phabricator:20141118.diffxaction.sql',1425312516),('phabricator:20141119.commitpedge.sql',1425312516),('phabricator:20141119.differential.diff.policy.sql',1425312516),('phabricator:20141119.sshtrust.sql',1425312516),('phabricator:20141123.taskpriority.1.sql',1425312516),('phabricator:20141123.taskpriority.2.sql',1425312516),('phabricator:20141210.maniphestsubscribersmig.1.sql',1425312516),('phabricator:20141210.maniphestsubscribersmig.2.sql',1425312516),('phabricator:20141210.reposervice.sql',1425312516),('phabricator:20141212.conduittoken.sql',1425312516),('phabricator:20141215.almanacservicetype.sql',1425312516),('phabricator:20141217.almanacdevicelock.sql',1425312516),('phabricator:20141217.almanaclock.sql',1425312516),('phabricator:20141218.maniphestcctxn.php',1425312516),('phabricator:20141222.maniphestprojtxn.php',1425312516),('phabricator:20141223.daemonloguser.sql',1425312516),('phabricator:20141223.daemonobjectphid.sql',1425312516),('phabricator:20141230.pasteeditpolicycolumn.sql',1425312516),('phabricator:20141230.pasteeditpolicyexisting.sql',1425312516),('phabricator:20150102.policyname.php',1425312516),('phabricator:20150102.tasksubscriber.sql',1425312516),('phabricator:20150105.conpsearch.sql',1425312516),('phabricator:20150114.oauthserver.client.policy.sql',1425312517),('phabricator:20150115.applicationemails.sql',1425312517),('phabricator:20150115.trigger.1.sql',1425312517),('phabricator:20150115.trigger.2.sql',1425312517),('phabricator:20150116.maniphestapplicationemails.php',1425312517),('phabricator:20150120.maniphestdefaultauthor.php',1425312517),('phabricator:20150124.subs.1.sql',1425312517),('phabricator:20150129.pastefileapplicationemails.php',1425312517),('phabricator:20150130.phortune.1.subphid.sql',1425312517),('phabricator:20150130.phortune.2.subkey.sql',1425312517),('phabricator:20150131.phortune.1.defaultpayment.sql',1425312517),('phabricator:20150205.authprovider.autologin.sql',1425312517),('phabricator:20150205.daemonenv.sql',1425312517),('phabricator:20150209.invite.sql',1425312517),('phabricator:20150209.oauthclient.trust.sql',1425312517),('phabricator:20150210.invitephid.sql',1425312517),('phabricator:20150212.legalpad.session.1.sql',1425312517),('phabricator:20150212.legalpad.session.2.sql',1425312517),('phabricator:20150219.scratch.nonmutable.sql',1425312517),('phabricator:20150223.daemon.1.id.sql',1425312517),('phabricator:20150223.daemon.2.idlegacy.sql',1425312517),('phabricator:20150223.daemon.3.idkey.sql',1425312517),('phabricator:daemonstatus.sql',1425312509),('phabricator:daemonstatuskey.sql',1425312509),('phabricator:daemontaskarchive.sql',1425312509),('phabricator:db.almanac',1425312504),('phabricator:db.audit',1425312504),('phabricator:db.auth',1425312504),('phabricator:db.cache',1425312504),('phabricator:db.calendar',1425312504),('phabricator:db.chatlog',1425312504),('phabricator:db.conduit',1425312504),('phabricator:db.config',1425312504),('phabricator:db.conpherence',1425312504),('phabricator:db.countdown',1425312504),('phabricator:db.daemon',1425312504),('phabricator:db.dashboard',1425312504),('phabricator:db.differential',1425312504),('phabricator:db.diviner',1425312504),('phabricator:db.doorkeeper',1425312504),('phabricator:db.draft',1425312504),('phabricator:db.drydock',1425312504),('phabricator:db.fact',1425312504),('phabricator:db.feed',1425312504),('phabricator:db.file',1425312504),('phabricator:db.flag',1425312504),('phabricator:db.fund',1425312504),('phabricator:db.harbormaster',1425312504),('phabricator:db.herald',1425312504),('phabricator:db.legalpad',1425312504),('phabricator:db.maniphest',1425312504),('phabricator:db.meta_data',1425312504),('phabricator:db.metamta',1425312504),('phabricator:db.nuance',1425312504),('phabricator:db.oauth_server',1425312504),('phabricator:db.owners',1425312504),('phabricator:db.passphrase',1425312504),('phabricator:db.pastebin',1425312504),('phabricator:db.phame',1425312504),('phabricator:db.phlux',1425312504),('phabricator:db.pholio',1425312504),('phabricator:db.phortune',1425312504),('phabricator:db.phragment',1425312504),('phabricator:db.phrequent',1425312504),('phabricator:db.phriction',1425312504),('phabricator:db.policy',1425312504),('phabricator:db.ponder',1425312504),('phabricator:db.project',1425312504),('phabricator:db.releeph',1425312504),('phabricator:db.repository',1425312504),('phabricator:db.search',1425312504),('phabricator:db.slowvote',1425312504),('phabricator:db.system',1425312504),('phabricator:db.timeline',1425312504),('phabricator:db.token',1425312504),('phabricator:db.user',1425312504),('phabricator:db.worker',1425312504),('phabricator:db.xhpastview',1425312504),('phabricator:db.xhprof',1425312504),('phabricator:differentialbookmarks.sql',1425312509),('phabricator:draft-metadata.sql',1425312509),('phabricator:dropfileproxyimage.sql',1425312509),('phabricator:drydockresoucetype.sql',1425312509),('phabricator:drydocktaskid.sql',1425312509),('phabricator:edgetype.sql',1425312509),('phabricator:emailtable.sql',1425312509),('phabricator:emailtableport.sql',1425312509),('phabricator:emailtableremove.sql',1425312509),('phabricator:fact-raw.sql',1425312509),('phabricator:harbormasterobject.sql',1425312509),('phabricator:holidays.sql',1425312509),('phabricator:ldapinfo.sql',1425312509),('phabricator:legalpad-mailkey-populate.php',1425312511),('phabricator:legalpad-mailkey.sql',1425312511),('phabricator:liskcounters-task.sql',1425312509),('phabricator:liskcounters.php',1425312509),('phabricator:liskcounters.sql',1425312509),('phabricator:maniphestxcache.sql',1425312509),('phabricator:markupcache.sql',1425312509),('phabricator:migrate-differential-dependencies.php',1425312509),('phabricator:migrate-maniphest-dependencies.php',1425312509),('phabricator:migrate-maniphest-revisions.php',1425312509),('phabricator:migrate-project-edges.php',1425312509),('phabricator:owners-exclude.sql',1425312509),('phabricator:pastepolicy.sql',1425312509),('phabricator:phameblog.sql',1425312509),('phabricator:phamedomain.sql',1425312509),('phabricator:phameoneblog.sql',1425312509),('phabricator:phamepolicy.sql',1425312509),('phabricator:phiddrop.sql',1425312509),('phabricator:pholio.sql',1425312509),('phabricator:policy-project.sql',1425312509),('phabricator:ponder-comments.sql',1425312509),('phabricator:ponder-mailkey-populate.php',1425312509),('phabricator:ponder-mailkey.sql',1425312509),('phabricator:ponder.sql',1425312509),('phabricator:releeph.sql',1425312510),('phabricator:repository-lint.sql',1425312509),('phabricator:statustxt.sql',1425312509),('phabricator:symbolcontexts.sql',1425312509),('phabricator:testdatabase.sql',1425312509),('phabricator:threadtopic.sql',1425312509),('phabricator:userstatus.sql',1425312509),('phabricator:usertranslation.sql',1425312509),('phabricator:xhprof.sql',1425312509); +INSERT INTO `patch_status` VALUES ('phabricator:000.project.sql',1443545049),('phabricator:0000.legacy.sql',1443545049),('phabricator:001.maniphest_projects.sql',1443545049),('phabricator:002.oauth.sql',1443545049),('phabricator:003.more_oauth.sql',1443545049),('phabricator:004.daemonrepos.sql',1443545049),('phabricator:005.workers.sql',1443545049),('phabricator:006.repository.sql',1443545049),('phabricator:007.daemonlog.sql',1443545049),('phabricator:008.repoopt.sql',1443545049),('phabricator:009.repo_summary.sql',1443545049),('phabricator:010.herald.sql',1443545049),('phabricator:011.badcommit.sql',1443545049),('phabricator:012.dropphidtype.sql',1443545049),('phabricator:013.commitdetail.sql',1443545049),('phabricator:014.shortcuts.sql',1443545049),('phabricator:015.preferences.sql',1443545049),('phabricator:016.userrealnameindex.sql',1443545049),('phabricator:017.sessionkeys.sql',1443545049),('phabricator:018.owners.sql',1443545049),('phabricator:019.arcprojects.sql',1443545049),('phabricator:020.pathcapital.sql',1443545049),('phabricator:021.xhpastview.sql',1443545049),('phabricator:022.differentialcommit.sql',1443545049),('phabricator:023.dxkeys.sql',1443545049),('phabricator:024.mlistkeys.sql',1443545049),('phabricator:025.commentopt.sql',1443545049),('phabricator:026.diffpropkey.sql',1443545049),('phabricator:027.metamtakeys.sql',1443545049),('phabricator:028.systemagent.sql',1443545049),('phabricator:029.cursors.sql',1443545049),('phabricator:030.imagemacro.sql',1443545049),('phabricator:031.workerrace.sql',1443545049),('phabricator:032.viewtime.sql',1443545049),('phabricator:033.privtest.sql',1443545049),('phabricator:034.savedheader.sql',1443545049),('phabricator:035.proxyimage.sql',1443545049),('phabricator:036.mailkey.sql',1443545049),('phabricator:037.setuptest.sql',1443545049),('phabricator:038.admin.sql',1443545049),('phabricator:039.userlog.sql',1443545050),('phabricator:040.transform.sql',1443545050),('phabricator:041.heraldrepetition.sql',1443545050),('phabricator:042.commentmetadata.sql',1443545050),('phabricator:043.pastebin.sql',1443545050),('phabricator:044.countdown.sql',1443545050),('phabricator:045.timezone.sql',1443545050),('phabricator:046.conduittoken.sql',1443545050),('phabricator:047.projectstatus.sql',1443545050),('phabricator:048.relationshipkeys.sql',1443545050),('phabricator:049.projectowner.sql',1443545050),('phabricator:050.taskdenormal.sql',1443545050),('phabricator:051.projectfilter.sql',1443545050),('phabricator:052.pastelanguage.sql',1443545050),('phabricator:053.feed.sql',1443545050),('phabricator:054.subscribers.sql',1443545050),('phabricator:055.add_author_to_files.sql',1443545050),('phabricator:056.slowvote.sql',1443545050),('phabricator:057.parsecache.sql',1443545050),('phabricator:058.missingkeys.sql',1443545050),('phabricator:059.engines.php',1443545050),('phabricator:060.phriction.sql',1443545050),('phabricator:061.phrictioncontent.sql',1443545050),('phabricator:062.phrictionmenu.sql',1443545050),('phabricator:063.pasteforks.sql',1443545050),('phabricator:064.subprojects.sql',1443545050),('phabricator:065.sshkeys.sql',1443545050),('phabricator:066.phrictioncontent.sql',1443545050),('phabricator:067.preferences.sql',1443545050),('phabricator:068.maniphestauxiliarystorage.sql',1443545050),('phabricator:069.heraldxscript.sql',1443545050),('phabricator:070.differentialaux.sql',1443545050),('phabricator:071.contentsource.sql',1443545050),('phabricator:072.blamerevert.sql',1443545050),('phabricator:073.reposymbols.sql',1443545050),('phabricator:074.affectedpath.sql',1443545050),('phabricator:075.revisionhash.sql',1443545050),('phabricator:076.indexedlanguages.sql',1443545050),('phabricator:077.originalemail.sql',1443545050),('phabricator:078.nametoken.sql',1443545050),('phabricator:079.nametokenindex.php',1443545050),('phabricator:080.filekeys.sql',1443545050),('phabricator:081.filekeys.php',1443545050),('phabricator:082.xactionkey.sql',1443545050),('phabricator:083.dxviewtime.sql',1443545051),('phabricator:084.pasteauthorkey.sql',1443545051),('phabricator:085.packagecommitrelationship.sql',1443545051),('phabricator:086.formeraffil.sql',1443545051),('phabricator:087.phrictiondelete.sql',1443545051),('phabricator:088.audit.sql',1443545051),('phabricator:089.projectwiki.sql',1443545051),('phabricator:090.forceuniqueprojectnames.php',1443545051),('phabricator:091.uniqueslugkey.sql',1443545051),('phabricator:092.dropgithubnotification.sql',1443545051),('phabricator:093.gitremotes.php',1443545051),('phabricator:094.phrictioncolumn.sql',1443545051),('phabricator:095.directory.sql',1443545051),('phabricator:096.filename.sql',1443545051),('phabricator:097.heraldruletypes.sql',1443545051),('phabricator:098.heraldruletypemigration.php',1443545051),('phabricator:099.drydock.sql',1443545051),('phabricator:100.projectxaction.sql',1443545051),('phabricator:101.heraldruleapplied.sql',1443545051),('phabricator:102.heraldcleanup.php',1443545051),('phabricator:103.heraldedithistory.sql',1443545051),('phabricator:104.searchkey.sql',1443545051),('phabricator:105.mimetype.sql',1443545051),('phabricator:106.chatlog.sql',1443545051),('phabricator:107.oauthserver.sql',1443545051),('phabricator:108.oauthscope.sql',1443545051),('phabricator:109.oauthclientphidkey.sql',1443545051),('phabricator:110.commitaudit.sql',1443545051),('phabricator:111.commitauditmigration.php',1443545051),('phabricator:112.oauthaccesscoderedirecturi.sql',1443545051),('phabricator:113.lastreviewer.sql',1443545051),('phabricator:114.auditrequest.sql',1443545051),('phabricator:115.prepareutf8.sql',1443545051),('phabricator:116.utf8-backup-first-expect-wait.sql',1443545053),('phabricator:117.repositorydescription.php',1443545053),('phabricator:118.auditinline.sql',1443545053),('phabricator:119.filehash.sql',1443545053),('phabricator:120.noop.sql',1443545053),('phabricator:121.drydocklog.sql',1443545053),('phabricator:122.flag.sql',1443545053),('phabricator:123.heraldrulelog.sql',1443545053),('phabricator:124.subpriority.sql',1443545053),('phabricator:125.ipv6.sql',1443545053),('phabricator:126.edges.sql',1443545053),('phabricator:127.userkeybody.sql',1443545053),('phabricator:128.phabricatorcom.sql',1443545053),('phabricator:129.savedquery.sql',1443545053),('phabricator:130.denormalrevisionquery.sql',1443545053),('phabricator:131.migraterevisionquery.php',1443545053),('phabricator:132.phame.sql',1443545053),('phabricator:133.imagemacro.sql',1443545053),('phabricator:134.emptysearch.sql',1443545053),('phabricator:135.datecommitted.sql',1443545053),('phabricator:136.sex.sql',1443545053),('phabricator:137.auditmetadata.sql',1443545053),('phabricator:138.notification.sql',1443545053),('phabricator:20121209.pholioxactions.sql',1443545054),('phabricator:20121209.xmacroadd.sql',1443545054),('phabricator:20121209.xmacromigrate.php',1443545054),('phabricator:20121209.xmacromigratekey.sql',1443545054),('phabricator:20121220.generalcache.sql',1443545054),('phabricator:20121226.config.sql',1443545054),('phabricator:20130101.confxaction.sql',1443545054),('phabricator:20130102.metamtareceivedmailmessageidhash.sql',1443545054),('phabricator:20130103.filemetadata.sql',1443545054),('phabricator:20130111.conpherence.sql',1443545054),('phabricator:20130127.altheraldtranscript.sql',1443545054),('phabricator:20130131.conpherencepics.sql',1443545054),('phabricator:20130201.revisionunsubscribed.php',1443545054),('phabricator:20130201.revisionunsubscribed.sql',1443545054),('phabricator:20130214.chatlogchannel.sql',1443545054),('phabricator:20130214.chatlogchannelid.sql',1443545054),('phabricator:20130214.token.sql',1443545054),('phabricator:20130215.phabricatorfileaddttl.sql',1443545054),('phabricator:20130217.cachettl.sql',1443545054),('phabricator:20130218.longdaemon.sql',1443545054),('phabricator:20130218.updatechannelid.php',1443545054),('phabricator:20130219.commitsummary.sql',1443545054),('phabricator:20130219.commitsummarymig.php',1443545054),('phabricator:20130222.dropchannel.sql',1443545054),('phabricator:20130226.commitkey.sql',1443545054),('phabricator:20130304.lintauthor.sql',1443545054),('phabricator:20130310.xactionmeta.sql',1443545055),('phabricator:20130317.phrictionedge.sql',1443545055),('phabricator:20130319.conpherence.sql',1443545054),('phabricator:20130319.phabricatorfileexplicitupload.sql',1443545054),('phabricator:20130320.phlux.sql',1443545055),('phabricator:20130321.token.sql',1443545055),('phabricator:20130322.phortune.sql',1443545055),('phabricator:20130323.phortunepayment.sql',1443545055),('phabricator:20130324.phortuneproduct.sql',1443545055),('phabricator:20130330.phrequent.sql',1443545055),('phabricator:20130403.conpherencecache.sql',1443545055),('phabricator:20130403.conpherencecachemig.php',1443545055),('phabricator:20130409.commitdrev.php',1443545055),('phabricator:20130417.externalaccount.sql',1443545055),('phabricator:20130423.conpherenceindices.sql',1443545055),('phabricator:20130423.phortunepaymentrevised.sql',1443545055),('phabricator:20130423.updateexternalaccount.sql',1443545055),('phabricator:20130426.search_savedquery.sql',1443545055),('phabricator:20130502.countdownrevamp1.sql',1443545055),('phabricator:20130502.countdownrevamp2.php',1443545055),('phabricator:20130502.countdownrevamp3.sql',1443545055),('phabricator:20130507.releephrqmailkey.sql',1443545055),('phabricator:20130507.releephrqmailkeypop.php',1443545055),('phabricator:20130507.releephrqsimplifycols.sql',1443545055),('phabricator:20130508.releephtransactions.sql',1443545055),('phabricator:20130508.releephtransactionsmig.php',1443545055),('phabricator:20130508.search_namedquery.sql',1443545055),('phabricator:20130513.receviedmailstatus.sql',1443545055),('phabricator:20130519.diviner.sql',1443545055),('phabricator:20130521.dropconphimages.sql',1443545055),('phabricator:20130523.maniphest_owners.sql',1443545055),('phabricator:20130524.repoxactions.sql',1443545055),('phabricator:20130529.macroauthor.sql',1443545055),('phabricator:20130529.macroauthormig.php',1443545055),('phabricator:20130530.macrodatekey.sql',1443545055),('phabricator:20130530.pastekeys.sql',1443545055),('phabricator:20130530.sessionhash.php',1443545055),('phabricator:20130531.filekeys.sql',1443545055),('phabricator:20130602.morediviner.sql',1443545055),('phabricator:20130602.namedqueries.sql',1443545055),('phabricator:20130606.userxactions.sql',1443545055),('phabricator:20130607.xaccount.sql',1443545055),('phabricator:20130611.migrateoauth.php',1443545055),('phabricator:20130611.nukeldap.php',1443545055),('phabricator:20130613.authdb.sql',1443545055),('phabricator:20130619.authconf.php',1443545055),('phabricator:20130620.diffxactions.sql',1443545055),('phabricator:20130621.diffcommentphid.sql',1443545055),('phabricator:20130621.diffcommentphidmig.php',1443545055),('phabricator:20130621.diffcommentunphid.sql',1443545056),('phabricator:20130622.doorkeeper.sql',1443545056),('phabricator:20130628.legalpadv0.sql',1443545056),('phabricator:20130701.conduitlog.sql',1443545056),('phabricator:20130703.legalpaddocdenorm.php',1443545056),('phabricator:20130703.legalpaddocdenorm.sql',1443545056),('phabricator:20130709.droptimeline.sql',1443545056),('phabricator:20130709.legalpadsignature.sql',1443545056),('phabricator:20130711.pholioimageobsolete.php',1443545056),('phabricator:20130711.pholioimageobsolete.sql',1443545056),('phabricator:20130711.pholioimageobsolete2.sql',1443545056),('phabricator:20130711.trimrealnames.php',1443545056),('phabricator:20130714.votexactions.sql',1443545056),('phabricator:20130715.votecomments.php',1443545056),('phabricator:20130715.voteedges.sql',1443545056),('phabricator:20130716.archivememberlessprojects.php',1443545056),('phabricator:20130722.pholioreplace.sql',1443545056),('phabricator:20130723.taskstarttime.sql',1443545056),('phabricator:20130726.ponderxactions.sql',1443545056),('phabricator:20130727.ponderquestionstatus.sql',1443545056),('phabricator:20130728.ponderunique.php',1443545056),('phabricator:20130728.ponderuniquekey.sql',1443545056),('phabricator:20130728.ponderxcomment.php',1443545056),('phabricator:20130731.releephcutpointidentifier.sql',1443545056),('phabricator:20130731.releephproject.sql',1443545056),('phabricator:20130731.releephrepoid.sql',1443545056),('phabricator:20130801.pastexactions.php',1443545056),('phabricator:20130801.pastexactions.sql',1443545056),('phabricator:20130802.heraldphid.sql',1443545056),('phabricator:20130802.heraldphids.php',1443545056),('phabricator:20130802.heraldphidukey.sql',1443545056),('phabricator:20130802.heraldxactions.sql',1443545056),('phabricator:20130805.pasteedges.sql',1443545056),('phabricator:20130805.pastemailkey.sql',1443545056),('phabricator:20130805.pastemailkeypop.php',1443545056),('phabricator:20130814.usercustom.sql',1443545056),('phabricator:20130820.file-mailkey-populate.php',1443545056),('phabricator:20130820.filemailkey.sql',1443545056),('phabricator:20130820.filexactions.sql',1443545056),('phabricator:20130820.releephxactions.sql',1443545056),('phabricator:20130826.divinernode.sql',1443545056),('phabricator:20130912.maniphest.1.touch.sql',1443545056),('phabricator:20130912.maniphest.2.created.sql',1443545056),('phabricator:20130912.maniphest.3.nameindex.sql',1443545056),('phabricator:20130912.maniphest.4.fillindex.php',1443545056),('phabricator:20130913.maniphest.1.migratesearch.php',1443545056),('phabricator:20130914.usercustom.sql',1443545056),('phabricator:20130915.maniphestcustom.sql',1443545056),('phabricator:20130915.maniphestmigrate.php',1443545056),('phabricator:20130915.maniphestqdrop.sql',1443545057),('phabricator:20130919.mfieldconf.php',1443545056),('phabricator:20130920.repokeyspolicy.sql',1443545056),('phabricator:20130921.mtransactions.sql',1443545056),('phabricator:20130921.xmigratemaniphest.php',1443545056),('phabricator:20130923.mrename.sql',1443545056),('phabricator:20130924.mdraftkey.sql',1443545056),('phabricator:20130925.mpolicy.sql',1443545056),('phabricator:20130925.xpolicy.sql',1443545057),('phabricator:20130926.dcustom.sql',1443545057),('phabricator:20130926.dinkeys.sql',1443545057),('phabricator:20130926.dinline.php',1443545057),('phabricator:20130927.audiomacro.sql',1443545057),('phabricator:20130929.filepolicy.sql',1443545057),('phabricator:20131004.dxedgekey.sql',1443545057),('phabricator:20131004.dxreviewers.php',1443545057),('phabricator:20131006.hdisable.sql',1443545057),('phabricator:20131010.pstorage.sql',1443545057),('phabricator:20131015.cpolicy.sql',1443545057),('phabricator:20131020.col1.sql',1443545057),('phabricator:20131020.harbormaster.sql',1443545057),('phabricator:20131020.pcustom.sql',1443545057),('phabricator:20131020.pxaction.sql',1443545057),('phabricator:20131020.pxactionmig.php',1443545057),('phabricator:20131025.repopush.sql',1443545057),('phabricator:20131026.commitstatus.sql',1443545057),('phabricator:20131030.repostatusmessage.sql',1443545057),('phabricator:20131031.vcspassword.sql',1443545057),('phabricator:20131105.buildstep.sql',1443545057),('phabricator:20131106.diffphid.1.col.sql',1443545057),('phabricator:20131106.diffphid.2.mig.php',1443545057),('phabricator:20131106.diffphid.3.key.sql',1443545057),('phabricator:20131106.nuance-v0.sql',1443545057),('phabricator:20131107.buildlog.sql',1443545057),('phabricator:20131112.userverified.1.col.sql',1443545057),('phabricator:20131112.userverified.2.mig.php',1443545057),('phabricator:20131118.ownerorder.php',1443545057),('phabricator:20131119.passphrase.sql',1443545057),('phabricator:20131120.nuancesourcetype.sql',1443545057),('phabricator:20131121.passphraseedge.sql',1443545057),('phabricator:20131121.repocredentials.1.col.sql',1443545057),('phabricator:20131121.repocredentials.2.mig.php',1443545057),('phabricator:20131122.repomirror.sql',1443545057),('phabricator:20131123.drydockblueprintpolicy.sql',1443545057),('phabricator:20131129.drydockresourceblueprint.sql',1443545057),('phabricator:20131204.pushlog.sql',1443545057),('phabricator:20131205.buildsteporder.sql',1443545058),('phabricator:20131205.buildstepordermig.php',1443545058),('phabricator:20131205.buildtargets.sql',1443545057),('phabricator:20131206.phragment.sql',1443545058),('phabricator:20131206.phragmentnull.sql',1443545058),('phabricator:20131208.phragmentsnapshot.sql',1443545058),('phabricator:20131211.phragmentedges.sql',1443545058),('phabricator:20131217.pushlogphid.1.col.sql',1443545058),('phabricator:20131217.pushlogphid.2.mig.php',1443545058),('phabricator:20131217.pushlogphid.3.key.sql',1443545058),('phabricator:20131219.pxdrop.sql',1443545058),('phabricator:20131224.harbormanual.sql',1443545058),('phabricator:20131227.heraldobject.sql',1443545058),('phabricator:20131231.dropshortcut.sql',1443545058),('phabricator:20131302.maniphestvalue.sql',1443545054),('phabricator:20140104.harbormastercmd.sql',1443545058),('phabricator:20140106.macromailkey.1.sql',1443545058),('phabricator:20140106.macromailkey.2.php',1443545058),('phabricator:20140108.ddbpname.1.sql',1443545058),('phabricator:20140108.ddbpname.2.php',1443545058),('phabricator:20140109.ddxactions.sql',1443545058),('phabricator:20140109.projectcolumnsdates.sql',1443545058),('phabricator:20140113.legalpadsig.1.sql',1443545058),('phabricator:20140113.legalpadsig.2.php',1443545058),('phabricator:20140115.auth.1.id.sql',1443545058),('phabricator:20140115.auth.2.expires.sql',1443545058),('phabricator:20140115.auth.3.unlimit.php',1443545058),('phabricator:20140115.legalpadsigkey.sql',1443545058),('phabricator:20140116.reporefcursor.sql',1443545058),('phabricator:20140126.diff.1.parentrevisionid.sql',1443545058),('phabricator:20140126.diff.2.repositoryphid.sql',1443545058),('phabricator:20140130.dash.1.board.sql',1443545058),('phabricator:20140130.dash.2.panel.sql',1443545058),('phabricator:20140130.dash.3.boardxaction.sql',1443545058),('phabricator:20140130.dash.4.panelxaction.sql',1443545058),('phabricator:20140130.mail.1.retry.sql',1443545058),('phabricator:20140130.mail.2.next.sql',1443545058),('phabricator:20140201.gc.1.mailsent.sql',1443545058),('phabricator:20140201.gc.2.mailreceived.sql',1443545058),('phabricator:20140205.cal.1.rename.sql',1443545058),('phabricator:20140205.cal.2.phid-col.sql',1443545058),('phabricator:20140205.cal.3.phid-mig.php',1443545058),('phabricator:20140205.cal.4.phid-key.sql',1443545058),('phabricator:20140210.herald.rule-condition-mig.php',1443545058),('phabricator:20140210.projcfield.1.blurb.php',1443545058),('phabricator:20140210.projcfield.2.piccol.sql',1443545058),('phabricator:20140210.projcfield.3.picmig.sql',1443545058),('phabricator:20140210.projcfield.4.memmig.sql',1443545058),('phabricator:20140210.projcfield.5.dropprofile.sql',1443545058),('phabricator:20140211.dx.1.nullablechangesetid.sql',1443545058),('phabricator:20140211.dx.2.migcommenttext.php',1443545058),('phabricator:20140211.dx.3.migsubscriptions.sql',1443545058),('phabricator:20140211.dx.999.drop.relationships.sql',1443545058),('phabricator:20140212.dx.1.armageddon.php',1443545058),('phabricator:20140214.clean.1.legacycommentid.sql',1443545058),('phabricator:20140214.clean.2.dropcomment.sql',1443545058),('phabricator:20140214.clean.3.dropinline.sql',1443545058),('phabricator:20140218.differentialdraft.sql',1443545058),('phabricator:20140218.passwords.1.extend.sql',1443545058),('phabricator:20140218.passwords.2.prefix.sql',1443545058),('phabricator:20140218.passwords.3.vcsextend.sql',1443545058),('phabricator:20140218.passwords.4.vcs.php',1443545058),('phabricator:20140223.bigutf8scratch.sql',1443545058),('phabricator:20140224.dxclean.1.datecommitted.sql',1443545058),('phabricator:20140226.dxcustom.1.fielddata.php',1443545058),('phabricator:20140226.dxcustom.99.drop.sql',1443545058),('phabricator:20140228.dxcomment.1.sql',1443545058),('phabricator:20140305.diviner.1.slugcol.sql',1443545058),('phabricator:20140305.diviner.2.slugkey.sql',1443545058),('phabricator:20140311.mdroplegacy.sql',1443545058),('phabricator:20140314.projectcolumn.1.statuscol.sql',1443545058),('phabricator:20140314.projectcolumn.2.statuskey.sql',1443545058),('phabricator:20140317.mupdatedkey.sql',1443545058),('phabricator:20140321.harbor.1.bxaction.sql',1443545058),('phabricator:20140321.mstatus.1.col.sql',1443545058),('phabricator:20140321.mstatus.2.mig.php',1443545058),('phabricator:20140323.harbor.1.renames.php',1443545058),('phabricator:20140323.harbor.2.message.sql',1443545058),('phabricator:20140325.push.1.event.sql',1443545058),('phabricator:20140325.push.2.eventphid.sql',1443545059),('phabricator:20140325.push.3.groups.php',1443545059),('phabricator:20140325.push.4.prune.sql',1443545059),('phabricator:20140326.project.1.colxaction.sql',1443545059),('phabricator:20140328.releeph.1.productxaction.sql',1443545059),('phabricator:20140330.flagtext.sql',1443545059),('phabricator:20140402.actionlog.sql',1443545059),('phabricator:20140410.accountsecret.1.sql',1443545059),('phabricator:20140410.accountsecret.2.php',1443545059),('phabricator:20140416.harbor.1.sql',1443545059),('phabricator:20140420.rel.1.objectphid.sql',1443545059),('phabricator:20140420.rel.2.objectmig.php',1443545059),('phabricator:20140421.slowvotecolumnsisclosed.sql',1443545059),('phabricator:20140423.session.1.hisec.sql',1443545059),('phabricator:20140427.mfactor.1.sql',1443545059),('phabricator:20140430.auth.1.partial.sql',1443545059),('phabricator:20140430.dash.1.paneltype.sql',1443545059),('phabricator:20140430.dash.2.edge.sql',1443545059),('phabricator:20140501.passphraselockcredential.sql',1443545059),('phabricator:20140501.remove.1.dlog.sql',1443545059),('phabricator:20140507.smstable.sql',1443545059),('phabricator:20140509.coverage.1.sql',1443545059),('phabricator:20140509.dashboardlayoutconfig.sql',1443545059),('phabricator:20140512.dparents.1.sql',1443545059),('phabricator:20140514.harbormasterbuildabletransaction.sql',1443545059),('phabricator:20140514.pholiomockclose.sql',1443545059),('phabricator:20140515.trust-emails.sql',1443545059),('phabricator:20140517.dxbinarycache.sql',1443545059),('phabricator:20140518.dxmorebinarycache.sql',1443545059),('phabricator:20140519.dashboardinstall.sql',1443545059),('phabricator:20140520.authtemptoken.sql',1443545059),('phabricator:20140521.projectslug.1.create.sql',1443545059),('phabricator:20140521.projectslug.2.mig.php',1443545059),('phabricator:20140522.projecticon.sql',1443545059),('phabricator:20140524.auth.mfa.cache.sql',1443545059),('phabricator:20140525.hunkmodern.sql',1443545059),('phabricator:20140615.pholioedit.1.sql',1443545059),('phabricator:20140615.pholioedit.2.sql',1443545059),('phabricator:20140617.daemon.explicit-argv.sql',1443545059),('phabricator:20140617.daemonlog.sql',1443545059),('phabricator:20140624.projcolor.1.sql',1443545059),('phabricator:20140624.projcolor.2.sql',1443545059),('phabricator:20140629.dasharchive.1.sql',1443545059),('phabricator:20140629.legalsig.1.sql',1443545059),('phabricator:20140629.legalsig.2.php',1443545059),('phabricator:20140701.legalexemption.1.sql',1443545059),('phabricator:20140701.legalexemption.2.sql',1443545059),('phabricator:20140703.legalcorp.1.sql',1443545059),('phabricator:20140703.legalcorp.2.sql',1443545059),('phabricator:20140703.legalcorp.3.sql',1443545059),('phabricator:20140703.legalcorp.4.sql',1443545059),('phabricator:20140703.legalcorp.5.sql',1443545059),('phabricator:20140704.harbormasterstep.1.sql',1443545059),('phabricator:20140704.harbormasterstep.2.sql',1443545059),('phabricator:20140704.legalpreamble.1.sql',1443545059),('phabricator:20140706.harbormasterdepend.1.php',1443545059),('phabricator:20140706.pedge.1.sql',1443545059),('phabricator:20140711.pnames.1.sql',1443545059),('phabricator:20140711.pnames.2.php',1443545059),('phabricator:20140711.workerpriority.sql',1443545059),('phabricator:20140712.projcoluniq.sql',1443545059),('phabricator:20140721.phortune.1.cart.sql',1443545059),('phabricator:20140721.phortune.2.purchase.sql',1443545059),('phabricator:20140721.phortune.3.charge.sql',1443545059),('phabricator:20140721.phortune.4.cartstatus.sql',1443545059),('phabricator:20140721.phortune.5.cstatusdefault.sql',1443545059),('phabricator:20140721.phortune.6.onetimecharge.sql',1443545060),('phabricator:20140721.phortune.7.nullmethod.sql',1443545060),('phabricator:20140722.appname.php',1443545060),('phabricator:20140722.audit.1.xactions.sql',1443545060),('phabricator:20140722.audit.2.comments.sql',1443545060),('phabricator:20140722.audit.3.miginlines.php',1443545060),('phabricator:20140722.audit.4.migtext.php',1443545060),('phabricator:20140722.renameauth.php',1443545060),('phabricator:20140723.apprenamexaction.sql',1443545060),('phabricator:20140725.audit.1.migxactions.php',1443545060),('phabricator:20140731.audit.1.subscribers.php',1443545060),('phabricator:20140731.cancdn.php',1443545060),('phabricator:20140731.harbormasterstepdesc.sql',1443545060),('phabricator:20140805.boardcol.1.sql',1443545060),('phabricator:20140805.boardcol.2.php',1443545060),('phabricator:20140807.harbormastertargettime.sql',1443545060),('phabricator:20140808.boardprop.1.sql',1443545060),('phabricator:20140808.boardprop.2.sql',1443545060),('phabricator:20140808.boardprop.3.php',1443545060),('phabricator:20140811.blob.1.sql',1443545060),('phabricator:20140811.blob.2.sql',1443545060),('phabricator:20140812.projkey.1.sql',1443545060),('phabricator:20140812.projkey.2.sql',1443545060),('phabricator:20140814.passphrasecredentialconduit.sql',1443545060),('phabricator:20140815.cancdncase.php',1443545060),('phabricator:20140818.harbormasterindex.1.sql',1443545060),('phabricator:20140821.harbormasterbuildgen.1.sql',1443545060),('phabricator:20140822.daemonenvhash.sql',1443545060),('phabricator:20140902.almanacdevice.1.sql',1443545060),('phabricator:20140904.macroattach.php',1443545060),('phabricator:20140911.fund.1.initiative.sql',1443545060),('phabricator:20140911.fund.2.xaction.sql',1443545060),('phabricator:20140911.fund.3.edge.sql',1443545060),('phabricator:20140911.fund.4.backer.sql',1443545060),('phabricator:20140911.fund.5.backxaction.sql',1443545060),('phabricator:20140914.betaproto.php',1443545060),('phabricator:20140917.project.canlock.sql',1443545060),('phabricator:20140918.schema.1.dropaudit.sql',1443545060),('phabricator:20140918.schema.2.dropauditinline.sql',1443545060),('phabricator:20140918.schema.3.wipecache.sql',1443545060),('phabricator:20140918.schema.4.cachetype.sql',1443545060),('phabricator:20140918.schema.5.slowvote.sql',1443545060),('phabricator:20140919.schema.01.calstatus.sql',1443545060),('phabricator:20140919.schema.02.calname.sql',1443545060),('phabricator:20140919.schema.03.dropaux.sql',1443545060),('phabricator:20140919.schema.04.droptaskproj.sql',1443545060),('phabricator:20140926.schema.01.droprelev.sql',1443545060),('phabricator:20140926.schema.02.droprelreqev.sql',1443545060),('phabricator:20140926.schema.03.dropldapinfo.sql',1443545060),('phabricator:20140926.schema.04.dropoauthinfo.sql',1443545060),('phabricator:20140926.schema.05.dropprojaffil.sql',1443545060),('phabricator:20140926.schema.06.dropsubproject.sql',1443545060),('phabricator:20140926.schema.07.droppondcom.sql',1443545060),('phabricator:20140927.schema.01.dropsearchq.sql',1443545060),('phabricator:20140927.schema.02.pholio1.sql',1443545060),('phabricator:20140927.schema.03.pholio2.sql',1443545060),('phabricator:20140927.schema.04.pholio3.sql',1443545060),('phabricator:20140927.schema.05.phragment1.sql',1443545060),('phabricator:20140927.schema.06.releeph1.sql',1443545060),('phabricator:20141001.schema.01.version.sql',1443545060),('phabricator:20141001.schema.02.taskmail.sql',1443545060),('phabricator:20141002.schema.01.liskcounter.sql',1443545060),('phabricator:20141002.schema.02.draftnull.sql',1443545060),('phabricator:20141004.currency.01.sql',1443545060),('phabricator:20141004.currency.02.sql',1443545060),('phabricator:20141004.currency.03.sql',1443545060),('phabricator:20141004.currency.04.sql',1443545060),('phabricator:20141004.currency.05.sql',1443545060),('phabricator:20141004.currency.06.sql',1443545060),('phabricator:20141004.harborliskcounter.sql',1443545060),('phabricator:20141005.phortuneproduct.sql',1443545060),('phabricator:20141006.phortunecart.sql',1443545060),('phabricator:20141006.phortunemerchant.sql',1443545060),('phabricator:20141006.phortunemerchantx.sql',1443545060),('phabricator:20141007.fundmerchant.sql',1443545060),('phabricator:20141007.fundrisks.sql',1443545060),('phabricator:20141007.fundtotal.sql',1443545060),('phabricator:20141007.phortunecartmerchant.sql',1443545060),('phabricator:20141007.phortunecharge.sql',1443545061),('phabricator:20141007.phortunepayment.sql',1443545061),('phabricator:20141007.phortuneprovider.sql',1443545061),('phabricator:20141007.phortuneproviderx.sql',1443545061),('phabricator:20141008.phortunemerchdesc.sql',1443545061),('phabricator:20141008.phortuneprovdis.sql',1443545061),('phabricator:20141008.phortunerefund.sql',1443545061),('phabricator:20141010.fundmailkey.sql',1443545061),('phabricator:20141011.phortunemerchedit.sql',1443545061),('phabricator:20141012.phortunecartxaction.sql',1443545061),('phabricator:20141013.phortunecartkey.sql',1443545061),('phabricator:20141016.almanac.device.sql',1443545061),('phabricator:20141016.almanac.dxaction.sql',1443545061),('phabricator:20141016.almanac.interface.sql',1443545061),('phabricator:20141016.almanac.network.sql',1443545061),('phabricator:20141016.almanac.nxaction.sql',1443545061),('phabricator:20141016.almanac.service.sql',1443545061),('phabricator:20141016.almanac.sxaction.sql',1443545061),('phabricator:20141017.almanac.binding.sql',1443545061),('phabricator:20141017.almanac.bxaction.sql',1443545061),('phabricator:20141025.phriction.1.xaction.sql',1443545061),('phabricator:20141025.phriction.2.xaction.sql',1443545061),('phabricator:20141025.phriction.mailkey.sql',1443545061),('phabricator:20141103.almanac.1.delprop.sql',1443545061),('phabricator:20141103.almanac.2.addprop.sql',1443545061),('phabricator:20141104.almanac.3.edge.sql',1443545061),('phabricator:20141105.ssh.1.rename.sql',1443545061),('phabricator:20141106.dropold.sql',1443545061),('phabricator:20141106.uniqdrafts.php',1443545061),('phabricator:20141107.phriction.policy.1.sql',1443545061),('phabricator:20141107.phriction.policy.2.php',1443545061),('phabricator:20141107.phriction.popkeys.php',1443545061),('phabricator:20141107.ssh.1.colname.sql',1443545061),('phabricator:20141107.ssh.2.keyhash.sql',1443545061),('phabricator:20141107.ssh.3.keyindex.sql',1443545061),('phabricator:20141107.ssh.4.keymig.php',1443545061),('phabricator:20141107.ssh.5.indexnull.sql',1443545061),('phabricator:20141107.ssh.6.indexkey.sql',1443545061),('phabricator:20141107.ssh.7.colnull.sql',1443545061),('phabricator:20141113.auditdupes.php',1443545061),('phabricator:20141118.diffxaction.sql',1443545061),('phabricator:20141119.commitpedge.sql',1443545061),('phabricator:20141119.differential.diff.policy.sql',1443545061),('phabricator:20141119.sshtrust.sql',1443545061),('phabricator:20141123.taskpriority.1.sql',1443545061),('phabricator:20141123.taskpriority.2.sql',1443545061),('phabricator:20141210.maniphestsubscribersmig.1.sql',1443545061),('phabricator:20141210.maniphestsubscribersmig.2.sql',1443545061),('phabricator:20141210.reposervice.sql',1443545061),('phabricator:20141212.conduittoken.sql',1443545061),('phabricator:20141215.almanacservicetype.sql',1443545061),('phabricator:20141217.almanacdevicelock.sql',1443545061),('phabricator:20141217.almanaclock.sql',1443545061),('phabricator:20141218.maniphestcctxn.php',1443545061),('phabricator:20141222.maniphestprojtxn.php',1443545061),('phabricator:20141223.daemonloguser.sql',1443545061),('phabricator:20141223.daemonobjectphid.sql',1443545061),('phabricator:20141230.pasteeditpolicycolumn.sql',1443545061),('phabricator:20141230.pasteeditpolicyexisting.sql',1443545061),('phabricator:20150102.policyname.php',1443545061),('phabricator:20150102.tasksubscriber.sql',1443545061),('phabricator:20150105.conpsearch.sql',1443545061),('phabricator:20150114.oauthserver.client.policy.sql',1443545062),('phabricator:20150115.applicationemails.sql',1443545062),('phabricator:20150115.trigger.1.sql',1443545062),('phabricator:20150115.trigger.2.sql',1443545062),('phabricator:20150116.maniphestapplicationemails.php',1443545062),('phabricator:20150120.maniphestdefaultauthor.php',1443545062),('phabricator:20150124.subs.1.sql',1443545062),('phabricator:20150129.pastefileapplicationemails.php',1443545062),('phabricator:20150130.phortune.1.subphid.sql',1443545062),('phabricator:20150130.phortune.2.subkey.sql',1443545062),('phabricator:20150131.phortune.1.defaultpayment.sql',1443545062),('phabricator:20150205.authprovider.autologin.sql',1443545062),('phabricator:20150205.daemonenv.sql',1443545062),('phabricator:20150209.invite.sql',1443545062),('phabricator:20150209.oauthclient.trust.sql',1443545062),('phabricator:20150210.invitephid.sql',1443545062),('phabricator:20150212.legalpad.session.1.sql',1443545062),('phabricator:20150212.legalpad.session.2.sql',1443545062),('phabricator:20150219.scratch.nonmutable.sql',1443545062),('phabricator:20150223.daemon.1.id.sql',1443545062),('phabricator:20150223.daemon.2.idlegacy.sql',1443545062),('phabricator:20150223.daemon.3.idkey.sql',1443545062),('phabricator:20150312.filechunk.1.sql',1443545062),('phabricator:20150312.filechunk.2.sql',1443545062),('phabricator:20150312.filechunk.3.sql',1443545062),('phabricator:20150317.conpherence.isroom.1.sql',1443545062),('phabricator:20150317.conpherence.isroom.2.sql',1443545062),('phabricator:20150317.conpherence.policy.sql',1443545062),('phabricator:20150410.nukeruleedit.sql',1443545062),('phabricator:20150420.invoice.1.sql',1443545062),('phabricator:20150420.invoice.2.sql',1443545062),('phabricator:20150425.isclosed.sql',1443545062),('phabricator:20150427.calendar.1.edge.sql',1443545062),('phabricator:20150427.calendar.1.xaction.sql',1443545062),('phabricator:20150427.calendar.2.xaction.sql',1443545062),('phabricator:20150428.calendar.1.iscancelled.sql',1443545062),('phabricator:20150428.calendar.1.name.sql',1443545062),('phabricator:20150429.calendar.1.invitee.sql',1443545062),('phabricator:20150430.calendar.1.policies.sql',1443545062),('phabricator:20150430.multimeter.1.sql',1443545062),('phabricator:20150430.multimeter.2.host.sql',1443545062),('phabricator:20150430.multimeter.3.viewer.sql',1443545062),('phabricator:20150430.multimeter.4.context.sql',1443545062),('phabricator:20150430.multimeter.5.label.sql',1443545062),('phabricator:20150501.calendar.1.reply.sql',1443545062),('phabricator:20150501.calendar.2.reply.php',1443545062),('phabricator:20150501.conpherencepics.sql',1443545062),('phabricator:20150503.repositorysymbols.1.sql',1443545062),('phabricator:20150503.repositorysymbols.2.php',1443545062),('phabricator:20150503.repositorysymbols.3.sql',1443545062),('phabricator:20150504.symbolsproject.1.php',1443545062),('phabricator:20150504.symbolsproject.2.sql',1443545062),('phabricator:20150506.calendarunnamedevents.1.php',1443545062),('phabricator:20150507.calendar.1.isallday.sql',1443545062),('phabricator:20150513.user.cache.1.sql',1443545062),('phabricator:20150514.calendar.status.sql',1443545062),('phabricator:20150514.phame.blog.xaction.sql',1443545062),('phabricator:20150514.user.cache.2.sql',1443545062),('phabricator:20150515.phame.post.xaction.sql',1443545062),('phabricator:20150515.project.mailkey.1.sql',1443545062),('phabricator:20150515.project.mailkey.2.php',1443545062),('phabricator:20150519.calendar.calendaricon.sql',1443545062),('phabricator:20150521.releephrepository.sql',1443545062),('phabricator:20150525.diff.hidden.1.sql',1443545062),('phabricator:20150526.owners.mailkey.1.sql',1443545062),('phabricator:20150526.owners.mailkey.2.php',1443545062),('phabricator:20150526.owners.xaction.sql',1443545062),('phabricator:20150527.calendar.recurringevents.sql',1443545062),('phabricator:20150601.spaces.1.namespace.sql',1443545063),('phabricator:20150601.spaces.2.xaction.sql',1443545063),('phabricator:20150602.mlist.1.sql',1443545063),('phabricator:20150602.mlist.2.php',1443545063),('phabricator:20150604.spaces.1.sql',1443545063),('phabricator:20150605.diviner.edges.sql',1443545063),('phabricator:20150605.diviner.editPolicy.sql',1443545063),('phabricator:20150605.diviner.xaction.sql',1443545063),('phabricator:20150606.mlist.1.php',1443545063),('phabricator:20150609.inline.sql',1443545063),('phabricator:20150609.spaces.1.pholio.sql',1443545063),('phabricator:20150609.spaces.2.maniphest.sql',1443545063),('phabricator:20150610.spaces.1.desc.sql',1443545063),('phabricator:20150610.spaces.2.edge.sql',1443545063),('phabricator:20150610.spaces.3.archive.sql',1443545063),('phabricator:20150611.spaces.1.mailxaction.sql',1443545063),('phabricator:20150611.spaces.2.appmail.sql',1443545063),('phabricator:20150616.divinerrepository.sql',1443545063),('phabricator:20150617.harbor.1.lint.sql',1443545063),('phabricator:20150617.harbor.2.unit.sql',1443545063),('phabricator:20150618.harbor.1.planauto.sql',1443545063),('phabricator:20150618.harbor.2.stepauto.sql',1443545063),('phabricator:20150618.harbor.3.buildauto.sql',1443545063),('phabricator:20150619.conpherencerooms.1.sql',1443545063),('phabricator:20150619.conpherencerooms.2.sql',1443545063),('phabricator:20150619.conpherencerooms.3.sql',1443545063),('phabricator:20150621.phrase.1.sql',1443545063),('phabricator:20150621.phrase.2.sql',1443545063),('phabricator:20150622.bulk.1.job.sql',1443545063),('phabricator:20150622.bulk.2.task.sql',1443545063),('phabricator:20150622.bulk.3.xaction.sql',1443545063),('phabricator:20150622.bulk.4.edge.sql',1443545063),('phabricator:20150622.metamta.1.phid-col.sql',1443545063),('phabricator:20150622.metamta.2.phid-mig.php',1443545063),('phabricator:20150622.metamta.3.phid-key.sql',1443545063),('phabricator:20150622.metamta.4.actor-phid-col.sql',1443545063),('phabricator:20150622.metamta.5.actor-phid-mig.php',1443545063),('phabricator:20150622.metamta.6.actor-phid-key.sql',1443545063),('phabricator:20150624.spaces.1.repo.sql',1443545063),('phabricator:20150626.spaces.1.calendar.sql',1443545063),('phabricator:20150630.herald.1.sql',1443545063),('phabricator:20150630.herald.2.sql',1443545063),('phabricator:20150701.herald.1.sql',1443545063),('phabricator:20150701.herald.2.sql',1443545063),('phabricator:20150702.spaces.1.slowvote.sql',1443545063),('phabricator:20150706.herald.1.sql',1443545063),('phabricator:20150707.herald.1.sql',1443545063),('phabricator:20150708.arcanistproject.sql',1443545063),('phabricator:20150708.herald.1.sql',1443545063),('phabricator:20150708.herald.2.sql',1443545063),('phabricator:20150708.herald.3.sql',1443545063),('phabricator:20150712.badges.1.sql',1443545063),('phabricator:20150714.spaces.countdown.1.sql',1443545063),('phabricator:20150717.herald.1.sql',1443545063),('phabricator:20150719.countdown.1.sql',1443545063),('phabricator:20150719.countdown.2.sql',1443545063),('phabricator:20150719.countdown.3.sql',1443545063),('phabricator:20150721.phurl.1.url.sql',1443545063),('phabricator:20150721.phurl.2.xaction.sql',1443545063),('phabricator:20150721.phurl.3.xactioncomment.sql',1443545063),('phabricator:20150721.phurl.4.url.sql',1443545063),('phabricator:20150721.phurl.5.edge.sql',1443545063),('phabricator:20150721.phurl.6.alias.sql',1443545063),('phabricator:20150721.phurl.7.authorphid.sql',1443545063),('phabricator:20150722.dashboard.1.sql',1443545063),('phabricator:20150722.dashboard.2.sql',1443545063),('phabricator:20150723.countdown.1.sql',1443545063),('phabricator:20150724.badges.comments.1.sql',1443545063),('phabricator:20150724.countdown.comments.1.sql',1443545063),('phabricator:20150725.badges.mailkey.1.sql',1443545063),('phabricator:20150725.badges.mailkey.2.php',1443545063),('phabricator:20150725.badges.viewpolicy.3.sql',1443545063),('phabricator:20150725.countdown.mailkey.1.sql',1443545063),('phabricator:20150725.countdown.mailkey.2.php',1443545063),('phabricator:20150725.slowvote.mailkey.1.sql',1443545063),('phabricator:20150725.slowvote.mailkey.2.php',1443545063),('phabricator:20150727.heraldaction.1.sql',1443545063),('phabricator:20150730.herald.1.sql',1443545063),('phabricator:20150730.herald.2.sql',1443545063),('phabricator:20150730.herald.3.sql',1443545063),('phabricator:20150730.herald.4.sql',1443545063),('phabricator:20150730.herald.5.sql',1443545063),('phabricator:20150730.herald.6.sql',1443545063),('phabricator:20150730.herald.7.sql',1443545063),('phabricator:20150803.herald.1.sql',1443545063),('phabricator:20150803.herald.2.sql',1443545063),('phabricator:20150804.ponder.answer.mailkey.1.sql',1443545063),('phabricator:20150804.ponder.answer.mailkey.2.php',1443545063),('phabricator:20150804.ponder.question.1.sql',1443545064),('phabricator:20150804.ponder.question.2.sql',1443545064),('phabricator:20150804.ponder.question.3.sql',1443545064),('phabricator:20150804.ponder.spaces.4.sql',1443545064),('phabricator:20150805.paste.status.1.sql',1443545064),('phabricator:20150805.paste.status.2.sql',1443545064),('phabricator:20150806.ponder.answer.1.sql',1443545064),('phabricator:20150806.ponder.editpolicy.2.sql',1443545064),('phabricator:20150806.ponder.status.1.sql',1443545064),('phabricator:20150806.ponder.status.2.sql',1443545064),('phabricator:20150806.ponder.status.3.sql',1443545064),('phabricator:20150808.ponder.vote.1.sql',1443545064),('phabricator:20150808.ponder.vote.2.sql',1443545064),('phabricator:20150812.ponder.answer.1.sql',1443545064),('phabricator:20150812.ponder.answer.2.sql',1443545064),('phabricator:20150814.harbormater.artifact.phid.sql',1443545064),('phabricator:20150815.owners.status.1.sql',1443545064),('phabricator:20150815.owners.status.2.sql',1443545064),('phabricator:20150823.nuance.queue.1.sql',1443545064),('phabricator:20150823.nuance.queue.2.sql',1443545064),('phabricator:20150823.nuance.queue.3.sql',1443545064),('phabricator:20150823.nuance.queue.4.sql',1443545064),('phabricator:20150828.ponder.wiki.1.sql',1443545064),('phabricator:20150829.ponder.dupe.1.sql',1443545064),('phabricator:20150904.herald.1.sql',1443545064),('phabricator:20150910.owners.custom.1.sql',1443545064),('phabricator:20150916.drydock.slotlocks.1.sql',1443545064),('phabricator:20150922.drydock.commands.1.sql',1443545064),('phabricator:20150923.drydock.resourceid.1.sql',1443545064),('phabricator:20150923.drydock.resourceid.2.sql',1443545064),('phabricator:20150923.drydock.resourceid.3.sql',1443545064),('phabricator:20150923.drydock.taskid.1.sql',1443545064),('phabricator:20150924.drydock.disable.1.sql',1443545064),('phabricator:20150924.drydock.status.1.sql',1443545064),('phabricator:20150928.drydock.rexpire.1.sql',1443545064),('phabricator:daemonstatus.sql',1443545053),('phabricator:daemonstatuskey.sql',1443545054),('phabricator:daemontaskarchive.sql',1443545054),('phabricator:db.almanac',1443545048),('phabricator:db.audit',1443545048),('phabricator:db.auth',1443545048),('phabricator:db.badges',1443545048),('phabricator:db.cache',1443545048),('phabricator:db.calendar',1443545048),('phabricator:db.chatlog',1443545048),('phabricator:db.conduit',1443545048),('phabricator:db.config',1443545048),('phabricator:db.conpherence',1443545048),('phabricator:db.countdown',1443545048),('phabricator:db.daemon',1443545048),('phabricator:db.dashboard',1443545048),('phabricator:db.differential',1443545048),('phabricator:db.diviner',1443545048),('phabricator:db.doorkeeper',1443545048),('phabricator:db.draft',1443545048),('phabricator:db.drydock',1443545048),('phabricator:db.fact',1443545048),('phabricator:db.feed',1443545048),('phabricator:db.file',1443545048),('phabricator:db.flag',1443545048),('phabricator:db.fund',1443545048),('phabricator:db.harbormaster',1443545048),('phabricator:db.herald',1443545048),('phabricator:db.legalpad',1443545048),('phabricator:db.maniphest',1443545048),('phabricator:db.meta_data',1443545048),('phabricator:db.metamta',1443545048),('phabricator:db.multimeter',1443545048),('phabricator:db.nuance',1443545048),('phabricator:db.oauth_server',1443545048),('phabricator:db.owners',1443545048),('phabricator:db.passphrase',1443545048),('phabricator:db.pastebin',1443545048),('phabricator:db.phame',1443545048),('phabricator:db.phlux',1443545048),('phabricator:db.pholio',1443545048),('phabricator:db.phortune',1443545048),('phabricator:db.phragment',1443545048),('phabricator:db.phrequent',1443545048),('phabricator:db.phriction',1443545048),('phabricator:db.phurl',1443545048),('phabricator:db.policy',1443545048),('phabricator:db.ponder',1443545048),('phabricator:db.project',1443545048),('phabricator:db.releeph',1443545048),('phabricator:db.repository',1443545048),('phabricator:db.search',1443545048),('phabricator:db.slowvote',1443545048),('phabricator:db.spaces',1443545048),('phabricator:db.system',1443545048),('phabricator:db.timeline',1443545048),('phabricator:db.token',1443545048),('phabricator:db.user',1443545048),('phabricator:db.worker',1443545048),('phabricator:db.xhpastview',1443545048),('phabricator:db.xhprof',1443545048),('phabricator:differentialbookmarks.sql',1443545053),('phabricator:draft-metadata.sql',1443545054),('phabricator:dropfileproxyimage.sql',1443545054),('phabricator:drydockresoucetype.sql',1443545054),('phabricator:drydocktaskid.sql',1443545054),('phabricator:edgetype.sql',1443545054),('phabricator:emailtable.sql',1443545053),('phabricator:emailtableport.sql',1443545053),('phabricator:emailtableremove.sql',1443545053),('phabricator:fact-raw.sql',1443545053),('phabricator:harbormasterobject.sql',1443545053),('phabricator:holidays.sql',1443545053),('phabricator:ldapinfo.sql',1443545053),('phabricator:legalpad-mailkey-populate.php',1443545056),('phabricator:legalpad-mailkey.sql',1443545056),('phabricator:liskcounters-task.sql',1443545054),('phabricator:liskcounters.php',1443545054),('phabricator:liskcounters.sql',1443545054),('phabricator:maniphestxcache.sql',1443545053),('phabricator:markupcache.sql',1443545053),('phabricator:migrate-differential-dependencies.php',1443545053),('phabricator:migrate-maniphest-dependencies.php',1443545053),('phabricator:migrate-maniphest-revisions.php',1443545053),('phabricator:migrate-project-edges.php',1443545053),('phabricator:owners-exclude.sql',1443545054),('phabricator:pastepolicy.sql',1443545054),('phabricator:phameblog.sql',1443545053),('phabricator:phamedomain.sql',1443545054),('phabricator:phameoneblog.sql',1443545054),('phabricator:phamepolicy.sql',1443545054),('phabricator:phiddrop.sql',1443545053),('phabricator:pholio.sql',1443545054),('phabricator:policy-project.sql',1443545054),('phabricator:ponder-comments.sql',1443545054),('phabricator:ponder-mailkey-populate.php',1443545054),('phabricator:ponder-mailkey.sql',1443545054),('phabricator:ponder.sql',1443545054),('phabricator:releeph.sql',1443545054),('phabricator:repository-lint.sql',1443545054),('phabricator:statustxt.sql',1443545054),('phabricator:symbolcontexts.sql',1443545053),('phabricator:testdatabase.sql',1443545053),('phabricator:threadtopic.sql',1443545053),('phabricator:userstatus.sql',1443545053),('phabricator:usertranslation.sql',1443545053),('phabricator:xhprof.sql',1443545054); CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_metamta` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; @@ -1393,14 +1618,39 @@ CREATE TABLE `metamta_applicationemail` ( `configData` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_address` (`address`), UNIQUE KEY `key_phid` (`phid`), - KEY `key_application` (`applicationPHID`) + KEY `key_application` (`applicationPHID`), + KEY `key_space` (`spacePHID`) ) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `metamta_applicationemailtransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `metamta_mail` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `actorPHID` varbinary(64) DEFAULT NULL, `parameters` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `message` longtext COLLATE {$COLLATE_TEXT}, @@ -1408,8 +1658,10 @@ CREATE TABLE `metamta_mail` ( `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), KEY `relatedPHID` (`relatedPHID`), KEY `key_created` (`dateCreated`), + KEY `key_actorPHID` (`actorPHID`), KEY `status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -1522,6 +1774,35 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_owners` /*!40100 DEFAULT USE `{$NAMESPACE}_owners`; +CREATE TABLE `owners_customfieldnumericindex` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `indexKey` binary(12) NOT NULL, + `indexValue` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`), + KEY `key_find` (`indexKey`,`indexValue`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `owners_customfieldstorage` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `fieldIndex` binary(12) NOT NULL, + `fieldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `owners_customfieldstringindex` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `indexKey` binary(12) NOT NULL, + `indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)), + KEY `key_find` (`indexKey`,`indexValue`(64)) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `owners_owner` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `packageID` int(10) unsigned NOT NULL, @@ -1539,11 +1820,34 @@ CREATE TABLE `owners_package` ( `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `primaryOwnerPHID` varbinary(64) DEFAULT NULL, `auditingEnabled` tinyint(1) NOT NULL DEFAULT '0', + `mailKey` binary(20) NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `owners_packagetransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `owners_path` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `packageID` int(10) unsigned NOT NULL, @@ -1589,12 +1893,15 @@ CREATE TABLE `pastebin_paste` ( `viewPolicy` varbinary(64) DEFAULT NULL, `editPolicy` varbinary(64) NOT NULL, `mailKey` binary(20) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), KEY `parentPHID` (`parentPHID`), KEY `authorPHID` (`authorPHID`), KEY `key_dateCreated` (`dateCreated`), - KEY `key_language` (`language`) + KEY `key_language` (`language`), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `pastebin_pastetransaction` ( @@ -1678,6 +1985,27 @@ CREATE TABLE `phame_blog` ( UNIQUE KEY `domain` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `phame_blogtransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `phame_post` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -1697,6 +2025,27 @@ CREATE TABLE `phame_post` ( KEY `bloggerPosts` (`bloggerPHID`,`visibility`,`datePublished`,`id`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `phame_posttransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phriction` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; USE `{$NAMESPACE}_phriction`; @@ -1832,6 +2181,7 @@ CREATE TABLE `project` ( `profileImagePHID` varbinary(64) DEFAULT NULL, `icon` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `color` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `mailKey` binary(20) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), UNIQUE KEY `name` (`name`), @@ -1996,23 +2346,13 @@ CREATE TABLE `repository` ( `pushPolicy` varbinary(64) NOT NULL, `credentialPHID` varbinary(64) DEFAULT NULL, `almanacServicePHID` varbinary(64) DEFAULT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `callsign` (`callsign`), UNIQUE KEY `phid` (`phid`), KEY `key_vcs` (`versionControlSystem`), - KEY `key_name` (`name`(128)) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; - -CREATE TABLE `repository_arcanistproject` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `phid` varbinary(64) NOT NULL, - `name` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, - `repositoryID` int(10) unsigned DEFAULT NULL, - `symbolIndexLanguages` longtext COLLATE {$COLLATE_TEXT} NOT NULL, - `symbolIndexProjects` longtext COLLATE {$COLLATE_TEXT} NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `phid` (`phid`), - UNIQUE KEY `name` (`name`) + KEY `key_name` (`name`(128)), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `repository_auditrequest` ( @@ -2060,7 +2400,9 @@ CREATE TABLE `repository_commit` ( UNIQUE KEY `key_commit_identity` (`commitIdentifier`,`repositoryID`), KEY `repositoryID_2` (`repositoryID`,`epoch`), KEY `authorPHID` (`authorPHID`,`auditStatus`,`epoch`), - KEY `repositoryID` (`repositoryID`,`importStatus`) + KEY `repositoryID` (`repositoryID`,`importStatus`), + KEY `key_epoch` (`epoch`), + KEY `key_author` (`authorPHID`,`epoch`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `repository_commitdata` ( @@ -2201,6 +2543,7 @@ CREATE TABLE `repository_refcursor` ( `refNameRaw` longblob NOT NULL, `refNameEncoding` varchar(16) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `commitIdentifier` varchar(40) COLLATE {$COLLATE_TEXT} NOT NULL, + `isClosed` tinyint(1) NOT NULL, PRIMARY KEY (`id`), KEY `key_cursor` (`repositoryPHID`,`refType`,`refNameHash`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -2226,7 +2569,7 @@ CREATE TABLE `repository_summary` ( ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `repository_symbol` ( - `arcanistProjectID` int(10) unsigned NOT NULL, + `repositoryPHID` varbinary(64) NOT NULL, `symbolContext` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, `symbolName` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, `symbolType` varchar(12) COLLATE {$COLLATE_TEXT} NOT NULL, @@ -2385,8 +2728,11 @@ CREATE TABLE `slowvote_poll` ( `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `viewPolicy` varbinary(64) NOT NULL, `isClosed` tinyint(1) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, + `mailKey` binary(20) NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `phid` (`phid`) + UNIQUE KEY `phid` (`phid`), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `slowvote_transaction` ( @@ -2490,6 +2836,10 @@ CREATE TABLE `user` ( `isApproved` int(10) unsigned NOT NULL, `accountSecret` binary(64) NOT NULL, `isEnrolledInMultiFactor` tinyint(1) NOT NULL DEFAULT '0', + `profileImageCache` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, + `availabilityCache` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, + `availabilityCacheTTL` int(10) unsigned DEFAULT NULL, + `isMailingList` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `userName` (`userName`), UNIQUE KEY `phid` (`phid`), @@ -2651,6 +3001,24 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_worker` /*!40100 DEFAULT USE `{$NAMESPACE}_worker`; +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `lisk_counter` ( `counterName` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `counterValue` bigint(20) unsigned NOT NULL, @@ -2698,6 +3066,55 @@ CREATE TABLE `worker_archivetask` ( KEY `key_object` (`objectPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `worker_bulkjob` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `jobTypeKey` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `parameters` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `size` int(10) unsigned NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_type` (`jobTypeKey`), + KEY `key_author` (`authorPHID`), + KEY `key_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `worker_bulkjobtransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `worker_bulktask` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `bulkJobPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_job` (`bulkJobPHID`,`status`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `worker_taskdata` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, @@ -2837,12 +3254,14 @@ CREATE TABLE `ponder_answer` ( `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, - `contentSource` longtext COLLATE {$COLLATE_TEXT}, + `mailKey` binary(20) NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), UNIQUE KEY `key_oneanswerperquestion` (`questionID`,`authorPHID`), KEY `questionID` (`questionID`), - KEY `authorPHID` (`authorPHID`) + KEY `authorPHID` (`authorPHID`), + KEY `status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `ponder_answertransaction` ( @@ -2888,21 +3307,22 @@ CREATE TABLE `ponder_question` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, `phid` varbinary(64) NOT NULL, - `voteCount` int(10) NOT NULL, `authorPHID` varbinary(64) NOT NULL, - `status` int(10) unsigned NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `contentSource` longtext COLLATE {$COLLATE_TEXT}, - `heat` double NOT NULL, `answerCount` int(10) unsigned NOT NULL, `mailKey` binary(20) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, + `answerWiki` longtext COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), KEY `authorPHID` (`authorPHID`), - KEY `heat` (`heat`), - KEY `status` (`status`) + KEY `status` (`status`), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `ponder_questiontransaction` ( @@ -3016,9 +3436,11 @@ CREATE TABLE `pholio_mock` ( `dateModified` int(10) unsigned NOT NULL, `status` varchar(12) COLLATE {$COLLATE_TEXT} NOT NULL, `editPolicy` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `phid` (`phid`), - KEY `authorPHID` (`authorPHID`) + KEY `authorPHID` (`authorPHID`), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `pholio_transaction` ( @@ -3104,8 +3526,12 @@ CREATE TABLE `conpherence_thread` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, `title` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, + `imagePHIDs` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `messageCount` bigint(20) unsigned NOT NULL, `recentParticipantPHIDs` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `joinPolicy` varbinary(64) NOT NULL, `mailKey` varchar(20) COLLATE {$COLLATE_TEXT} NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, @@ -3311,7 +3737,6 @@ CREATE TABLE `releeph_project` ( `name` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, `trunkBranch` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, `repositoryPHID` varbinary(64) NOT NULL, - `arcanistProjectID` int(10) unsigned NOT NULL, `createdByUserPHID` varbinary(64) NOT NULL, `isActive` tinyint(1) NOT NULL DEFAULT '1', `details` longtext COLLATE {$COLLATE_TEXT} NOT NULL, @@ -3487,6 +3912,7 @@ CREATE TABLE `phortune_cart` ( `merchantPHID` varbinary(64) NOT NULL, `mailKey` binary(20) NOT NULL, `subscriptionPHID` varbinary(64) DEFAULT NULL, + `isInvoice` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), KEY `key_account` (`accountPHID`), @@ -3719,7 +4145,9 @@ CREATE TABLE `diviner_livebook` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, `name` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, + `repositoryPHID` varbinary(64) DEFAULT NULL, `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, `configurationData` longtext COLLATE {$COLLATE_TEXT} NOT NULL, @@ -3728,10 +4156,32 @@ CREATE TABLE `diviner_livebook` ( UNIQUE KEY `phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `diviner_livebooktransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `diviner_livesymbol` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, `bookPHID` varbinary(64) NOT NULL, + `repositoryPHID` varbinary(64) DEFAULT NULL, `context` varchar(255) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `type` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, @@ -3754,6 +4204,24 @@ CREATE TABLE `diviner_livesymbol` ( KEY `name` (`name`(64)) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_auth` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; USE `{$NAMESPACE}_auth`; @@ -4061,12 +4529,13 @@ CREATE TABLE `nuance_item` ( `mailKey` binary(20) NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, - `dateNuanced` int(10) unsigned NOT NULL, + `queuePHID` varbinary(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), - KEY `key_source` (`sourcePHID`,`status`,`dateNuanced`,`id`), - KEY `key_owner` (`ownerPHID`,`status`,`dateNuanced`,`id`), - KEY `key_contacter` (`requestorPHID`,`status`,`dateNuanced`,`id`) + KEY `key_source` (`sourcePHID`,`status`), + KEY `key_owner` (`ownerPHID`,`status`), + KEY `key_requestor` (`requestorPHID`,`status`), + KEY `key_queue` (`queuePHID`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `nuance_itemtransaction` ( @@ -4121,19 +4590,6 @@ CREATE TABLE `nuance_queue` ( UNIQUE KEY `key_phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; -CREATE TABLE `nuance_queueitem` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `queuePHID` varbinary(64) NOT NULL, - `itemPHID` varbinary(64) NOT NULL, - `itemStatus` int(10) unsigned NOT NULL, - `itemDateNuanced` int(10) unsigned NOT NULL, - `dateCreated` int(10) unsigned NOT NULL, - `dateModified` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `key_one_per_queue` (`itemPHID`,`queuePHID`), - KEY `key_queue` (`queuePHID`,`itemStatus`,`itemDateNuanced`,`id`) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; - CREATE TABLE `nuance_queuetransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -4247,6 +4703,7 @@ CREATE TABLE `nuance_source` ( `editPolicy` varbinary(64) NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, + `defaultQueuePHID` varbinary(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), KEY `key_type` (`type`,`dateModified`) @@ -4329,11 +4786,14 @@ CREATE TABLE `passphrase_credential` ( `dateModified` int(10) unsigned NOT NULL, `isLocked` tinyint(1) NOT NULL, `allowConduit` tinyint(1) NOT NULL DEFAULT '0', + `authorPHID` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), UNIQUE KEY `key_secret` (`secretID`), KEY `key_type` (`credentialType`), - KEY `key_provides` (`providesType`) + KEY `key_provides` (`providesType`), + KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `passphrase_credentialtransaction` ( @@ -4449,6 +4909,7 @@ CREATE TABLE `dashboard` ( `editPolicy` varbinary(64) NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; @@ -4863,3 +5324,275 @@ CREATE TABLE `edgedata` ( `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_multimeter` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; + +USE `{$NAMESPACE}_multimeter`; + +CREATE TABLE `multimeter_context` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `nameHash` binary(12) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_hash` (`nameHash`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `multimeter_event` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `eventType` int(10) unsigned NOT NULL, + `eventLabelID` int(10) unsigned NOT NULL, + `resourceCost` bigint(20) NOT NULL, + `sampleRate` int(10) unsigned NOT NULL, + `eventContextID` int(10) unsigned NOT NULL, + `eventHostID` int(10) unsigned NOT NULL, + `eventViewerID` int(10) unsigned NOT NULL, + `epoch` int(10) unsigned NOT NULL, + `requestKey` binary(12) NOT NULL, + PRIMARY KEY (`id`), + KEY `key_request` (`requestKey`), + KEY `key_type` (`eventType`,`epoch`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `multimeter_host` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `nameHash` binary(12) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_hash` (`nameHash`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `multimeter_label` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `nameHash` binary(12) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_hash` (`nameHash`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `multimeter_viewer` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `nameHash` binary(12) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_hash` (`nameHash`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_spaces` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; + +USE `{$NAMESPACE}_spaces`; + +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `spaces_namespace` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `namespaceName` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `isDefaultNamespace` tinyint(1) DEFAULT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isArchived` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_default` (`isDefaultNamespace`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `spaces_namespacetransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phurl` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; + +USE `{$NAMESPACE}_phurl`; + +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phurl_url` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `name` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `longURL` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `spacePHID` varbinary(64) DEFAULT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + `alias` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL, + `authorPHID` varbinary(64) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_instance` (`alias`), + KEY `key_author` (`authorPHID`), + KEY `key_space` (`spacePHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phurl_urltransaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phurl_urltransaction_comment` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `transactionPHID` varbinary(64) DEFAULT NULL, + `authorPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isDeleted` tinyint(1) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_badges` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; + +USE `{$NAMESPACE}_badges`; + +CREATE TABLE `badges_badge` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `name` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `flavor` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `icon` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `quality` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `status` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `creatorPHID` varbinary(64) NOT NULL, + `mailKey` binary(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_creator` (`creatorPHID`,`dateModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `badges_transaction` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `authorPHID` varbinary(64) NOT NULL, + `objectPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentPHID` varbinary(64) DEFAULT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL, + `oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `badges_transaction_comment` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `phid` varbinary(64) NOT NULL, + `transactionPHID` varbinary(64) DEFAULT NULL, + `authorPHID` varbinary(64) NOT NULL, + `viewPolicy` varbinary(64) NOT NULL, + `editPolicy` varbinary(64) NOT NULL, + `commentVersion` int(10) unsigned NOT NULL, + `content` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `isDeleted` tinyint(1) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edge` ( + `src` varbinary(64) NOT NULL, + `type` int(10) unsigned NOT NULL, + `dst` varbinary(64) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `seq` int(10) unsigned NOT NULL, + `dataID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`src`,`type`,`dst`), + UNIQUE KEY `key_dst` (`dst`,`type`,`src`), + KEY `src` (`src`,`type`,`dateCreated`,`seq`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `edgedata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `data` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; From 1ac919c29c3af5dd00d483ddbc869112fdb04c5c Mon Sep 17 00:00:00 2001 From: Ray Lillywhite Date: Tue, 29 Sep 2015 14:14:31 -0700 Subject: [PATCH 20/43] Improved example setting for differential.generated-paths Summary: This is to make it more obvious how to ignore a folder that may be at the root, and it resolves https://secure.phabricator.com/T8894 Test Plan: Just a documentation change Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, nornagon Differential Revision: https://secure.phabricator.com/D14193 --- .../config/PhabricatorDifferentialConfigOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index 7d0eae99c2..a2bb63a2a2 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -133,7 +133,7 @@ final class PhabricatorDifferentialConfigOptions 'to affect existing revisions. For instructions, see '. '**[[ %s | Managing Caches ]]** in the documentation.', $caches_href)) - ->addExample("/config\.h$/\n#/autobuilt/#", pht('Valid Setting')), + ->addExample("/config\.h$/\n#(^|/)autobuilt/#", pht('Valid Setting')), $this->newOption('differential.sticky-accept', 'bool', true) ->setBoolOptions( array( From ae082c60338d3bbf166f75d25de5f8baa7a17ab7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 29 Sep 2015 14:25:28 -0700 Subject: [PATCH 21/43] Make Ponder Emails a little more consistently delivered Summary: Ref T9271, maybe fixes it. This restores feed publishing for answers (broken in D13951) and sends the author of the question an email for new answers. Also, unsure how to pull all question subsribers to the answer email, or is it automagical? Test Plan: notchad asks a question, chad answers, log into notchad and see that mail was delivered, see feed story. Reviewers: epriestley Reviewed By: epriestley Subscribers: revi, Korvin Maniphest Tasks: T9271 Differential Revision: https://secure.phabricator.com/D14171 --- .../ponder/editor/PonderAnswerEditor.php | 21 +++++++++++++++++++ .../ponder/editor/PonderEditor.php | 7 ------- .../ponder/editor/PonderQuestionEditor.php | 7 +++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index fdb5c48437..521c1948ed 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -85,6 +85,27 @@ final class PonderAnswerEditor extends PonderEditor { return true; } + protected function getMailTo(PhabricatorLiskDAO $object) { + $phids = array(); + $phids[] = $object->getAuthorPHID(); + $phids[] = $this->requireActor()->getPHID(); + + $question = id(new PonderQuestionQuery()) + ->setViewer($this->requireActor()) + ->withIDs(array($object->getQuestionID())) + ->executeOne(); + + $phids[] = $question->getAuthorPHID(); + + return $phids; + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PonderAnswerReplyHandler()) ->setMailReceiver($object); diff --git a/src/applications/ponder/editor/PonderEditor.php b/src/applications/ponder/editor/PonderEditor.php index 24c6f2d8d2..fcfa981f16 100644 --- a/src/applications/ponder/editor/PonderEditor.php +++ b/src/applications/ponder/editor/PonderEditor.php @@ -7,13 +7,6 @@ abstract class PonderEditor return 'PhabricatorPonderApplication'; } - protected function getMailTo(PhabricatorLiskDAO $object) { - return array( - $object->getAuthorPHID(), - $this->requireActor()->getPHID(), - ); - } - protected function getMailSubjectPrefix() { return '[Ponder]'; } diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index b2f143b176..73d9ed0ce0 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -209,6 +209,13 @@ final class PonderQuestionEditor return true; } + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getAuthorPHID(), + $this->requireActor()->getPHID(), + ); + } + protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { From 8f2f841d172bf68412fda05b60b6a138eb5b37b7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Sep 2015 07:44:54 -0700 Subject: [PATCH 22/43] Fix some links to "Adding New Classes" in docs Summary: Fixes T9483. This bookname is `phabcontrib`, not `contributor`. Test Plan: `grep` / clicked these links. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9483 Differential Revision: https://secure.phabricator.com/D14195 --- src/docs/contributor/internationalization.diviner | 9 ++++++--- src/docs/user/configuration/custom_fields.diviner | 2 +- src/docs/user/configuration/managing_daemons.diviner | 4 ++-- src/docs/user/userguide/arcanist_lint_unit.diviner | 2 +- src/docs/user/userguide/arcanist_new_project.diviner | 2 +- src/docs/user/userguide/diffusion_symbols.diviner | 2 +- src/docs/user/userguide/events.diviner | 4 ++-- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner index f937ec1ae2..f00d09a5db 100644 --- a/src/docs/contributor/internationalization.diviner +++ b/src/docs/contributor/internationalization.diviner @@ -22,7 +22,8 @@ introduce a new locale, like "German" or "Klingon". Once you've created a locale, applications can add translations for that locale. -For instructions on adding new classes, see @{article:Adding New Classes}. +For instructions on adding new classes, see +@{article@phabcontrib:Adding New Classes}. Adding Translations to Locale @@ -36,7 +37,8 @@ Translations are separated from locales so that third-party applications can provide translations into different locales without needing to define those locales themselves. -For instructions on adding new classes, see @{article:Adding New Classes}. +For instructions on adding new classes, see +@{article@phabcontrib:Adding New Classes}. Writing Translatable Code @@ -375,4 +377,5 @@ Next Steps Continue by: - - adding a new locale or translation file with @{article:Adding New Classes}. + - adding a new locale or translation file with + @{article@phabcontrib:Adding New Classes}. diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner index 9ada0efa06..7ecfa98723 100644 --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -213,5 +213,5 @@ integrations, see the base class for your application and Continue by: - learning more about extending Phabricator with custom code in - @{article@contributor:Adding New Classes}; + @{article@phabcontrib:Adding New Classes}; - or returning to the @{article: Configuration Guide}. diff --git a/src/docs/user/configuration/managing_daemons.diviner b/src/docs/user/configuration/managing_daemons.diviner index b8d102a13d..4382e12c8a 100644 --- a/src/docs/user/configuration/managing_daemons.diviner +++ b/src/docs/user/configuration/managing_daemons.diviner @@ -109,7 +109,7 @@ This daemon will daemonize and run normally. just those started with `phd start`. If you're writing a restart script, have it launch any custom daemons explicitly after `phd restart`. - You can write your own daemons and manage them with `phd` by extending - @{class:PhabricatorDaemon}. See @{article@contributor:Adding New Classes}. + @{class:PhabricatorDaemon}. See @{article@phabcontrib:Adding New Classes}. - See @{article:Diffusion User Guide} for details about tuning the repository daemon. @@ -137,4 +137,4 @@ Continue by: - learning about the repository daemon with @{article:Diffusion User Guide}; or - - writing your own daemons with @{article@contributor:Adding New Classes}. + - writing your own daemons with @{article@phabcontrib:Adding New Classes}. diff --git a/src/docs/user/userguide/arcanist_lint_unit.diviner b/src/docs/user/userguide/arcanist_lint_unit.diviner index 00ad4e237b..6ecc9aaa3e 100644 --- a/src/docs/user/userguide/arcanist_lint_unit.diviner +++ b/src/docs/user/userguide/arcanist_lint_unit.diviner @@ -38,7 +38,7 @@ make this work, you need to do three things: If you haven't created a library for the class to live in yet, you need to do that first. Follow the instructions in -@{article@contributor:Adding New Classes}, then make the library loadable by +@{article@phabcontrib:Adding New Classes}, then make the library loadable by adding it to your `.arcconfig` like this: { diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner index b414a1e997..a8e8e49202 100644 --- a/src/docs/user/userguide/arcanist_new_project.diviner +++ b/src/docs/user/userguide/arcanist_new_project.diviner @@ -47,7 +47,7 @@ Other options include: - **load**: list of additional Phutil libraries to load at startup. See below for details about path resolution, or see - @{article@contributor:Adding New Classes} for a general introduction to + @{article@phabcontrib:Adding New Classes} for a general introduction to libphutil libraries. - **https.cabundle**: specifies the path to an alternate certificate bundle for use when making HTTPS connections. diff --git a/src/docs/user/userguide/diffusion_symbols.diviner b/src/docs/user/userguide/diffusion_symbols.diviner index c58ca72e1d..f5da8aefe0 100644 --- a/src/docs/user/userguide/diffusion_symbols.diviner +++ b/src/docs/user/userguide/diffusion_symbols.diviner @@ -88,7 +88,7 @@ You can configure some more options by going to {nav Diffusion > (Select == External Symbols == -By @{article:Adding New Classes}, you can teach Phabricator +By @{article@phabcontrib:Adding New Classes}, you can teach Phabricator about symbols from the outside world. Extend @{class:DiffusionExternalSymbolsSource}; Once loaded, your new implementation will be used any time a symbol is queried. diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner index 8959585f4a..8374a47101 100644 --- a/src/docs/user/userguide/events.diviner +++ b/src/docs/user/userguide/events.diviner @@ -21,7 +21,7 @@ To install event listeners in Phabricator, follow these steps: - Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Add it to a libphutil library, or create a new library (for instructions, - see @{article@contributor:Adding New Classes}. + see @{article@phabcontrib:Adding New Classes}. - Configure Phabricator to load the library by adding it to `load-libraries` in the Phabricator config. - Configure Phabricator to install the event listener by adding the class @@ -38,7 +38,7 @@ To install event listeners in Arcanist, follow these steps: - Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Add it to a libphutil library, or create a new library (for instructions, - see @{article@contributor:Adding New Classes}. + see @{article@phabcontrib:Adding New Classes}. - Configure Phabricator to load the library by adding it to `load` in the Arcanist config (e.g., `.arcconfig`, or user/global config). - Configure Arcanist to install the event listener by adding the class From 9d997df9643ba53595cac6f7c5af48d0400fd503 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Sep 2015 07:45:02 -0700 Subject: [PATCH 23/43] Reset Drydock git working copies better Summary: Ref T9252. We're currently resetting to the local branch, but should be resetting to the origin branch. Test Plan: Restarted a build, had it run `git show`, saw proper HEAD. Reviewers: chad Reviewed By: chad Subscribers: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14194 --- .../blueprint/DrydockWorkingCopyBlueprintImplementation.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 2527a34cdc..843f043baa 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -213,7 +213,10 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git reset --hard %s'; $arg[] = $commit; } else if ($branch !== null) { - $cmd[] = 'git reset --hard %s'; + $cmd[] = 'git checkout %s'; + $arg[] = $branch; + + $cmd[] = 'git reset --hard origin/%s'; $arg[] = $branch; } else { $cmd[] = 'git reset --hard HEAD'; From 98006f2cf3657efd8b146d65afa390ae5568bd64 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 30 Sep 2015 10:15:53 -0700 Subject: [PATCH 24/43] Update Asana Logo Summary: Updates to their new logo Test Plan: review in photoshop Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14199 --- resources/celerity/map.php | 8 ++++---- resources/sprite/login_1x/Asana.png | Bin 1079 -> 1000 bytes resources/sprite/login_2x/Asana.png | Bin 2391 -> 2256 bytes resources/sprite/manifest/login.json | 4 ++-- webroot/rsrc/image/sprite-login-X2.png | Bin 37512 -> 37742 bytes webroot/rsrc/image/sprite-login.png | Bin 16260 -> 16110 bytes 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7c927cd290..cb661a9bd9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -151,7 +151,7 @@ return array( 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', - 'rsrc/css/sprite-login.css' => '1ebb9bf9', + 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-main-header.css' => 'f07bbb87', 'rsrc/css/sprite-menu.css' => '9dd65b92', 'rsrc/css/sprite-projects.css' => 'e5ad842a', @@ -310,8 +310,8 @@ return array( 'rsrc/image/people/washington.png' => '40dd301c', 'rsrc/image/phrequent_active.png' => 'a466a8ed', 'rsrc/image/phrequent_inactive.png' => 'bfc15a69', - 'rsrc/image/sprite-login-X2.png' => 'a4bf0a98', - 'rsrc/image/sprite-login.png' => '5f4d0069', + 'rsrc/image/sprite-login-X2.png' => 'e3991e37', + 'rsrc/image/sprite-login.png' => '03d5af29', 'rsrc/image/sprite-main-header.png' => '3673af44', 'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5', 'rsrc/image/sprite-menu.png' => 'd7a99faa', @@ -817,7 +817,7 @@ return array( 'releeph-request-differential-create-dialog' => '8d8b92cd', 'releeph-request-typeahead-css' => '667a48ae', 'setup-issue-css' => 'db7e9c40', - 'sprite-login-css' => '1ebb9bf9', + 'sprite-login-css' => '60e8560e', 'sprite-main-header-css' => 'f07bbb87', 'sprite-menu-css' => '9dd65b92', 'sprite-projects-css' => 'e5ad842a', diff --git a/resources/sprite/login_1x/Asana.png b/resources/sprite/login_1x/Asana.png index 41292fc3227c5fc15e9d073b935afe4489e792d3..9b2fc8523915ff4abf5facd21d77d38c575eb499 100644 GIT binary patch delta 979 zcmV;^11$Wv2004R>004l5008;`004mK z004C`008P>0026e000+ooVrmw00006VoOIv00000008+zyMF)x010qNS#tmY3labT z3lag+-G2N400T@(L_t(Y$L*BAi(FL{$3OSIH}B5Oo43wp9e=^jV!<>?$clwlffQDW z$Z9661Vs^|0TYD$C@i!|Vj&2!Bp8CENZ8HpU}5Dypb{~~+N>mnsVuS*-TC$Id-vUA z@n$Cz$(RwhQF7ok_k7R!oO8M7!ejfdoBAhW^XftaToaMqMYdW4e_5|yy}Ru|^V(ax zAioaYPVfe&4u5d#5FFAgmw(zeplQC_K;&DLKZ^=R(gehU>b%+IhVAZ8Wip@1o+3u8KFVsoHO+{bS$X8`b9*o}ZS^%qP-* za)EXua+?h>ejC@mjO(1yOP`rhz4svOs0Z#6$O#}G2}Qs=AXoB>dxsA#|6)%7Tq$Bi zBl;MT6Np?g&G)>qKwP>H+>yZ_3AqP(sc;*D)qe+&VE(?oG*6}vU^UnhVuNBg#Q_53 zN04;ic%Y;QksQd2AH;c(l_9MlCPI=aB#HMA^+|2NBvW7_$SPpVMU&WpJ8jE(0$G zm4851Mfy|43$rCe_wEe!-5qyaS^*Oknk)c%AO>7IXgt3){z`EzoarH^NRkAk4{3mO zr;+ZAmCdJChW%|eR_M%F=}to$f=db?=9?apx5ff>tF;cd*iH002ovPDHLkV1o0S B!g>Gz delta 1059 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 diff --git a/resources/sprite/manifest/login.json b/resources/sprite/manifest/login.json index 3fc9c318b4..a75d2fa69e 100644 --- a/resources/sprite/manifest/login.json +++ b/resources/sprite/manifest/login.json @@ -9,7 +9,7 @@ "login-Asana": { "name": "login-Asana", "rule": ".login-Asana", - "hash": "f8d322843355da1abce614983044c0f8" + "hash": "cfc35b62afb103c5d484a715071e3cc3" }, "login-Bitbucket": { "name": "login-Bitbucket", @@ -131,6 +131,6 @@ 1, 2 ], - "header": "\/**\n * @provides sprite-login-css\n * @generated\n *\/\n\n.sprite-login {\n background-image: url(\/rsrc\/image\/sprite-login.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-login {\n background-image: url(\/rsrc\/image\/sprite-login-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n", + "header": "/**\n * @provides sprite-login-css\n * @generated\n */\n\n.sprite-login {\n background-image: url(/rsrc/image/sprite-login.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-login {\n background-image: url(/rsrc/image/sprite-login-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n", "type": "standard" } diff --git a/webroot/rsrc/image/sprite-login-X2.png b/webroot/rsrc/image/sprite-login-X2.png index 5570fa92e194b07a6e2783c19658b4db926f2934..3a75a1890a2e29ad1ccf6da2485cd2b2c42c6102 100644 GIT binary patch delta 37428 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 z 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

EA1%prsdpt`p8DtW*kxHDWct#%gURe0L8g@N zb6_UTsrIe7EoRg?Fe&h{v)FOL`rzt1QcCnT;j&I;jy4T993~-yu5MV;9g?XSq&Di4 z5zL|^ipnKs>S|>*>-J(SS^02|nCx2-!{HZRR!$bi<$oc=vRm!Osf6ZCQ!p9-2NGzB z!)Gz5VoAF!-su$OcZRCVP(g|2(3ix~$WuJx9b@4&CJ_q`?vm%J~&El~?% z;FniY@-J14$v?9HQ>g}ppLA7LbLSmHJK13R9ddI=GDRHt7?Y-1Q||*?`nAw9HOKyR z<26hRMXm0u+GY(b8yyrAJ|u6h<53+8cawcxKm`6@P5F#W>#X4i9Pa|x*C((~$9%b3 zg~yA@%5Urr1W>X4PO$037{p}0nNO)FhEB5I@_jQMs~|}nc>7slYLDfju}vaKUMTDB zE06q7%MENr`MN_7r!rE0UEF#&Jcv-~EiI0T?A+t! zd@GkDW<_;0lO8|%zH#(U&zOCA@fJ#gU<)H0PyVwXoD7oSq*kyF46WM+jj8vD@tCk^ ziWI&_{{=&-t3sn*%&R{f&9nRSqkZD*h|=ffq!`@i^`X<+)HJ1}x;m%2vQj6qfpAMa zj-vDRvi)~fW+vNIHt(9pMw?61lR7am@kR3q>r%g191#8*Q7|_kS`J2V)B0$&<4WgA zL(fJ2!>oy zAXOF|!y;ThCIPVm5`BnEQBxMS1W4m7g`@_1M5X z%Jpm6mt`D{D6_!Gm*IjV0ifs^L z=LT%@#q!T8Ycm0R%D``RzFqb%G)X zVj}`YctUlHV*%I|WD-%s3WtP(mA#;S?Ae@DN8|BSMtZ2+mG_r>DRVmSeVV#_^qEYj zc+sMSN^ydm1zc_zW=3Hj-s6+lmWIU>CI=>S)XBbKx{=4`plA=xSw zJKJNXX4!nUmj=6|EkfI%LLRhoK};q@49bNdCWQGVR)m+y;ShLndg>{;fr@@IU#c|q zalfQzdSQ<2VeHK%a;)pah08H^NVTxpZd5c}d+))C-MZCo&%{q4R*`A)i-h*yLyLwJ z!>W9r01NyH3f3|Cy??mj$$A1{P#BLOKN?L!h~}Rsen;$ZRa0Y*XfPkrYttLfOcH~c zkPw7ZPat&I8ln*3{bqdCuhx)EQ-pSqTfRMvp5QvYB#etas7 z9e852^uw>G;;l{xyz`4=$d;)tw3}8bOIao0&`3SFH8^xCQs z$Y{dKJO#eIzIrJH^vut*w2Q}mXJlhzdmasxS*p_Eyz01`TA?qSJiuWy~&)Iy6=l4|Y>?jvd`Lx)Wv0r+1{`ziPL07qwa^Vu2IMwN%lI9&QZD zBwk8!iX^1jX0copq^8B`U<)s-9;)>b++P~}Hd)=-ovBu>OH^4#M7%p`^HopavuD~@e(C5-Yct(}0Deqa5pM;}c`X=`da64_KP{fG-S!I&(6m#U zqDy|?9h`t)ue%a$iVzO$DP9m(_^t_H5)fD|SL=0S(&E;voX^s!Re`K?I_!6Pedmhh z9n-@ElJQ<@n$UnN6dYY7F|;YkeP9%yeaCWdn%6tRQ%nA8lW6SE^Jj zyHR7kQundz^#1<1wfa?-4pBZ5b1L4kQd)YI#~|j03!-*fw`WpXO{j@{TKX}m>Kt1( zmRDi@yY@|*sgDbzhyJtpB!oh1={`@ekRCN+C`h|BWvBFPfUszTBeG@YU@gs&^sb`| zyoo3x1RgO~X_A7WJ_9D9x7_&hmOj;XqitY}8%0ARC_!azt*=k`QgUQ8nGPAX!BaM! z#iFKz@!{ct@pdw9eE{p!lNg_Nbi(mZ^pOea5|S`fAGJ>sWYWdDa|;`zZeHiPlQvb2 zqnQltplWgnd7=4z9d*wIK}9o^hy^H)Hdo>T25_TID&D%NfH-znCFtmn3H2;``}?$+ zI7X#HR^}9cGapx>9e6O*-;b-Msfj%b8IYBw(@<8O=k<7*a64&dYpaWog;mrNC0 zlf$G+4Q>()mSVyAHY9u&>OT~4+xjlf&dzQsS)Z31?M=#pjt&=F-F}&En{h{+cpFZC zycJ?A^Z)oW*V#J$@fq>TPMPg)+{-BNp9rj4PQOA^h52$s?q>~Kyuo;Gw4w3>+4OFl$2%k z#Z;V}X|103r^rAZNC=g_m3p%U?DI+l{-cx#l37eTKrRy|Ldry3X>5!6#%%3CcQs1i zkf|>h)E5Vkn?E`@?ld8peba9&U@_OMDEXFO#am#0HIpt%zI6-#tJDpn`S0pYkZI#h zkkxO2KVe}*nJHlUW@f+N_eU_2m;ADLJZ5XYFz5uR6V=}S>h03Se&A=jmAvzhRN+vt z#oQelxu?i};x?m*n|(y!M#B-J{C3>IE}sv%)e-lrYorRFiA8?;5h%G(mi_hVBVY$4 z)7QTX*QG5+E+t3=4krMN+B*eBe3$bOgs2-js~p~*ZeBHCZWy$htdrZSpaV=rrIwgn z&zJj7z3uR6>0IbgK5ALLZc_)N6;MbtXNSgaJou%7Ki2TJley2Vj@~@xC~qfo(uZ!e zF@q{8wF^|+jP?wAvOqBxy`YZ*<RyQ+m)&o`evSR7-2(WIK_H;%Vdjc%P2vAcf2SY{j%geP!49_8*O&m^Vy8u%Sjf6ALWzo!2zmhF@*(u+ z!AD9^N#=OEt&G%KW4~}!6nQ-`-kJbaWhA*oI&xF|;yc;x+&{v66e6zS#_(yU`?V&U zv(hT7FLbEqgFSFl!94N|E!75^NC0+p@#b z5~-uW2AW87HyN0m%NSpkz9!X{y1v8{#ecUgpz&(q)eLCBBJnx6lT(aJ6O%{N;y z_Ufr((2OHouTV`4_qw~^aP0+NflvwtR?w9orG!H(e=Ev%{6p&EaUmYpLfW)bUsYEl zF{~rB1jbxWgo^0SD#~AX<@BNdo{w^ftq+EBT%T`ElFeYmMMRDP)PEYBfjPb}Ckir8 zXza=WTftVohPQAoXzhjqhx;K%kE>U5WX2X`8eb~XhG#S1M6P| z5WP}}a>tp!+I8J$z{pI8Q`}0>MG7@Wm4;v?v&AVK$lcr?J)%|d(`>f7e5%U)aK9Ey z_Qt9Uy-u5RUYlPHmLK#9wx`$f*NW6l|D201U%}uXnM`}3;2&}kGP;beCUsj49?Y~G zWHa{R)x;$O&=S9b+>=oA*LanO22krQlu@D*#S`bIG$)=pa{{@wu}WFFN!pt= z=r~2nAZDyE8)&7HQ3|%VxA|IP%PGLrwX|?PU$43$mG84WpRNu32naHIQ>wtJxUxCE z8milTOI+d-8|j2)V`*0Qy|4xUe1F6oP5+5-YwD*o=I}{F`Z;7lN*I^Zlw$p$rSLFx zC`z%m*m}h9_JjU)EI}k11O+hYIsXjjYAm~Uio$IuArafhCwD717mw@`iFIt}{4W19 zkFmrAuFN7x6wA72wo2yV_5N(ha&2j|(~Igq_9(g4Of!Fng~s$`Ld?_|H9;ZPOXWNrf5Q^pkcYHRD4baS z`$T+3%EfJ~04hcinhyTRs7G?ysNg;|5yr2}_+xzXAqa$DG|pIdM|n>uM2@ZydBdBk zGhwM~vLvG_17|lkHyY$01!hcXF1%h39V?Yu&B`}JU)ZC~@inIGx}FCOzJym>wHF@) z1H(pj_s8l^`&HMSas5gN67-99^_S8GZorRz`(Wg=`}n85o{Xl&#<>-1Mg7n5v5dz{ zdk5)m)$b~1?DaB3s8^;>BGwT6d^cv(Z<-NMSje*T-2(-jjFLzf604wS%WHXYym+7G9Q zIPAC8)zp?$&u0F}W^p|QedEJrw^?oQC=iQTUHd|E^3cGE2Mu~`26}qwgCfen;c&E8 zX3FG0aUZBXeF@Z#K>nJw3ztLB2aXj|vr(KUqXjCB$%3@6xLBGD zMv*qNxJ7CUt?CD9Gjrj(asXsmL->ah3E+zh*aUMqA5UL*f4(yy35J7>_XNQfkI;#+ zva%k05&GtIi4NBbO*dnO8{F#JGuXOZR5_aIIm_YE{|_9%=BOqtL(qZ0JM)3qD~3kG1x;oHxs?3=qR9UyO+~FUKXc z1^-P5;-lm@lh(I|s3WTotf&f*+73{6nc-m}l{VM@`Ra)X4h@x!hv{kF@+An&Y4`i= zy7{s}KJ=N=AVT*areU$NKIi24k!|V1Co?(sDq@#(_b(jMmd|wO?aVLd757s*9TZDR z9*9jRoA*ZRyKrUc4~{0^(zTq}E&cxl!JTZYo{nd(Vu>V;9bpO{V{7@gDB_Gaeg7)G zc~xWrqs^mAF6t~7%D_UEcHCwVRB zOOR~>rX#WFAQtZ1(C8yY157&Rv%BqGeOHLJUOG=Ypy_;PeA~=r33XX9AZ#T+#v&b! zkYUke3z02M#zhMqE)W48<;u4yH(CK90O;CSw$PBUu<{GojQfM3#xz5T@YX5}D3!e8(69RvPKmA( zK0eR%BdnSU=UuZmgJhmEKe@|Il?SSnZ3MJ~sNM97B>AkJ?z}xllJRS*SJvn=*lg<3 zIUIIH9e(~hnWM5@Z*g$q<>9eTz!Ir=A&Ck(I6Eu1e}mCb{D>4dF0bxtYx8~gq;u&y zJeMf&&@jg0t!*!#6Da9&Fxez%H)U*YyzBa8BlzvLts_|WBP&BD2$V+h>$i5vUnbsU z=z|jTa`iHsXlfl{F7y}f{TtEH-&#l=O3l#~>U9KVm& z-u9Z0x4WszHd8(C8`0~0(Wr{~cTY~K!$ISi_mWasq-y(vFQ{#NS!`r)OIdw&6F2qa zZ3D0Fbx0YPs_?O&^8aw90jkT`L~xAij25qsNiSAfxh#yUFj;Ato>@fNL>w zs^Nj3POnz2MJ9gM2K+i4+$h4-uWJq+t53@b|C%K&Nb;-7ibu~J3Rf$IDdBJYg_55g zn@KHAs+nIfyqLex)ZlWt*2wxxg<_O!&y=e%gfuH&ewia+3xr?zD!u+CMxPeS)tJ}L zT)I3Jrs!{V95{UngVYO{#SPGR1MwJH-3nGQ89Wu9k|s2srQmcU#$I0LULp%#59hWh z4>Uzw=G$H5>1W1T6kzu;2WS;{1#6wcJI*gme=g2^nPpJ0u?Yip^Yin{fTiJ{9)qxa zC-3+BCGP&X#=5#uQS9HZA&5BY>DTyHiRhl#^v|T=ebiCw&AJWe+05^Bz9Vd(K`J{( zgtvz>%yj%MTD=3ipFR@p!&hp;9~0%O3rp4GW1kjco-ZWGKC`XTM^uc;f>qaf!Z(!z~K++9{aR06CC);br6qI{LUi1vH(2kvyp{3#H z*S61D<}k5*4C||Ej;D}h-Jtr9wpmR21~V%9R^)&nu{ITYT?xPmeWB*R8OPWRKzjlc) z0{(3}e43KTtZ?7OSswp(@?4gy z#cyO^O+4H`SQBKRwB>hyvoo&!L43hAw8N`I@RCvROp+fsu29POfoYT?8Ph7kNEzcB zluU=C7zu^c?DSV19Rlf5UZ0*jjzziT(o(+cj$U-B<{AlVeH69f$gAEjW2KkaOp9yh zbjb-?NF%-2W7_zM^)6k&byw&a)x|0@ugdH1cF}r%Oj*A@$Dr!2(*BN$W_>FNTUMe< zzb_rXoZL9Jie!&x!R(3PaGew2eOX)R`Z@Z?f2edRl`7CMTl&A)A}xC-MpdG2B>$RD z_?BnS_!K;{{u}JqybZH+VF2b2NVd=+=$hNk)hZGM-r48fcLMB2Rcs>IDK}Ql)HsrU zzuwRVDf%imrsJO+Oye(}8liafsYV@$$XCKGcHDf-P*nv>nWZ`}pG#eIR8&7HNs&bn z>>$I%$PS`<$e=BzmQrD1I}isBX+&4U#KIg+wo|l(P%Gmb3`KsDtdpNN<-IEZjSpBt z&~QJI^rx2QTkp5Md9HY`Y9t7U=~@f$IRbEJ+zmsxK4rjuo@||%3i2RY&T80y_}i?W zQlwBt61#;KOHls&U_LZRsr?8S$k}wFCGP&+LX~^QXDUwAp+s-cSseNeFUzAn=e4*% z-L5Fv`Iat}cy|5T4j1)c)IazN*KVXMK-kxqGz$b(wPpPs((!wRvymfKAV_@FrMH$~ zj#W*6H_vO^n**ioNrz%reD|kfzfA!|3QJ+S4!1;939lfFUD?j?TbMPdeG5E(wHrtmJY)b-qRuE0`T^Y+R z>w8*PdO)c&jn_3pI{xCkm!M@z&5Gs=ZbzRo?EZkMTNZjA21l1&J~md*XC3szKbys}o_D4$}QCW`C*~ zn0Nv!I2AlN`O?sMpQ(-=akabQcAU1OZt4@5WX_bo6Kp|d18jfCPG^6<3F|PhxY&iN z%fH&hhizE}HCO_|3n540L2jM3T8ljVlicY-lW8PQd<#L;2&H=5rO8}C00fxSR9{l} z<=DpcEEx|pt}SV0n-&*Sxv}yOO^zbyqV`*4!TFJ-0T zm-f4yO^`}s*4I##lUD^tFtSE9Ffw1TUG$Ywnr}^ zusE^*0vkfx47pPe4YQ`E>o^8~p4P@1%$fm&r82^CEHwPsV05Omfpdb)5mrJ=6VuJd zRTBI+g{@p9mFaMR>`W8aiVT{;IKk6E=lCR9fz=#CZ*{~woN{mxT)0(`#zn;{E#%DY zG$ow=lGR~^-$9`HLXk3Qcsf0i2c6aIRESg&+%P1X2T^xuCtG+!o6K_FdjO94qYjj& zYL1AdMqF!nZg3@B%L+;s-z{_t9@{{{=EyCH-hJAclqFrCdO?4W-Xld?0XAdAMS$Gc5w#uE+f zVv!;qpV$y>4*|eL0iC6gF3rxl`=M;g>!)DrARFrTa_Bcqo$aDiVG75i7EV+7hk2wY zZ7B|~8Y_$L?_vgIp*781J~s~QnG{|@SIa*l{USk4L z3k{*S4Ax&HYdRDfEHW9w8R{ZBL$+T%!A<^oMcgv~o+gofqRpmo z8C{>$oSrcbCxZ{hVE+&akA zoxVR*RD+t!t4B{(-LD4QYq;%Gx6c^3rqI4=k~LR+3zV;<^fzB^us{JF=@0nvXII0{ zO2%qM&X+c}&O-x?3hyhyj9Ql6%drPFB4P+OMl+u>+bm(wU;@8CyS0~&1}^N5JxWdX z)etFcM`+h>#dBS>&NX;5_^t3|GJ#c+Erle)(ia7VKlSKw-}hv`{QCAZCG{fUlZHH> zyeg!#!hM}zL1oYEh56}IXQizODt<|_r!>|+0SiIniYHz~v&?tSqnAcRkaY?o0352W>5C| z%0(FWT_euW#wLqq865*sq5A;>Ef6{iN{(7FmC*>s`*Fi9c=u?|_Y`h6lKAuE^~zwe zLc>2!5MLs2MEC!(c8=YVMcW#Vjf!pCc2YsdNyi=A?AW%|u{*Y{j%}-B+nwarxj*2Z zFL#U@^`)wIjkOo&obUT=+igA%W>+1_5uHF7MX5y$g5C(@NpZTirDk@EfC_^&LI7V= zYu0ZsKxiN~UYj{1KaHBt*9sy!a|-vVxRE8?BSgWvF*ELMTbUmcLyg%FKe6HFWDRvK zAhnbZ%>GDb=SQ2Phwti|m~yHeYKhfRLY^UvWaM(6uPO3BAV*o(P)K1Vk?2i4cX7@o zl)nR4aJEP++3*a05`^8}+;mL>x+U`1MyKf29CL!}G@-Wi`ppFj|4|@#W)X(tHO0-J zQwe(r4Cha-uz8xW1jh*g4C*N~Qnj$p&s2s#MhnEG1GA!D9&Zn)tQOORFQ=t>hPQu* z(4$65SPX-j{*DZdfTBDw)?ch1H`%z>9*T2>XAp^a^TQf_XmjGWr&WtC_0^Nmzy=Cr za;o77N{-4$p~3#$)@b$$2AF6ROCOp!mt3t-|MP;8X10J$@rYv>`{)oh|KUE1cVoks zX7IImurq!>$t`5IS{1}6E1@GK;;72<}M%5_p30WL?~Ft_B) z;~PRy8Lm}N5WSzS1VP+>1qyJg**t_Q1Z)6!^YZ=;-ZTxKw5Um^?8ML39=fiJnJ3lH zC$5j24TkRXeS_i|j=_5Dl~RW3NMgTI6TSZTSqDtpOBrq$#Y((PLW=VgFfFBlk(|hm z2?8iGRmbIsZ^kkGS(Nc_JGdu|ujcy5fd2)hl(K;1W?x}w#Sgss4?q zfV>_uh333JxHtB!RAD(@x^!pA@FC7Zt0SOh3yK&bG5QZzB|LES%}Eq&e56jR!89_) z%fn;wa~rZJs5?3 z6$y?77~HB12;w-@!fmJ_&{~Y08EninCM(YL1InMV$Rg6R!yV#>Rcfl3c<;elq|C%^ zf&w|cu8AP57qJu3MgPoF>o%??$92DIfBc1lPDAE{zZSiv@GfIHfoKj*Q_iq}?jmH^ z6)_JcRUtVe{uwgP5SGxMg^aqADGm}j6{epJhLUJGdNC6yc4iE`zNP_!i937cQU7XW ziJsX*71|J?{Yi*+10}sF@t9R;KN0hS}ZNuq<_KdM-V5c&J!`8Xhy+Q5_H^$ z9SGOIpEdYwI&8R4L)MFqnL1EmG3)tAMPBs7$I1YYbY7fvetvnVI~X?%MrSh81nKwX z+B{zc=@dFB)(gnnfv(_P?qg^Q9w#R#+ua2MP0GG;!1#r*39fHKC#{fIj#g5HwBrrJ z^I&J|3`Cqz^gy{P|IcG`xsF2qyzQX4s($}$-BGvaOl*-* zHx04Snhrn|328RkwF?^(O}*^}1>2p9OMwob)z6yA_irKIT8a1!{u`Qk0)`Eu62=a= zqBtt8=M9SEwQH!w^JIWa6UV>v=*^lX`L?6!u%d}o$JtJf3+b9wa0hO}JLAFfr6WiC z0zOJF{5}d1Y_-q~Q3W;W-K}#tX!n@CJy(sboM_wre|XgJ)L|W2c#}Q#G1Df2Mo%Kb z&QnK~7(h_g1#)C-izto7fVm)@S#LmN7OGg@b3|Cr7z(uITj_&Cl1BcH24;IILC=_ zGPD&JYptR+cPb5Edn91P+^+T0iZq!>et7d#kqCC%K*oQzRQsbF?I=5qpuf}~-CP)O zA4t~i4PU1@t|TKr3uQQZmeUbF8TKpID%M$fO0Ql{1(EJd1G7vYow~cuzfBYd7|7?;w zy^q`bIe_1o<)c=)qdMzlfsYe7s;$?5MkSY4T&qmeB|U!ID^xH>+#Oq{@`R65 z+8v2Q5Qp2|sVib+A9}mj{|hK1Gdd*^jxxltuBS7@q6&;}UO8kSlTuq9VDB0piikM# zrV2RWYw+8a*fnAlrfG6!)$-$$t7=gB=BXg`EAgH?-9uNoLlPLFUwefV4XwKYjzRX% zV}^h8cW|Bwf;ygUnJfpmSP)uQ+eAp0$uQvD3Lc)}aqlgP*>QXe0fh{3sXrVdF@e0h z2=Tq`sS)kpyA5nxX9WkP{VBx1^I>J|KvB$6R4mj-G7%M$HtpXD#9I{ih%^)ZG1E1j zxZVEHVIMyqI+|$85rM1pVrfw!#C*;N9fcz}-alci?5qUc?qx)gN9;)?C=R9=i;6Bi zPaV!O3yx`;!N_BExG;t+Fi|vE;1dxGPc9J1m1NeJ*n#&)E;gpaD~mjR0-tE6g(hvX z_YocL?kB?bXPn!?S>0j*HUqh&U}oB)o=JE&pKL)EU#xqcU2I)3JVDu8)CnhMNRc+b z0C&;Q*^Z3?Cibi7?4b;ZjJwmu)SLRIV9(1L{rkSd3GPxP6~sRS25_kzN$?B#1jM8S zF=W}13s~0*e=eQvWn9N+LFT-7I)c=HOE85ye(%V|kN;lpucC+z-WUjqQ*Sk*>hx@X zfM_<|EI;5qfz3<-G7@(Ye>A7o4bhkqNveeLztqS@%G$%2QX*i#A;ytBBDR75WiF%_ zRRg^DqA3+_$L{~GFQ8rl(C7VvV3|TdFs=%4Ss68SdaJov9FV+ufqxP2WOX2`k3l{zHtH_iY;qiRaMs=a&YU(D}qMAp;s!e%qIGEKIOk>0QsBpmVYWvd=qER9P|upZh^R?`#@PfU-o!HxI?QgAu^z$NWbY5EseS~gEOj=MtsOA5-3 zLzXDA^D#ZK7ODFT1XT8P0+Ag|{fL$$XFdjthkxl^mj}1Q(H}SZ$VAW+6|PhAQ5Oa_x*8o1DKWX4{%c^Hxh>d+*+_0H>(7*MQnhb z6u-vBs?mXynu+Iu%hu4alC)QyUX;FO2t%&)B6`Ht;kfIR4%FP8a-@1=O)YAn=w_gb z*53jum4%^MZi3;uUyO!z!qV48>JOPi)b%WKlVs=2mA9YYDu*+X2zE<0BqYxSNM%|f z{U8XSp5^o365+eXPmuOHe@?KFupV}gUck7*=M+3x!_sz{gDs5>D`!|CN;)m~DKvt8 zem5n;%9N0b(f+t_kT1F0)0ZMG(B7j1b zkDn8y)?8A>hQx`cb#Jb^jW;||Xl(qrkLIjXLb&oA{$YMN4q;;H>PX$w9C*Ct%Hib` zCzitf>%-R9Ta|7Qy@xq7XD4{}kN1W&!VIFj-!38A-K+6m+lLcKEY^ShESJ+WbFrraSZBFcB7wBO*Vm|jqXI)rq(=G2B|9+7LqgT|x1d2|A$XP^p$#fhNJI_1(z1rSyZDPf zB2%*xGZrnBmXHE{hed&ija=37-;k(m7UNyc`ILEw&l>!Sx5DCeSu-hL=Fp5nU6}#f z4m%syd{ql;#@0({vXSnIn41+cY`b|pQvvr5?vcV=?5l5yldeP$ALhC29g4Y#otfzFu!$sz+cJ6F%NZ>YY58V z3#X97N#=0YGWZ)qb17AmJy{f0ZQFST8*120p+pdl8M4w*7}*N_O${@Yk%zbeR_FJ8&ON~yU_UCci?P|Ft1n^FUPoT zUt+=5f&5TvYE93ellm=4-VKUk+tA9IrfVyeKRQ>I^0Kkw<2G)%1IwDBhJ}qy-kaE; z=wp#U$xKg>6*WpdioSbXbOV33>?4lPiszW(SfSosbqlA5=;z}KO-)Q z6DvHTr!^Vm_jz8&CQ8XP*LNBoeyN*Kr`$G6mA4G`#qQa3nMrORg9>+ z6aWo&F0WD(??qf?jb!Uz7Jx|Ia%R7k{EZGcal7Tc_mw`cORhqtW=q1FUnB7wRah04 zf>(cOJ4?H7dO~N^_t*}yw?<6_ykxI!e)lkyej~**&bc&ZAB(9DPG1fubHtU$+c9#} z=0`+GTDy6rnanzIvdsbMg~~)|_$P~>`bDIW88LG7ek`BRTy2btUlE#g)fYTX>YZyl zrLJl09$2z!(tj4!zi;oi5SwCs$RknPz9#T1CAyJ;lJj)c0c9jZY}L>;Iaao1(_G1$ zZ_;oOx*_zw3_b5KEx8fL#iNvpg5qbzh%YimQ6LyfTu6QyRk^Kr*0-*HMF zMa;a?Q_slmnSsdm+dE&J9XIeqW(iyM%40THp@54 zVHF}Gm=>=$|3l-(<{$pvx*bMh!+9`?Y-B$aT0%g(K$I0Y?Od&=F4V^`y@BxdBWk*( zJ_|IA9)eAnwFL0=vc#3n2vZ8Y)iDDX-3dj-kU(2@bZXza??=c!AMddRgeOB!ELKna zzVKiOb_zDkv?ID8d44?}epCF%mvpoYsdTpvue}7~2=uH&)!iYe><(_Sr@coV(VN7) zMnK0G{_;DT*vL+RxYI@?4{LdKilVbrm-3g+zb zO6`7hqki5(UTNp{TWRAyPpBEf7uz(h1irN1)Y~h}6deQimm`PauQwt{D7bqh^;QI1 z-mlSg& zV4d8SO)uivv;Kk%8@A0PD=aN6-$%glDzP7M1FQ`pjpM5bYl3M~C1e%6Q+SNti>jcL5ptgfl1fBQg)Z^vgn;I z%fd(v`{-7^n?pf7Jee5i0Jb#P>{#`S^hUj>?b+D-Y)(7>F+mqDr^1Gr!w(6P_xVPN zBv}@%qn=LNRJ}KWvkLXA4a>#6v~~xFf~)+if~z9pd}5uWv_HsWzR~oae}H);4Ij_i zuL4!V&B2$c`Vx{x@99h~aukdlpR$5h4BHBYbQ@LD^7cXWVuuCxMNj#YpDQ^hvwYIV zi$8TM!BHLZ>Vtfv&PEx6iRX`fd3t-Xo;Ou9j#V#sNPAwCm1v(h!v2Dp(86;DCYhAw z>no$%B2bko7nHGn_werE=@N>rwYYsGJQKv|Vc{Ej$`K1Ks`L2aD9VmLNZ1As^w_meXZrw6wJ-K?>BtVuQm8LM zWq_(A?rGdkx{B^yPjka`>F5G&{pto>6e@WHN&yN%xbri~htv&B{JXiPJJhm!`q#McvhKgv zh~Gd!I|CRR9m?bD&Kr~jIf+2|X?6AbLOJu-cLqy%(G_1Vc^72$_5HG)f5wcS`$ljM zPo$WBRJ^2=g7BD7NKm8Ti(=Fyi)1M**Qw=yG+_v->96g`L$h#f;@7t(;v`0j_CJl` zLVs}O^V*hcQq1uED$rni7RFPjZfoaIQMrIMj7ASP^x3pIQiG0=0jHH)6klY1;*03^ z%1c{F!YPRJ_WrX7*1sP(u!sR6&o>u~~`Rl_^J_X@8sb_@r{yb>n` zU<$_MJGRcjvR-l>?LNSesYZA;=9xk`0GJ&BYW}Y>>Jb}qa~t4LYPWtGX=f@Q2DkUb zR-L-qpi8SPAY8d0nf>E)(llJ>fLvI5BUxpKWp1%YP&AAJXk)I`E2BmI>{92fF%qcb5n~B7MfBU>g%r=x9PQ^@ zygr}~Vv!vMivrx?O$b zDcT~raDgv8rs%=wo&rQ;a_Eaz6ZU>MZu@uV`cm2v9+tBki_rT07 z=X<}o$+!NaT;;kUusaoKxKIdp3B^~Dq82u?{5yBtf*aRTv@QwMG=^Mke< zbA<6E8<7?ShjG1Krq@pe32xsMsMOfZe>_1dV#vEmf6zCC%ev+D8Ra```UseTOxT|s ze~DW8#)-KQmefvZp5Cj~x;2h|?6?G@*d*+8IM8b^imMwocX?D`7+M2M*ehrv)}c%BjnMjUc+uxj0(b58vKTt4ZP z?-fFq*xJ3>fHtamF5?@ka!I}5Vxj=i?!uKkICQCOeLM5@fu!7B9^9|!cJ?iudcsU&;P-%KpC zmBGtmbQTf+2GTw|fECQB%yA*LQrga%&_kji2Th6!84_XiFD;gyZ>PCmOxp@jb#1M_ z56KFN#Z&m#B*&EP>9j} zFZ^&YD4ehyP{{@L@IYqn0P<&4J7w?ex=USt6yo?extoICy(OJRe2P&guAnNjEz;{7 zh+ddBt7Am*(%(TJXI0XZ1cw1$22-)^QB>TG2Zy0rDBm=eI|KcTCOkz0bru)IhS)vU zc%0$5Ovtb}^WmhJ)^3|uTdvnE@aaia&c02rJ07JpjZ}5iA&D&xhK8DPfloazfkk&q zJDemJWEHd<&~x5Log&fcHF8eCZiK&o4S@32)PL|R?7rF+=nwrKSe3!uFxe{ zlgnxgyG9eI5y_jb!d~#yMi9^W(-MkB7m3USe1O*UoF8ku$%mG)Y5;Nkdjd^cRFr9c zl6$ZYt>r=(Y*RkWzZ{rlG@ySeVi!Zv+8d+qQqexAyYNE2n~D!E?Sdq>M8^z? zMYBj(1upWUSspGv{ailZGMVj#A3xBjw#P7#IN?#5p^iuxNh*dAWl(xGXo}S)BpLeG z16MFKMeN?Als>^?E`yDC^MlqVn`WWLF1K15qWB3cg-6dM+&uPkNJ{&9g8%D{8kV>C zK30m=p#B!L5Y2r^J9t{Q{84IjQlPxphEA7hMT20kSeddK`@Oz>p<8~F@*ZRFAgl^+ z@OSSg;q0Z-g9r6xMUfN%2a$5T@$BbUTmkNGC5bIb_+!cW0CgNS&i9Q`xz%XvqCYm) zw~3@`d&0(%4hS0xzi<@Zkx~{-o}{{~m*UWybI&XBQbzBjbnelBj}x%!%CHY%VYUHB z;kku8DG%VlTzJ+`RddsvnTe9tgF|)+k9I<#L=pfDGaMuY!~hv0Sz>dPRkK>{ToIq} zAOOkxdB2M!X!eAy$Y@ea0N0Ku402vMB>9_&izA*LROq?ZmU>k;Nr@fC&l9apCl&27 z9JyFhmq;{IM(&WN(69vl9Y9jgN^r;;3{|az9;@bGWPvF6`&9BN!b(ub$`ed* zk3{w?t&tv*zrU_G92YoJxIg~j6G&D};WJ;Sf2SLi08YtEAi9aka2^v!Vl|ky-pwPGuC`?qc=U&;(#nCF_y{b@x`@_X1XbA|6wmRM=kz)G{(Qa?<%1e zLV-txV61&DQ|XZ8WfT#zP@Yi@3K$NW5S)ww>s9Nzpd+3X76?r)PuWn+0AA? ziKU_6InG=my>r_x$%#g_hA>f7S)dXFZIB16@k8v+I7-OY{wlw4=97mMlgfgfH{#8? z3e`S*J*1gVvIQK!?9Whsz=n#B0^=z$_7Wnuza626Km`S-1k(2@UyX8_oLCqQ1%{)N z&-WFy)cyQPGU{UVn1<_Q=Xl9vnmH2}?iDvX_A3}HGgg208ICMu27?ANL7pc-dckYp zW9-dwLJ{(c1q25_@H{(ygi5TETWJkD{I31#vE5?gY|lE5!DaIP8cde8vWq+2bw_mW zVeJD)Szy{-uG7WdiggeXLMjX7EBiKrWVH+cZmd3$Z*#B3Ge@;DMcdsU*>&oS*kttg zG@?H#iFqh6H=OeM;sb!+AZmG26vzs0{0bS%^(=n;yAKUOumR8^%(Ml@FjUB>tw4z9 zl`>CqZ6)ZL3 zlZ(#?8}Z|Y-1vC`uT=UR^2h4!#v@AjkHUQTYNXKabO^EI0*FV$C@|QK6<1K=$)|AU zv7h_**r9?>?%Qx@7lAeG#&%CsmNIvbWrbl&s{?XeYjCPk<$j4gWN`q++o)x9s^I}OcEo&ou}#=#dM&jz~&R zXH=Pfk}nQeUm2GZ_|t`NaHV)JXCcm4@K(jzW!EKMe62LPPOKsI6n2IWAx?%oevGSo z=XG;M{pSSVM>C38m;~f>czmgCCREn_GJ_vH%F9y9-)=Db?+-eONi!B9|Dp+_UOw(_ z^9r?bNuk=u%!k^v@B{eN2&NzHKw_V?i}L+gh6%Fl3bD0`pe!4TjhgR6$ZaX98MLdp zn!)=_2dD{E!F&vG)js&RK-7a2rb_B`HLa2J_DUG#2*6Kr&Jg~}VmVeauGXA4nRr2a z)}k&)c{u9KIF-tpckTHjI(})$Owv8y~YiBX*WjxwHM_VC1x1Ln}%ubJ|p{ z#N`YbhLnjh%V9Kb9SNKoI7uU?;^Q7dGE@qpzQLIhZ`fS$k3`lD#Kj78!Q#?FziieJ zCmE~z#@7+Tw0;cE27ZvCO_(Z}nf01IetVkdF84(zijK>O7)gtn3zaVVxw{8P)W)L= zYmd4Rn6=FBxTQ)^qh;BDD5e8o6Gaq%=nW!>ws>2*9k!by2x)7|Y+zdJ=;CYoHMq`aHE5-+4E!}$Y`yU)Mk(Zn2ymrPL zJXkHi(`@4dgG`&*9FBxyJC?HH?-f3FS&tpozofd!M{+w#)^Jpo#mFhSZRUkfVv?pots7 z7!=`{U#!P=4w^JRs zlpVQ4|2Ip78Bi9PB(@lmPI`PCj1IkV;$Ta!tXET_O#z)~omL{`beqV5lZLlqT6k`a z!m04{3-W96M}$saIedjxeCSOi_`vWY^rux1cILWm=+T1D?t)qA-U8Fy&1sMHd`OeQ z5*WGfz(7NH{M|X%?sY*<0i}H#_a6!*I4P(w%Ej+ck_evCNW^#;#R$!3F!#s;0|EAz zVnsGMv&#$67$y`aX2wX=Ww}C+1<_$JxbBx=-yit<#9Uk-Jbsd5oTKl`D7^ykr$|F` zl^MlPP=b{?#ZNLrx&OrhI!tSzOJfDdUSKG(DTVDT4+3Mn5XPI5U4kXy$K_4;fiDvO zTF1&QDtB?Gv``|)Lsssa#MeA>W1aiRCa|_JZBfmIevE_7;Vm-vw?O{C)oZw<3mjkG*8$QW&{K7O8(ofwSI|f(5Yba7Mh?9GGOLC6Fs& zmJ*AGNMkV8{q5Ulj`sQb+Y=(S=L}Aai8!?*l4vo6tD=&AEZS{du2#5eZa3pwX4@-* zo{de~uNw^qzu5_&>F-z?ocMbX#!u+Lfp?SySNUWo3K-UYLNz8D6i|uQ8vcZ>l(ZG& zHQpIsRe>`{VWRi@8Mqo$5*!AeUI=+`FjUJL3SD`9A^;$omE@y-(%zGd#F!p|U7G{L zqbwX!yyrIBVO#_CLx8#z(Qf0{y~D}so{jSLA~@%wK(KjhP5xFl!HJ~}nNRWj+&mPM z0`e@-;aia$&B=VZP?Pln3?qN@o*2um9#Y3FIodI6>}#Se6&jXnho-(6_&LW4o=UovY9zVRL8K26K<;r&SGoG zV1?{_Cc%tY2eoAvi(a~eWl}H&ys4sPb!AP>+a5D9cU%!Mhd%qzcq? z4k$tahhcu6zj{^hj-QVmA-}~mbHhU1ftsm$4JU*RHT*qfWD9LQ|Jjwm@_8wJ_Vm1E zHlx3cV4$osj6bN90hYv0%{?R;NnB5$C7k(kLoB_vH;DV|9-0t8AEn&Q>#$(eS4@$1 z{SGq3j{+{kO8l3Ycf!REmw|%h@O{j|bP_dB7LVddOg5DV4@TU^E!nk8r5t_z=%C>|iO* zXry%RuKLXLLy!P3qNVIWN?~l2!*b1EPWzB60*X}}j_b4=UdI@2*i$%apo0GlgWIE? z&P)8qb@%%3s{3eSbJEC`#lKx>5DKU!v)52C_?^3@1^n zXUpx7mgJ5d?w3e9@>@#+do6LZ$U+4*MC)RjP&nm!8{DklTZJg&m-h{&XFH>BI?X69 ziHiPkl8EEb@@+K|ZR&L{N+=A4@x(J+OYu`7b+49faaZBQ_J)*o5zJ*S;Za!@MUwh2 zCM4^r8%23d;V-WK{YlKo=(htWT3nulA>_{F)R6hPeumR~-Hswm&_eL(N((PsZnt_^ zge$CyN*gFApA}v@^raGCVM4nA}Q)K24wbeMAfxT5{W-bw7H%NawT^t7pFh`Nh6$;+fjvrgYAa6iD34mD#@ z=HzzP(h35#`-^?oYAm3xsnh%Koo2V6=I;kHDgV~_bRhB>iY8N2JDUJcRZ}}_WB`y@ zZqre(?)-`C=byS5QQ*AcSs3d)C2m0ng%9uCF-jW|S#hM9jLu7sK-%S60TWQ`XLu)f zhhwm(*h;v}2)xSms+r5FR)OOnilb+jneAR<$}+MKMeAW~4@}y#_NYHCiAaj^{gmRZ zw-XfcEM@=OQmZ=m@WFntr5Od73IktzN0@4EVJdct@q(_zjG9icj6RPas^-h}tI_Pg z(MD^bd?oE#&F>Fx>#rhAVpz?n9y+UZtb8-O2aPc*XfqDXof((MUkA-D@@|plR~zi9 zp3v!xUNX$cC)0re0it_zB!me+Nr!dLn&s!Nz&<*gf`xN70>72E7zT)*&h5#~O7@-n z_1=mzNR^8Z*?3TadD$*r(456NN4Z!h4zq3wPd>_Yj7C-~W)o*;IUyGtMItku$D7v1 zH9y>po%xgV`GUG71mE*OBCq7^D%=zd)Rw&jS#6*|&>8}ORU$=ILW z+U{}eDWp$`VoeK{;X+3j2^nrZGXa5|C$3rReXNp0djl24Os%tC*|ItNiC;{4e zz#;?*4orQ-uiDG3YHL2pqEgC;y}>>wSVGTFE9g}ezMU!tj)m6BPOS5UA-Xm6_E3(> z5Gj(yhNAk$A2CR%!X{g>Zmk}4`H(X9?U+#rLw-a_x^n_NheWh@qu7}=R!@mx`w zmQZC1b>Ts`VCf?H2G4(wo#d6Zus=d#x_Bh@!@zQ#R@Bsv8h-X1Y9y9(HzX1!8%UZM zE}lTDm3>O{m^96y6bZQiMyJcnR{QO@oS+Kc^gY|TKCxfoh-5yZ#4cLEiPp%^b+MP# z0#wzcB+dqBmme0#2RVGigJP_d$ye4bJXSMq(3YS4C%^ z#RQ9*zSvypo%cBERL|3$nn}B%H0+~-VDT0_gqK^WC?qmnLJYhNz9WJW+WHR!D(w@u z-+7qym>OvLrV0Eb3u}JtH!?ybbUww~sGj=$a9yN=Qu+3!Jtz$DRFI{bIzk-!BaDPYs_5n~emilg*G0@O@b^Wx(Le;e81yB_q;8(pJHXILB zAAewXO206FX3)5or{(*tV((S%-_t(lap8{2><2&nIqc+0Lv%B(%P@5qVcE;qpt)H( zMe_5?*^`N9c5Jf1;kBQI4npb_Weu~Zq4VL;2nS%)SRjgfyN0qt|9vVFk1262yMmW+ zg&TN^GwKX{)9X{}ge1HwSMd@$SzQcSTzmo%Q_p`TCog0UutOxgd`DLZCDj>XSP4N^ zTr!&IJgJ{iSp7UCAp&x7c`Yi}&OVqTbXE}1eMCNE@TT6G`O&@ngaDO&jio8#KgBuuq54j;9;=&lO<=uhV) z{!OO;^w;sp&Q*(lD2qixZ+XrRu@g~}nH`sxpY_T}D%24K!X9hQlr{AgTq3xFNN-VO*{b)v**cdK|7^@KnvuR*ak;x)RNhY*h zqbJ}$7>4`=;$1Y}K0USc&+M;M|KLG=zWl}Ka&$OXBz=tjtBg7D3m?6p6~+y0@WYdf3qm6^Gj%CDWI|p5 zHj@sf*X>_Ph1)ER`YA$eus97r)K1H5LP3PV^2r|Gcjwt(JPz9#0RaKRk9It?w4;_|NmL_n_KQz9dxQ70` z`}9qy%C{`x5G0tOVw)J%bu&oQN^r;s4T-`;uUj@euyNW>V}_swPT@h&-5Aywm{5b5xF%m1F;1K-cxp*r^bg$$;|d zjgYV?RYin=P$fic#x{?`@zjiAP`&Z5~vG{ZsgP8zCar3)(A~; zDx)=6rIG?$U6xt|Pa3M=6XxK+TF|s*d9 z7#SQa+6O_W*9rNYiw{AhEQSzA++{94r@ufK2Z!rrwX3Jg4Xiu2AFcLFc>F&tGbS<6 zqny=1UbXIjpilV2!r(F|kT1|;&zpP2 zWy*j6Itb|+U!pq>%xzEr@55qU5|O&TRw9>fOG{YDCJq{bcpxla^iOB!j=1Y<#cW=- zx#i`Sh>mrq9~YoT@UjvLxJ~A^pvi+QgXynRJUfEF*v-bLKyz1j$axN=xQriq8#`G` zNm$uGJJacR*=~0!O^c0<6$hw9NU^ zWqmSfimaNzd~jIUaLW*RRxh5N#QSHX(WaYrJX9QBU zFa*PZIwSnx`R^6!cYD{3W(g_pt+Y&>$txe*yTpm9cOz(fsKV9wq{=8(IO1|enQobN zPU6jvUI&K}f3_R=SFd}&?4@qV93u2X_2IoZul?UId2%Y&;o#sL(T}{~fG1RD!+-wJ z63RM(*6GwmIqDEVAc)9oRyw;dPAzKe!K)D|;)k3@!{8SbyI3?j5 z?~Hxo-4bt*Cb|M;7ATu`&x2VoO~)cliCd@jJ-&>(K-bl7+!@%=D>g>zTno(`q>`*$ zeGb>0Nvi{t-LhS(P!CKY7)YSU?@reW3SiZ2^#A&#cd-WQe_s5i<$xxY zm29Uy1cvU1o028ni5?hi*O14LszCFR!^E?kfklfinsLKek)Ll_$9Ggi0k*IcRlNrj z!Xe>YS72dbmF^xNbEJ1cIZu$wd+CS)mbZ$xhs!NC{?!|cFSHf45L9gU&6^*<~ofTdI{ zzWNPl^=Ba#@>+Czx!IG%{5_U08X(%;>+=fYU)oIP3fU;w-3E$Z#t;kfU(bkqRW*!Q zfy@#r4TM;ceN8UM(|qvo@CrR2PaB;0hMpiUqc@g^q!%L%<&4dk*^Om+KC>t1)S8XW zA|0PH#jI7Kj&fXNs>8I+pbmIMVVT}xkPNGKvpI31|0C&Nh0JPtX-uZq)Zj>xkMbW_c9Ez4m5@sk*HF z*V^d5!WdsV3$MU1)7c(V*6l|qTa@AyCXpPxD|dl-o58PJ@EkOwqfozFeLn&dzZiTX z;WpdRZzi}H_TV;!>B4PUL<@0WJWy0`0#j|Kc|43wB3e1@(BX*tvR(UbEO{RGTFX)A zUy-OOFSdnGUBq5$nJ{ectz&q}6jEj@N7=3RTW8|ifG{(a2D@l%rl?&BG3?wA5FAZC z&du-b!7BP&GIa@pgg_92myY`McZr#iv@^C7ColoeyJEgeiFo?e#l;lCVj_Al6^x^` zO*Jrzpb#@IJLO+T6JD#PkMmlsKJV6Ns_wo?yT70~luB|a{d;h}e5Zs8DxQ`gt7)7I-qu|e2I5P>BMu|A{-3z+LnIwy->i#t(cCgy*gThbw z;FH}3Ab;=_^c2?opUTerk?QY{>ZH^S!wy8QV5qzu8V7r zD+(D&C8ONbrEE7Ng(%}1$-39NF8UsS!uS0Tyni|4^?ba}>-oe#u8HvIN|IxrsxGd< zPJ9o&q{DGMXr?1M|;6S z`Sgp6hR*YTS=;$;)z28ncj=!x4TR>=Q=<&;NkzZL!Q=vO*AqCg<6P@js_tP2tS1TF zh%^sPO2Db3@i3&KpJ1uLv2lZ#z>O{QSlOw(^#fdLs6@V~c9}zSZjJs}3YRcDEvlFh z;dBQ>3uu@ah03d(+~2d%uf~_xEhO`!<{e3Mj&9_my4pNb-u*p*NIgt*&A3eu3U|{LdO2&>l+ep zP9LwGMeR=Z)3kV_Q&&w{n!8Xyf}Bm>BhgfD@qhY8>IGNkgK`= z@3?@78it9I_`yc}&|vkOv!;nW2~9s4MZx>Vbq#X{_27=@kgzZ}f6^Wb*I!q$l=8up z-9mrqz==m6fV$3?oo~d6+~2RNCM#<)rE#pA+5SV&6bWs=x14xf6zw}IJvSms4FP0a?@BP!U8!AFlC2^eW{^s#44o2B;J1vB|f{FTnFaCG_n zqL`ZqTRKdIMsI+GG4>gHt(vE3?FWxyK? zl#Oz(6ucJ^uts>#cJ0xpb#i{`tbLsk$tn_2f9Km)tc*B`>T}g*G%99D%3C+pK0soc zN?5o(9*#`wO?|z$Sa%iF!Pm@d8&?=9hOy?`hIYAt9kV#@@g` z_#?0Y&sqUC&5v)rV}Aq&9!*XPF!g478DF3t8nO!x#)OPIUXF}pXuV~F$8rXV76eK1 z7?sp?)C3}M(vX4FDmMF(QRzGUD)VD$cu(}>m1-(U61r=T8UE^%Y**Wj%n`0MdC><` zZT09;6mO*VZ2tUx5J@0yW|ng3^KRVF<{2&akXyPeZGU8Z+l6qfl@UdMtL-+VW}{HD z4Tt4MB~3V#EA~>pB<6zWy@j2XCSiz+kT!LWM5u%= zf4t>+dXm4}-SEXh*|XlGzzua|ST`Z86jX8>+I@U8w)7|W7ryaNz6+}^s#Cr1>uzOb z6>!O{jP86maP>u}FaO-qRhC%rY-`5x2@|T{LZ@HPx!%1?I-{}aVVpr{6_qNj#Qe3= zgNo5CHGQ=i!z(O^QgSQU4WbsnZd&wo|2vC#=sc!#Znenca0fkz@=IGWm(e$D+xbN|1oUADXI;Zm2 z0aB_R*2j4)^=vXX^WBfbwz3i#+jqsC$E^xtIFyYBXHuNPKFOK;ll>n)`)T*|N(6>S z%C8aeFQUVUV^wNM>Q77Z6o5M~x@KOzdD8;~;R_A!86aLy@dvH9saT+Fi*%f0J#EpR zrNF9r*WO)ST|K{M9^13bKA(2lv!%&sbrj%(Fa-$EpVwD_68!*`X)SOZEF^V+C#^O? zD2ePZJ76nUf?hV~ z7PoCKId?u=?t6tOM+G}mJ%2|MpS;LymfT%ukjz-bT{9Wv+1>x(~JEBH%((4&7ij-Fe?~xqJsM<6^HPpE6`EXXm`Y)Z$-sD0$&cLBY7D zTON?pw$C*8_V$!itmdbtUR@9*47ip6H;yd%j`b#g1wAfK&g$XN#%q1~20;-kKb!5j z=|vh4%#Yul>esIwXbwAHg1g9yrTef@FxR*?Uqab*Mt9Edun54QHk%JQ%(x|GkU^$N zhugt&?rlrQTFqdg64$3ooP6Z#18dw_f4i+H1lheGTW2wmPzeMN}Ty84(#6O z4hbG^N)TY86*c3Sd=EIp&!47Yu5t711lWd|*e$&APpRzYd#6xpOiz$>=8}!SeEBj( z?!)a!rs#T&M`*AAX1-vfy1Kfo%~AyI&6hlSs+9|=pR(B$t!!;?!gzV%*?MB#YR8ri zE%WZYEgpa@o;PevI34#BD12Y%FISUoqP+W>C;oJ}?chsnPWy;4RiHk&I`(VL zbm%e#iTA9V=-*+%6ejFmjDoxk`z&j6ZH);vE?25y`*OuN^EDdU%72LX%;TR-4|*C zxp53_cc*0?d~vDU!R8hw9`C6f)0wP))LJReOe1(F-C{CRD!1}#pQP+3bK9gi(ZI^| zaw)izKL`iXGoI|K4;+$VWn~pG<$U^m=ni$ZOQ8J0*GPZ=`EiXzE`&_J_=r6i*I1bd z#!0%MKac#V!Zil&m#)ur_#Z-?pLhjT)f=WWISV%p)s*v*)q<$myz5m`T-8`jr7%(z z+;D?ggG#t^2v3t9|Gl! zo-L{IB)%Jr_yDZ2ErPiLA{tjPb8;T<$v&+sy^-vT%b9%b_xNOM4TxhqiB*)sDIk%P z1}*T-<9+dU>pA!#SV~Jn#`fry8#dleqRSgawWdG#2sO<~MO-oBty66S=kgzh>@^evY6{)SQ=c2&|jTTyxhU4ew zmqDJY^7eKfotcs^2Cw_jUV`lz1F!xr*4D2*e8j;!bbc}E^|a{VTEop>AFMU#nR-Rb zSvBydcWUdETO_cyvybLo=;w|v-C&$Jfff;chmtysaWw(f~pm~uM+>><@ z?tdh-TzYO0sFFvFL$JL*mEODHx7aWJ z&z?QgC<4^A5FGx5NL$>S@`m+L#e{gy&o}pc;ySx+{cwpVtm}7w0A%^!Tz^py;wpxu z==M4v3Gx@dRFp5HOibRUS*K)S`{mR+M@Q+q0NGl%-@F7GG}VJ3z398|zk_|p<>lp7 zUIT>ztC!*7;duZhBGECoCmKS=Ww_>5T8Ko(4uer}00I``G(*OGSEr|6-uHS*0?iswQT^whGv znngqLT$Z+uj_F}V6=Y{;7rHKd*w^^u=igtri1AF@A1t$Ml=P7L>VDqZ^1{2%H#I|kQwr!ge+nm_8zMT4Q)xD?c{@A_y=YD#3^;*wb ztHb4G#o(Z^p@D#a;3ULF6o20F|9PMwe#XI7O%fm={$>dgL1nk~%WN2JnYH(CS21dd z1X7}Q=>*hv_~`zFoH$YmvT{-)2{N5H(r77UYV(B61k&eoR`Qbmv^Z8$i3DXwQn9;_ zoMx@i1qH6syU*C~bKcsOW9KIg&&x)h_b$&I)9UW!byyOna0%%@$cbq~q{~h$xh$<1 z>)re;lV(INH7eao+gRxxh(pL@h3IvzD|Gkg1ZS)|Z% z(t}}aN23@KQTTdHYOZv4O|YlgGGXr!NeluO99XO11(=gUqoFvRgp zUJXyM72~emK9cPnbZcU~^ zw$bC3q>b2RNoewK~fGE|WyfW3J;xEM5?~ zPFx1!9iRrJhk546oN60CP%}k|;n!)tqo%RRX#f^Ma?yuORlOdZ;#;X7)~nq!25Dz1 z%Z1O7PgME+iFk4eWZiE{q?Br15rW7|g+t-ww!7J!vyPnQTV<1x$zqGYkC%MPCqNA zw@+4J>+@G0$2!ae$_j#$;hRoWJ%8#DEu@`SuWI%<+o(m?%$6%rm~C}9(|Z)Mf}m0< zsj5OyiCPFX3K`We26K3F-O%#+b8JBBsjY-21LaHOxV-oR$U#F0>&a6JDW%dIEUsJ?1u~l1OqkSP1UwYd!05B9wt`@`1+7F zf}FoY1B;392LgrUa$WXK=={;Mg4LkETyGAH!Q%<2s=Cmj{Uc#DSMWQiqJkPRp99)V zHH@~~&@IKMFB2sU8X`vpYDv_q7hmvBtUAJ+Si~Q!09#IkH`13Yh3t<8PzqA4VXOmy z?sstBy!y#%-7&(J#XxRl8e%aNutIc40C!-AAGes^H9L?3mt=nay;b|jQURf_MvEk#5uMt8kbhH6a1otacM$dA*WVK*SNmP~FT*{yxyJRbMB1jGd}+T}0mk zSp+je8w}{5XG-iDIbll_h=?#&^2uIQQ_~qx0dldr+$T}liw6Mt)B{Hp&E3H(XYqi& z@=n9=&6=j`%B93fyIbO=_)bCC1+}EU4k@oIr*3qOfaaG~3IT>7bBZ}tqR<@)to_pX zWzws=!o83Y%EjF@KjJpsU5KmHX5RQzv#8Y8ZgwQb%>@$W2AmAEDU@G&h@a_elMYUv zjS$eS|DQxVB>@s9!U`BDe91?cQTG*lZKR4`%Hyq^{_I7rvR#AGya1ujB4iGQ@9kj< z$!s25x0K4B9>SeZ?R0m?KEudeWvUj1Aa-bLAjbmgpt6SQKUz=Tj=~Q%{hGU45SI4R<^E23?%_V1!}wh!e#=;X&9 z#2}>6)K!qgaGGLx$zj}K4anR*H7QxGV}LAr9iGp=iK&0OB$GU=a z$oYk4!Bw`RLrP#9x+(+ant{fX(gRhTr5ZotEwf}h%d;o7Q< z*8eKV@)U83&zxT`?Ly~&Rl(f;gRois@<(aw{!eRC1@Nu_Ik%jfQ$&tQ_tsg(%BfFR{v z$L-)6MQxG41Ii6&22^Bc9!7=#n1EuGJi8=VSX0n@NYEY0o}m&B_c4R7njI~CPZXKA zkQKj3##tJ!E3Aj6MdHhRd(VDK7Im}A;H=nLLp5iDvLTA^SkcKW16@laXoVn=5E164u;i->~rWw>T7pRy^2@^+4C($4#ymwIINak zR-84VPG!*0UO?O3%5QPnmsg~e#Gb-Dn*sMZC*PTGB9Xx%f3v0F28nETRHH_qSD2>B z7re05E&}x1kBIBTJEjjq?U^L_g&keDkge;-q9>-J;uc^?Mq#4g}FKM~Ev2L#ckP z)W%>|J@6X|870@M{9bA>Ui~vj-mAixYI@q3Zd$J_DjilJK)&!%^6vD_+7TPX^RL=t zuV>r_M1muDOdB=~8)bSEJf;vP=$(MspwLzyg(7r8S)d)8hT?K*F1T_`A5FY{+Dd<-G-)XVEgI@{f^es zLP#q$FW}rKbz%x5o0@I3BX!l&1Xw6161S5Xp#uU#ThFb+DQZlBo|I9Dw4`jNUEeqIWGLSHSmsS@DBiO%s!M*^_32?WgC&QsT(S=XmPVXF6mLR9fr3`=C);Z*5SBspunIpLWa9sg?CDq!=4P zx$BXgaz;CG>R*2Z|2=CKawq}HXc2l*1?s=z`nZCTxsHUn7rS+4`5_dbu92vAnf=n9D?V4|(BYVAT?Vbnlp zxWR}tBXjrs5xGETCOv+Dq&zMKQ=+7^ot9BMD zY;yR*idBS+t++AX_84E~sewxoc_f4;Xz0o;e4%HOBk(PVf8r>#{>d^IJ-Ea%dXur< zW+SZ5w0|*V?w)?Q@$BVln?dTHm1O_8J+c}y z^uA@;^y63nCTfU6Lm_q`VaGF$-dHa>jlD6;7Z0co+cu z$9!q!Q#_?ZaCqNGrmm!FNl~Ylq+`oYRR6O*-&z8h*%J>x^}6#*D#aJwmxOsn*QA%+BB z!d%#2F!5ap5gHDR{B96f792V#DdwFVYB3purap&BIFnVjwao_h%ZEY=!X`I&OhohyOYjUjWy% zEh*r*JiirMk^w54YxQvX{JXRS>+KTQG`4EjToR+YihUNZ?U3&{)|l1TjVo%i>dmx|U<%f%u*Y1~7t*`|P6++#;q)47kbSsc#%q6aHkIdPGwCv%3Q>XvEU>^`rGvjII}8tVA~>z%Uim_M2H zx*Ww}xXdXXri57ZXPb5fIcGS6g;%QA{BgPUAva|28kYT3%B>MMl8LnBW|DS<_UO1*Ot;d`ouDWDzikZp|Ltggrbsw8Sj}j| zcs5vnKOd;u@W_hBxX++hB4OWCYW%CKFBwTlNL-DL4>`Ru10;tp-DvOAIBd7oI>B#v zowCipm_wyrr7mobqrP*VcOkvU(isar;!oQ?e97S%-b~-*f`>a>Gt{)D_CH36B7)>1 zD%`VWP#?=b_FYh;2%T@d^%Ct)xXDo$eOAXR7RC@~hZ_j03)eh$KzN^?o^)X0;Di?! z7wUp1ooX<97fp zkdLQ4PY$1*G2-DW`Pci^ZEaX>t`}qr-M-L>*A=y;Vc9m- z+p9bKZaOaBwo(hjt8?U5&eeO(X-Ffd%zuV{CW<#Ms`LBN3|3@wCV$UX%p3I<06ImS zxAH<)M|XwYRbB;RAjSF0Q3ksMICIQ=3HHpJm)EXvT8umD$U~u!g+)QbH;5(>;P0Y3 zrsqShb@a8fbu+UVs%~@tY@9=w;i!%6s$9Uj^4-DE#oIRBjzauha3|kNB|b1-Kp1U~ zEi#}h4pek)uLU}&_VOi=#~&%z4u>ZS#sMi3sz6AJFWFv>;duLRSkQ|Q6%7qWp^L$P zp+p7;f0^cAud9m-y!pkelu!7>bygPyYi1!wc$AxxocD8N1TB{Yc;v928 zQZxoP-EUjWbZ;Q|OsCW36Z5N}pa7fABahO`aER|R=4NJ*N#5n-#?&xxT$X}{k2!a2 z>Pr_ghf^b%14U%hqsG*mo5JQ?8md5!C`zV`N?b?OHXwSgcL~JoIbXma?3EtV8UJ~0ycE#}ooB68POb(yp3!<5Yg$c~n;LvL$+EATw3AxjU&e!c$r>pNO zWmt<9)XyfM*(6wo)crz!)rd*%-Bf@O){$8D!@prRDP2}Y9a zA8gv2tNIJx)g{KP2TYvsT~tsZk5(31R%CJ~A~fYSu};U*9FJo#Gbe}G?K3Eh&;2`koKs^lf8Eu@w?fi2_52%)Cl1k25rUHvZded!6PT%eU58go=eO*xC8;LNMv0R z1`(y8L({Z~ivWDkw9D>UCt@AZu9TjI}SZ zaOWgMkyUJ#HTeCb(Ev{L__%(LA`|f{&siUsuha_iZyA}c6fzK*r@N_4jz~Tv`~8t< z=mT}&Fv*O4vFT=3g}+!lP0a6*d~Uq19wuvl4Wadrh>>e(74Vxt=i!@M(zQ(4i1}s= zFP|d7;{&hlUu*`JMTJGUQOl+1%8vzthOD)cRwJETEtaTrzxP|s=0E}h0*0o!0s;e_ z|JHxZ#3v?B*wzqm+xBO%tPMKvea&v)j5$ckYK$W_*MiMCE8}*LfTLEdk_)mH$c7aF z5VW+4*1j5=0xm&`y#=o;wpRi|9yHau+()l`RlXob7+!*{i^D;YzWVPdDT8RV)~9(ltBx zb+@!1(1j$?QSYp(e)}vlWfDM{PQFlThVsoGeA>}x9m}H3MJi?iCZdq*PCPHn=Ov5! z5wtx9wdDAL&JOtx2iP>ik#}Q~qaiYQu7!!wX_@}(_8$P1*rW?xIi&2^-ofDx?J8Wi z*&=zY70t`rTZW0C!ucZr90t32Y$XyeAD9b86i~C&G;a!=bL$?HViVloQa`XEK2*ur#>Qr+-calg zwEtwT(6H{*hShw&Xa@QDc7N1TE{k){>|iXO$$1N&%k3slor8vk#nv95A5a?qjI(+W4 zTO_zb86Y>fO%m<6Q#O}B`;vy4iOC@GLATrE@XkMt#dKP5?`?W|dhT&cE|bYHWL1i& zceRD-#FkCNtz)3QpWyVhD|z>I#@}EHR)Kunf0LjvXEdDsJ2L_0x5N2` zewNNVKQ_!9l0n$hog<+ERQsgw?EL)vL8sH{0Q;PR|_$% z2W4Xi(ERKyWslo!!Rp^n0rJB8#t^gqd$=CGtd(!8=+X%tIu*+8p7M604j~+dk1opk zn4|(E#HpC7QJ7zXN$Lb53Ip9+-x6VAms_1`YJ!6P-HyF>djk+^>wvZqsFsni^%iR_ zEG(?@l{81w*v0(>bajI~*N0Rh%~Q#{f`%fnL=(TVxB-QjiYgK>6{Xqn(|1&R$AXBC zL5)j9m0XJXO7JPOS}1NZj>_8W_GKWgXM;Icj%g?OgjNfAo@?ofpa}zbES5wTHlY?-G-y^ zzb7qSZh_~PmYPv(Sj=V#S)_*qMMWVPu@Jsrk8_hV9Hu#3zp$V5lYlpu&Vab~LsWai zUe@;A>wK$YhUbSqJ>20_+!02+^#(pK6UI3}6pYlX`=1&~&M}h+{{9YY!<=qAXu(1f z8v2<-Dd>Q{^rrj2jf{-AP*YP&oEY$HE&d%G9L)QHWr>oKlBX%>o`yxzz|s53h6dl+ z(2Mxby}2m+8>4*Z;f3T#M|_4l%KtDK8tvXUyVrD7chNt{l{2~{SBtdkEpwl9RT5MP zq6m{Bc~?i$xc1$B6Ks3#;(o)KJgr;_m8Ad0UnQ#Gak>CVXfnm5?S#lE;MiD;jQ-E` zlz9yplmyeu$!SApKqnC3wGn`t%pvJtJnf$w=%-KsH(pRuONcus zwdZT!tdfPR$h>M5_}ZM?m@iK|s>KcTzg${39!)COZ?sB zV!e8gL>3W&}kDVzJ4a;#RYqX}x~S-%8M#sFI=psbfL}>4Hr=fLYuk z#XU&^pCD=>%zqKo9E}j; zSXZ;rWk^l0YZMd8E0qKr6tDbpDYFGEj?|>+oqOQ2B{y($M3#RIYC?ZC87ODK z2@v+%s|*HGrn4isHL_OCM#L$wEEU7Fx?eEDAlSiQrv~1x9GK``7jiEFK{Xy1-qw@6 zInsbkGcAepx>R1F_0z3mX~Vmfy|6|spWe@WbP=ESGI2cmK)2h78T4k&RWPFud)cEx zIV6?m2P^(vOd)?2)A4@Gc}pl+3_A3;Z70wvrKC);as8CP>b~j-NT6a z(s-FZ+;dp8wQKj3nqLFXoqW!S{|628&6zEin@gpPvqruLn;*8ew93KwgrzCtWGs{7 z<-*bK%Mu)`i*BHOmm96gvzo?RkCSG$=dHA6#~&K4HFtHn$U7;e zKmVgv(n+4~>x@*C_X+o3QNt>0+d=;FUWVUx;1N_VlYE*v_dK?S&IM_x)@&?<<=mO- z9hhsV{i8X?{x%1vWHPCmYr$VC7UF&0=OqT=fTxL0SC6~n8&ob_EtWW)#ii84gvj;F zKVER68f=`_e9bz)L-mgksz|6wf_+b-e5YAj1d=>$Ha4or^ChwA{!Ud z5g_Ulc=ka6E@}|iE0zn{-%yvAk^S8w!8#}+Jv96|0F>>p2>(_xXS5e~&b4JuT8`&m z`da^Yyuqg}Z}$qQ-$r_B{5;$FG!mDt1~#bsUxypHBIJWB8-(_`%e|k?s1{*4Z`&1_rj~=2*6-rmk&Fi1Dgw{)o8wPI|bR;wx66 zzn1=@JS(HZHuU+i=(znlVVw5jF0b*M2HT58IGTU{@GjDg=e&Q=2xBZydE^I9d1u!5 z8Gx&c_(tKIW|F^?2u7oF)!%A=XLP$KGmEYR&z=cs6Ugm*Y6@kv@Wh5e4wb3ug;F3b z?;N;4 zV88iNg>g$I0qw<4rN?Ehb3`ZMivSEw&m2Fvhy%i?nX=zywD zATQ<>ZMvp z=2b-887+c+4Yl2(1`<6FwUCdpmfJl=NE<~%oV{xPrlrP8fx^g+ntn;~5@y)!y?SRy z2Z#{Z)z#I_`*m-q_X`m1=BuubH^H>=?(iM^@bQziDpYjXt&@uj1SBLR;bK*pL`8|7%>&*$Kg&E7`h9mFcC~4G*a4ECOxwV3_|=77iyAPgMOFCJ-o0Q zv6G(b5sR5aPV$O}@4Yz|<8-K>hQ~uBqJ7g5C<)ormwKV-bHq2F3QPlo<;{2lVo@gE z?$eu#-CR3dP_=wO92<0o1H#yb+O za0f;eGFSOGTqM-&_3jXZmzQ@;TIc1wwwE{PSwLr%IZwYG`Cvmlb+8)@oE;c+*8Bcw zs_5e4VmB%Z7P90oyIX$^1dzo%GNqGDmI#p?su0R0z@Kqs#Xu8Z(2<+Qqk{<^C%}-E0*vXpsYm>6yx}IX=S4d+BVMo?|K}oQvrZr}V&HR;L#q*LCUxJ!9 z6U7;SETHJ>xGK++OagQo_YBsT)b)U0;#^)OXANtcUp25*?O=D!MWbzh3wwU+aBr>O zMLxW`LF{;AK%keZue)ivxJ0Sb$aRGPcHBB$f-Pr^K4oD`h%{`uM#pn{VghElN<$dE z&JJT+@2VL0X1)CZ&@No)~!= zeg&`y;Ou-mF_z%~Y8N|70ZV7)AcpZYZ$QMennjT~Z5y(GqlcjS?fn$0cE$fvx7?=? zhq~30chkY0v~>!f9LvV^tPs@ddQf4DuTV|fSp{NCKQYkzyp;DKD7I@tGdK*Ir9DUx zvZ=4h1-zgb4i}x}lS7qtJv(9FvniL9oNSDaiwi52N)JBi=mv9fX(_V|wv)ndT)CJx zSxVAsr2-jT3oge_?QcVX4noz9MR*o~tqUoVdh#Wbe^=eaTRN9B_BxSsGJ(j3J6)s< zp(#}kFK44w(%Y(|;^i9|qavM%CfC*9 zM2Z$4AD`W$BT+yASxw}iHEwF1!Ba|UjhHilXV;^p`oN<}^#dE|SIf|*xke45_|#pS zwLduc%YqvNl;98pLg`^Th_@KA+&dbq>32`HJ261z5t7~N5!Ebvi<}F|KP-qfh%)v_ zWXMCGZ{#+v?0FiIkg8?ndDK90U6W69Js6zIG<$QU#_duvI5ag;Wn6FDR;znV^hxn4 z*d*O6qxxt!<*54H7oa1OMEAi9J9TA2p*hsmZc{;)DY~k z3_9l|xun!Xbv-HTO#5qB@ILF*UWV3*iLOlu)y}c))z(U#ufuyh8-ts9ODZ}(I0O~j zGgq;%WCusb)EpjHdg~v3aLGsO521A-c;8+-M+J<{x;LC(kO9~5lgY{j4@r~UzLtMk zAj8+qMB?hZKHi6{pea~s;Eu#oj=B`B(mjQ-DPWBwxjR<$Dmxyh?$TSX!CB=tE#(2C zs>^jhB%n!QSGo5AoN~nVYIA+wP~rSQ@U4M- z${P-mdSN$vTowSVg*#$`X-l=zb3BRjP0}8dD=Z#+I{h|jgY*o;Fs59=}G)+Ho zGsK%LaB-}U^?}g}3#;xZNBTEp-R*h8t(Z~W`g{qTB*}7fXn;Ko{2)9>itR8Z^*9U4 zVn-n@jrlj67?XB7JV6gF1W&w0uP42O)3PTXd@*m~C-FugsKqUVuBbM0NAjx?yC zHE9`!TE6~P`hgM(S z4fao4DuQHz@~0+#-vUAQ?pq2Q*3d`GzD*b%_rt>dRY_bGO60wTVn233%tm4 zCgiEZ-{XHJ@}K=0jT^6bESOgX%XLZB<_oVsYCi13TUZ0d)Dh5s06B~MF1_qgFKtC& z%vJ+h=!Y9wy=d2+J`86iKeQf?T5JsmZMT)fM(^blXn46iB#784$@dw(v=l&9E7&gC ze^T-44*1E-1WI0vdV3B$Bpjq7Pn{U{-JKS8^L|rMz(q;I7_ZZTL-0k5V@F{(;HLpw z`o-IWv__Y@hCAd$)bV#kh=aPJY!Pe^!TNFZ?z(gVE0e<%skw@fz9bZFoVubJUH6|FsI*$+|Hx`zMASal*iNv_;G4I z3IyXEaDI2W3Yn0AHFQw7*zIzli6hKY{~TQbsQNR=IuyQH)lIoG>obHyq8IE{obhhu zyt_O2b8_8XfQP~L{2GZ-n+?MotR@+EJaeD`#TCuh@~1+nJW3CU$hNbzUz-#`aEA1^ z0T@AS1+`rjE*>Pqde+B6?`PrC8{`8ucTaBDto#FG2aW%!38?h*Yv04}#8;xYyPTJ` zA@rp$bXAt`dGu)Nv`Wi1VXc$5FC`o{P%#cfU*#_F4Z{F1&s*T9o3~jx6Kc=7EmVUJ z`pamUx9bJ;Fy5F>;Cc$ z@MvM~vooMXrjJ0Fyhc#4I9w3qF&qI`5CQ@F2mUv}-O8_T{%nscut93Tp!ux&P^b(G ze#bQLDc_#PxgD4oDq3#Z?Af#$fC(5g%6$RLWx+grU!SZpOV0SYybq?iL`q6$nO0&1 zIC8p8U7^E){c^NnFv_lYxu%0TUuxU4YQrDF0&e{Y6pKHKnS2Qp+<^#rx=9V>V1TintXA5C95!p=0G!ikc0c~7IDY7!m`F!cvwjE78w1& z-%VC+fID+4dH$Fbq`WAwaXz!x)Fbb;f;&o1qZCQ<`_p#wAuWnR`Z}mP}TFjK!uL)l_n{a~a20+31Cpukooo=Utxcl*p zR^!rjY1Z%^`z3>_6GT8h>KNy|uA^@g@=_3eEGxiqy=FkX4$OYNY1OP}E}-g46dBKp3t!BJM83vWg| z**)98(`T*c-^koe%|Uk-Ql{y9WWS#_oMcKRnTRS1;9@$R*ixj7-aG;k;TSj4hs~<0 zek@FLN}Rfm5tv6O5i?vW`IiXY(9=H?M}msf=!E+jPz_YZgedP&X*cisb`KJPatc1Y zwt#Jv4WFCE8UfyBF|NnZuHZKyn-04Ldqn9h8|V-d$3EvHurj+#$l}OHq8$YS=sFvJ zJe!$?P;EdJNQfitqM$>tHE-+$m74ryC!llT(3`AczE;>fK*#ZZ3pbEP4wuCF5#%bKYS+0eaACkhb|^~o^nfIn`+ zFt&CLnocGrXfkCSw`C7`5R>dTIhRV`Cnf{6FB=#`Kb?6A`p_mQGwESIvIhccRdZ;BH1YuSN3I`*vFd#Yq$ zzx~cyWw?GC;-tdF1x}}6_8Y#8 zVB5ckJqQbWD)SG+q6uTSi;X`}mKVkdDI362#9=jf8Z1$7cS(44eBITXNxRjHjKgYU zmZm!STM#xg%0O#PPWl;0JV6#zvDRIc!5^oy^DJ9y^5$efJ1m_1>9d#eiX9+lPdAaS zWS0GD_j~NJ-SX@^>=ebYWMMBg6kg8V>qU`gVRU}0;ms8opE(g#3uTBWp#LT8kW1H=5u`w3I~$+{V?+-9PG9HcsFMRTJ4( zm{3SUj0iya_#$>_aT`m`isAr6QN+*JdZ$@`ovoNNmTiY2c7E{5t%);_R8&g-sXsBB zlUKft^w8k~mQ7C?Et|IhSDX*=Syx5S!*Q{VH61+{GWjbF6zefq3F6GItP%Dgo14Gl z7Y0vGFCo}?z;+$3C=W)lLWxtlz?Y=8p8G4>vbZ=2jPePqu-JYwIlzegF5Jcl4ZA#c zOM80zYS)$d=>~`^6D&^3!~cfY2>72!K1Re?NS{{i=7jr_r0tHc{8qBccG)OXQOxW# zkL4;3ee`_D&d7uRPShI!8^rmwl;7@)!kTlmf+GuW0cVW1y3y zv|mkxcLLM>6TTIjK3&tI9w@U>9~Q8LA^2F$JDw48u5SQk)aCbI^B4y7!lhznRV6HV zGuI#z?Kv=Cj&C4cL=yJi=EsXZ6Hbg~cFg6Yc+N;`O-@eWI@b#c%Uw{p@!U4I$AwI*Ic0n{5}Qn2PA?|+F1Et4g_((z`qzJ1+kAF?+D~nc^;i4V^7Bc+%amu)IsnzCc{`C64cPqu}!`hWRFK+yiW4 z-g?B`Js=%eY*(SFm)~ZyjT#0fGN9e@IAm>OL(68XW65qJg)XGc+Q8Jb6-{t_ zlzRzE>4D=J{BrdA-YE?g0e!HrJ<)lfUFp%qLFR#^O-X^=1L=a@J-b3o@6mFc{2A_8 zGbZdP!SHfqq?q-2$Jpng7Ms=Xq!>)50MCondirngu#sUIArfXB53AtU{U2Xo5_?7nAdU#! zvd9gDtPDJkZG5BGRby;yjEjSl34(wStMd={d2bLB%hvX~h)6g@`Q+>jB%~+n=Yqww z?-oiiu?QnR5%<$0(L9WD2y^QgzNtMpR-P@jSiv*G87zKvwE9dK4?gJ z+mDunb#QPnMyt_4y8ZVr71%u?4+aK?=a1p5^CPrUfWD;1#UZkRL5W6VgQqzKmXwfN zZM0PPn_kU7f`gfQ*p)+{Mxit3j0r9F;+?NAP%RoU7d28MX739tr&4$*^#{6MZ~J=5 z{lw#T;D0NIjf8{6Gk={dfe@nRXMx}2LdR!9As*&AeMJI&K5f|E6^g}%_)gIRxFAt3LOxDjVtbA*?)}Yoy}40S2We&2MlO#;09D<|Sd~cHzujq^e@3k)cB@$eQa`aNROkuV*2w ztZ^FyPU)5D#Xbm=dlLv`=MLr2-nvytP%#K5Ewg-jgj1GDJM$B zGqe3_2Hfcsd0ub2y;0lKw4VMop1(8my~!Nz<9?*B8sl|SJ)DTHo8@U@H9vb!KI$ty zL*HF0bjI-RELgQ?umwA@FjD@tvMJo6ATL&390U|iRzCSo07a8WtBgZ2r_35PfDyVFhy}Vs&^*Pfw5i z+uIwU3yu9a75ZwU+q2dB`D%-Zl5z~Y)l(*iw_P6BR$Lebig#EmHe~_^_Ft_dcT!&x zHM5hdQk0!4q{tA8Gsc~W7WQ>GrFgpxIFw4TqRk0!rmr`b&E8x<K~|%Ox!kV|L)+OptGJHHU7Y5n5N22|i0LJu zyY^VSdMe4Zlu`TaVWnexF5?1G!}NCb%vP#kf}|e4VGDXRnMlzg%n9prIGN4Y{9ZaK zFNt?(AmEd$_P%EZRP5>!-7@UXI~;`X4a&wZi1iPFgfeIA9X>ThcH-%l{lW{sC+ z%?)9hb1cAVb}Z2G9gw3HAwCxIKn*AXI0+^>x@UXfmw(?2AikTHPu$#u?#91u+AQ~Y zSyl#lt;XvjAEYQ|)$dy@^&j?flXrfze-mz0ybW3|Ml=Px!(*6WKzyIhOnd8P_e?td zt4HeMb0sNi{X*-qT#N@sbT;+06ZYoq@9)2mRt@UQIN)(<5fM2l5VKq~6h0sG4%6=X!BlhSl*TZuw z=wxNcBdHn1zPa?7k4^xbZ*oH2*;xyvattB}Lr=1ah(CV0ln{DIdxYonRefcSPP@Zz zgTwLUzdwN{^CAg3z($=xU?yP3nLLDt{aCZnWQvrxr7Rxtv2-sAA+8x}^rkc>7Nxl{ z{z&BCGm7z-0uXzlVZXw^NO)sW%bL{nzQLq%Ga~rmVskn9&D~U ztU26Q-qF#KhUH4lXInsMYpW~oDJLnRLBix$7>~<^-P8Sjq^z9W3hAc=Y1LG&KrWoQ z@5gOuL3ue9MmOe0!A6I(C1Jp8T|A;Wj9?MP08^luEZMsBF~cLulIgPz+CbA6kHMQ? z_O4NvUe%J^-%t9W{HA!_v9k8egNb<)Tj%#B!6G#CRGoSRt=zrFrhIN-VBpO9U{%`L zEDmCCp`zlDcK8j+``cTIe-sNXEiG$C1g98(1mU2WDU3h%tE;N+en16jOFWD?*37{0{#snuHT#fFHw3~JYJDH>=ySNIO?_N3ejDiM{&xtM zz>wcs-WH>;kal6p%x_F(vsz~)>*dIKVi@Bd;P=nxoux6p#{JRQSQN7-3`F@7nM~EF zcQ&SYe#fg#E_PCzot~}G18LHJe*1qzq(NphLL(jx6R8XcB)QIS52yHl21w-8elekp zhl-08LWBSA$T4*PQynxAIyUDf05gv{MaQV%$z$fYTt~+QGCli*V_;QlZFR@@)tR9N zG8Yj3p3(KiJdiKrncEAd0DBLIyxvqt=?j&o8r@s5L{Rib5}7NH`g z$Pln0z-XPY&$JZahd;vl95~_zqumtCxY6EJi*a_t1o`_V5e*dbAX-^)pg`IV4>10< z1zj%HKgnU1NFhhQAF6m-9fvRQ^+JM)jol#ez|i}lNJHD%)>Z_E%Zcdmd-}`?Zt99-}{2h>C;?4xNk*GauxwA1TpzDOV4cr`#boV&j!XoI=Gqe;y@l z92WnrO*YE7Q-;Uq(X=v~FASXc$0diy{NMA%8VchreTVzKNj1-XC_G-n`9djzeu7SR zLIU&T))rsTrkXHA@%@yyJ=kyH-(AS7_4w3R%8ow#LizpXOmRi;Kb2K$qGZvWkn1k$@rZON1r^C1-L4cD^$|=AmM6 zf7xR;*yPvjwy=}bldr8cnsTMB3>j`K$s`#4us;#2?aZLyV7T=k=1fRIL1Ad+{raad z=TRglC#U`gjvW$cDC%cyhpo1&>b+haI36U;f3o_tS*+%YOi1l3?1m#zo$&DRJ?dgg j4#oeElkv6jZa literal 16260 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 From 2ef5b5321d1fec56e5f33fc91858df92d6fe083c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:06:23 -0700 Subject: [PATCH 25/43] Move Drydock logs to PHIDs and increased structure Summary: Ref T9252. Several general changes here: - Moves logs to use PHIDs instead of IDs. This generally improves flexibility (for example, it's a lot easier to render handles). - Adds `blueprintPHID` to logs. Although you can usually figure this out from the leasePHID or resourcePHID, it lets us query relevant logs on Blueprint views. - Instead of making logs a top-level object, make them strictly a sub-object of Blueprints, Resources and Leases. So you go Drydock > Lease > Logs, etc., to get to logs. - I might restore the "everything" view eventually, but it doesn't interact well with policies and I'm not sure it's very useful. A policy-violating `bin/drydock log` might be cleaner. - Policy-wise, we always show you that logs exist, we just don't show you log content if it's about something you can't see. This is similar to seeing restricted handles in other applications. - Instead of just having a message, give logs "type" + "data". This will let logs be more structured and translatable. This is similar to recent changes to Herald which seem to have worked well. Test Plan: Added some placeholder log writes, viewed those logs in the UI. {F855199} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14196 --- .../autopatches/20150930.drydock.log.1.sql | 25 ++++ .../PhabricatorDrydockApplication.php | 15 +- .../DrydockBlueprintImplementation.php | 40 ----- .../DrydockBlueprintViewController.php | 8 + .../drydock/controller/DrydockController.php | 27 ++++ .../controller/DrydockLeaseViewController.php | 25 +--- .../controller/DrydockLogController.php | 104 ++++++++++++- .../controller/DrydockLogListController.php | 48 +++++- .../DrydockResourceViewController.php | 26 +--- .../drydock/query/DrydockLogQuery.php | 127 ++++++++-------- .../drydock/query/DrydockLogSearchEngine.php | 137 ++++++++++-------- .../drydock/storage/DrydockLease.php | 21 +++ .../drydock/storage/DrydockLog.php | 71 ++++++--- .../drydock/view/DrydockLogListView.php | 66 +++++---- 14 files changed, 488 insertions(+), 252 deletions(-) create mode 100644 resources/sql/autopatches/20150930.drydock.log.1.sql diff --git a/resources/sql/autopatches/20150930.drydock.log.1.sql b/resources/sql/autopatches/20150930.drydock.log.1.sql new file mode 100644 index 0000000000..e84859b718 --- /dev/null +++ b/resources/sql/autopatches/20150930.drydock.log.1.sql @@ -0,0 +1,25 @@ +TRUNCATE {$NAMESPACE}_drydock.drydock_log; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP resourceID; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP leaseID; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP message; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD blueprintPHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD resourcePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD leasePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD type VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 5df54593ee..e662fea9e6 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -47,7 +47,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { return array( '/drydock/' => array( '' => 'DrydockConsoleController', - 'blueprint/' => array( + '(?Pblueprint)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockBlueprintViewController', @@ -55,29 +55,32 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { 'DrydockBlueprintDisableController', 'resources/(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), - 'resource/' => array( + '(?Presource)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockResourceViewController', 'release/' => 'DrydockResourceReleaseController', 'leases/(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), ), - 'lease/' => array( + '(?Please)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockLeaseViewController', 'release/' => 'DrydockLeaseReleaseController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), ), - 'log/' => array( - '(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', - ), ), ); } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index f58767c4fc..b7e39a49a4 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -250,46 +250,6 @@ abstract class DrydockBlueprintImplementation extends Phobject { /* -( Logging )------------------------------------------------------------ */ - /** - * @task log - */ - protected function logException(Exception $ex) { - $this->log($ex->getMessage()); - } - - - /** - * @task log - */ - protected function log($message) { - self::writeLog(null, null, $message); - } - - - /** - * @task log - */ - public static function writeLog( - DrydockResource $resource = null, - DrydockLease $lease = null, - $message = null) { - - $log = id(new DrydockLog()) - ->setEpoch(time()) - ->setMessage($message); - - if ($resource) { - $log->setResourceID($resource->getID()); - } - - if ($lease) { - $log->setLeaseID($lease->getID()); - } - - $log->save(); - } - - public static function getAllBlueprintImplementations() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 6991e18fa2..7102962600 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -56,11 +56,19 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { new DrydockBlueprintTransactionQuery()); $timeline->setShouldTerminate(true); + $log_query = id(new DrydockLogQuery()) + ->withBlueprintPHIDs(array($blueprint->getPHID())); + + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); + return $this->buildApplicationPage( array( $crumbs, $object_box, $resource_box, + $log_box, $timeline, ), array( diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index e0130bdf56..760334cbdf 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -85,4 +85,31 @@ abstract class DrydockController extends PhabricatorController { ->addRawContent($table); } + protected function buildLogBox(DrydockLogQuery $query, $all_uri) { + $viewer = $this->getViewer(); + + $logs = $query + ->setViewer($viewer) + ->setLimit(100) + ->execute(); + + $log_table = id(new DrydockLogListView()) + ->setUser($viewer) + ->setLogs($logs) + ->render(); + + $log_header = id(new PHUIHeaderView()) + ->setHeader(pht('Logs')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($all_uri) + ->setIconFont('fa-search') + ->setText(pht('View All Logs'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($log_header) + ->setTable($log_table); + } + } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index af893ca49b..b9cf592313 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -15,7 +15,8 @@ final class DrydockLeaseViewController extends DrydockLeaseController { return new Aphront404Response(); } - $lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/'); + $id = $lease->getID(); + $lease_uri = $this->getApplicationURI("lease/{$id}/"); $title = pht('Lease %d', $lease->getID()); @@ -29,20 +30,12 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $actions = $this->buildActionListView($lease); $properties = $this->buildPropertyListView($lease, $actions); - $pager = new PHUIPagerView(); - $pager->setURI(new PhutilURI($lease_uri), 'offset'); - $pager->setOffset($request->getInt('offset')); + $log_query = id(new DrydockLogQuery()) + ->withLeasePHIDs(array($lease->getPHID())); - $logs = id(new DrydockLogQuery()) - ->setViewer($viewer) - ->withLeaseIDs(array($lease->getID())) - ->executeWithOffsetPager($pager); - - $log_table = id(new DrydockLogListView()) - ->setUser($viewer) - ->setLogs($logs) - ->render(); - $log_table->appendChild($pager); + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); @@ -56,10 +49,6 @@ final class DrydockLeaseViewController extends DrydockLeaseController { ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $log_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Lease Logs')) - ->setTable($log_table); - return $this->buildApplicationPage( array( $crumbs, diff --git a/src/applications/drydock/controller/DrydockLogController.php b/src/applications/drydock/controller/DrydockLogController.php index 0e28c62cb4..5ae87e6aad 100644 --- a/src/applications/drydock/controller/DrydockLogController.php +++ b/src/applications/drydock/controller/DrydockLogController.php @@ -3,13 +3,60 @@ abstract class DrydockLogController extends DrydockController { + private $blueprint; + private $resource; + private $lease; + + public function setBlueprint(DrydockBlueprint $blueprint) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->blueprint; + } + + public function setResource(DrydockResource $resource) { + $this->resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + + public function setLease(DrydockLease $lease) { + $this->lease = $lease; + return $this; + } + + public function getLease() { + return $this->lease; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockLogSearchEngine()) - ->setViewer($this->getRequest()->getUser()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockLogSearchEngine()) + ->setViewer($this->getRequest()->getUser()); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $engine->setBlueprint($blueprint); + } + + $resource = $this->getResource(); + if ($resource) { + $engine->setResource($resource); + } + + $lease = $this->getLease(); + if ($lease) { + $engine->setLease($lease); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); @@ -18,9 +65,54 @@ abstract class DrydockLogController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Logs'), - $this->getApplicationURI('log/')); + + $blueprint = $this->getBlueprint(); + $resource = $this->getResource(); + $lease = $this->getLease(); + if ($blueprint) { + $id = $blueprint->getID(); + + $crumbs->addTextCrumb( + pht('Blueprints'), + $this->getApplicationURI('blueprint/')); + + $crumbs->addTextCrumb( + $blueprint->getBlueprintName(), + $this->getApplicationURI("blueprint/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("blueprint/{$id}/logs/")); + } else if ($resource) { + $id = $resource->getID(); + + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI('resource/')); + + $crumbs->addTextCrumb( + $resource->getName(), + $this->getApplicationURI("resource/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("resource/{$id}/logs/")); + } else if ($lease) { + $id = $lease->getID(); + + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI('lease/')); + + $crumbs->addTextCrumb( + $lease->getLeaseName(), + $this->getApplicationURI("lease/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("lease/{$id}/logs/")); + } + return $crumbs; } diff --git a/src/applications/drydock/controller/DrydockLogListController.php b/src/applications/drydock/controller/DrydockLogListController.php index aecf77dc77..b5e4d4ff0c 100644 --- a/src/applications/drydock/controller/DrydockLogListController.php +++ b/src/applications/drydock/controller/DrydockLogListController.php @@ -8,11 +8,53 @@ final class DrydockLogListController extends DrydockLogController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $querykey = $request->getURIData('queryKey'); + $engine = new DrydockLogSearchEngine(); + + $id = $request->getURIData('id'); + $type = $request->getURIData('type'); + switch ($type) { + case 'blueprint': + $blueprint = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$blueprint) { + return new Aphront404Response(); + } + $engine->setBlueprint($blueprint); + $this->setBlueprint($blueprint); + break; + case 'resource': + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$resource) { + return new Aphront404Response(); + } + $engine->setResource($resource); + $this->setResource($resource); + break; + case 'lease': + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$lease) { + return new Aphront404Response(); + } + $engine->setLease($lease); + $this->setLease($lease); + break; + default: + return new Aphront404Response(); + } + + $query_key = $request->getURIData('queryKey'); $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new DrydockLogSearchEngine()) + ->setQueryKey($query_key) + ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 23f81c5225..f97081e673 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -29,23 +29,15 @@ final class DrydockResourceViewController extends DrydockResourceController { $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource, $actions); - $resource_uri = 'resource/'.$resource->getID().'/'; - $resource_uri = $this->getApplicationURI($resource_uri); + $id = $resource->getID(); + $resource_uri = $this->getApplicationURI("resource/{$id}/"); - $pager = new PHUIPagerView(); - $pager->setURI(new PhutilURI($resource_uri), 'offset'); - $pager->setOffset($request->getInt('offset')); + $log_query = id(new DrydockLogQuery()) + ->withResourcePHIDs(array($resource->getPHID())); - $logs = id(new DrydockLogQuery()) - ->setViewer($viewer) - ->withResourceIDs(array($resource->getID())) - ->executeWithOffsetPager($pager); - - $log_table = id(new DrydockLogListView()) - ->setUser($viewer) - ->setLogs($logs) - ->render(); - $log_table->appendChild($pager); + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("resource/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); @@ -61,10 +53,6 @@ final class DrydockResourceViewController extends DrydockResourceController { $lease_box = $this->buildLeaseBox($resource); - $log_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Resource Logs')) - ->setTable($log_table); - return $this->buildApplicationPage( array( $crumbs, diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php index 47a6795463..00980edb4d 100644 --- a/src/applications/drydock/query/DrydockLogQuery.php +++ b/src/applications/drydock/query/DrydockLogQuery.php @@ -2,112 +2,125 @@ final class DrydockLogQuery extends DrydockQuery { - private $resourceIDs; - private $leaseIDs; + private $blueprintPHIDs; + private $resourcePHIDs; + private $leasePHIDs; - public function withResourceIDs(array $ids) { - $this->resourceIDs = $ids; + public function withBlueprintPHIDs(array $phids) { + $this->blueprintPHIDs = $phids; return $this; } - public function withLeaseIDs(array $ids) { - $this->leaseIDs = $ids; + public function withResourcePHIDs(array $phids) { + $this->resourcePHIDs = $phids; return $this; } + public function withLeasePHIDs(array $phids) { + $this->leasePHIDs = $phids; + return $this; + } + + public function newResultObject() { + return new DrydockLog(); + } + protected function loadPage() { - $table = new DrydockLog(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT log.* FROM %T log %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } - protected function willFilterPage(array $logs) { - $resource_ids = array_filter(mpull($logs, 'getResourceID')); - if ($resource_ids) { + protected function didFilterPage(array $logs) { + $blueprint_phids = array_filter(mpull($logs, 'getBlueprintPHID')); + if ($blueprint_phids) { + $blueprints = id(new DrydockBlueprintQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($blueprint_phids) + ->execute(); + $blueprints = mpull($blueprints, null, 'getPHID'); + } else { + $blueprints = array(); + } + + foreach ($logs as $key => $log) { + $blueprint = null; + $blueprint_phid = $log->getBlueprintPHID(); + if ($blueprint_phid) { + $blueprint = idx($blueprints, $blueprint_phid); + } + $log->attachBlueprint($blueprint); + } + + $resource_phids = array_filter(mpull($logs, 'getResourcePHID')); + if ($resource_phids) { $resources = id(new DrydockResourceQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) - ->withIDs(array_unique($resource_ids)) + ->withPHIDs($resource_phids) ->execute(); + $resources = mpull($resources, null, 'getPHID'); } else { $resources = array(); } foreach ($logs as $key => $log) { $resource = null; - if ($log->getResourceID()) { - $resource = idx($resources, $log->getResourceID()); - if (!$resource) { - unset($logs[$key]); - continue; - } + $resource_phid = $log->getResourcePHID(); + if ($resource_phid) { + $resource = idx($resources, $resource_phid); } $log->attachResource($resource); } - $lease_ids = array_filter(mpull($logs, 'getLeaseID')); - if ($lease_ids) { + $lease_phids = array_filter(mpull($logs, 'getLeasePHID')); + if ($lease_phids) { $leases = id(new DrydockLeaseQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) - ->withIDs(array_unique($lease_ids)) + ->withPHIDs($lease_phids) ->execute(); + $leases = mpull($leases, null, 'getPHID'); } else { $leases = array(); } foreach ($logs as $key => $log) { $lease = null; - if ($log->getLeaseID()) { - $lease = idx($leases, $log->getLeaseID()); - if (!$lease) { - unset($logs[$key]); - continue; - } + $lease_phid = $log->getLeasePHID(); + if ($lease_phid) { + $lease = idx($leases, $lease_phid); } $log->attachLease($lease); } - // These logs are meaningless and their policies aren't computable. They - // shouldn't exist, but throw them away if they do. - foreach ($logs as $key => $log) { - if (!$log->getResource() && !$log->getLease()) { - unset($logs[$key]); - } - } - return $logs; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->resourceIDs !== null) { + if ($this->blueprintPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'resourceID IN (%Ld)', - $this->resourceIDs); + $conn, + 'blueprintPHID IN (%Ls)', + $this->blueprintPHIDs); } - if ($this->leaseIDs !== null) { + if ($this->resourcePHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'leaseID IN (%Ld)', - $this->leaseIDs); + $conn, + 'resourcePHID IN (%Ls)', + $this->resourcePHIDs); } - $where[] = $this->buildPagingClause($conn_r); + if ($this->leasePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'leasePHID IN (%Ls)', + $this->leasePHIDs); + } - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/drydock/query/DrydockLogSearchEngine.php b/src/applications/drydock/query/DrydockLogSearchEngine.php index 13777031d6..43b1511c01 100644 --- a/src/applications/drydock/query/DrydockLogSearchEngine.php +++ b/src/applications/drydock/query/DrydockLogSearchEngine.php @@ -2,6 +2,43 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { + private $blueprint; + private $resource; + private $lease; + + public function setBlueprint(DrydockBlueprint $blueprint) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->blueprint; + } + + public function setResource(DrydockResource $resource) { + $this->resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + + public function setLease(DrydockLease $lease) { + $this->lease = $lease; + return $this; + } + + public function getLease() { + return $this->lease; + } + + public function canUseInPanelContext() { + // Prevent use on Dashboard panels since all log queries currently need a + // parent object and these don't seem particularly useful in any case. + return false; + } + public function getResultTypeDescription() { return pht('Drydock Logs'); } @@ -10,75 +47,59 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { return 'PhabricatorDrydockApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $query = new PhabricatorSavedQuery(); - - $query->setParameter( - 'resourcePHIDs', - $this->readListFromRequest($request, 'resources')); - $query->setParameter( - 'leasePHIDs', - $this->readListFromRequest($request, 'leases')); - - return $query; - } - - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $resource_phids = $saved->getParameter('resourcePHIDs', array()); - $lease_phids = $saved->getParameter('leasePHIDs', array()); - - // TODO: Change logs to use PHIDs instead of IDs. - $resource_ids = array(); - $lease_ids = array(); - - if ($resource_phids) { - $resource_ids = id(new DrydockResourceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($resource_phids) - ->execute(); - $resource_ids = mpull($resource_ids, 'getID'); - } - - if ($lease_phids) { - $lease_ids = id(new DrydockLeaseQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($lease_phids) - ->execute(); - $lease_ids = mpull($lease_ids, 'getID'); - } - + public function newQuery() { $query = new DrydockLogQuery(); - if ($resource_ids) { - $query->withResourceIDs($resource_ids); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $query->withBlueprintPHIDs(array($blueprint->getPHID())); } - if ($lease_ids) { - $query->withLeaseIDs($lease_ids); + + $resource = $this->getResource(); + if ($resource) { + $query->withResourcePHIDs(array($resource->getPHID())); + } + + $lease = $this->getLease(); + if ($lease) { + $query->withLeasePHIDs(array($lease->getPHID())); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new DrydockResourceDatasource()) - ->setName('resources') - ->setLabel(pht('Resources')) - ->setValue($saved->getParameter('resourcePHIDs', array()))) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new DrydockLeaseDatasource()) - ->setName('leases') - ->setLabel(pht('Leases')) - ->setValue($saved->getParameter('leasePHIDs', array()))); + return $query; + } + + protected function buildCustomSearchFields() { + return array(); } protected function getURI($path) { - return '/drydock/log/'.$path; + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $id = $blueprint->getID(); + return "/drydock/blueprint/{$id}/logs/{$path}"; + } + + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + return "/drydock/resource/{$id}/logs/{$path}"; + } + + $lease = $this->getLease(); + if ($lease) { + $id = $lease->getID(); + return "/drydock/lease/{$id}/logs/{$path}"; + } + + throw new Exception( + pht( + 'Search engine has no blueprint, resource, or lease.')); } protected function getBuiltinQueryNames() { diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index af0b322b62..b16efbabbb 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -347,6 +347,9 @@ final class DrydockLease extends DrydockDAO $viewer = PhabricatorUser::getOmnipotentUser(); $need_update = false; + // TODO: This is just a placeholder to get some data in the table. + $this->logEvent('activated'); + $commands = id(new DrydockCommandQuery()) ->setViewer($viewer) ->withTargetPHIDs(array($this->getPHID())) @@ -371,6 +374,24 @@ final class DrydockLease extends DrydockDAO } } + public function logEvent($type, array $data = array()) { + $log = id(new DrydockLog()) + ->setEpoch(PhabricatorTime::getNow()) + ->setType($type) + ->setData($data); + + $log->setLeasePHID($this->getPHID()); + + $resource = $this->getResource(); + if ($resource) { + $log->setResourcePHID($resource->getPHID()); + $log->setBlueprintPHID($resource->getBlueprintPHID()); + } + + return $log->save(); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockLog.php b/src/applications/drydock/storage/DrydockLog.php index 36d310c510..5d75d82c65 100644 --- a/src/applications/drydock/storage/DrydockLog.php +++ b/src/applications/drydock/storage/DrydockLog.php @@ -3,28 +3,38 @@ final class DrydockLog extends DrydockDAO implements PhabricatorPolicyInterface { - protected $resourceID; - protected $leaseID; + protected $blueprintPHID; + protected $resourcePHID; + protected $leasePHID; protected $epoch; - protected $message; + protected $type; + protected $data = array(); + private $blueprint = self::ATTACHABLE; private $resource = self::ATTACHABLE; private $lease = self::ATTACHABLE; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'data' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( - 'resourceID' => 'id?', - 'leaseID' => 'id?', - 'message' => 'text', + 'blueprintPHID' => 'phid?', + 'resourcePHID' => 'phid?', + 'leasePHID' => 'phid?', + 'type' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( - 'resourceID' => array( - 'columns' => array('resourceID', 'epoch'), + 'key_blueprint' => array( + 'columns' => array('blueprintPHID', 'type'), ), - 'leaseID' => array( - 'columns' => array('leaseID', 'epoch'), + 'key_resource' => array( + 'columns' => array('resourcePHID', 'type'), + ), + 'key_lease' => array( + 'columns' => array('leasePHID', 'type'), ), 'epoch' => array( 'columns' => array('epoch'), @@ -33,6 +43,15 @@ final class DrydockLog extends DrydockDAO ) + parent::getConfiguration(); } + public function attachBlueprint(DrydockBlueprint $blueprint = null) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->assertAttached($this->blueprint); + } + public function attachResource(DrydockResource $resource = null) { $this->resource = $resource; return $this; @@ -51,6 +70,22 @@ final class DrydockLog extends DrydockDAO return $this->assertAttached($this->lease); } + public function isComplete() { + if ($this->getBlueprintPHID() && !$this->getBlueprint()) { + return false; + } + + if ($this->getResourcePHID() && !$this->getResource()) { + return false; + } + + if ($this->getLeasePHID() && !$this->getLease()) { + return false; + } + + return true; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -62,21 +97,19 @@ final class DrydockLog extends DrydockDAO } public function getPolicy($capability) { - if ($this->getResource()) { - return $this->getResource()->getPolicy($capability); - } - return $this->getLease()->getPolicy($capability); + // NOTE: We let you see that logs exist no matter what, but don't actually + // show you log content unless you can see all of the associated objects. + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($this->getResource()) { - return $this->getResource()->hasAutomaticCapability($capability, $viewer); - } - return $this->getLease()->hasAutomaticCapability($capability, $viewer); + return false; } public function describeAutomaticCapability($capability) { - return pht('Logs inherit the policy of their resources.'); + return pht( + 'To view log details, you must be able to view the associated '. + 'blueprint, resource and lease.'); } } diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index 22e280939b..f6560270a0 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -18,28 +18,45 @@ final class DrydockLogListView extends AphrontView { $rows = array(); foreach ($logs as $log) { - $resource_uri = '/drydock/resource/'.$log->getResourceID().'/'; - $lease_uri = '/drydock/lease/'.$log->getLeaseID().'/'; + $blueprint_phid = $log->getBlueprintPHID(); + if ($blueprint_phid) { + $blueprint = $viewer->renderHandle($blueprint_phid); + } else { + $blueprint = null; + } - $resource_name = $log->getResourceID(); - if ($log->getResourceID() !== null) { - $resource_name = $log->getResource()->getName(); + $resource_phid = $log->getResourcePHID(); + if ($resource_phid) { + $resource = $viewer->renderHandle($resource_phid); + } else { + $resource = null; + } + + $lease_phid = $log->getLeasePHID(); + if ($lease_phid) { + $lease = $viewer->renderHandle($lease_phid); + } else { + $lease = null; + } + + if ($log->isComplete()) { + // TODO: This is a placeholder. + $type = $log->getType(); + $data = print_r($log->getData(), true); + } else { + $type = phutil_tag('em', array(), pht('Restricted')); + $data = phutil_tag( + 'em', + array(), + pht('You do not have permission to view this log event.')); } $rows[] = array( - phutil_tag( - 'a', - array( - 'href' => $resource_uri, - ), - $resource_name), - phutil_tag( - 'a', - array( - 'href' => $lease_uri, - ), - $log->getLeaseID()), - $log->getMessage(), + $blueprint, + $resource, + $lease, + $type, + $data, phabricator_datetime($log->getEpoch(), $viewer), ); } @@ -48,20 +65,17 @@ final class DrydockLogListView extends AphrontView { $table->setDeviceReadyTable(true); $table->setHeaders( array( + pht('Blueprint'), pht('Resource'), pht('Lease'), - pht('Message'), + pht('Type'), + pht('Data'), pht('Date'), )); - $table->setShortHeaders( - array( - pht('R'), - pht('L'), - pht('Message'), - '', - )); $table->setColumnClasses( array( + '', + '', '', '', 'wide', From 06f92725029070efe3518c9f2f193fec3576ec98 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:09:27 -0700 Subject: [PATCH 26/43] Garbage collect Drydock logs after 30 days Summary: Ref T9252. Drydock logs are almost exclusively useful as a diagnostic tool for debugging immediate problems, so GC them fairly aggressively. (I expect 99% of the usefulness of these logs to be within the first 24 hours, basically "why isn't my thing working". I can't really think of any cases where having old logs would be useful.) Test Plan: - Ran GC, saw it hit the log table (with no effect). - Changed TTL from 30 days to 30 seconds, ran GC, saw it wipe recent logs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14197 --- src/__phutil_library_map__.php | 2 ++ .../DrydockLogGarbageCollector.php | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 064d80740a..804147e248 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -845,6 +845,7 @@ phutil_register_library_map(array( 'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', + 'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php', 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', 'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', @@ -4586,6 +4587,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'DrydockLogController' => 'DrydockController', + 'DrydockLogGarbageCollector' => 'PhabricatorGarbageCollector', 'DrydockLogListController' => 'DrydockLogController', 'DrydockLogListView' => 'AphrontView', 'DrydockLogQuery' => 'DrydockQuery', diff --git a/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php new file mode 100644 index 0000000000..6cae627a74 --- /dev/null +++ b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php @@ -0,0 +1,22 @@ +establishConnection('w'); + + $now = PhabricatorTime::getNow(); + $ttl = phutil_units('30 days in seconds'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE epoch <= %d LIMIT 100', + $log_table->getTableName(), + $now - $ttl); + + return ($conn_w->getAffectedRows() == 100); + } + +} From 8bf59050247df5afd82d8a048b37d2e1c8766de6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:10:07 -0700 Subject: [PATCH 27/43] Add Drydock log types and more logging Summary: Ref T9252. Make log types modular so they can be translated and have complicated rendering logic if necessary (currently, none have this). Test Plan: {F855330} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14198 --- src/__phutil_library_map__.php | 12 ++++ .../logtype/DrydockLeaseAcquiredLogType.php | 19 +++++ .../logtype/DrydockLeaseActivatedLogType.php | 19 +++++ .../logtype/DrydockLeaseDestroyedLogType.php | 19 +++++ .../logtype/DrydockLeaseQueuedLogType.php | 19 +++++ .../logtype/DrydockLeaseReleasedLogType.php | 19 +++++ .../drydock/logtype/DrydockLogType.php | 69 +++++++++++++++++++ .../drydock/storage/DrydockLease.php | 14 ++-- .../drydock/view/DrydockLogListView.php | 65 ++++++++++------- .../worker/DrydockLeaseDestroyWorker.php | 2 + .../worker/DrydockLeaseUpdateWorker.php | 2 + 11 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 src/applications/drydock/logtype/DrydockLeaseAcquiredLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseActivatedLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseDestroyedLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseQueuedLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseReleasedLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLogType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 804147e248..f07f29299e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -830,14 +830,19 @@ phutil_register_library_map(array( 'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php', 'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php', 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', + 'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php', + 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', 'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php', + 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', + 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', + 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', @@ -850,6 +855,7 @@ phutil_register_library_map(array( 'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', + 'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php', 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php', @@ -4569,14 +4575,19 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockLeaseAcquiredLogType' => 'DrydockLogType', + 'DrydockLeaseActivatedLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockLeaseDestroyWorker' => 'DrydockWorker', + 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', + 'DrydockLeaseQueuedLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', + 'DrydockLeaseReleasedLogType' => 'DrydockLogType', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', @@ -4592,6 +4603,7 @@ phutil_register_library_map(array( 'DrydockLogListView' => 'AphrontView', 'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'DrydockLogType' => 'Phobject', 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow', diff --git a/src/applications/drydock/logtype/DrydockLeaseAcquiredLogType.php b/src/applications/drydock/logtype/DrydockLeaseAcquiredLogType.php new file mode 100644 index 0000000000..c9eef61922 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseAcquiredLogType.php @@ -0,0 +1,19 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + final public function setLog(DrydockLog $log) { + $this->log = $log; + return $this; + } + + final public function getLog() { + return $this->log; + } + + final public function getLogTypeConstant() { + $class = new ReflectionClass($this); + + $const = $class->getConstant('LOGCONST'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'LOGCONST')); + } + + $limit = self::getLogTypeConstantByteLimit(); + if (!is_string($const) || (strlen($const) > $limit)) { + throw new Exception( + pht( + '"%s" class "%s" has an invalid "%s" property. Field constants '. + 'must be strings and no more than %s bytes in length.', + __CLASS__, + get_class($this), + 'LOGCONST', + new PhutilNumber($limit))); + } + + return $const; + } + + final private static function getLogTypeConstantByteLimit() { + return 64; + } + + final public static function getAllLogTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getLogTypeConstant') + ->execute(); + } + +} diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index b16efbabbb..1dc9f0e180 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -144,6 +144,8 @@ final class DrydockLease extends DrydockDAO 'objectPHID' => $this->getPHID(), )); + $this->logEvent(DrydockLeaseQueuedLogType::LOGCONST); + return $this; } @@ -240,6 +242,7 @@ final class DrydockLease extends DrydockDAO $this ->setResourcePHID($resource->getPHID()) + ->attachResource($resource) ->setStatus($new_status) ->save(); @@ -250,6 +253,8 @@ final class DrydockLease extends DrydockDAO $this->isAcquired = true; + $this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST); + if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { $this->didActivate(); } @@ -347,8 +352,7 @@ final class DrydockLease extends DrydockDAO $viewer = PhabricatorUser::getOmnipotentUser(); $need_update = false; - // TODO: This is just a placeholder to get some data in the table. - $this->logEvent('activated'); + $this->logEvent(DrydockLeaseActivatedLogType::LOGCONST); $commands = id(new DrydockCommandQuery()) ->setViewer($viewer) @@ -382,8 +386,10 @@ final class DrydockLease extends DrydockDAO $log->setLeasePHID($this->getPHID()); - $resource = $this->getResource(); - if ($resource) { + $resource_phid = $this->getResourcePHID(); + if ($resource_phid) { + $resource = $this->getResource(); + $log->setResourcePHID($resource->getPHID()); $log->setBlueprintPHID($resource->getBlueprintPHID()); } diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index f6560270a0..4e1fe664cd 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -16,6 +16,8 @@ final class DrydockLogListView extends AphrontView { $view = new PHUIObjectItemListView(); + $types = DrydockLogType::getAllLogTypes(); + $rows = array(); foreach ($logs as $log) { $blueprint_phid = $log->getBlueprintPHID(); @@ -40,47 +42,64 @@ final class DrydockLogListView extends AphrontView { } if ($log->isComplete()) { - // TODO: This is a placeholder. - $type = $log->getType(); - $data = print_r($log->getData(), true); + $type_key = $log->getType(); + if (isset($types[$type_key])) { + $type_object = id(clone $types[$type_key]) + ->setLog($log) + ->setViewer($viewer); + + $log_data = $log->getData(); + + $type = $type_object->getLogTypeName(); + $icon = $type_object->getLogTypeIcon($log_data); + $data = $type_object->renderLog($log_data); + } else { + $type = pht('', $type_key); + $data = null; + $icon = 'fa-question-circle red'; + } } else { $type = phutil_tag('em', array(), pht('Restricted')); $data = phutil_tag( 'em', array(), pht('You do not have permission to view this log event.')); + $icon = 'fa-lock grey'; } $rows[] = array( $blueprint, $resource, $lease, + id(new PHUIIconView())->setIconFont($icon), $type, $data, phabricator_datetime($log->getEpoch(), $viewer), ); } - $table = new AphrontTableView($rows); - $table->setDeviceReadyTable(true); - $table->setHeaders( - array( - pht('Blueprint'), - pht('Resource'), - pht('Lease'), - pht('Type'), - pht('Data'), - pht('Date'), - )); - $table->setColumnClasses( - array( - '', - '', - '', - '', - 'wide', - '', - )); + $table = id(new AphrontTableView($rows)) + ->setDeviceReadyTable(true) + ->setHeaders( + array( + pht('Blueprint'), + pht('Resource'), + pht('Lease'), + null, + pht('Type'), + pht('Data'), + pht('Date'), + )) + ->setColumnClasses( + array( + '', + '', + '', + 'icon', + '', + 'wide', + '', + )); return $table; } diff --git a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php index e0b15095c7..12b9aa5a35 100644 --- a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php @@ -32,6 +32,8 @@ final class DrydockLeaseDestroyWorker extends DrydockWorker { $lease ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED) ->save(); + + $lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST); } } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 8f15201a2c..2003372b76 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -74,6 +74,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { 'objectPHID' => $lease->getPHID(), )); + $lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST); + $resource = $lease->getResource(); $blueprint = $resource->getBlueprint(); From 91e5ca0ee28ca0e0424608bcf20f4cae3a420bb2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:10:40 -0700 Subject: [PATCH 28/43] Merge the DrydockResource workers into a single worker Summary: Ref T9252. Currently, Drydock Leases and Resources have several workers: - Resources: ResourceWorker, ResourceUpdateWorker, ResourceDestroyWorker - Leases: AllocatorWorker, LeaseWorker, LeaseUpdateWorker, LeaseDestroyWorker This is kind of a lot of stuff, and it creates some problems. In particular, leases and resources in early lifecycle phases (pending/allocating/acquiring) can't process commands yet, because that code is only in the "UpdateWorker" classes. If they aren't able to move forward because of a bug, they also can't be released because they can't react to the release command until later in their lifecycle. This creates a soft hang where I have to go wipe stuff out of the database since there's no other way to get rid of it. Instead, I want leases and resources to be releasable from any (pre-release / pre-destroy) phase of their lifecycle. To support this, all the workers before the "UpdateWorker" need to be able to process commands. A second, similar issue is that logging and exception handling behaviors are underpowered right now. Elsewhere I began improving this, but ran into issues where all of the workers needed to share very similar exception code. Merging them will make this future change simpler. This diff fixes this for resources: it merges the Worker, UpdateWorker and DestroyWorker logic into UpdateWorker and throws away the other two workers. Test Plan: Nothing substantive yet, see next diff. I'll do the same thing for Leases, then test both more thoroughly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14201 --- src/__phutil_library_map__.php | 4 - .../drydock/storage/DrydockResource.php | 10 +- .../drydock/worker/DrydockAllocatorWorker.php | 2 +- .../worker/DrydockResourceDestroyWorker.php | 35 ------ .../worker/DrydockResourceUpdateWorker.php | 118 +++++++++++++++--- .../drydock/worker/DrydockResourceWorker.php | 45 ------- .../drydock/worker/DrydockWorker.php | 2 +- 7 files changed, 107 insertions(+), 109 deletions(-) delete mode 100644 src/applications/drydock/worker/DrydockResourceDestroyWorker.php delete mode 100644 src/applications/drydock/worker/DrydockResourceWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f07f29299e..d4bcc2b6a0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -867,7 +867,6 @@ phutil_register_library_map(array( 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', - 'DrydockResourceDestroyWorker' => 'applications/drydock/worker/DrydockResourceDestroyWorker.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', @@ -877,7 +876,6 @@ phutil_register_library_map(array( 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php', 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', - 'DrydockResourceWorker' => 'applications/drydock/worker/DrydockResourceWorker.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', @@ -4618,7 +4616,6 @@ phutil_register_library_map(array( ), 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', - 'DrydockResourceDestroyWorker' => 'DrydockWorker', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', @@ -4628,7 +4625,6 @@ phutil_register_library_map(array( 'DrydockResourceStatus' => 'DrydockConstants', 'DrydockResourceUpdateWorker' => 'DrydockWorker', 'DrydockResourceViewController' => 'DrydockResourceController', - 'DrydockResourceWorker' => 'DrydockWorker', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 'DrydockSlotLock' => 'DrydockDAO', diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index ab2230ce32..2070cf8b33 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -246,12 +246,14 @@ final class DrydockResource extends DrydockDAO } } - public function canUpdate() { + public function canReceiveCommands() { switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_ACTIVE: - return true; - default: + case DrydockResourceStatus::STATUS_RELEASED: + case DrydockResourceStatus::STATUS_BROKEN: + case DrydockResourceStatus::STATUS_DESTROYED: return false; + default: + return true; } } diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index a67f242c01..08a702b589 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -333,7 +333,7 @@ final class DrydockAllocatorWorker extends DrydockWorker { // activate it. if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { PhabricatorWorker::scheduleTask( - 'DrydockResourceWorker', + 'DrydockResourceUpdateWorker', array( 'resourcePHID' => $resource->getPHID(), ), diff --git a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php deleted file mode 100644 index af00ebeb2a..0000000000 --- a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php +++ /dev/null @@ -1,35 +0,0 @@ -getTaskDataValue('resourcePHID'); - $resource = $this->loadResource($resource_phid); - $this->destroyResource($resource); - } - - private function destroyResource(DrydockResource $resource) { - $status = $resource->getStatus(); - - switch ($status) { - case DrydockResourceStatus::STATUS_RELEASED: - case DrydockResourceStatus::STATUS_BROKEN: - break; - default: - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Unable to destroy resource ("%s"), resource has the wrong '. - 'status ("%s").', - $resource->getPHID(), - $status)); - } - - $blueprint = $resource->getBlueprint(); - $blueprint->destroyResource($resource); - - $resource - ->setStatus(DrydockResourceStatus::STATUS_DESTROYED) - ->save(); - } - -} diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 681741b6f1..9027829b9c 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -1,5 +1,11 @@ canUpdate()) { + $this->processResourceCommands($resource); + + $resource_status = $resource->getStatus(); + switch ($resource_status) { + case DrydockResourceStatus::STATUS_PENDING: + $this->activateResource($resource); + break; + case DrydockResourceStatus::STATUS_ACTIVE: + // Nothing to do. + break; + case DrydockResourceStatus::STATUS_RELEASED: + case DrydockResourceStatus::STATUS_BROKEN: + $this->destroyResource($resource); + break; + case DrydockResourceStatus::STATUS_DESTROYED: + // Nothing to do. + break; + } + + $this->yieldIfExpiringResource($resource); + } + + +/* -( Processing Commands )------------------------------------------------ */ + + + /** + * @task command + */ + private function processResourceCommands(DrydockResource $resource) { + if (!$resource->canReceiveCommands()) { return; } @@ -31,21 +67,23 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $commands = $this->loadCommands($resource->getPHID()); foreach ($commands as $command) { - if (!$resource->canUpdate()) { + if (!$resource->canReceiveCommands()) { break; } - $this->processCommand($resource, $command); + $this->processResourceCommand($resource, $command); $command ->setIsConsumed(true) ->save(); } - - $this->yieldIfExpiringResource($resource); } - private function processCommand( + + /** + * @task command + */ + private function processResourceCommand( DrydockResource $resource, DrydockCommand $command) { @@ -56,13 +94,47 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { } } - private function releaseResource(DrydockResource $resource) { - if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) { - // If we had multiple release commands - // This command is only meaningful to resources in the "Open" state. - return; + +/* -( Activating Resources )----------------------------------------------- */ + + + /** + * @task activate + */ + private function activateResource(DrydockResource $resource) { + $blueprint = $resource->getBlueprint(); + $blueprint->activateResource($resource); + $this->validateActivatedResource($blueprint, $resource); + } + + + /** + * @task activate + */ + private function validateActivatedResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + + if (!$resource->isActivatedResource()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. + 'must actually allocate the resource it returns.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()')); } + } + + +/* -( Releasing Resources )------------------------------------------------ */ + + + /** + * @task release + */ + private function releaseResource(DrydockResource $resource) { $viewer = $this->getViewer(); $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); @@ -97,14 +169,22 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $lease->scheduleUpdate(); } - PhabricatorWorker::scheduleTask( - 'DrydockResourceDestroyWorker', - array( - 'resourcePHID' => $resource->getPHID(), - ), - array( - 'objectPHID' => $resource->getPHID(), - )); + $this->destroyResource($resource); } + +/* -( Destroying Resources )----------------------------------------------- */ + + + /** + * @task destroy + */ + private function destroyResource(DrydockResource $resource) { + $blueprint = $resource->getBlueprint(); + $blueprint->destroyResource($resource); + + $resource + ->setStatus(DrydockResourceStatus::STATUS_DESTROYED) + ->save(); + } } diff --git a/src/applications/drydock/worker/DrydockResourceWorker.php b/src/applications/drydock/worker/DrydockResourceWorker.php deleted file mode 100644 index 45d63029f7..0000000000 --- a/src/applications/drydock/worker/DrydockResourceWorker.php +++ /dev/null @@ -1,45 +0,0 @@ -getTaskDataValue('resourcePHID'); - $resource = $this->loadResource($resource_phid); - - $this->activateResource($resource); - } - - - private function activateResource(DrydockResource $resource) { - $resource_status = $resource->getStatus(); - - if ($resource_status != DrydockResourceStatus::STATUS_PENDING) { - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Trying to activate resource from wrong status ("%s").', - $resource_status)); - } - - $blueprint = $resource->getBlueprint(); - $blueprint->activateResource($resource); - $this->validateActivatedResource($blueprint, $resource); - } - - - private function validateActivatedResource( - DrydockBlueprint $blueprint, - DrydockResource $resource) { - - if (!$resource->isActivatedResource()) { - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. - 'must actually allocate the resource it returns.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'allocateResource()')); - } - - } - -} diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index e64cfdafd7..a7273b2f9d 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -94,7 +94,7 @@ abstract class DrydockWorker extends PhabricatorWorker { } protected function yieldIfExpiringResource(DrydockResource $resource) { - if (!$resource->canUpdate()) { + if (!$resource->canReceiveCommands()) { return; } From 4ac82be5ed22a0d76f6363c648799f569e8ad1c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:11:02 -0700 Subject: [PATCH 29/43] Merge the DrydockLease workers into a single worker Summary: Ref T9252. This is the same as D14201, but for lease stuff instead of resource stuff. This one is a little heavier but still feels pretty reasonable to me at the end of the day (worker is <1K lines and has a ton of comment stuff). Also fixes a few random bugs I hit in the task queue. Test Plan: - Restarted some Harbormaster builds, saw them go through cleanly. - Released pre-activation resources/leases. - Probably still kinda buggy but I'll iron the details out over time. Logs are starting to look somewhat plausible: {F855747} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14202 --- src/__phutil_library_map__.php | 6 - .../drydock/storage/DrydockLease.php | 18 +- .../drydock/worker/DrydockAllocatorWorker.php | 479 -------------- .../worker/DrydockLeaseDestroyWorker.php | 39 -- .../worker/DrydockLeaseUpdateWorker.php | 617 +++++++++++++++++- .../drydock/worker/DrydockLeaseWorker.php | 74 --- .../drydock/worker/DrydockWorker.php | 2 +- .../storage/build/HarbormasterBuildLog.php | 3 +- .../workers/storage/PhabricatorWorkerTask.php | 5 +- 9 files changed, 615 insertions(+), 628 deletions(-) delete mode 100644 src/applications/drydock/worker/DrydockAllocatorWorker.php delete mode 100644 src/applications/drydock/worker/DrydockLeaseDestroyWorker.php delete mode 100644 src/applications/drydock/worker/DrydockLeaseWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d4bcc2b6a0..d00b105260 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -796,7 +796,6 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', - 'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', @@ -834,7 +833,6 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', - 'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php', 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', @@ -847,7 +845,6 @@ phutil_register_library_map(array( 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', - 'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php', @@ -4525,7 +4522,6 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', - 'DrydockAllocatorWorker' => 'DrydockWorker', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockBlueprint' => array( @@ -4577,7 +4573,6 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', - 'DrydockLeaseDestroyWorker' => 'DrydockWorker', 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', @@ -4590,7 +4585,6 @@ phutil_register_library_map(array( 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', - 'DrydockLeaseWorker' => 'DrydockWorker', 'DrydockLog' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 1dc9f0e180..c5b8b827c0 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -135,14 +135,7 @@ final class DrydockLease extends DrydockDAO ->setStatus(DrydockLeaseStatus::STATUS_PENDING) ->save(); - $task = PhabricatorWorker::scheduleTask( - 'DrydockAllocatorWorker', - array( - 'leasePHID' => $this->getPHID(), - ), - array( - 'objectPHID' => $this->getPHID(), - )); + $this->scheduleUpdate(); $this->logEvent(DrydockLeaseQueuedLogType::LOGCONST); @@ -321,12 +314,13 @@ final class DrydockLease extends DrydockDAO } } - public function canUpdate() { + public function canReceiveCommands() { switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_ACTIVE: - return true; - default: + case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_DESTROYED: return false; + default: + return true; } } diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php deleted file mode 100644 index 08a702b589..0000000000 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ /dev/null @@ -1,479 +0,0 @@ -getTaskDataValue('leasePHID'); - $lease = $this->loadLease($lease_phid); - - $this->allocateAndAcquireLease($lease); - } - - -/* -( Allocator )---------------------------------------------------------- */ - - - /** - * Find or build a resource which can satisfy a given lease request, then - * acquire the lease. - * - * @param DrydockLease Requested lease. - * @return void - * @task allocator - */ - private function allocateAndAcquireLease(DrydockLease $lease) { - $blueprints = $this->loadBlueprintsForAllocatingLease($lease); - - // If we get nothing back, that means no blueprint is defined which can - // ever build the requested resource. This is a permanent failure, since - // we don't expect to succeed no matter how many times we try. - if (!$blueprints) { - $lease - ->setStatus(DrydockLeaseStatus::STATUS_BROKEN) - ->save(); - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'No active Drydock blueprint exists which can ever allocate a '. - 'resource for lease "%s".', - $lease->getPHID())); - } - - // First, try to find a suitable open resource which we can acquire a new - // lease on. - $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease); - - // If no resources exist yet, see if we can build one. - if (!$resources) { - $usable_blueprints = $this->removeOverallocatedBlueprints( - $blueprints, - $lease); - - // If we get nothing back here, some blueprint claims it can eventually - // satisfy the lease, just not right now. This is a temporary failure, - // and we expect allocation to succeed eventually. - if (!$blueprints) { - // TODO: More formal temporary failure here. We should retry this - // "soon" but not "immediately". - throw new Exception( - pht('No blueprints have space to allocate a resource right now.')); - } - - $usable_blueprints = $this->rankBlueprints($blueprints, $lease); - - $exceptions = array(); - foreach ($usable_blueprints as $blueprint) { - try { - $resources[] = $this->allocateResource($blueprint, $lease); - - // Bail after allocating one resource, we don't need any more than - // this. - break; - } catch (Exception $ex) { - $exceptions[] = $ex; - } - } - - if (!$resources) { - // TODO: We should distinguish between temporary and permament failures - // here. If any blueprint failed temporarily, retry "soon". If none - // of these failures were temporary, maybe this should be a permanent - // failure? - throw new PhutilAggregateException( - pht( - 'All blueprints failed to allocate a suitable new resource when '. - 'trying to allocate lease "%s".', - $lease->getPHID()), - $exceptions); - } - - // NOTE: We have not acquired the lease yet, so it is possible that the - // resource we just built will be snatched up by some other lease before - // we can. This is not problematic: we'll retry a little later and should - // suceed eventually. - } - - $resources = $this->rankResources($resources, $lease); - - $exceptions = array(); - $allocated = false; - foreach ($resources as $resource) { - try { - $this->acquireLease($resource, $lease); - $allocated = true; - break; - } catch (Exception $ex) { - $exceptions[] = $ex; - } - } - - if (!$allocated) { - // TODO: We should distinguish between temporary and permanent failures - // here. If any failures were temporary (specifically, failed to acquire - // locks) - - throw new PhutilAggregateException( - pht( - 'Unable to acquire lease "%s" on any resouce.', - $lease->getPHID()), - $exceptions); - } - } - - - /** - * Get all the @{class:DrydockBlueprintImplementation}s which can possibly - * build a resource to satisfy a lease. - * - * This method returns blueprints which might, at some time, be able to - * build a resource which can satisfy the lease. They may not be able to - * build that resource right now. - * - * @param DrydockLease Requested lease. - * @return list List of qualifying blueprint - * implementations. - * @task allocator - */ - private function loadBlueprintImplementationsForAllocatingLease( - DrydockLease $lease) { - - $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); - - $keep = array(); - foreach ($impls as $key => $impl) { - // Don't use disabled blueprint types. - if (!$impl->isEnabled()) { - continue; - } - - // Don't use blueprint types which can't allocate the correct kind of - // resource. - if ($impl->getType() != $lease->getResourceType()) { - continue; - } - - if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { - continue; - } - - $keep[$key] = $impl; - } - - return $keep; - } - - - /** - * Get all the concrete @{class:DrydockBlueprint}s which can possibly - * build a resource to satisfy a lease. - * - * @param DrydockLease Requested lease. - * @return list List of qualifying blueprints. - * @task allocator - */ - private function loadBlueprintsForAllocatingLease( - DrydockLease $lease) { - $viewer = $this->getViewer(); - - $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); - if (!$impls) { - return array(); - } - - $blueprints = id(new DrydockBlueprintQuery()) - ->setViewer($viewer) - ->withBlueprintClasses(array_keys($impls)) - ->withDisabled(false) - ->execute(); - - $keep = array(); - foreach ($blueprints as $key => $blueprint) { - if (!$blueprint->canEverAllocateResourceForLease($lease)) { - continue; - } - - $keep[$key] = $blueprint; - } - - return $keep; - } - - - /** - * Load a list of all resources which a given lease can possibly be - * allocated against. - * - * @param list Blueprints which may produce suitable - * resources. - * @param DrydockLease Requested lease. - * @return list Resources which may be able to allocate - * the lease. - * @task allocator - */ - private function loadResourcesForAllocatingLease( - array $blueprints, - DrydockLease $lease) { - assert_instances_of($blueprints, 'DrydockBlueprint'); - $viewer = $this->getViewer(); - - $resources = id(new DrydockResourceQuery()) - ->setViewer($viewer) - ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) - ->withTypes(array($lease->getResourceType())) - ->withStatuses( - array( - DrydockResourceStatus::STATUS_PENDING, - DrydockResourceStatus::STATUS_ACTIVE, - )) - ->execute(); - - $keep = array(); - foreach ($resources as $key => $resource) { - $blueprint = $resource->getBlueprint(); - - if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) { - continue; - } - - $keep[$key] = $resource; - } - - return $keep; - } - - - /** - * Remove blueprints which are too heavily allocated to build a resource for - * a lease from a list of blueprints. - * - * @param list List of blueprints. - * @return list List with blueprints that can not allocate - * a resource for the lease right now removed. - * @task allocator - */ - private function removeOverallocatedBlueprints( - array $blueprints, - DrydockLease $lease) { - assert_instances_of($blueprints, 'DrydockBlueprint'); - - $keep = array(); - foreach ($blueprints as $key => $blueprint) { - if (!$blueprint->canAllocateResourceForLease($lease)) { - continue; - } - - $keep[$key] = $blueprint; - } - - return $keep; - } - - - /** - * Rank blueprints by suitability for building a new resource for a - * particular lease. - * - * @param list List of blueprints. - * @param DrydockLease Requested lease. - * @return list Ranked list of blueprints. - * @task allocator - */ - private function rankBlueprints(array $blueprints, DrydockLease $lease) { - assert_instances_of($blueprints, 'DrydockBlueprint'); - - // TODO: Implement improvements to this ranking algorithm if they become - // available. - shuffle($blueprints); - - return $blueprints; - } - - - /** - * Rank resources by suitability for allocating a particular lease. - * - * @param list List of resources. - * @param DrydockLease Requested lease. - * @return list Ranked list of resources. - * @task allocator - */ - private function rankResources(array $resources, DrydockLease $lease) { - assert_instances_of($resources, 'DrydockResource'); - - // TODO: Implement improvements to this ranking algorithm if they become - // available. - shuffle($resources); - - return $resources; - } - - -/* -( Managing Resources )------------------------------------------------- */ - - - /** - * Perform an actual resource allocation with a particular blueprint. - * - * @param DrydockBlueprint The blueprint to allocate a resource from. - * @param DrydockLease Requested lease. - * @return DrydockResource Allocated resource. - * @task resource - */ - private function allocateResource( - DrydockBlueprint $blueprint, - DrydockLease $lease) { - $resource = $blueprint->allocateResource($lease); - $this->validateAllocatedResource($blueprint, $resource, $lease); - - // If this resource was allocated as a pending resource, queue a task to - // activate it. - if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { - PhabricatorWorker::scheduleTask( - 'DrydockResourceUpdateWorker', - array( - 'resourcePHID' => $resource->getPHID(), - ), - array( - 'objectPHID' => $resource->getPHID(), - )); - } - - return $resource; - } - - - /** - * Check that the resource a blueprint allocated is roughly the sort of - * object we expect. - * - * @param DrydockBlueprint Blueprint which built the resource. - * @param wild Thing which the blueprint claims is a valid resource. - * @param DrydockLease Lease the resource was allocated for. - * @return void - * @task resource - */ - private function validateAllocatedResource( - DrydockBlueprint $blueprint, - $resource, - DrydockLease $lease) { - - if (!($resource instanceof DrydockResource)) { - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '. - 'return an object of type %s or throw, but returned something else.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'allocateResource()', - 'DrydockResource')); - } - - if (!$resource->isAllocatedResource()) { - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. - 'must actually allocate the resource it returns.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'allocateResource()')); - } - - $resource_type = $resource->getType(); - $lease_type = $lease->getResourceType(); - - if ($resource_type !== $lease_type) { - // TODO: Destroy the resource here? - - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: it '. - 'built a resource of type "%s" to satisfy a lease requesting a '. - 'resource of type "%s".', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - $resource_type, - $lease_type)); - } - } - - -/* -( Managing Leases )---------------------------------------------------- */ - - - /** - * Perform an actual lease acquisition on a particular resource. - * - * @param DrydockResource Resource to acquire a lease on. - * @param DrydockLease Lease to acquire. - * @return void - * @task lease - */ - private function acquireLease( - DrydockResource $resource, - DrydockLease $lease) { - - $blueprint = $resource->getBlueprint(); - $blueprint->acquireLease($resource, $lease); - - $this->validateAcquiredLease($blueprint, $resource, $lease); - - // If this lease has been acquired but not activated, queue a task to - // activate it. - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) { - PhabricatorWorker::scheduleTask( - 'DrydockLeaseWorker', - array( - 'leasePHID' => $lease->getPHID(), - ), - array( - 'objectPHID' => $lease->getPHID(), - )); - } - } - - - /** - * Make sure that a lease was really acquired properly. - * - * @param DrydockBlueprint Blueprint which created the resource. - * @param DrydockResource Resource which was acquired. - * @param DrydockLease The lease which was supposedly acquired. - * @return void - * @task lease - */ - private function validateAcquiredLease( - DrydockBlueprint $blueprint, - DrydockResource $resource, - DrydockLease $lease) { - - if (!$lease->isAcquiredLease()) { - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: it '. - 'returned from "%s" without acquiring a lease.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'acquireLease()')); - } - - $lease_phid = $lease->getResourcePHID(); - $resource_phid = $resource->getPHID(); - - if ($lease_phid !== $resource_phid) { - // TODO: Destroy the lease? - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: it '. - 'returned from "%s" with a lease acquired on the wrong resource.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'acquireLease()')); - } - } - - -} diff --git a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php deleted file mode 100644 index 12b9aa5a35..0000000000 --- a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php +++ /dev/null @@ -1,39 +0,0 @@ -getTaskDataValue('leasePHID'); - $lease = $this->loadLease($lease_phid); - $this->destroyLease($lease); - } - - private function destroyLease(DrydockLease $lease) { - $status = $lease->getStatus(); - - switch ($status) { - case DrydockLeaseStatus::STATUS_RELEASED: - case DrydockLeaseStatus::STATUS_BROKEN: - break; - default: - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Unable to destroy lease ("%s"), lease has the wrong '. - 'status ("%s").', - $lease->getPHID(), - $status)); - } - - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - - $blueprint->destroyLease($resource, $lease); - - $lease - ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED) - ->save(); - - $lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST); - } - -} diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 2003372b76..942db6d7c0 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -1,5 +1,14 @@ unlock(); } + +/* -( Updating Leases )---------------------------------------------------- */ + + + /** + * @task update + */ private function updateLease(DrydockLease $lease) { - if (!$lease->canUpdate()) { + $this->processLeaseCommands($lease); + + $lease_status = $lease->getStatus(); + switch ($lease_status) { + case DrydockLeaseStatus::STATUS_PENDING: + $this->executeAllocator($lease); + break; + case DrydockLeaseStatus::STATUS_ACQUIRED: + $this->activateLease($lease); + break; + case DrydockLeaseStatus::STATUS_ACTIVE: + // Nothing to do. + break; + case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_BROKEN: + $this->destroyLease($lease); + break; + case DrydockLeaseStatus::STATUS_DESTROYED: + break; + } + + $this->yieldIfExpiringLease($lease); + } + + +/* -( Processing Commands )------------------------------------------------ */ + + + /** + * @task command + */ + private function processLeaseCommands(DrydockLease $lease) { + if (!$lease->canReceiveCommands()) { return; } @@ -31,21 +79,23 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $commands = $this->loadCommands($lease->getPHID()); foreach ($commands as $command) { - if (!$lease->canUpdate()) { + if (!$lease->canReceiveCommands()) { break; } - $this->processCommand($lease, $command); + $this->processLeaseCommand($lease, $command); $command ->setIsConsumed(true) ->save(); } - - $this->yieldIfExpiringLease($lease); } - private function processCommand( + + /** + * @task command + */ + private function processLeaseCommand( DrydockLease $lease, DrydockCommand $command) { switch ($command->getCommand()) { @@ -55,6 +105,530 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } } + +/* -( Drydock Allocator )-------------------------------------------------- */ + + + /** + * Find or build a resource which can satisfy a given lease request, then + * acquire the lease. + * + * @param DrydockLease Requested lease. + * @return void + * @task allocator + */ + private function executeAllocator(DrydockLease $lease) { + $blueprints = $this->loadBlueprintsForAllocatingLease($lease); + + // If we get nothing back, that means no blueprint is defined which can + // ever build the requested resource. This is a permanent failure, since + // we don't expect to succeed no matter how many times we try. + if (!$blueprints) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'No active Drydock blueprint exists which can ever allocate a '. + 'resource for lease "%s".', + $lease->getPHID())); + } + + // First, try to find a suitable open resource which we can acquire a new + // lease on. + $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease); + + // If no resources exist yet, see if we can build one. + if (!$resources) { + $usable_blueprints = $this->removeOverallocatedBlueprints( + $blueprints, + $lease); + + // If we get nothing back here, some blueprint claims it can eventually + // satisfy the lease, just not right now. This is a temporary failure, + // and we expect allocation to succeed eventually. + if (!$usable_blueprints) { + // TODO: More formal temporary failure here. We should retry this + // "soon" but not "immediately". + throw new Exception( + pht('No blueprints have space to allocate a resource right now.')); + } + + $usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease); + + $exceptions = array(); + foreach ($usable_blueprints as $blueprint) { + try { + $resources[] = $this->allocateResource($blueprint, $lease); + + // Bail after allocating one resource, we don't need any more than + // this. + break; + } catch (Exception $ex) { + $exceptions[] = $ex; + } + } + + if (!$resources) { + // TODO: We should distinguish between temporary and permament failures + // here. If any blueprint failed temporarily, retry "soon". If none + // of these failures were temporary, maybe this should be a permanent + // failure? + throw new PhutilAggregateException( + pht( + 'All blueprints failed to allocate a suitable new resource when '. + 'trying to allocate lease "%s".', + $lease->getPHID()), + $exceptions); + } + + // NOTE: We have not acquired the lease yet, so it is possible that the + // resource we just built will be snatched up by some other lease before + // we can. This is not problematic: we'll retry a little later and should + // suceed eventually. + } + + $resources = $this->rankResources($resources, $lease); + + $exceptions = array(); + $allocated = false; + foreach ($resources as $resource) { + try { + $this->acquireLease($resource, $lease); + $allocated = true; + break; + } catch (Exception $ex) { + $exceptions[] = $ex; + } + } + + if (!$allocated) { + // TODO: We should distinguish between temporary and permanent failures + // here. If any failures were temporary (specifically, failed to acquire + // locks) + + throw new PhutilAggregateException( + pht( + 'Unable to acquire lease "%s" on any resouce.', + $lease->getPHID()), + $exceptions); + } + } + + + /** + * Get all the @{class:DrydockBlueprintImplementation}s which can possibly + * build a resource to satisfy a lease. + * + * This method returns blueprints which might, at some time, be able to + * build a resource which can satisfy the lease. They may not be able to + * build that resource right now. + * + * @param DrydockLease Requested lease. + * @return list List of qualifying blueprint + * implementations. + * @task allocator + */ + private function loadBlueprintImplementationsForAllocatingLease( + DrydockLease $lease) { + + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + + $keep = array(); + foreach ($impls as $key => $impl) { + // Don't use disabled blueprint types. + if (!$impl->isEnabled()) { + continue; + } + + // Don't use blueprint types which can't allocate the correct kind of + // resource. + if ($impl->getType() != $lease->getResourceType()) { + continue; + } + + if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $impl; + } + + return $keep; + } + + + /** + * Get all the concrete @{class:DrydockBlueprint}s which can possibly + * build a resource to satisfy a lease. + * + * @param DrydockLease Requested lease. + * @return list List of qualifying blueprints. + * @task allocator + */ + private function loadBlueprintsForAllocatingLease( + DrydockLease $lease) { + $viewer = $this->getViewer(); + + $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); + if (!$impls) { + return array(); + } + + $blueprints = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withBlueprintClasses(array_keys($impls)) + ->withDisabled(false) + ->execute(); + + $keep = array(); + foreach ($blueprints as $key => $blueprint) { + if (!$blueprint->canEverAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $blueprint; + } + + return $keep; + } + + + /** + * Load a list of all resources which a given lease can possibly be + * allocated against. + * + * @param list Blueprints which may produce suitable + * resources. + * @param DrydockLease Requested lease. + * @return list Resources which may be able to allocate + * the lease. + * @task allocator + */ + private function loadResourcesForAllocatingLease( + array $blueprints, + DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + $viewer = $this->getViewer(); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) + ->withTypes(array($lease->getResourceType())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_PENDING, + DrydockResourceStatus::STATUS_ACTIVE, + )) + ->execute(); + + $keep = array(); + foreach ($resources as $key => $resource) { + $blueprint = $resource->getBlueprint(); + + if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) { + continue; + } + + $keep[$key] = $resource; + } + + return $keep; + } + + + /** + * Remove blueprints which are too heavily allocated to build a resource for + * a lease from a list of blueprints. + * + * @param list List of blueprints. + * @return list List with blueprints that can not allocate + * a resource for the lease right now removed. + * @task allocator + */ + private function removeOverallocatedBlueprints( + array $blueprints, + DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + + $keep = array(); + foreach ($blueprints as $key => $blueprint) { + if (!$blueprint->canAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $blueprint; + } + + return $keep; + } + + + /** + * Rank blueprints by suitability for building a new resource for a + * particular lease. + * + * @param list List of blueprints. + * @param DrydockLease Requested lease. + * @return list Ranked list of blueprints. + * @task allocator + */ + private function rankBlueprints(array $blueprints, DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($blueprints); + + return $blueprints; + } + + + /** + * Rank resources by suitability for allocating a particular lease. + * + * @param list List of resources. + * @param DrydockLease Requested lease. + * @return list Ranked list of resources. + * @task allocator + */ + private function rankResources(array $resources, DrydockLease $lease) { + assert_instances_of($resources, 'DrydockResource'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($resources); + + return $resources; + } + + + /** + * Perform an actual resource allocation with a particular blueprint. + * + * @param DrydockBlueprint The blueprint to allocate a resource from. + * @param DrydockLease Requested lease. + * @return DrydockResource Allocated resource. + * @task allocator + */ + private function allocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + $resource = $blueprint->allocateResource($lease); + $this->validateAllocatedResource($blueprint, $resource, $lease); + + // If this resource was allocated as a pending resource, queue a task to + // activate it. + if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { + PhabricatorWorker::scheduleTask( + 'DrydockResourceUpdateWorker', + array( + 'resourcePHID' => $resource->getPHID(), + ), + array( + 'objectPHID' => $resource->getPHID(), + )); + } + + return $resource; + } + + + /** + * Check that the resource a blueprint allocated is roughly the sort of + * object we expect. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param wild Thing which the blueprint claims is a valid resource. + * @param DrydockLease Lease the resource was allocated for. + * @return void + * @task allocator + */ + private function validateAllocatedResource( + DrydockBlueprint $blueprint, + $resource, + DrydockLease $lease) { + + if (!($resource instanceof DrydockResource)) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '. + 'return an object of type %s or throw, but returned something else.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()', + 'DrydockResource')); + } + + if (!$resource->isAllocatedResource()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. + 'must actually allocate the resource it returns.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()')); + } + + $resource_type = $resource->getType(); + $lease_type = $lease->getResourceType(); + + if ($resource_type !== $lease_type) { + // TODO: Destroy the resource here? + + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'built a resource of type "%s" to satisfy a lease requesting a '. + 'resource of type "%s".', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + $resource_type, + $lease_type)); + } + } + + +/* -( Acquiring Leases )--------------------------------------------------- */ + + + /** + * Perform an actual lease acquisition on a particular resource. + * + * @param DrydockResource Resource to acquire a lease on. + * @param DrydockLease Lease to acquire. + * @return void + * @task acquire + */ + private function acquireLease( + DrydockResource $resource, + DrydockLease $lease) { + + $blueprint = $resource->getBlueprint(); + $blueprint->acquireLease($resource, $lease); + + $this->validateAcquiredLease($blueprint, $resource, $lease); + + // If this lease has been acquired but not activated, queue a task to + // activate it. + if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) { + PhabricatorWorker::scheduleTask( + __CLASS__, + array( + 'leasePHID' => $lease->getPHID(), + ), + array( + 'objectPHID' => $lease->getPHID(), + )); + } + } + + + /** + * Make sure that a lease was really acquired properly. + * + * @param DrydockBlueprint Blueprint which created the resource. + * @param DrydockResource Resource which was acquired. + * @param DrydockLease The lease which was supposedly acquired. + * @return void + * @task acquire + */ + private function validateAcquiredLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + if (!$lease->isAcquiredLease()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" without acquiring a lease.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + + $lease_phid = $lease->getResourcePHID(); + $resource_phid = $resource->getPHID(); + + if ($lease_phid !== $resource_phid) { + // TODO: Destroy the lease? + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" with a lease acquired on the wrong resource.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + } + + +/* -( Activating Leases )-------------------------------------------------- */ + + + /** + * @task activate + */ + private function activateLease(DrydockLease $lease) { + $resource = $lease->getResource(); + if (!$resource) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Trying to activate lease with no resource.')); + } + + $resource_status = $resource->getStatus(); + + if ($resource_status == DrydockResourceStatus::STATUS_PENDING) { + // TODO: This is explicitly a temporary failure -- we are waiting for + // the resource to come up. + throw new Exception(pht('Resource still activating.')); + } + + if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Trying to activate lease on a dead resource (in status "%s").', + $resource_status)); + } + + // NOTE: We can race resource destruction here. Between the time we + // performed the read above and now, the resource might have closed, so + // we may activate leases on dead resources. At least for now, this seems + // fine: a resource dying right before we activate a lease on it should not + // be distinguisahble from a resource dying right after we activate a lease + // on it. We end up with an active lease on a dead resource either way, and + // can not prevent resources dying from lightning strikes. + + $blueprint = $resource->getBlueprint(); + $blueprint->activateLease($resource, $lease); + $this->validateActivatedLease($blueprint, $resource, $lease); + } + + /** + * @task activate + */ + private function validateActivatedLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + if (!$lease->isActivatedLease()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" without activating a lease.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + + } + + +/* -( Releasing Leases )--------------------------------------------------- */ + + + /** + * @task release + */ private function releaseLease(DrydockLease $lease) { $lease->openTransaction(); $lease @@ -65,21 +639,34 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { DrydockSlotLock::releaseLocks($lease->getPHID()); $lease->saveTransaction(); - PhabricatorWorker::scheduleTask( - 'DrydockLeaseDestroyWorker', - array( - 'leasePHID' => $lease->getPHID(), - ), - array( - 'objectPHID' => $lease->getPHID(), - )); - $lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST); $resource = $lease->getResource(); $blueprint = $resource->getBlueprint(); $blueprint->didReleaseLease($resource, $lease); + + $this->destroyLease($lease); + } + + +/* -( Destroying Leases )-------------------------------------------------- */ + + + /** + * @task destroy + */ + private function destroyLease(DrydockLease $lease) { + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + $blueprint->destroyLease($resource, $lease); + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED) + ->save(); + + $lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST); } } diff --git a/src/applications/drydock/worker/DrydockLeaseWorker.php b/src/applications/drydock/worker/DrydockLeaseWorker.php deleted file mode 100644 index 5919c59613..0000000000 --- a/src/applications/drydock/worker/DrydockLeaseWorker.php +++ /dev/null @@ -1,74 +0,0 @@ -getTaskDataValue('leasePHID'); - $lease = $this->loadLease($lease_phid); - - $this->activateLease($lease); - } - - - private function activateLease(DrydockLease $lease) { - $actual_status = $lease->getStatus(); - - if ($actual_status != DrydockLeaseStatus::STATUS_ACQUIRED) { - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Trying to activate lease from wrong status ("%s").', - $actual_status)); - } - - $resource = $lease->getResource(); - if (!$resource) { - throw new PhabricatorWorkerPermanentFailureException( - pht('Trying to activate lease with no resource.')); - } - - $resource_status = $resource->getStatus(); - - if ($resource_status == DrydockResourceStatus::STATUS_PENDING) { - // TODO: This is explicitly a temporary failure -- we are waiting for - // the resource to come up. - throw new Exception(pht('Resource still activating.')); - } - - if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { - throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Trying to activate lease on a dead resource (in status "%s").', - $resource_status)); - } - - // NOTE: We can race resource destruction here. Between the time we - // performed the read above and now, the resource might have closed, so - // we may activate leases on dead resources. At least for now, this seems - // fine: a resource dying right before we activate a lease on it should not - // be distinguisahble from a resource dying right after we activate a lease - // on it. We end up with an active lease on a dead resource either way, and - // can not prevent resources dying from lightning strikes. - - $blueprint = $resource->getBlueprint(); - $blueprint->activateLease($resource, $lease); - $this->validateActivatedLease($blueprint, $resource, $lease); - } - - private function validateActivatedLease( - DrydockBlueprint $blueprint, - DrydockResource $resource, - DrydockLease $lease) { - - if (!$lease->isActivatedLease()) { - throw new Exception( - pht( - 'Blueprint "%s" (of type "%s") is not properly implemented: it '. - 'returned from "%s" without activating a lease.', - $blueprint->getBlueprintName(), - $blueprint->getClassName(), - 'acquireLease()')); - } - - } - -} diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index a7273b2f9d..fb2666f735 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -86,7 +86,7 @@ abstract class DrydockWorker extends PhabricatorWorker { } protected function yieldIfExpiringLease(DrydockLease $lease) { - if (!$lease->canUpdate()) { + if (!$lease->canReceiveCommands()) { return; } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 761f445e4e..85e7ae2411 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -154,7 +154,8 @@ final class HarbormasterBuildLog extends HarbormasterDAO public function finalize($start = 0) { if (!$this->getLive()) { - throw new Exception(pht('Start logging before finalizing it.')); + // TODO: Clean up this API. + return; } // TODO: Encode the log contents in a gzipped format. diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php index 3d8c6887c6..b0937c4e6d 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php @@ -60,7 +60,10 @@ abstract class PhabricatorWorkerTask extends PhabricatorWorkerDAO { $id = $this->getID(); $class = $this->getTaskClass(); - if (!class_exists($class)) { + try { + // NOTE: If the class does not exist, libphutil will throw an exception. + class_exists($class); + } catch (PhutilMissingSymbolException $ex) { throw new PhabricatorWorkerPermanentFailureException( pht( "Task class '%s' does not exist!", From 6b775e6090536c422fa30998b2a8c05e0a8c1231 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:11:42 -0700 Subject: [PATCH 30/43] Add more Drydock log types and some additional logging Summary: Ref T9252. Add a bit more logging and improve some behaviors. Test Plan: Restarted a build, got a good result. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14210 --- src/__phutil_library_map__.php | 12 +++++++ ...dockWorkingCopyBlueprintImplementation.php | 16 +++++---- .../exception/DrydockSlotLockException.php | 4 +++ .../DrydockLeaseActivationFailureLogType.php | 22 ++++++++++++ .../DrydockLeaseActivationYieldLogType.php | 23 ++++++++++++ ...DrydockLeaseWaitingForResourcesLogType.php | 25 +++++++++++++ ...rydockResourceActivationFailureLogType.php | 22 ++++++++++++ .../DrydockResourceActivationYieldLogType.php | 23 ++++++++++++ .../logtype/DrydockSlotLockFailureLogType.php | 26 ++++++++++++++ .../drydock/storage/DrydockLease.php | 20 ++++++++--- .../drydock/storage/DrydockResource.php | 35 ++++++++++++++++--- 11 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php create mode 100644 src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php create mode 100644 src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php create mode 100644 src/applications/drydock/logtype/DrydockResourceActivationYieldLogType.php create mode 100644 src/applications/drydock/logtype/DrydockSlotLockFailureLogType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d00b105260..f816856112 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -831,6 +831,8 @@ phutil_register_library_map(array( 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', 'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php', 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', + 'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php', + 'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', @@ -845,6 +847,7 @@ phutil_register_library_map(array( 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', + 'DrydockLeaseWaitingForResourcesLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php', @@ -862,6 +865,8 @@ phutil_register_library_map(array( 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', + 'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php', + 'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', @@ -877,6 +882,7 @@ phutil_register_library_map(array( 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', 'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php', + 'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', 'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', @@ -4571,6 +4577,8 @@ phutil_register_library_map(array( ), 'DrydockLeaseAcquiredLogType' => 'DrydockLogType', 'DrydockLeaseActivatedLogType' => 'DrydockLogType', + 'DrydockLeaseActivationFailureLogType' => 'DrydockLogType', + 'DrydockLeaseActivationYieldLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', @@ -4585,6 +4593,7 @@ phutil_register_library_map(array( 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', + 'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType', 'DrydockLog' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', @@ -4608,6 +4617,8 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockResourceActivationFailureLogType' => 'DrydockLogType', + 'DrydockResourceActivationYieldLogType' => 'DrydockLogType', 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', @@ -4623,6 +4634,7 @@ phutil_register_library_map(array( 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 'DrydockSlotLock' => 'DrydockDAO', 'DrydockSlotLockException' => 'Exception', + 'DrydockSlotLockFailureLogType' => 'DrydockLogType', 'DrydockWebrootInterface' => 'DrydockInterface', 'DrydockWorker' => 'PhabricatorWorker', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 843f043baa..f9a8794391 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -172,14 +172,16 @@ final class DrydockWorkingCopyBlueprintImplementation // Destroy the lease on the host. $lease->releaseOnDestruction(); - // Destroy the working copy on disk. - $command_type = DrydockCommandInterface::INTERFACE_TYPE; - $interface = $lease->getInterface($command_type); + if ($lease->isActive()) { + // Destroy the working copy on disk. + $command_type = DrydockCommandInterface::INTERFACE_TYPE; + $interface = $lease->getInterface($command_type); - $root_key = 'workingcopy.root'; - $root = $resource->getAttribute($root_key); - if (strlen($root)) { - $interface->execx('rm -rf -- %s', $root); + $root_key = 'workingcopy.root'; + $root = $resource->getAttribute($root_key); + if (strlen($root)) { + $interface->execx('rm -rf -- %s', $root); + } } } diff --git a/src/applications/drydock/exception/DrydockSlotLockException.php b/src/applications/drydock/exception/DrydockSlotLockException.php index 2bcf5f8cd7..3ee40f1b32 100644 --- a/src/applications/drydock/exception/DrydockSlotLockException.php +++ b/src/applications/drydock/exception/DrydockSlotLockException.php @@ -22,4 +22,8 @@ final class DrydockSlotLockException extends Exception { parent::__construct($message); } + public function getLockMap() { + return $this->lockMap; + } + } diff --git a/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php b/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php new file mode 100644 index 0000000000..4078e66d04 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php @@ -0,0 +1,22 @@ +getViewer(); + + $blueprint_phids = idx($data, 'blueprintPHIDs', array()); + + return pht( + 'Waiting for available resources from: %s.', + $viewer->renderHandleList($blueprint_phids)); + } + +} diff --git a/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php b/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php new file mode 100644 index 0000000000..d5fac38da4 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php @@ -0,0 +1,22 @@ +openTransaction(); + try { + try { + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + throw $ex; + } $this ->setResourcePHID($resource->getPHID()) ->attachResource($resource) ->setStatus($new_status) ->save(); - - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - + } catch (Exception $ex) { + $this->killTransaction(); + throw $ex; + } $this->saveTransaction(); $this->isAcquired = true; diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 2070cf8b33..1d442620ed 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -135,15 +135,30 @@ final class DrydockResource extends DrydockDAO $new_status = DrydockResourceStatus::STATUS_PENDING; } + $phid = $this->generatePHID(); + $this->openTransaction(); + try { + try { + DrydockSlotLock::acquireLocks($phid, $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + throw $ex; + } $this + ->setPHID($phid) ->setStatus($new_status) ->save(); - - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - + } catch (Exception $ex) { + $this->killTransaction(); + throw $ex; + } $this->saveTransaction(); $this->isAllocated = true; @@ -257,6 +272,18 @@ final class DrydockResource extends DrydockDAO } } + public function logEvent($type, array $data = array()) { + $log = id(new DrydockLog()) + ->setEpoch(PhabricatorTime::getNow()) + ->setType($type) + ->setData($data); + + $log->setResourcePHID($this->getPHID()); + $log->setBlueprintPHID($this->getBlueprintPHID()); + + return $log->save(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ From e589d152310a0b3727ee9454d2ca772ae5694261 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:12:51 -0700 Subject: [PATCH 31/43] Improve error and exception handling for Drydock resources Summary: Ref T9252. Currently, error handling behavior isn't great and a lot of errors aren't dealt with properly. Try to improve this by making default behaviors better: - Yields, slot lock exceptions, and aggregate or proxy exceptions containing an excpetion of these types turn into yields. - All other exceptions are considered permanent failures. They break the resource and This feels a little bit "magical" but I want to try to get the default behaviors to align reasonably well with expectations so that blueprints mostly don't need to have a ton of error handling. This will probably need at least some refinement down the road, but it's a reasonable rule for all exception/error conditions we currently have. Test Plan: I did a clean build, but haven't vetted this super thoroughly. Next diff will do the same thing to leases, then I'll work on stabilizing this code better. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14211 --- ...anacServiceHostBlueprintImplementation.php | 1 - ...dockWorkingCopyBlueprintImplementation.php | 17 ++- .../worker/DrydockResourceUpdateWorker.php | 109 ++++++++++++++++-- .../drydock/worker/DrydockWorker.php | 41 +++++++ 4 files changed, 152 insertions(+), 16 deletions(-) diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 4ae64b39be..9a71474624 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -163,7 +163,6 @@ final class DrydockAlmanacServiceHostBlueprintImplementation ->withPHIDs(array($binding_phid)) ->executeOne(); if (!$binding) { - // TODO: This is probably a permanent failure, destroy this resource? throw new Exception( pht( 'Unable to load binding "%s" to create command interface.', diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index f9a8794391..a61af96ebf 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -297,7 +297,6 @@ final class DrydockWorkingCopyBlueprintImplementation foreach ($phids as $phid) { if (empty($repositories[$phid])) { - // TODO: Permanent failure. throw new Exception( pht( 'Repository PHID "%s" does not exist.', @@ -306,12 +305,16 @@ final class DrydockWorkingCopyBlueprintImplementation } foreach ($repositories as $repository) { - switch ($repository->getVersionControlSystem()) { + $repository_vcs = $repository->getVersionControlSystem(); + switch ($repository_vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; default: - // TODO: Permanent failure. - throw new Exception(pht('Unsupported VCS!')); + throw new Exception( + pht( + 'Repository ("%s") has unsupported VCS ("%s").', + $repository->getPHID(), + $repository_vcs)); } } @@ -328,8 +331,10 @@ final class DrydockWorkingCopyBlueprintImplementation ->withPHIDs(array($lease_phid)) ->executeOne(); if (!$lease) { - // TODO: Permanent failure. - throw new Exception(pht('Unable to load lease "%s".', $lease_phid)); + throw new Exception( + pht( + 'Unable to load lease ("%s").', + $lease_phid)); } return $lease; diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 9027829b9c..e0deabdb1b 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -1,9 +1,11 @@ loadResource($resource_phid); - $this->updateResource($resource); + $this->handleUpdate($resource); } catch (Exception $ex) { $lock->unlock(); throw $ex; @@ -28,6 +30,37 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $lock->unlock(); } + +/* -( Updating Resources )------------------------------------------------- */ + + + /** + * Update a resource, handling exceptions thrown during the update. + * + * @param DrydockReosource Resource to update. + * @return void + * @task update + */ + private function handleUpdate(DrydockResource $resource) { + try { + $this->updateResource($resource); + } catch (Exception $ex) { + if ($this->isTemporaryException($ex)) { + $this->yieldResource($resource, $ex); + } else { + $this->breakResource($resource, $ex); + } + } + } + + + /** + * Update a resource. + * + * @param DrydockResource Resource to update. + * @return void + * @task update + */ private function updateResource(DrydockResource $resource) { $this->processResourceCommands($resource); @@ -52,6 +85,26 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { } + /** + * Convert a temporary exception into a yield. + * + * @param DrydockResource Resource to yield. + * @param Exception Temporary exception worker encountered. + * @task update + */ + private function yieldResource(DrydockResource $resource, Exception $ex) { + $duration = $this->getYieldDurationFromException($ex); + + $resource->logEvent( + DrydockResourceActivationYieldLogType::LOGCONST, + array( + 'duration' => $duration, + )); + + throw new PhabricatorWorkerYieldException($duration); + } + + /* -( Processing Commands )------------------------------------------------ */ @@ -138,14 +191,9 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $viewer = $this->getViewer(); $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); - $resource->openTransaction(); - $resource - ->setStatus(DrydockResourceStatus::STATUS_RELEASED) - ->save(); - - // TODO: Hold slot locks until destruction? - DrydockSlotLock::releaseLocks($resource->getPHID()); - $resource->saveTransaction(); + $resource + ->setStatus(DrydockResourceStatus::STATUS_RELEASED) + ->save(); $statuses = array( DrydockLeaseStatus::STATUS_PENDING, @@ -173,6 +221,47 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { } +/* -( Breaking Resources )------------------------------------------------- */ + + + /** + * @task break + */ + private function breakResource(DrydockResource $resource, Exception $ex) { + switch ($resource->getStatus()) { + case DrydockResourceStatus::STATUS_BROKEN: + case DrydockResourceStatus::STATUS_RELEASED: + case DrydockResourceStatus::STATUS_DESTROYED: + // If the resource was already broken, just throw a normal exception. + // This will retry the task eventually. + throw new PhutilProxyException( + pht( + 'Unexpected failure while destroying resource ("%s").', + $resource->getPHID()), + $ex); + } + + $resource + ->setStatus(DrydockResourceStatus::STATUS_BROKEN) + ->save(); + + $resource->scheduleUpdate(); + + $resource->logEvent( + DrydockResourceActivationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Permanent failure while activating resource ("%s"): %s', + $resource->getPHID(), + $ex->getMessage())); + } + + /* -( Destroying Resources )----------------------------------------------- */ @@ -183,6 +272,8 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $blueprint = $resource->getBlueprint(); $blueprint->destroyResource($resource); + DrydockSlotLock::releaseLocks($resource->getPHID()); + $resource ->setStatus(DrydockResourceStatus::STATUS_DESTROYED) ->save(); diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index fb2666f735..d2dc1ca399 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -114,4 +114,45 @@ abstract class DrydockWorker extends PhabricatorWorker { throw new PhabricatorWorkerYieldException($expires - $now); } + protected function isTemporaryException(Exception $ex) { + if ($ex instanceof PhabricatorWorkerYieldException) { + return true; + } + + if ($ex instanceof DrydockSlotLockException) { + return true; + } + + if ($ex instanceof PhutilAggregateException) { + $any_temporary = false; + foreach ($ex->getExceptions() as $sub) { + if ($this->isTemporaryException($sub)) { + $any_temporary = true; + break; + } + } + if ($any_temporary) { + return true; + } + } + + if ($ex instanceof PhutilProxyException) { + return $this->isTemporaryException($ex->getPreviousException()); + } + + return false; + } + + protected function getYieldDurationFromException(Exception $ex) { + if ($ex instanceof PhabricatorWorkerYieldException) { + return $ex->getDuration(); + } + + if ($ex instanceof DrydockSlotLockException) { + return 5; + } + + return 15; + } + } From b219bcfb3d70b4eb4b1d99e4099e78653aa988d5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:13:20 -0700 Subject: [PATCH 32/43] Improve error and exception handling for Drydock leases Summary: Ref T9252. See companion change in D14211. This does the same thing for leases. Particularly, most of the TODOs about error handling can just be removed because they'll do the right things by default now. This and D14211 also move slot lock release to after resource destruction. This feels cleaner than trying to release early at release/break. Test Plan: Restarted a Harbormaster build, got a clean build result. This needs more vetting but I'll clean up any issues as I hit them. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14212 --- .../worker/DrydockLeaseUpdateWorker.php | 117 +++++++++++++----- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 942db6d7c0..f5ecc8389c 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -7,6 +7,7 @@ * @task acquire Acquiring Leases * @task activate Activating Leases * @task release Releasing Leases + * @task break Breaking Leases * @task destroy Destroying Leases */ final class DrydockLeaseUpdateWorker extends DrydockWorker { @@ -22,7 +23,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { try { $lease = $this->loadLease($lease_phid); - $this->updateLease($lease); + $this->handleUpdate($lease); } catch (Exception $ex) { $lock->unlock(); throw $ex; @@ -35,6 +36,22 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { /* -( Updating Leases )---------------------------------------------------- */ + /** + * @task update + */ + private function handleUpdate(DrydockLease $lease) { + try { + $this->updateLease($lease); + } catch (Exception $ex) { + if ($this->isTemporaryException($ex)) { + $this->yieldLease($lease, $ex); + } else { + $this->breakLease($lease, $ex); + } + } + } + + /** * @task update */ @@ -64,6 +81,22 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } + /** + * @task update + */ + private function yieldLease(DrydockLease $lease, Exception $ex) { + $duration = $this->getYieldDurationFromException($ex); + + $lease->logEvent( + DrydockLeaseActivationYieldLogType::LOGCONST, + array( + 'duration' => $duration, + )); + + throw new PhabricatorWorkerYieldException($duration); + } + + /* -( Processing Commands )------------------------------------------------ */ @@ -145,10 +178,13 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { // satisfy the lease, just not right now. This is a temporary failure, // and we expect allocation to succeed eventually. if (!$usable_blueprints) { - // TODO: More formal temporary failure here. We should retry this - // "soon" but not "immediately". - throw new Exception( - pht('No blueprints have space to allocate a resource right now.')); + $lease->logEvent( + DrydockLeaseWaitingForResourcesLogType::LOGCONST, + array( + 'blueprintPHIDs' => mpull($blueprints, 'getPHID'), + )); + + throw new PhabricatorWorkerYieldException(15); } $usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease); @@ -167,10 +203,6 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } if (!$resources) { - // TODO: We should distinguish between temporary and permament failures - // here. If any blueprint failed temporarily, retry "soon". If none - // of these failures were temporary, maybe this should be a permanent - // failure? throw new PhutilAggregateException( pht( 'All blueprints failed to allocate a suitable new resource when '. @@ -200,10 +232,6 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } if (!$allocated) { - // TODO: We should distinguish between temporary and permanent failures - // here. If any failures were temporary (specifically, failed to acquire - // locks) - throw new PhutilAggregateException( pht( 'Unable to acquire lease "%s" on any resouce.', @@ -471,8 +499,6 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $lease_type = $lease->getResourceType(); if ($resource_type !== $lease_type) { - // TODO: Destroy the resource here? - throw new Exception( pht( 'Blueprint "%s" (of type "%s") is not properly implemented: it '. @@ -549,7 +575,6 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $resource_phid = $resource->getPHID(); if ($lease_phid !== $resource_phid) { - // TODO: Destroy the lease? throw new Exception( pht( 'Blueprint "%s" (of type "%s") is not properly implemented: it '. @@ -570,20 +595,18 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { private function activateLease(DrydockLease $lease) { $resource = $lease->getResource(); if (!$resource) { - throw new PhabricatorWorkerPermanentFailureException( + throw new Exception( pht('Trying to activate lease with no resource.')); } $resource_status = $resource->getStatus(); if ($resource_status == DrydockResourceStatus::STATUS_PENDING) { - // TODO: This is explicitly a temporary failure -- we are waiting for - // the resource to come up. - throw new Exception(pht('Resource still activating.')); + throw new PhabricatorWorkerYieldException(15); } if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { - throw new PhabricatorWorkerPermanentFailureException( + throw new Exception( pht( 'Trying to activate lease on a dead resource (in status "%s").', $resource_status)); @@ -630,14 +653,9 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { * @task release */ private function releaseLease(DrydockLease $lease) { - $lease->openTransaction(); - $lease - ->setStatus(DrydockLeaseStatus::STATUS_RELEASED) - ->save(); - - // TODO: Hold slot locks until destruction? - DrydockSlotLock::releaseLocks($lease->getPHID()); - $lease->saveTransaction(); + $lease + ->setStatus(DrydockLeaseStatus::STATUS_RELEASED) + ->save(); $lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST); @@ -650,6 +668,45 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } +/* -( Breaking Leases )---------------------------------------------------- */ + + + /** + * @task break + */ + protected function breakLease(DrydockLease $lease, Exception $ex) { + switch ($lease->getStatus()) { + case DrydockLeaseStatus::STATUS_BROKEN: + case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_DESTROYED: + throw new PhutilProxyException( + pht( + 'Unexpected failure while destroying lease ("%s").', + $lease->getPHID()), + $ex); + } + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_BROKEN) + ->save(); + + $lease->scheduleDestruction(); + + $lease->logEvent( + DrydockLeaseActivationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Permanent failure while activating lease ("%s"): %s', + $lease->getPHID(), + $ex->getMessage())); + } + + /* -( Destroying Leases )-------------------------------------------------- */ @@ -662,6 +719,8 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $blueprint->destroyLease($resource, $lease); + DrydockSlotLock::releaseLocks($lease->getPHID()); + $lease ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED) ->save(); From d4a0b1c8709b61b218c4cb408cdad08caf4b016d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 08:13:43 -0700 Subject: [PATCH 33/43] Remove names from Drydock resources Summary: Ref T9252. Long ago you sometimes manually created resources, so they had human-enterable names. However, users never make resources manually any more, so this field isn't really useful any more. In particular, it means we write a lot of untranslatable strings like "Working Copy" to the database in the default locale. Instead, do the call at runtime so resource names are translatable. Also clean up a few minor things I hit while kicking the tires here. It's possible we might eventually want to introduce a human-choosable label so you can rename your favorite resources and this would just be a default name. I don't really have much of a use case for that yet, though, and I'm not sure there will ever be one. Test Plan: - Restarted a Harbormaster build, got a clean build. - Released all leases/resources, restarted build, got a clean build with proper resource names. Reviewers: hach-que, chad Reviewed By: hach-que, chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14213 --- .../autopatches/20151001.drydock.rname.1.sql | 2 ++ ...anacServiceHostBlueprintImplementation.php | 12 ++++++++++- .../DrydockBlueprintImplementation.php | 20 +++++++++++++----- ...dockWorkingCopyBlueprintImplementation.php | 11 +++++++--- .../controller/DrydockLeaseController.php | 2 +- .../controller/DrydockLogController.php | 2 +- .../DrydockResourceViewController.php | 5 ++++- .../drydock/phid/DrydockResourcePHIDType.php | 2 +- .../drydock/storage/DrydockBlueprint.php | 10 +++++++++ .../drydock/storage/DrydockResource.php | 21 +++++++++++++------ .../drydock/view/DrydockLeaseListView.php | 15 +++---------- .../drydock/view/DrydockResourceListView.php | 7 ++++--- .../worker/DrydockLeaseUpdateWorker.php | 8 ++++--- 13 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 resources/sql/autopatches/20151001.drydock.rname.1.sql diff --git a/resources/sql/autopatches/20151001.drydock.rname.1.sql b/resources/sql/autopatches/20151001.drydock.rname.1.sql new file mode 100644 index 0000000000..3dbd66c579 --- /dev/null +++ b/resources/sql/autopatches/20151001.drydock.rname.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_resource + DROP name; diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 9a71474624..e458020600 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -69,8 +69,9 @@ final class DrydockAlmanacServiceHostBlueprintImplementation $binding_phid = $binding->getPHID(); - $resource = $this->newResourceTemplate($blueprint, $device_name) + $resource = $this->newResourceTemplate($blueprint) ->setActivateWhenAllocated(true) + ->setAttribute('almanacDeviceName', $device_name) ->setAttribute('almanacServicePHID', $binding->getServicePHID()) ->setAttribute('almanacBindingPHID', $binding_phid) ->needSlotLock("almanac.host.binding({$binding_phid})"); @@ -95,6 +96,15 @@ final class DrydockAlmanacServiceHostBlueprintImplementation return; } + public function getResourceName( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + $device_name = $resource->getAttribute( + 'almanacDeviceName', + pht('')); + return pht('Host (%s)', $device_name); + } + public function canAcquireLeaseOnResource( DrydockBlueprint $blueprint, DrydockResource $resource, diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index b7e39a49a4..5ec2d60ae5 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -237,6 +237,19 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockResource $resource); + /** + * Get a human readable name for a resource. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource to get the name of. + * @return string Human-readable resource name. + * @task resource + */ + abstract public function getResourceName( + DrydockBlueprint $blueprint, + DrydockResource $resource); + + /* -( Resource Interfaces )------------------------------------------------ */ @@ -260,16 +273,13 @@ abstract class DrydockBlueprintImplementation extends Phobject { return idx(self::getAllBlueprintImplementations(), $class); } - protected function newResourceTemplate( - DrydockBlueprint $blueprint, - $name) { + protected function newResourceTemplate(DrydockBlueprint $blueprint) { $resource = id(new DrydockResource()) ->setBlueprintPHID($blueprint->getPHID()) ->attachBlueprint($blueprint) ->setType($this->getType()) - ->setStatus(DrydockResourceStatus::STATUS_PENDING) - ->setName($name); + ->setStatus(DrydockResourceStatus::STATUS_PENDING); // Pre-allocate the resource PHID. $resource->setPHID($resource->generatePHID()); diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index a61af96ebf..1c6b71a298 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -97,9 +97,7 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockBlueprint $blueprint, DrydockLease $lease) { - $resource = $this->newResourceTemplate( - $blueprint, - pht('Working Copy')); + $resource = $this->newResourceTemplate($blueprint); $resource_phid = $resource->getPHID(); @@ -185,6 +183,13 @@ final class DrydockWorkingCopyBlueprintImplementation } } + public function getResourceName( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + return pht('Working Copy'); + } + + public function activateLease( DrydockBlueprint $blueprint, DrydockResource $resource, diff --git a/src/applications/drydock/controller/DrydockLeaseController.php b/src/applications/drydock/controller/DrydockLeaseController.php index d5a6335454..48082b8728 100644 --- a/src/applications/drydock/controller/DrydockLeaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseController.php @@ -44,7 +44,7 @@ abstract class DrydockLeaseController $this->getApplicationURI('resource/')); $crumbs->addTextCrumb( - $resource->getName(), + $resource->getResourceName(), $this->getApplicationURI("resource/{$id}/")); $crumbs->addTextCrumb( diff --git a/src/applications/drydock/controller/DrydockLogController.php b/src/applications/drydock/controller/DrydockLogController.php index 5ae87e6aad..704cbb53b9 100644 --- a/src/applications/drydock/controller/DrydockLogController.php +++ b/src/applications/drydock/controller/DrydockLogController.php @@ -91,7 +91,7 @@ abstract class DrydockLogController $this->getApplicationURI('resource/')); $crumbs->addTextCrumb( - $resource->getName(), + $resource->getResourceName(), $this->getApplicationURI("resource/{$id}/")); $crumbs->addTextCrumb( diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index f97081e673..4809cf970c 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -15,7 +15,10 @@ final class DrydockResourceViewController extends DrydockResourceController { return new Aphront404Response(); } - $title = pht('Resource %s %s', $resource->getID(), $resource->getName()); + $title = pht( + 'Resource %s %s', + $resource->getID(), + $resource->getResourceName()); $header = id(new PHUIHeaderView()) ->setUser($viewer) diff --git a/src/applications/drydock/phid/DrydockResourcePHIDType.php b/src/applications/drydock/phid/DrydockResourcePHIDType.php index 6b266ff169..9eb85e7561 100644 --- a/src/applications/drydock/phid/DrydockResourcePHIDType.php +++ b/src/applications/drydock/phid/DrydockResourcePHIDType.php @@ -33,7 +33,7 @@ final class DrydockResourcePHIDType extends PhabricatorPHIDType { pht( 'Resource %d: %s', $id, - $resource->getName())); + $resource->getResourceName())); $handle->setURI("/drydock/resource/{$id}/"); } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 429e5c2971..a0d440e4ea 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -162,6 +162,16 @@ final class DrydockBlueprint extends DrydockDAO } + /** + * @task resource + */ + public function getResourceName(DrydockResource $resource) { + return $this->getImplementation()->getResourceName( + $this, + $resource); + } + + /* -( Acquiring Leases )--------------------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 1d442620ed..7aad1064f4 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -9,7 +9,6 @@ final class DrydockResource extends DrydockDAO protected $status; protected $until; protected $type; - protected $name; protected $attributes = array(); protected $capabilities = array(); protected $ownerPHID; @@ -30,7 +29,6 @@ final class DrydockResource extends DrydockDAO 'capabilities' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255', 'ownerPHID' => 'phid?', 'status' => 'text32', 'type' => 'text64', @@ -51,6 +49,10 @@ final class DrydockResource extends DrydockDAO return PhabricatorPHID::generateNewPHID(DrydockResourcePHIDType::TYPECONST); } + public function getResourceName() { + return $this->getBlueprint()->getResourceName($this); + } + public function getAttribute($key, $default = null) { return idx($this->attributes, $key, $default); } @@ -118,6 +120,16 @@ final class DrydockResource extends DrydockDAO 'Only new resources may be allocated.')); } + // We expect resources to have a pregenerated PHID, as they should have + // been created by a call to DrydockBlueprint->newResourceTemplate(). + if (!$this->getPHID()) { + throw new Exception( + pht( + 'Trying to allocate a resource with no generated PHID. Use "%s" to '. + 'create new resource templates.', + 'newResourceTemplate()')); + } + $expect_status = DrydockResourceStatus::STATUS_PENDING; $actual_status = $this->getStatus(); if ($actual_status != $expect_status) { @@ -135,12 +147,10 @@ final class DrydockResource extends DrydockDAO $new_status = DrydockResourceStatus::STATUS_PENDING; } - $phid = $this->generatePHID(); - $this->openTransaction(); try { try { - DrydockSlotLock::acquireLocks($phid, $this->slotLocks); + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); $this->slotLocks = array(); } catch (DrydockSlotLockException $ex) { $this->logEvent( @@ -152,7 +162,6 @@ final class DrydockResource extends DrydockDAO } $this - ->setPHID($phid) ->setStatus($new_status) ->save(); } catch (Exception $ex) { diff --git a/src/applications/drydock/view/DrydockLeaseListView.php b/src/applications/drydock/view/DrydockLeaseListView.php index d3507546ad..8c161b00ce 100644 --- a/src/applications/drydock/view/DrydockLeaseListView.php +++ b/src/applications/drydock/view/DrydockLeaseListView.php @@ -22,19 +22,10 @@ final class DrydockLeaseListView extends AphrontView { ->setHeader($lease->getLeaseName()) ->setHref('/drydock/lease/'.$lease->getID().'/'); - if ($lease->hasAttachedResource()) { - $resource = $lease->getResource(); - - $resource_href = '/drydock/resource/'.$resource->getID().'/'; - $resource_name = $resource->getName(); - + $resource_phid = $lease->getResourcePHID(); + if ($resource_phid) { $item->addAttribute( - phutil_tag( - 'a', - array( - 'href' => $resource_href, - ), - $resource_name)); + $viewer->renderHandle($resource_phid)); } $status = DrydockLeaseStatus::getNameForStatus($lease->getStatus()); diff --git a/src/applications/drydock/view/DrydockResourceListView.php b/src/applications/drydock/view/DrydockResourceListView.php index 9b7706c38b..739b464bdb 100644 --- a/src/applications/drydock/view/DrydockResourceListView.php +++ b/src/applications/drydock/view/DrydockResourceListView.php @@ -16,11 +16,12 @@ final class DrydockResourceListView extends AphrontView { $view = new PHUIObjectItemListView(); foreach ($resources as $resource) { - $name = pht('Resource %d', $resource->getID()).': '.$resource->getName(); + $id = $resource->getID(); $item = id(new PHUIObjectItemView()) - ->setHref('/drydock/resource/'.$resource->getID().'/') - ->setHeader($name); + ->setHref("/drydock/resource/{$id}/") + ->setObjectName(pht('Resource %d', $id)) + ->setHeader($resource->getResourceName()); $status = DrydockResourceStatus::getNameForStatus($resource->getStatus()); $item->addAttribute($status); diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index f5ecc8389c..4ac6a9775c 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -690,7 +690,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { ->setStatus(DrydockLeaseStatus::STATUS_BROKEN) ->save(); - $lease->scheduleDestruction(); + $lease->scheduleUpdate(); $lease->logEvent( DrydockLeaseActivationFailureLogType::LOGCONST, @@ -715,9 +715,11 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { */ private function destroyLease(DrydockLease $lease) { $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - $blueprint->destroyLease($resource, $lease); + if ($resource) { + $blueprint = $resource->getBlueprint(); + $blueprint->destroyLease($resource, $lease); + } DrydockSlotLock::releaseLocks($lease->getPHID()); From 4496176924892298bef4e12178ce1809d2449da8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 16:55:01 -0700 Subject: [PATCH 34/43] Add staging area support to Harbormaster/Drydock + various fixes Summary: Ref T9252. This primarily allows Harbormaster to request (and Drydock to fulfill) working copies with a patch from a staging area. Doing this means we can do builds on in-review changes from `arc diff`. This is a little cobbled-together but should basically work. Also fix some other issues: - Yielded, awakend workers are fine to update but could complain. - We can't log slot lock failures to resources if we don't end up saving them. - Killing the transaction would wipe out the log. - Fix some TODOs, etc. Test Plan: Ran Harbormaster builds on a local revision. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14214 --- .../differential/storage/DifferentialDiff.php | 11 ++++++ .../DrydockBlueprintImplementation.php | 12 ++++--- ...dockWorkingCopyBlueprintImplementation.php | 16 +++++++++ .../drydock/storage/DrydockBlueprint.php | 11 ++++++ .../drydock/storage/DrydockLease.php | 25 ++++++++------ .../drydock/storage/DrydockResource.php | 31 ++++++++++------- .../worker/DrydockLeaseUpdateWorker.php | 7 ++-- ...easeWorkingCopyBuildStepImplementation.php | 34 ++++++++++++++++--- .../storage/PhabricatorWorkerActiveTask.php | 26 +++++++++----- 9 files changed, 130 insertions(+), 43 deletions(-) diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 25421bafe6..7b3a31635a 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -446,6 +446,13 @@ final class DifferentialDiff $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); + + // TODO: We're just hoping to get lucky. Instead, `arc` should store + // where it sent changes and we should only provide staging details + // if we reasonably believe they are accurate. + $staging_ref = 'refs/tags/phabricator/diff/'.$this->getID(); + $results['repository.staging.uri'] = $repo->getStagingURI(); + $results['repository.staging.ref'] = $staging_ref; } } @@ -466,6 +473,10 @@ final class DifferentialDiff pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => pht('The URI to clone or checkout the repository from.'), + 'repository.staging.uri' => + pht('The URI of the staging repository.'), + 'repository.staging.ref' => + pht('The ref name for this change in the staging repository.'), ); } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 5ec2d60ae5..513d7b37f9 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -295,14 +295,18 @@ abstract class DrydockBlueprintImplementation extends Phobject { $lease_status = $lease->getStatus(); switch ($lease_status) { + case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRED: - // TODO: Temporary failure. - throw new Exception(pht('Lease still activating.')); + throw new PhabricatorWorkerYieldException(15); case DrydockLeaseStatus::STATUS_ACTIVE: return; default: - // TODO: Permanent failure. - throw new Exception(pht('Lease in bad state.')); + throw new Exception( + pht( + 'Lease ("%s") is in bad state ("%s"), expected "%s".', + $lease->getPHID(), + $lease_status, + DrydockLeaseStatus::STATUS_ACTIVE)); } } diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 1c6b71a298..7a34e2d3bc 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -216,6 +216,8 @@ final class DrydockWorkingCopyBlueprintImplementation $commit = idx($spec, 'commit'); $branch = idx($spec, 'branch'); + $ref = idx($spec, 'ref'); + if ($commit !== null) { $cmd[] = 'git reset --hard %s'; $arg[] = $commit; @@ -225,6 +227,20 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git reset --hard origin/%s'; $arg[] = $branch; + } else if ($ref) { + $ref_uri = $ref['uri']; + $ref_ref = $ref['ref']; + + $cmd[] = 'git fetch --no-tags -- %s +%s:%s'; + $arg[] = $ref_uri; + $arg[] = $ref_ref; + $arg[] = $ref_ref; + + $cmd[] = 'git checkout %s'; + $arg[] = $ref_ref; + + $cmd[] = 'git reset --hard %s'; + $arg[] = $ref_ref; } else { $cmd[] = 'git reset --hard HEAD'; } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index a0d440e4ea..4bc3dbe86d 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -107,6 +107,17 @@ final class DrydockBlueprint extends DrydockDAO return $this->fields; } + public function logEvent($type, array $data = array()) { + $log = id(new DrydockLog()) + ->setEpoch(PhabricatorTime::getNow()) + ->setType($type) + ->setData($data); + + $log->setBlueprintPHID($this->getPHID()); + + return $log->save(); + } + /* -( Allocating Resources )----------------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index a734137653..aa2ea753f0 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -233,18 +233,21 @@ final class DrydockLease extends DrydockDAO $this->openTransaction(); try { - try { - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - } catch (DrydockSlotLockException $ex) { - $this->logEvent( - DrydockSlotLockFailureLogType::LOGCONST, - array( - 'locks' => $ex->getLockMap(), - )); - throw $ex; - } + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->killTransaction(); + $this->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + + throw $ex; + } + + try { $this ->setResourcePHID($resource->getPHID()) ->attachResource($resource) diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 7aad1064f4..da97a089d9 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -148,19 +148,25 @@ final class DrydockResource extends DrydockDAO } $this->openTransaction(); - try { - try { - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - } catch (DrydockSlotLockException $ex) { - $this->logEvent( - DrydockSlotLockFailureLogType::LOGCONST, - array( - 'locks' => $ex->getLockMap(), - )); - throw $ex; - } + try { + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->killTransaction(); + + // NOTE: We have to log this on the blueprint, as the resource is not + // going to be saved so the PHID will vanish. + $this->getBlueprint()->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + + throw $ex; + } + + try { $this ->setStatus($new_status) ->save(); @@ -168,6 +174,7 @@ final class DrydockResource extends DrydockDAO $this->killTransaction(); throw $ex; } + $this->saveTransaction(); $this->isAllocated = true; diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 4ac6a9775c..b6ac6cec52 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -660,9 +660,10 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST); $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - - $blueprint->didReleaseLease($resource, $lease); + if ($resource) { + $blueprint = $resource->getBlueprint(); + $blueprint->didReleaseLease($resource, $lease); + } $this->destroyLease($lease); } diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php index f10f1a4a50..a1486af257 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -113,6 +113,13 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation $variables = $build_target->getVariables(); $repository_phid = idx($variables, 'repository.phid'); + if (!$repository_phid) { + throw new Exception( + pht( + 'Unable to determine how to clone the repository for this '. + 'buildable: it is not associated with a tracked repository.')); + } + $also_phids = $build_target->getFieldValue('repositoryPHIDs'); $all_phids = $also_phids; @@ -133,8 +140,6 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation } } - $commit = idx($variables, 'repository.commit'); - $map = array(); foreach ($also_phids as $also_phid) { @@ -147,12 +152,33 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation $repository = $repositories[$repository_phid]; + $commit = idx($variables, 'repository.commit'); + $ref_uri = idx($variables, 'repository.staging.uri'); + $ref_ref = idx($variables, 'repository.staging.ref'); + if ($commit) { + $spec = array( + 'commit' => $commit, + ); + } else if ($ref_uri && $ref_ref) { + $spec = array( + 'ref' => array( + 'uri' => $ref_uri, + 'ref' => $ref_ref, + ), + ); + } else { + throw new Exception( + pht( + 'Unable to determine how to fetch changes: this buildable does not '. + 'identify a commit or a staging ref. You may need to configure a '. + 'repository staging area.')); + } + $directory = $repository->getCloneName(); $map[$directory] = array( 'phid' => $repository->getPHID(), - 'commit' => $commit, 'default' => true, - ); + ) + $spec; return $map; } diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php index 069b237e4d..6c2f5b4461 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php @@ -86,15 +86,23 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask { } protected function checkLease() { - if ($this->leaseOwner) { - $current_server_time = $this->serverTime + (time() - $this->localTime); - if ($current_server_time >= $this->leaseExpires) { - throw new Exception( - pht( - 'Trying to update Task %d (%s) after lease expiration!', - $this->getID(), - $this->getTaskClass())); - } + $owner = $this->leaseOwner; + + if (!$owner) { + return; + } + + if ($owner == PhabricatorWorker::YIELD_OWNER) { + return; + } + + $current_server_time = $this->serverTime + (time() - $this->localTime); + if ($current_server_time >= $this->leaseExpires) { + throw new Exception( + pht( + 'Trying to update Task %d (%s) after lease expiration!', + $this->getID(), + $this->getTaskClass())); } } From c95fcb8970ca649b7b2b3e0a084450755f7ab5f0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 16:55:24 -0700 Subject: [PATCH 35/43] Add a little Drydock documentation Summary: Ref T9252. Provide some general descriptions of Drydock in the docs. Test Plan: Reading. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14215 --- src/docs/user/userguide/drydock.diviner | 58 +++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/docs/user/userguide/drydock.diviner b/src/docs/user/userguide/drydock.diviner index 2ba4f241fd..a03346f1cb 100644 --- a/src/docs/user/userguide/drydock.diviner +++ b/src/docs/user/userguide/drydock.diviner @@ -1,8 +1,60 @@ @title Drydock User Guide @group userguide -Configuring Drydock for machine resource management. +Drydock, a software and hardware resource manager. -= Overview = +Overview +======== + +WARNING: Drydock is very new and has many sharp edges. Prepare yourself for +a challenging adventure in unmapped territory, not a streamlined experience +where things work properly or make sense. + +Drydock is an infrastructure application that primarily helps other +applications coordinate during complex build and deployment tasks. Typically, +you will configure Drydock to enable capabilities in other applications: + + - Harbormaster can use Drydock to host builds. + - In the future, Differential will be able to use Drydock to perform + server-side merges. + +Users will not normally interact with Drydock directly. + + +What Drydock Does +================= + +Drydock manages working copies, build hosts, and other software and hardware +resources that build and deployment processes may require in order to perform +useful work. + +Many useful processes need a working copy of a repository (or some similar sort +of resource) so they can read files, perform version control operations, or +execute code. + +For example, you might want to be able to automatically run unit tests, build a +binary, or generate documentation every time a new commit is pushed. Or you +might want to automatically merge a revision or cherry-pick a commit from a +development branch to a release branch. Any of these tasks need a working copy +of the repository before they can get underway. + +These processes could just clone a new working copy when they started and +delete it when they finished. This works reasonably well at a small scale, but +will eventually hit limitations if you want to do things like: expand the build +tier to multiple machines; or automatically scale the tier up and down based on +usage; or reuse working copies to improve performance; or make sure things get +cleaned up after a process fails; or have jobs wait if the tier is too busy. +Solving these problems effectively requires coordination between the processes +doing the actual work. + +Drydock solves these scaling problems by providing a central allocation +framework for //resources//, which are physical or virtual resources like a +build host or a working copy. Processes which need to share hardware or +software can use Drydock to coordinate creation, access, and destruction of +those resources. + +Applications ask Drydock for resources matching a description, and it allocates +a corresponding resource by either finding a suitable unused resource or +creating a new resource. When work completes, the resource is returned to the +resource pool or destroyed. -NOTE: Drydock is extremely new and not very useful yet. From e431ab2189239487ebf37ba0cee6e5392194ec7c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 16:56:21 -0700 Subject: [PATCH 36/43] Use getPhobjectClassConstant() to access class constants Summary: Ref T9494. Depends on D14216. Remove 10 copies of this code. Test Plan: Ran `arc unit --everything`, browsed Config > Modules, clicked around Herald / etc. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9494 Differential Revision: https://secure.phabricator.com/D14217 --- .../drydock/logtype/DrydockLogType.php | 30 +------------------ .../artifact/HarbormasterArtifact.php | 30 +------------------ .../stepgroup/HarbormasterBuildStepGroup.php | 14 +-------- .../herald/action/HeraldAction.php | 30 +------------------ .../herald/action/HeraldActionGroup.php | 14 +-------- src/applications/herald/field/HeraldField.php | 28 ++--------------- .../herald/field/HeraldFieldGroup.php | 14 +-------- .../phid/type/PhabricatorPHIDType.php | 12 +------- .../PhabricatorPolicyCapability.php | 24 +-------------- .../edges/type/PhabricatorEdgeType.php | 12 +------- 10 files changed, 12 insertions(+), 196 deletions(-) diff --git a/src/applications/drydock/logtype/DrydockLogType.php b/src/applications/drydock/logtype/DrydockLogType.php index aa1f4fc4e0..7faab42dfe 100644 --- a/src/applications/drydock/logtype/DrydockLogType.php +++ b/src/applications/drydock/logtype/DrydockLogType.php @@ -28,35 +28,7 @@ abstract class DrydockLogType extends Phobject { } final public function getLogTypeConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('LOGCONST'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'LOGCONST')); - } - - $limit = self::getLogTypeConstantByteLimit(); - if (!is_string($const) || (strlen($const) > $limit)) { - throw new Exception( - pht( - '"%s" class "%s" has an invalid "%s" property. Field constants '. - 'must be strings and no more than %s bytes in length.', - __CLASS__, - get_class($this), - 'LOGCONST', - new PhutilNumber($limit))); - } - - return $const; - } - - final private static function getLogTypeConstantByteLimit() { - return 64; + return $this->getPhobjectClassConstant('LOGCONST', 64); } final public static function getAllLogTypes() { diff --git a/src/applications/harbormaster/artifact/HarbormasterArtifact.php b/src/applications/harbormaster/artifact/HarbormasterArtifact.php index a0d3a86101..8d3d8dd169 100644 --- a/src/applications/harbormaster/artifact/HarbormasterArtifact.php +++ b/src/applications/harbormaster/artifact/HarbormasterArtifact.php @@ -43,35 +43,7 @@ abstract class HarbormasterArtifact extends Phobject { } final public function getArtifactConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('ARTIFACTCONST'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'ARTIFACTCONST')); - } - - $limit = self::getArtifactConstantByteLimit(); - if (!is_string($const) || (strlen($const) > $limit)) { - throw new Exception( - pht( - '"%s" class "%s" has an invalid "%s" property. Action constants '. - 'must be strings and no more than %s bytes in length.', - __CLASS__, - get_class($this), - 'ARTIFACTCONST', - new PhutilNumber($limit))); - } - - return $const; - } - - final public static function getArtifactConstantByteLimit() { - return 32; + return $this->getPhobjectClassConstant('ARTIFACTCONST', 32); } final public static function getAllArtifactTypes() { diff --git a/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php index bb439b75b4..5806861836 100644 --- a/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php +++ b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php @@ -14,19 +14,7 @@ abstract class HarbormasterBuildStepGroup extends Phobject { } final public function getGroupKey() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('GROUPKEY'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'GROUPKEY')); - } - - return $const; + return $this->getPhobjectClassConstant('GROUPKEY'); } final public static function getAllGroups() { diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index 02a9dfa60a..f4217cd4db 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -122,35 +122,7 @@ abstract class HeraldAction extends Phobject { } final public function getActionConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('ACTIONCONST'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'ACTIONCONST')); - } - - $limit = self::getActionConstantByteLimit(); - if (!is_string($const) || (strlen($const) > $limit)) { - throw new Exception( - pht( - '"%s" class "%s" has an invalid "%s" property. Action constants '. - 'must be strings and no more than %s bytes in length.', - __CLASS__, - get_class($this), - 'ACTIONCONST', - new PhutilNumber($limit))); - } - - return $const; - } - - final public static function getActionConstantByteLimit() { - return 64; + return $this->getPhobjectClassConstant('ACTIONCONST', 64); } final public static function getAllActions() { diff --git a/src/applications/herald/action/HeraldActionGroup.php b/src/applications/herald/action/HeraldActionGroup.php index a087909609..ad4fecd6b6 100644 --- a/src/applications/herald/action/HeraldActionGroup.php +++ b/src/applications/herald/action/HeraldActionGroup.php @@ -3,19 +3,7 @@ abstract class HeraldActionGroup extends HeraldGroup { final public function getGroupKey() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('ACTIONGROUPKEY'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'ACTIONGROUPKEY')); - } - - return $const; + return $this->getPhobjectClassConstant('ACTIONGROUPKEY'); } final public static function getAllActionGroups() { diff --git a/src/applications/herald/field/HeraldField.php b/src/applications/herald/field/HeraldField.php index a6d2e580c2..2aba443077 100644 --- a/src/applications/herald/field/HeraldField.php +++ b/src/applications/herald/field/HeraldField.php @@ -169,31 +169,9 @@ abstract class HeraldField extends Phobject { } final public function getFieldConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('FIELDCONST'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'FIELDCONST')); - } - - $limit = self::getFieldConstantByteLimit(); - if (!is_string($const) || (strlen($const) > $limit)) { - throw new Exception( - pht( - '"%s" class "%s" has an invalid "%s" property. Field constants '. - 'must be strings and no more than %s bytes in length.', - __CLASS__, - get_class($this), - 'FIELDCONST', - new PhutilNumber($limit))); - } - - return $const; + return $this->getPhobjectClassConstant( + 'FIELDCONST', + self::getFieldConstantByteLimit()); } final public static function getFieldConstantByteLimit() { diff --git a/src/applications/herald/field/HeraldFieldGroup.php b/src/applications/herald/field/HeraldFieldGroup.php index adb7fbe372..9e13f17cb7 100644 --- a/src/applications/herald/field/HeraldFieldGroup.php +++ b/src/applications/herald/field/HeraldFieldGroup.php @@ -3,19 +3,7 @@ abstract class HeraldFieldGroup extends HeraldGroup { final public function getGroupKey() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('FIELDGROUPKEY'); - if ($const === false) { - throw new Exception( - pht( - '"%s" class "%s" must define a "%s" property.', - __CLASS__, - get_class($this), - 'FIELDGROUPKEY')); - } - - return $const; + return $this->getPhobjectClassConstant('FIELDGROUPKEY'); } final public static function getAllFieldGroups() { diff --git a/src/applications/phid/type/PhabricatorPHIDType.php b/src/applications/phid/type/PhabricatorPHIDType.php index c69075bdad..a8502b12f7 100644 --- a/src/applications/phid/type/PhabricatorPHIDType.php +++ b/src/applications/phid/type/PhabricatorPHIDType.php @@ -3,17 +3,7 @@ abstract class PhabricatorPHIDType extends Phobject { final public function getTypeConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('TYPECONST'); - if ($const === false) { - throw new Exception( - pht( - '%s class "%s" must define a %s property.', - __CLASS__, - get_class($this), - 'TYPECONST')); - } + $const = $this->getPhobjectClassConstant('TYPECONST'); if (!is_string($const) || !preg_match('/^[A-Z]{4}$/', $const)) { throw new Exception( diff --git a/src/applications/policy/capability/PhabricatorPolicyCapability.php b/src/applications/policy/capability/PhabricatorPolicyCapability.php index b7ff6a060b..36b8ea87c0 100644 --- a/src/applications/policy/capability/PhabricatorPolicyCapability.php +++ b/src/applications/policy/capability/PhabricatorPolicyCapability.php @@ -15,29 +15,7 @@ abstract class PhabricatorPolicyCapability extends Phobject { * @return string Globally unique capability key. */ final public function getCapabilityKey() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('CAPABILITY'); - if ($const === false) { - throw new Exception( - pht( - '%s class "%s" must define a %s property.', - __CLASS__, - get_class($this), - 'CAPABILITY')); - } - - if (!is_string($const)) { - throw new Exception( - pht( - '%s class "%s" has an invalid %s property. '. - 'Capability constants must be a string.', - __CLASS__, - get_class($this), - 'CAPABILITY')); - } - - return $const; + return $this->getPhobjectClassConstant('CAPABILITY'); } diff --git a/src/infrastructure/edges/type/PhabricatorEdgeType.php b/src/infrastructure/edges/type/PhabricatorEdgeType.php index c86af5c027..1729256050 100644 --- a/src/infrastructure/edges/type/PhabricatorEdgeType.php +++ b/src/infrastructure/edges/type/PhabricatorEdgeType.php @@ -12,17 +12,7 @@ abstract class PhabricatorEdgeType extends Phobject { final public function getEdgeConstant() { - $class = new ReflectionClass($this); - - $const = $class->getConstant('EDGECONST'); - if ($const === false) { - throw new Exception( - pht( - '%s class "%s" must define an %s property.', - __CLASS__, - get_class($this), - 'EDGECONST')); - } + $const = $this->getPhobjectClassConstant('EDGECONST'); if (!is_int($const) || ($const <= 0)) { throw new Exception( From 878a493301d25b662c6283e21d5fd20841166060 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 1 Oct 2015 16:58:43 -0700 Subject: [PATCH 37/43] Begin standardizing garbage collectors Summary: Ref T9494. Improve support infrastructure for garbage collectors. Test Plan: - Ran `bin/phd debug trigger`, saw collectors execute. {F857852} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9494 Differential Revision: https://secure.phabricator.com/D14218 --- src/__phutil_library_map__.php | 2 + ...PhabricatorAuthSessionGarbageCollector.php | 10 +++ ...atorAuthTemporaryTokenGarbageCollector.php | 10 +++ ...habricatorCacheGeneralGarbageCollector.php | 10 +++ ...PhabricatorCacheMarkupGarbageCollector.php | 10 +++ .../PhabricatorCacheTTLGarbageCollector.php | 10 +++ .../ConduitConnectionGarbageCollector.php | 10 +++ .../ConduitLogGarbageCollector.php | 10 +++ .../ConduitTokenGarbageCollector.php | 10 +++ .../PhabricatorConfigCollectorsModule.php | 61 ++++++++++++++++ ...bricatorDaemonLogEventGarbageCollector.php | 10 +++ .../PhabricatorDaemonLogGarbageCollector.php | 10 +++ .../PhabricatorDaemonTaskGarbageCollector.php | 10 +++ ...DifferentialParseCacheGarbageCollector.php | 10 +++ .../DrydockLogGarbageCollector.php | 10 +++ ...abricatorFileTemporaryGarbageCollector.php | 10 +++ .../HeraldTranscriptGarbageCollector.php | 10 +++ .../MetaMTAMailReceivedGarbageCollector.php | 10 +++ .../MetaMTAMailSentGarbageCollector.php | 10 +++ .../MultimeterEventGarbageCollector.php | 10 +++ .../FeedStoryNotificationGarbageCollector.php | 10 +++ .../PeopleUserLogGarbageCollector.php | 10 +++ ...habricatorSystemActionGarbageCollector.php | 10 +++ ...catorSystemDestructionGarbageCollector.php | 10 +++ .../PhabricatorGarbageCollector.php | 73 +++++++++++++++++++ .../workers/PhabricatorTriggerDaemon.php | 18 +---- .../PhabricatorUSEnglishTranslation.php | 5 ++ 27 files changed, 364 insertions(+), 15 deletions(-) create mode 100644 src/applications/config/module/PhabricatorConfigCollectorsModule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f816856112..8f8bc085fd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1829,6 +1829,7 @@ phutil_register_library_map(array( 'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php', 'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php', 'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php', + 'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php', 'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php', 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', @@ -5761,6 +5762,7 @@ phutil_register_library_map(array( 'PhabricatorConfigAllController' => 'PhabricatorConfigController', 'PhabricatorConfigApplication' => 'PhabricatorApplication', 'PhabricatorConfigCacheController' => 'PhabricatorConfigController', + 'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule', 'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConfigController' => 'PhabricatorController', diff --git a/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php b/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php index f9994c2fd6..3e47199a4b 100644 --- a/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php +++ b/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorAuthSessionGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'auth.sessions'; + + public function getCollectorName() { + return pht('Auth Sessions'); + } + + public function hasAutomaticPolicy() { + return true; + } + public function collectGarbage() { $session_table = new PhabricatorAuthSession(); $conn_w = $session_table->establishConnection('w'); diff --git a/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php b/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php index 49d1ffb05b..6f6d6c3bb6 100644 --- a/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php +++ b/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorAuthTemporaryTokenGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'auth.tokens'; + + public function getCollectorName() { + return pht('Auth Tokens'); + } + + public function hasAutomaticPolicy() { + return true; + } + public function collectGarbage() { $session_table = new PhabricatorAuthTemporaryToken(); $conn_w = $session_table->establishConnection('w'); diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php index 29ad8ffb18..425ee86b8a 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorCacheGeneralGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'cache.general'; + + public function getCollectorName() { + return pht('General Cache'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('30 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.general-cache'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php index 75b25e2530..cdabac577e 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorCacheMarkupGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'cache.markup'; + + public function getCollectorName() { + return pht('Markup Cache'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('30 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.markup-cache'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php index c459553396..b0e5b6908e 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorCacheTTLGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'cache.general.ttl'; + + public function getCollectorName() { + return pht('General Cache (TTL)'); + } + + public function hasAutomaticPolicy() { + return true; + } + public function collectGarbage() { $cache = new PhabricatorKeyValueDatabaseCache(); $conn_w = $cache->establishConnection('w'); diff --git a/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php index 14467af2bf..9bc7673240 100644 --- a/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php @@ -3,6 +3,16 @@ final class ConduitConnectionGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'conduit.connections'; + + public function getCollectorName() { + return pht('Conduit Connections'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('180 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.conduit-logs'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php index 9ed39d5832..e5d426b30c 100644 --- a/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php @@ -3,6 +3,16 @@ final class ConduitLogGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'conduit.logs'; + + public function getCollectorName() { + return pht('Conduit Logs'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('180 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.conduit-logs'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php index f57a23e3bb..b3571713b4 100644 --- a/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php @@ -3,6 +3,16 @@ final class ConduitTokenGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'conduit.tokens'; + + public function getCollectorName() { + return pht('Conduit Tokens'); + } + + public function hasAutomaticPolicy() { + return true; + } + public function collectGarbage() { $table = new PhabricatorConduitToken(); $conn_w = $table->establishConnection('w'); diff --git a/src/applications/config/module/PhabricatorConfigCollectorsModule.php b/src/applications/config/module/PhabricatorConfigCollectorsModule.php new file mode 100644 index 0000000000..729b6a2a18 --- /dev/null +++ b/src/applications/config/module/PhabricatorConfigCollectorsModule.php @@ -0,0 +1,61 @@ +getViewer(); + + $collectors = PhabricatorGarbageCollector::getAllCollectors(); + $collectors = msort($collectors, 'getCollectorConstant'); + + $rows = array(); + foreach ($collectors as $key => $collector) { + if ($collector->hasAutomaticPolicy()) { + $policy_view = phutil_tag('em', array(), pht('Automatic')); + } else { + $policy = $collector->getDefaultRetentionPolicy(); + if ($policy === null) { + $policy_view = pht('Indefinite'); + } else { + $days = ceil($policy / phutil_units('1 day in seconds')); + $policy_view = pht( + '%s Day(s)', + new PhutilNumber($days)); + } + } + + $rows[] = array( + $collector->getCollectorConstant(), + $collector->getCollectorName(), + $policy_view, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Constant'), + pht('Name'), + pht('Retention Policy'), + )) + ->setColumnClasses( + array( + null, + 'pri wide', + null, + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Garbage Collectors')) + ->setTable($table); + } + +} diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php index c5a502c599..5d02b7075b 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorDaemonLogEventGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'daemon.processes'; + + public function getCollectorName() { + return pht('Daemon Processes'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('7 days in seconds'); + } + public function collectGarbage() { $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs'); if ($ttl <= 0) { diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php index bd8b92bf92..c278349e33 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorDaemonLogGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'daemon.logs'; + + public function getCollectorName() { + return pht('Daemon Logs'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('7 days in seconds'); + } + public function collectGarbage() { $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs'); if ($ttl <= 0) { diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php index 9f320289ab..7cbab418a1 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorDaemonTaskGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'worker.tasks'; + + public function getCollectorName() { + return pht('Archived Tasks'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('14 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.task-archive'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php b/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php index 08cdfe97db..f6dde8a685 100644 --- a/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php +++ b/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php @@ -3,6 +3,16 @@ final class DifferentialParseCacheGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'differential.parse'; + + public function getCollectorName() { + return pht('Differential Parse Cache'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('14 days in seconds'); + } + public function collectGarbage() { $key = 'gcdaemon.ttl.differential-parse-cache'; $ttl = PhabricatorEnv::getEnvConfig($key); diff --git a/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php index 6cae627a74..e13ced3b36 100644 --- a/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php +++ b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php @@ -3,6 +3,16 @@ final class DrydockLogGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'drydock.logs'; + + public function getCollectorName() { + return pht('Drydock Logs'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('30 days in seconds'); + } + public function collectGarbage() { $log_table = new DrydockLog(); $conn_w = $log_table->establishConnection('w'); diff --git a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php index 624ec01097..ed246eecf3 100644 --- a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php +++ b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorFileTemporaryGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'files.ttl'; + + public function getCollectorName() { + return pht('Files (TTL)'); + } + + public function hasAutomaticPolicy() { + return true; + } + public function collectGarbage() { $files = id(new PhabricatorFile())->loadAllWhere( 'ttl < %d LIMIT 100', diff --git a/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php b/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php index 583778e535..ba8a97dd3a 100644 --- a/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php +++ b/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php @@ -3,6 +3,16 @@ final class HeraldTranscriptGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'herald.transcripts'; + + public function getCollectorName() { + return pht('Herald Transcripts'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('30 days in seconds'); + } + public function collectGarbage() { $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.herald-transcripts'); if ($ttl <= 0) { diff --git a/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php b/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php index 02911354b5..17b2b1705b 100644 --- a/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php +++ b/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php @@ -3,6 +3,16 @@ final class MetaMTAMailReceivedGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'metamta.received'; + + public function getCollectorName() { + return pht('Mail (Received)'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('90 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('90 days in seconds'); diff --git a/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php b/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php index 99103a2f2e..80c6f8a48d 100644 --- a/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php +++ b/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php @@ -3,6 +3,16 @@ final class MetaMTAMailSentGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'metamta.sent'; + + public function getCollectorName() { + return pht('Mail (Sent)'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('90 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('90 days in seconds'); diff --git a/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php b/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php index 3b8095ce05..bc9ade1091 100644 --- a/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php +++ b/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php @@ -3,6 +3,16 @@ final class MultimeterEventGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'multimeter.events'; + + public function getCollectorName() { + return pht('Multimeter Events'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('90 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('90 days in seconds'); diff --git a/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php b/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php index 5b74237205..48636c40de 100644 --- a/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php +++ b/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php @@ -3,6 +3,16 @@ final class FeedStoryNotificationGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'feed.notifications'; + + public function getCollectorName() { + return pht('Notifications'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('90 days in seconds'); + } + public function collectGarbage() { $ttl = 90 * 24 * 60 * 60; diff --git a/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php b/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php index 1d8d13a81e..19382f242d 100644 --- a/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php +++ b/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php @@ -3,6 +3,16 @@ final class PeopleUserLogGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'user.logs'; + + public function getCollectorName() { + return pht('User Activity Logs'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('180 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('180 days in seconds'); diff --git a/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php b/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php index 9b9728ac8c..9af6af663c 100644 --- a/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php +++ b/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorSystemActionGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'system.actions'; + + public function getCollectorName() { + return pht('Rate Limiting Actions'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('3 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('3 days in seconds'); diff --git a/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php b/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php index ad068cd5c9..3e78e29f9e 100644 --- a/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php +++ b/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php @@ -3,6 +3,16 @@ final class PhabricatorSystemDestructionGarbageCollector extends PhabricatorGarbageCollector { + const COLLECTORCONST = 'system.destruction.logs'; + + public function getCollectorName() { + return pht('Destruction Logs'); + } + + public function getDefaultRetentionPolicy() { + return phutil_units('90 days in seconds'); + } + public function collectGarbage() { $ttl = phutil_units('90 days in seconds'); diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php index 5aea82c845..18606028f3 100644 --- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php +++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php @@ -1,12 +1,85 @@ getPhobjectClassConstant('COLLECTORCONST', 64); + } + + +/* -( Collecting Garbage )------------------------------------------------- */ + + /** * Collect garbage from whatever source this GC handles. * * @return bool True if there is more garbage to collect. + * @task collect */ abstract public function collectGarbage(); + + /** + * Load all of the available garbage collectors. + * + * @return list Garbage collectors. + * @task collect + */ + final public static function getAllCollectors() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getCollectorConstant') + ->execute(); + } + } diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php index 9c40aba315..4748ff4f74 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php @@ -348,7 +348,9 @@ final class PhabricatorTriggerDaemon $next = $this->nextCollection; if ($next && (PhabricatorTime::getNow() >= $next)) { $this->nextCollection = null; - $this->garbageCollectors = $this->loadGarbageCollectors(); + + $all_collectors = PhabricatorGarbageCollector::getAllCollectors(); + $this->garbageCollectors = $all_collectors; } // If we're in a collection cycle, continue collection. @@ -377,18 +379,4 @@ final class PhabricatorTriggerDaemon return false; } - - /** - * Load all of the available garbage collectors. - * - * @return list Garbage collectors. - * @task garbage - */ - private function loadGarbageCollectors() { - return id(new PhutilClassMapQuery()) - ->setAncestorClass('PhabricatorGarbageCollector') - ->execute(); - } - - } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 7a5f8b78dd..9898f14a99 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1388,6 +1388,11 @@ final class PhabricatorUSEnglishTranslation 'Mail sent in the last %s days.', ), + '%s Day(s)' => array( + '%s Day', + '%s Days', + ), + ); } From 2728a9f9649e3be6abef5001d81da536a2106415 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 06:32:08 -0700 Subject: [PATCH 38/43] Allow builds to have parameters Summary: Ref T9352. See D13635. Build targets can have variables already, but let builds have them too. This mostly enables future use cases (sub-builds, more sophisticated build triggers). Test Plan: With a custom Herald rule + action like the one in T9352, updated a revision and saw it generate multiple builds with varying parameters. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9352 Differential Revision: https://secure.phabricator.com/D14222 --- .../20151002.harbormaster.bparam.1.sql | 5 +++ src/__phutil_library_map__.php | 2 + .../HeraldDifferentialRevisionAdapter.php | 11 ++--- .../diffusion/herald/HeraldCommitAdapter.php | 11 ++--- .../HarbormasterPlanRunController.php | 2 +- .../engine/HarbormasterBuildRequest.php | 37 ++++++++++++++++ .../engine/HarbormasterTargetEngine.php | 2 +- .../HarbormasterBuildableAdapterInterface.php | 5 ++- .../HarbormasterRunBuildPlansHeraldAction.php | 4 +- .../HarbormasterManagementBuildWorkflow.php | 2 +- .../HarbormasterBuildStepImplementation.php | 2 +- .../storage/HarbormasterBuildable.php | 42 +++++++++++++++---- .../storage/build/HarbormasterBuild.php | 8 ++++ ...habricatorApplicationTransactionEditor.php | 2 +- 14 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 resources/sql/autopatches/20151002.harbormaster.bparam.1.sql create mode 100644 src/applications/harbormaster/engine/HarbormasterBuildRequest.php diff --git a/resources/sql/autopatches/20151002.harbormaster.bparam.1.sql b/resources/sql/autopatches/20151002.harbormaster.bparam.1.sql new file mode 100644 index 0000000000..0deb71c503 --- /dev/null +++ b/resources/sql/autopatches/20151002.harbormaster.bparam.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build + ADD buildParameters LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; + +UPDATE {$NAMESPACE}_harbormaster.harbormaster_build + SET buildParameters = '{}' WHERE buildParameters = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8f8bc085fd..6fed2b21bd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -972,6 +972,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php', 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', + 'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php', 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', @@ -4757,6 +4758,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildRequest' => 'Phobject', 'HarbormasterBuildStep' => array( 'HarbormasterDAO', 'PhabricatorApplicationTransactionInterface', diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index a47d433357..d7b8c5dd33 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -10,7 +10,7 @@ final class HeraldDifferentialRevisionAdapter protected $changesets; private $haveHunks; - private $buildPlanPHIDs = array(); + private $buildRequests = array(); public function getAdapterApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -139,12 +139,13 @@ final class HeraldDifferentialRevisionAdapter return $this->getObject()->getPHID(); } - public function getQueuedHarbormasterBuildPlanPHIDs() { - return $this->buildPlanPHIDs; + public function getQueuedHarbormasterBuildRequests() { + return $this->buildRequests; } - public function queueHarbormasterBuildPlanPHID($phid) { - $this->buildPlanPHIDs[] = $phid; + public function queueHarbormasterBuildRequest( + HarbormasterBuildRequest $request) { + $this->buildRequests[] = $request; } } diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index ed82e90820..6530b72e6f 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -17,7 +17,7 @@ final class HeraldCommitAdapter protected $affectedPackages; protected $auditNeededPackages; - private $buildPlanPHIDs = array(); + private $buildRequests = array(); public function getAdapterApplicationClass() { return 'PhabricatorDiffusionApplication'; @@ -308,12 +308,13 @@ final class HeraldCommitAdapter return $this->getObject()->getRepository()->getPHID(); } - public function getQueuedHarbormasterBuildPlanPHIDs() { - return $this->buildPlanPHIDs; + public function getQueuedHarbormasterBuildRequests() { + return $this->buildRequests; } - public function queueHarbormasterBuildPlanPHID($phid) { - $this->buildPlanPHIDs[] = $phid; + public function queueHarbormasterBuildRequest( + HarbormasterBuildRequest $request) { + $this->buildRequests[] = $request; } } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index 980c7fad4b..be6d138cf7 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -62,7 +62,7 @@ final class HarbormasterPlanRunController extends HarbormasterController { if (!$errors) { $buildable->save(); - $buildable->applyPlan($plan); + $buildable->applyPlan($plan, array()); $buildable_uri = '/B'.$buildable->getID(); return id(new AphrontRedirectResponse())->setURI($buildable_uri); diff --git a/src/applications/harbormaster/engine/HarbormasterBuildRequest.php b/src/applications/harbormaster/engine/HarbormasterBuildRequest.php new file mode 100644 index 0000000000..1874f08b27 --- /dev/null +++ b/src/applications/harbormaster/engine/HarbormasterBuildRequest.php @@ -0,0 +1,37 @@ +buildPlanPHID = $build_plan_phid; + return $this; + } + + public function getBuildPlanPHID() { + return $this->buildPlanPHID; + } + + public function setBuildParameters(array $build_parameters) { + $this->buildParameters = $build_parameters; + return $this; + } + + public function getBuildParameters() { + return $this->buildParameters; + } + +} diff --git a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php index a8403385ac..b201891b6b 100644 --- a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php @@ -206,7 +206,7 @@ final class HarbormasterTargetEngine extends Phobject { // resource and "own" it, so we don't try to handle this, but may need // to be more careful here if use of autotargets expands. - $build = $buildable->applyPlan($plan); + $build = $buildable->applyPlan($plan, array()); PhabricatorWorker::setRunAllTasksInProcess(false); } catch (Exception $ex) { PhabricatorWorker::setRunAllTasksInProcess(false); diff --git a/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php b/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php index cf6cda8f95..6933306145 100644 --- a/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php +++ b/src/applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php @@ -4,8 +4,9 @@ interface HarbormasterBuildableAdapterInterface { public function getHarbormasterBuildablePHID(); public function getHarbormasterContainerPHID(); - public function getQueuedHarbormasterBuildPlanPHIDs(); - public function queueHarbormasterBuildPlanPHID($phid); + public function getQueuedHarbormasterBuildRequests(); + public function queueHarbormasterBuildRequest( + HarbormasterBuildRequest $request); } diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index 830598e812..db75b36d2f 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -31,7 +31,9 @@ final class HarbormasterRunBuildPlansHeraldAction $phids = array_fuse(array_keys($targets)); foreach ($phids as $phid) { - $adapter->queueHarbormasterBuildPlanPHID($phid); + $request = id(new HarbormasterBuildRequest()) + ->setBuildPlanPHID($phid); + $adapter->queueHarbormasterBuildRequest($request); } $this->logEffect(self::DO_BUILD, $phids); diff --git a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php index fc0f670633..bc6a52018c 100644 --- a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php +++ b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php @@ -89,7 +89,7 @@ final class HarbormasterManagementBuildWorkflow PhabricatorEnv::getProductionURI('/B'.$buildable->getID())); PhabricatorWorker::setRunAllTasksInProcess(true); - $buildable->applyPlan($plan); + $buildable->applyPlan($plan, array()); $console->writeOut("%s\n", pht('Done.')); diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index b47f1d00d1..87148b0830 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -194,7 +194,7 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { * @return string String with variables replaced safely into it. */ protected function mergeVariables($function, $pattern, array $variables) { - $regexp = '/\\$\\{(?P[a-z\\.]+)\\}/'; + $regexp = '@\\$\\{(?P[a-z\\./-]+)\\}@'; $matches = null; preg_match_all($regexp, $pattern, $matches); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 0376ebc890..e74471e879 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -96,15 +96,21 @@ final class HarbormasterBuildable extends HarbormasterDAO } /** - * Looks up the plan PHIDs and applies the plans to the specified - * object identified by it's PHID. + * Start builds for a given buildable. + * + * @param phid PHID of the object to build. + * @param phid Container PHID for the buildable. + * @param list List of builds to perform. + * @return void */ public static function applyBuildPlans( $phid, $container_phid, - array $plan_phids) { + array $requests) { - if (!$plan_phids) { + assert_instances_of($requests, 'HarbormasterBuildRequest'); + + if (!$requests) { return; } @@ -116,31 +122,49 @@ final class HarbormasterBuildable extends HarbormasterDAO return; } + $viewer = PhabricatorUser::getOmnipotentUser(); + $buildable = self::createOrLoadExisting( - PhabricatorUser::getOmnipotentUser(), + $viewer, $phid, $container_phid); + $plan_phids = mpull($requests, 'getBuildPlanPHID'); $plans = id(new HarbormasterBuildPlanQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withPHIDs($plan_phids) ->execute(); - foreach ($plans as $plan) { + $plans = mpull($plans, null, 'getPHID'); + + foreach ($requests as $request) { + $plan_phid = $request->getBuildPlanPHID(); + $plan = idx($plans, $plan_phid); + + if (!$plan) { + throw new Exception( + pht( + 'Failed to load build plan ("%s").', + $plan_phid)); + } + if ($plan->isDisabled()) { // TODO: This should be communicated more clearly -- maybe we should // create the build but set the status to "disabled" or "derelict". continue; } - $buildable->applyPlan($plan); + $parameters = $request->getBuildParameters(); + $buildable->applyPlan($plan, $parameters); } } - public function applyPlan(HarbormasterBuildPlan $plan) { + public function applyPlan(HarbormasterBuildPlan $plan, array $parameters) { + $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) + ->setBuildParameters($parameters) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING); $auto_key = $plan->getPlanAutoKey(); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 1c012b1842..154681d93f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -9,6 +9,7 @@ final class HarbormasterBuild extends HarbormasterDAO protected $buildPlanPHID; protected $buildStatus; protected $buildGeneration; + protected $buildParameters = array(); protected $planAutoKey; private $buildable = self::ATTACHABLE; @@ -156,6 +157,9 @@ final class HarbormasterBuild extends HarbormasterDAO protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'buildParameters' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'buildStatus' => 'text32', 'buildGeneration' => 'uint32', @@ -258,6 +262,10 @@ final class HarbormasterBuild extends HarbormasterDAO 'build.id' => null, ); + foreach ($this->getBuildParameters() as $key => $value) { + $results['build/'.$key] = $value; + } + $buildable = $this->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 851cc24cc0..2efd19a000 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -2837,7 +2837,7 @@ abstract class PhabricatorApplicationTransactionEditor HarbormasterBuildable::applyBuildPlans( $adapter->getHarbormasterBuildablePHID(), $adapter->getHarbormasterContainerPHID(), - $adapter->getQueuedHarbormasterBuildPlanPHIDs()); + $adapter->getQueuedHarbormasterBuildRequests()); } return array_merge( From bb4667cb8490633baf36097cd289cea29aca39e1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 06:37:17 -0700 Subject: [PATCH 39/43] Fix WorkingCopy step to read correct commit variables Summary: Ref T9252. This variable was always wrong but we fell back to just resetting to `HEAD` before. Use the correct variable name. Test Plan: Verified variable name. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14224 --- .../HarbormasterLeaseWorkingCopyBuildStepImplementation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php index a1486af257..001a981a60 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -152,7 +152,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation $repository = $repositories[$repository_phid]; - $commit = idx($variables, 'repository.commit'); + $commit = idx($variables, 'buildable.commit'); $ref_uri = idx($variables, 'repository.staging.uri'); $ref_ref = idx($variables, 'repository.staging.ref'); if ($commit) { From 9c798e5ccabb4a31004d86499dc883154abdada9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 09:17:24 -0700 Subject: [PATCH 40/43] Provide `bin/garbage` for interacting with garbage collection Summary: Fixes T9494. This: - Removes all the random GC.x.y.z config. - Puts it all in one place that's locked and which you use `bin/garbage set-policy ...` to adjust. - Makes every TTL-based GC configurable. - Simplifies the code in the actual GCs. Test Plan: - Ran `bin/garbage collect` to collect some garbage, until it stopped collecting. - Ran `bin/garbage set-policy ...` to shorten policy. Saw change in web UI. Ran `bin/garbage collect` again and saw it collect more garbage. - Set policy to indefinite and saw it not collect garabge. - Set policy to default and saw it reflected in web UI / `collect`. - Ran `bin/phd debug trigger` and saw all GCs fire with reasonable looking queries. - Read new docs. {F857928} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9494 Differential Revision: https://secure.phabricator.com/D14219 --- bin/garbage | 1 + scripts/setup/manage_garbage.php | 21 +++ src/__phutil_library_map__.php | 8 +- ...PhabricatorAuthSessionGarbageCollector.php | 4 +- ...atorAuthTemporaryTokenGarbageCollector.php | 4 +- ...habricatorCacheGeneralGarbageCollector.php | 10 +- ...PhabricatorCacheMarkupGarbageCollector.php | 10 +- .../PhabricatorCacheTTLGarbageCollector.php | 4 +- .../ConduitConnectionGarbageCollector.php | 11 +- .../ConduitLogGarbageCollector.php | 11 +- .../ConduitTokenGarbageCollector.php | 3 +- .../PhabricatorExtraConfigSetupCheck.php | 12 ++ .../PhabricatorConfigCollectorsModule.php | 22 ++- ...abricatorGarbageCollectorConfigOptions.php | 70 --------- .../option/PhabricatorPHDConfigOptions.php | 11 ++ ...bricatorDaemonLogEventGarbageCollector.php | 9 +- .../PhabricatorDaemonLogGarbageCollector.php | 9 +- .../PhabricatorDaemonTaskGarbageCollector.php | 12 +- ...DifferentialParseCacheGarbageCollector.php | 10 +- .../DrydockLogGarbageCollector.php | 7 +- ...abricatorFileTemporaryGarbageCollector.php | 4 +- .../HeraldTranscriptGarbageCollector.php | 9 +- .../MetaMTAMailReceivedGarbageCollector.php | 6 +- .../MetaMTAMailSentGarbageCollector.php | 6 +- .../MultimeterEventGarbageCollector.php | 6 +- .../FeedStoryNotificationGarbageCollector.php | 6 +- .../PeopleUserLogGarbageCollector.php | 6 +- ...habricatorSystemActionGarbageCollector.php | 6 +- ...catorSystemDestructionGarbageCollector.php | 6 +- .../configuration/managing_garbage.diviner | 68 +++++++++ .../PhabricatorGarbageCollector.php | 72 ++++++++- ...bageCollectorManagementCollectWorkflow.php | 50 +++++++ ...geCollectorManagementSetPolicyWorkflow.php | 141 ++++++++++++++++++ ...atorGarbageCollectorManagementWorkflow.php | 32 ++++ .../workers/PhabricatorTriggerDaemon.php | 2 +- .../PhabricatorUSEnglishTranslation.php | 5 + 36 files changed, 486 insertions(+), 188 deletions(-) create mode 120000 bin/garbage create mode 100755 scripts/setup/manage_garbage.php delete mode 100644 src/applications/config/option/PhabricatorGarbageCollectorConfigOptions.php create mode 100644 src/docs/user/configuration/managing_garbage.diviner create mode 100644 src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php create mode 100644 src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php create mode 100644 src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php diff --git a/bin/garbage b/bin/garbage new file mode 120000 index 0000000000..417438c09c --- /dev/null +++ b/bin/garbage @@ -0,0 +1 @@ +../scripts/setup/manage_garbage.php \ No newline at end of file diff --git a/scripts/setup/manage_garbage.php b/scripts/setup/manage_garbage.php new file mode 100755 index 0000000000..ba727eab60 --- /dev/null +++ b/scripts/setup/manage_garbage.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage garbage colletors')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorGarbageCollectorManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6fed2b21bd..61b4d1f390 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2197,7 +2197,9 @@ phutil_register_library_map(array( 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', - 'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php', + 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php', + 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php', + 'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php', 'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php', 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', 'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php', @@ -6197,7 +6199,9 @@ phutil_register_library_map(array( 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorGarbageCollector' => 'Phobject', - 'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', + 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', + 'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorGestureUIExample' => 'PhabricatorUIExample', 'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream', 'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider', diff --git a/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php b/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php index 3e47199a4b..de9b704d7a 100644 --- a/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php +++ b/src/applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php @@ -6,14 +6,14 @@ final class PhabricatorAuthSessionGarbageCollector const COLLECTORCONST = 'auth.sessions'; public function getCollectorName() { - return pht('Auth Sessions'); + return pht('Authentication Sessions'); } public function hasAutomaticPolicy() { return true; } - public function collectGarbage() { + protected function collectGarbage() { $session_table = new PhabricatorAuthSession(); $conn_w = $session_table->establishConnection('w'); diff --git a/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php b/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php index 6f6d6c3bb6..e163421351 100644 --- a/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php +++ b/src/applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php @@ -6,14 +6,14 @@ final class PhabricatorAuthTemporaryTokenGarbageCollector const COLLECTORCONST = 'auth.tokens'; public function getCollectorName() { - return pht('Auth Tokens'); + return pht('Authentication Tokens'); } public function hasAutomaticPolicy() { return true; } - public function collectGarbage() { + protected function collectGarbage() { $session_table = new PhabricatorAuthTemporaryToken(); $conn_w = $session_table->establishConnection('w'); diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php index 425ee86b8a..9f03fb459f 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php @@ -13,13 +13,7 @@ final class PhabricatorCacheGeneralGarbageCollector return phutil_units('30 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.general-cache'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $cache = new PhabricatorKeyValueDatabaseCache(); $conn_w = $cache->establishConnection('w'); @@ -28,7 +22,7 @@ final class PhabricatorCacheGeneralGarbageCollector 'DELETE FROM %T WHERE cacheCreated < %d ORDER BY cacheCreated ASC LIMIT 100', $cache->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php index cdabac577e..5f79ba171c 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php @@ -13,13 +13,7 @@ final class PhabricatorCacheMarkupGarbageCollector return phutil_units('30 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.markup-cache'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorMarkupCache(); $conn_w = $table->establishConnection('w'); @@ -27,7 +21,7 @@ final class PhabricatorCacheMarkupGarbageCollector $conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php b/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php index b0e5b6908e..0f6153f0ad 100644 --- a/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php +++ b/src/applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php @@ -13,7 +13,7 @@ final class PhabricatorCacheTTLGarbageCollector return true; } - public function collectGarbage() { + protected function collectGarbage() { $cache = new PhabricatorKeyValueDatabaseCache(); $conn_w = $cache->establishConnection('w'); @@ -22,7 +22,7 @@ final class PhabricatorCacheTTLGarbageCollector 'DELETE FROM %T WHERE cacheExpires < %d ORDER BY cacheExpires ASC LIMIT 100', $cache->getTableName(), - time()); + PhabricatorTime::getNow()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php index 9bc7673240..f87fc57572 100644 --- a/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php @@ -13,21 +13,16 @@ final class ConduitConnectionGarbageCollector return phutil_units('180 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.conduit-logs'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorConduitConnectionLog(); $conn_w = $table->establishConnection('w'); + queryfx( $conn_w, 'DELETE FROM %T WHERE dateCreated < %d ORDER BY dateCreated ASC LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php index e5d426b30c..318cb43ad1 100644 --- a/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitLogGarbageCollector.php @@ -13,21 +13,16 @@ final class ConduitLogGarbageCollector return phutil_units('180 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.conduit-logs'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorConduitMethodCallLog(); $conn_w = $table->establishConnection('w'); + queryfx( $conn_w, 'DELETE FROM %T WHERE dateCreated < %d ORDER BY dateCreated ASC LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php b/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php index b3571713b4..fab142b8e8 100644 --- a/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php +++ b/src/applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php @@ -13,9 +13,10 @@ final class ConduitTokenGarbageCollector return true; } - public function collectGarbage() { + protected function collectGarbage() { $table = new PhabricatorConduitToken(); $conn_w = $table->establishConnection('w'); + queryfx( $conn_w, 'DELETE FROM %T WHERE expires <= %d diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index b73f145033..92cafb9449 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -176,6 +176,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'Inbound mail addresses are now configured for each application '. 'in the Applications tool.'); + $gc_reason = pht( + 'Garbage collectors are now configured with "%s".', + 'bin/garbage set-policy'); + $ancient_config += array( 'phid.external-loaders' => pht( @@ -280,6 +284,14 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'auth.login-message' => pht( 'This configuration option has been replaced with a modular '. 'handler. See T9346.'), + + 'gcdaemon.ttl.herald-transcripts' => $gc_reason, + 'gcdaemon.ttl.daemon-logs' => $gc_reason, + 'gcdaemon.ttl.differential-parse-cache' => $gc_reason, + 'gcdaemon.ttl.markup-cache' => $gc_reason, + 'gcdaemon.ttl.task-archive' => $gc_reason, + 'gcdaemon.ttl.general-cache' => $gc_reason, + 'gcdaemon.ttl.conduit-logs' => $gc_reason, ); return $ancient_config; diff --git a/src/applications/config/module/PhabricatorConfigCollectorsModule.php b/src/applications/config/module/PhabricatorConfigCollectorsModule.php index 729b6a2a18..f00e9d3489 100644 --- a/src/applications/config/module/PhabricatorConfigCollectorsModule.php +++ b/src/applications/config/module/PhabricatorConfigCollectorsModule.php @@ -17,11 +17,13 @@ final class PhabricatorConfigCollectorsModule extends PhabricatorConfigModule { $collectors = msort($collectors, 'getCollectorConstant'); $rows = array(); + $rowc = array(); foreach ($collectors as $key => $collector) { + $class = null; if ($collector->hasAutomaticPolicy()) { $policy_view = phutil_tag('em', array(), pht('Automatic')); } else { - $policy = $collector->getDefaultRetentionPolicy(); + $policy = $collector->getRetentionPolicy(); if ($policy === null) { $policy_view = pht('Indefinite'); } else { @@ -30,8 +32,15 @@ final class PhabricatorConfigCollectorsModule extends PhabricatorConfigModule { '%s Day(s)', new PhutilNumber($days)); } + + $default = $collector->getDefaultRetentionPolicy(); + if ($policy !== $default) { + $class = 'highlighted'; + $policy_view = phutil_tag('strong', array(), $policy_view); + } } + $rowc[] = $class; $rows[] = array( $collector->getCollectorConstant(), $collector->getCollectorName(), @@ -40,6 +49,7 @@ final class PhabricatorConfigCollectorsModule extends PhabricatorConfigModule { } $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) ->setHeaders( array( pht('Constant'), @@ -53,8 +63,16 @@ final class PhabricatorConfigCollectorsModule extends PhabricatorConfigModule { null, )); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Garbage Collectors')) + ->setSubheader( + pht( + 'Collectors with custom policies are highlighted. Use '. + '%s to change retention policies.', + phutil_tag('tt', array(), 'bin/garbage set-policy'))); + return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Garbage Collectors')) + ->setHeader($header) ->setTable($table); } diff --git a/src/applications/config/option/PhabricatorGarbageCollectorConfigOptions.php b/src/applications/config/option/PhabricatorGarbageCollectorConfigOptions.php deleted file mode 100644 index c1095d1520..0000000000 --- a/src/applications/config/option/PhabricatorGarbageCollectorConfigOptions.php +++ /dev/null @@ -1,70 +0,0 @@ - array( - 30, - pht('Number of seconds to retain Herald transcripts for.'), - ), - 'gcdaemon.ttl.daemon-logs' => array( - 7, - pht('Number of seconds to retain Daemon logs for.'), - ), - 'gcdaemon.ttl.differential-parse-cache' => array( - 14, - pht('Number of seconds to retain Differential parse caches for.'), - ), - 'gcdaemon.ttl.markup-cache' => array( - 30, - pht('Number of seconds to retain Markup cache entries for.'), - ), - 'gcdaemon.ttl.task-archive' => array( - 14, - pht('Number of seconds to retain archived background tasks for.'), - ), - 'gcdaemon.ttl.general-cache' => array( - 30, - pht('Number of seconds to retain general cache entries for.'), - ), - 'gcdaemon.ttl.conduit-logs' => array( - 180, - pht('Number of seconds to retain Conduit call logs for.'), - ), - ); - - $result = array(); - foreach ($options as $key => $spec) { - list($default_days, $description) = $spec; - $result[] = $this - ->newOption($key, 'int', $default_days * (24 * 60 * 60)) - ->setDescription($description) - ->addExample((7 * 24 * 60 * 60), pht('Retain for 1 week')) - ->addExample((14 * 24 * 60 * 60), pht('Retain for 2 weeks')) - ->addExample((30 * 24 * 60 * 60), pht('Retain for 30 days')) - ->addExample((60 * 24 * 60 * 60), pht('Retain for 60 days')) - ->addExample(0, pht('Retain indefinitely')); - } - return $result; - } - -} diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php index 59cb8ea728..587194cd4d 100644 --- a/src/applications/config/option/PhabricatorPHDConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -80,6 +80,17 @@ final class PhabricatorPHDConfigOptions 'and the daemons. Primarily, this is a way to suppress the '. '"Daemons and Web Have Different Config" setup issue on a per '. 'config key basis.')), + $this->newOption('phd.garbage-collection', 'wild', array()) + ->setLocked(true) + ->setLockedMessage( + pht( + 'This option can not be edited from the web UI. Use %s to adjust '. + 'garbage collector policies.', + phutil_tag('tt', array(), 'bin/garbage set-policy'))) + ->setSummary(pht('Retention policies for garbage collection.')) + ->setDescription( + pht( + 'Customizes retention policies for garbage collectors.')), ); } diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php index 5d02b7075b..3f6d25bb5b 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php @@ -13,12 +13,7 @@ final class PhabricatorDaemonLogEventGarbageCollector return phutil_units('7 days in seconds'); } - public function collectGarbage() { - $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs'); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorDaemonLogEvent(); $conn_w = $table->establishConnection('w'); @@ -26,7 +21,7 @@ final class PhabricatorDaemonLogEventGarbageCollector $conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php index c278349e33..3ff26e3db7 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php @@ -13,12 +13,7 @@ final class PhabricatorDaemonLogGarbageCollector return phutil_units('7 days in seconds'); } - public function collectGarbage() { - $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs'); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorDaemonLog(); $conn_w = $table->establishConnection('w'); @@ -26,7 +21,7 @@ final class PhabricatorDaemonLogGarbageCollector $conn_w, 'DELETE FROM %T WHERE dateCreated < %d AND status != %s LIMIT 100', $table->getTableName(), - time() - $ttl, + $this->getGarbageEpoch(), PhabricatorDaemonLog::STATUS_RUNNING); return ($conn_w->getAffectedRows() == 100); diff --git a/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php b/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php index 7cbab418a1..e0b2bda79b 100644 --- a/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php +++ b/src/applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php @@ -13,21 +13,15 @@ final class PhabricatorDaemonTaskGarbageCollector return phutil_units('14 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.task-archive'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new PhabricatorWorkerArchiveTask(); $data_table = new PhabricatorWorkerTaskData(); $conn_w = $table->establishConnection('w'); $tasks = id(new PhabricatorWorkerArchiveTaskQuery()) - ->withDateCreatedBefore(time() - $ttl) + ->withDateCreatedBefore($this->getGarbageEpoch()) + ->setLimit(100) ->execute(); - if (!$tasks) { return false; } diff --git a/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php b/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php index f6dde8a685..b740060821 100644 --- a/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php +++ b/src/applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php @@ -13,13 +13,7 @@ final class DifferentialParseCacheGarbageCollector return phutil_units('14 days in seconds'); } - public function collectGarbage() { - $key = 'gcdaemon.ttl.differential-parse-cache'; - $ttl = PhabricatorEnv::getEnvConfig($key); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new DifferentialChangeset(); $conn_w = $table->establishConnection('w'); @@ -27,7 +21,7 @@ final class DifferentialParseCacheGarbageCollector $conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', DifferentialChangeset::TABLE_CACHE, - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php index e13ced3b36..0c9ccb7021 100644 --- a/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php +++ b/src/applications/drydock/garbagecollector/DrydockLogGarbageCollector.php @@ -13,18 +13,15 @@ final class DrydockLogGarbageCollector return phutil_units('30 days in seconds'); } - public function collectGarbage() { + protected function collectGarbage() { $log_table = new DrydockLog(); $conn_w = $log_table->establishConnection('w'); - $now = PhabricatorTime::getNow(); - $ttl = phutil_units('30 days in seconds'); - queryfx( $conn_w, 'DELETE FROM %T WHERE epoch <= %d LIMIT 100', $log_table->getTableName(), - $now - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php index ed246eecf3..c79bb9ba99 100644 --- a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php +++ b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php @@ -13,10 +13,10 @@ final class PhabricatorFileTemporaryGarbageCollector return true; } - public function collectGarbage() { + protected function collectGarbage() { $files = id(new PhabricatorFile())->loadAllWhere( 'ttl < %d LIMIT 100', - time()); + PhabricatorTime::getNow()); foreach ($files as $file) { $file->delete(); diff --git a/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php b/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php index ba8a97dd3a..2567bd86b3 100644 --- a/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php +++ b/src/applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php @@ -13,12 +13,7 @@ final class HeraldTranscriptGarbageCollector return phutil_units('30 days in seconds'); } - public function collectGarbage() { - $ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.herald-transcripts'); - if ($ttl <= 0) { - return false; - } - + protected function collectGarbage() { $table = new HeraldTranscript(); $conn_w = $table->establishConnection('w'); @@ -33,7 +28,7 @@ final class HeraldTranscriptGarbageCollector WHERE garbageCollected = 0 AND time < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php b/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php index 17b2b1705b..b23a006c65 100644 --- a/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php +++ b/src/applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php @@ -13,9 +13,7 @@ final class MetaMTAMailReceivedGarbageCollector return phutil_units('90 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('90 days in seconds'); - + protected function collectGarbage() { $table = new PhabricatorMetaMTAReceivedMail(); $conn_w = $table->establishConnection('w'); @@ -23,7 +21,7 @@ final class MetaMTAMailReceivedGarbageCollector $conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php b/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php index 80c6f8a48d..c9ca274436 100644 --- a/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php +++ b/src/applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php @@ -13,12 +13,10 @@ final class MetaMTAMailSentGarbageCollector return phutil_units('90 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('90 days in seconds'); - + protected function collectGarbage() { $mails = id(new PhabricatorMetaMTAMail())->loadAllWhere( 'dateCreated < %d LIMIT 100', - PhabricatorTime::getNow() - $ttl); + $this->getGarbageEpoch()); foreach ($mails as $mail) { $mail->delete(); diff --git a/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php b/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php index bc9ade1091..7a5677341c 100644 --- a/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php +++ b/src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php @@ -13,9 +13,7 @@ final class MultimeterEventGarbageCollector return phutil_units('90 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('90 days in seconds'); - + protected function collectGarbage() { $table = new MultimeterEvent(); $conn_w = $table->establishConnection('w'); @@ -23,7 +21,7 @@ final class MultimeterEventGarbageCollector $conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), - PhabricatorTime::getNow() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php b/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php index 48636c40de..8b134d44b4 100644 --- a/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php +++ b/src/applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php @@ -13,9 +13,7 @@ final class FeedStoryNotificationGarbageCollector return phutil_units('90 days in seconds'); } - public function collectGarbage() { - $ttl = 90 * 24 * 60 * 60; - + protected function collectGarbage() { $table = new PhabricatorFeedStoryNotification(); $conn_w = $table->establishConnection('w'); @@ -24,7 +22,7 @@ final class FeedStoryNotificationGarbageCollector 'DELETE FROM %T WHERE chronologicalKey < (%d << 32) ORDER BY chronologicalKey ASC LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php b/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php index 19382f242d..daa669540c 100644 --- a/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php +++ b/src/applications/people/garbagecollector/PeopleUserLogGarbageCollector.php @@ -13,9 +13,7 @@ final class PeopleUserLogGarbageCollector return phutil_units('180 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('180 days in seconds'); - + protected function collectGarbage() { $table = new PhabricatorUserLog(); $conn_w = $table->establishConnection('w'); @@ -23,7 +21,7 @@ final class PeopleUserLogGarbageCollector $conn_w, 'DELETE FROM %T WHERE dateCreated < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php b/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php index 9af6af663c..d8810da274 100644 --- a/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php +++ b/src/applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php @@ -13,9 +13,7 @@ final class PhabricatorSystemActionGarbageCollector return phutil_units('3 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('3 days in seconds'); - + protected function collectGarbage() { $table = new PhabricatorSystemActionLog(); $conn_w = $table->establishConnection('w'); @@ -23,7 +21,7 @@ final class PhabricatorSystemActionGarbageCollector $conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php b/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php index 3e78e29f9e..8c19f02874 100644 --- a/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php +++ b/src/applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php @@ -13,9 +13,7 @@ final class PhabricatorSystemDestructionGarbageCollector return phutil_units('90 days in seconds'); } - public function collectGarbage() { - $ttl = phutil_units('90 days in seconds'); - + protected function collectGarbage() { $table = new PhabricatorSystemDestructionLog(); $conn_w = $table->establishConnection('w'); @@ -23,7 +21,7 @@ final class PhabricatorSystemDestructionGarbageCollector $conn_w, 'DELETE FROM %T WHERE epoch < %d LIMIT 100', $table->getTableName(), - time() - $ttl); + $this->getGarbageEpoch()); return ($conn_w->getAffectedRows() == 100); } diff --git a/src/docs/user/configuration/managing_garbage.diviner b/src/docs/user/configuration/managing_garbage.diviner new file mode 100644 index 0000000000..1896a73904 --- /dev/null +++ b/src/docs/user/configuration/managing_garbage.diviner @@ -0,0 +1,68 @@ +@title Managing Garbage Collection +@group config + +Understanding and configuring garbage collection. + +Overview +======== + +Phabricator generates various logs and caches during normal operation. Some of +these logs and caches are usually of very little use after some time has +passed, so they are deleted automatically (often after a month or two) in a +process called "garbage collection". + +Garbage collection is performed automatically by the daemons. You can review +all of the installed garbage collectors by browsing to {nav Config > Garbage +Collectors}. + + +Configuring Retention Policies +============================== + +You can reconfigure the data retention policies for most collectors. + +The default retention polcies should be suitable for most installs. However, +you might want to **decrease** retention to reduce the amount of disk space +used by some high-volume log that you don't find particularly interesting, or +to adhere to an organizational data retention policy. + +Alternatively, you might want to **increase** retention if you want to retain +some logs for a longer period of time, perhaps for auditing or analytic +purposes. + +You can review the current retention policies in +{nav Config > Garbage Collectors}. To change a policy, use +`bin/garbage set-policy` to select a new policy: + +``` +phabricator/ $ ./bin/garbage set-policy --collector cache.markup --days 7 +``` + +You can use `--days` to select how long data is retained for. You can also use +`--indefinite` to set an indefinite retention policy. This will stop the +garbage collector from cleaning up any data. Finally, you can use `--default` +to restore the default policy. + +Your changes should be reflected in the web UI immediately, and will take +effect in the actual collector **the next time the daemons are restarted**. + + +Troubleshooting +=============== + +You can manually run a collector with `bin/garbage collect`. + +``` +phabricator/ $ ./bin/garbage collect --collector cache.general +``` + +By using the `--trace` flag, you can inspect the operation of the collector +in detail. + + +Next Steps +========== + +Continue by: + + - exploring other daemon topics with @{article:Managing Daemons with phd}. diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php index 18606028f3..3e5b70cc05 100644 --- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php +++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php @@ -46,6 +46,28 @@ abstract class PhabricatorGarbageCollector extends Phobject { } + /** + * Get the effective retention policy. + * + * @return int|null Lifetime, or `null` for indefinite retention. + * @task info + */ + public function getRetentionPolicy() { + if ($this->hasAutomaticPolicy()) { + throw new Exception( + pht( + 'Can not get retention policy of collector with automatic '. + 'policy.')); + } + + $config = PhabricatorEnv::getEnvConfig('phd.garbage-collection'); + $const = $this->getCollectorConstant(); + + return idx($config, $const, $this->getDefaultRetentionPolicy()); + } + + + /** * Get a unique string constant identifying this collector. * @@ -60,13 +82,61 @@ abstract class PhabricatorGarbageCollector extends Phobject { /* -( Collecting Garbage )------------------------------------------------- */ + /** + * Run the collector. + * + * @return bool True if there is more garbage to collect. + * @task collect + */ + final public function runCollector() { + // Don't do anything if this collector is configured with an indefinite + // retention policy. + if (!$this->hasAutomaticPolicy()) { + $policy = $this->getRetentionPolicy(); + if (!$policy) { + return false; + } + } + + return $this->collectGarbage(); + } + + /** * Collect garbage from whatever source this GC handles. * * @return bool True if there is more garbage to collect. * @task collect */ - abstract public function collectGarbage(); + abstract protected function collectGarbage(); + + + /** + * Get the most recent epoch timestamp that is considered garbage. + * + * Records older than this should be collected. + * + * @return int Most recent garbage timestamp. + * @task collect + */ + final protected function getGarbageEpoch() { + if ($this->hasAutomaticPolicy()) { + throw new Exception( + pht( + 'Can not get garbage epoch for a collector with an automatic '. + 'collection policy.')); + } + + $ttl = $this->getRetentionPolicy(); + if (!$ttl) { + throw new Exception( + pht( + 'Can not get garbage epoch for a collector with an indefinite '. + 'retention policy.')); + } + + return (PhabricatorTime::getNow() - $ttl); + } /** diff --git a/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php new file mode 100644 index 0000000000..8af86dc2d5 --- /dev/null +++ b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php @@ -0,0 +1,50 @@ +setName('collect') + ->setExamples('**collect** --collector __collector__') + ->setSynopsis( + pht('Run a garbage collector in the foreground.')) + ->setArguments( + array( + array( + 'name' => 'collector', + 'param' => 'const', + 'help' => pht( + 'Constant identifying the garbage collector to run.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $collector = $this->getCollector($args->getArg('collector')); + + echo tsprintf( + "%s\n", + pht('Collecting "%s" garbage...', $collector->getCollectorName())); + + $any = false; + while (true) { + $more = $collector->runCollector(); + if ($more) { + $any = true; + } else { + break; + } + } + + if ($any) { + $message = pht('Finished collecting all the garbage.'); + } else { + $message = pht('Could not find any garbage to collect.'); + } + echo tsprintf("\n%s\n", $message); + + return 0; + } + +} diff --git a/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php new file mode 100644 index 0000000000..83f74c6dde --- /dev/null +++ b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php @@ -0,0 +1,141 @@ +setName('set-policy') + ->setExamples( + "**set-policy** --collector __collector__ --days 30\n". + "**set-policy** --collector __collector__ --indefinite\n". + "**set-policy** --collector __collector__ --default") + ->setSynopsis( + pht( + 'Change retention policies for a garbage collector.')) + ->setArguments( + array( + array( + 'name' => 'collector', + 'param' => 'const', + 'help' => pht( + 'Constant identifying the garbage collector.'), + ), + array( + 'name' => 'indefinite', + 'help' => pht( + 'Set an indefinite retention policy.'), + ), + array( + 'name' => 'default', + 'help' => pht( + 'Use the default retention policy.'), + ), + array( + 'name' => 'days', + 'param' => 'count', + 'help' => pht( + 'Retain data for the specified number of days.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $config_key = 'phd.garbage-collection'; + + $collector = $this->getCollector($args->getArg('collector')); + + $days = $args->getArg('days'); + $indefinite = $args->getArg('indefinite'); + $default = $args->getArg('default'); + + $count = 0; + if ($days !== null) { + $count++; + } + if ($indefinite) { + $count++; + } + if ($default) { + $count++; + } + + if (!$count) { + throw new PhutilArgumentUsageException( + pht( + 'Choose a policy with "%s", "%s" or "%s".', + '--days', + '--indefinite', + '--default')); + } + + if ($count > 1) { + throw new PhutilArgumentUsageException( + pht( + 'Options "%s", "%s" and "%s" represent mutually exclusive ways '. + 'to choose a policy. Specify only one.', + '--days', + '--indefinite', + '--default')); + } + + if ($days !== null) { + $days = (int)$days; + if ($days < 1) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a positive number of days to retain data for.')); + } + } + + $collector_const = $collector->getCollectorConstant(); + $value = PhabricatorEnv::getEnvConfig($config_key); + + if ($days !== null) { + echo tsprintf( + "%s\n", + pht( + 'Setting retention policy for "%s" to %s day(s).', + $collector->getCollectorName(), + new PhutilNumber($days))); + + $value[$collector_const] = phutil_units($days.' days in seconds'); + } else if ($indefinite) { + echo tsprintf( + "%s\n", + pht( + 'Setting "%s" to be retained indefinitely.', + $collector->getCollectorName())); + + $value[$collector_const] = null; + } else { + echo tsprintf( + "%s\n", + pht( + 'Restoring "%s" to the default retention policy.', + $collector->getCollectorName())); + + unset($value[$collector_const]); + } + + id(new PhabricatorConfigLocalSource()) + ->setKeys( + array( + $config_key => $value, + )); + + echo tsprintf( + "%s\n", + pht( + 'Wrote new policy to local configuration.')); + + echo tsprintf( + "%s\n", + pht( + 'This change will take effect the next time the daemons are '. + 'restarted.')); + + return 0; + } + +} diff --git a/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php new file mode 100644 index 0000000000..eabb2f5b0d --- /dev/null +++ b/src/infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php @@ -0,0 +1,32 @@ +garbageCollectors) { foreach ($this->garbageCollectors as $key => $collector) { - $more_garbage = $collector->collectGarbage(); + $more_garbage = $collector->runCollector(); if (!$more_garbage) { unset($this->garbageCollectors[$key]); } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 9898f14a99..055b85f0df 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1393,6 +1393,11 @@ final class PhabricatorUSEnglishTranslation '%s Days', ), + 'Setting retention policy for "%s" to %s day(s).' => array( + 'Setting retention policy for "%s" to one day.', + 'Setting retention policy for "%s" to %s days.', + ), + ); } From 3c4b05bcd4b6958340aa8ce963709bddc7cb151a Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 09:17:43 -0700 Subject: [PATCH 41/43] Correct a Dashboard status constant in a migration Summary: Fixes T9500. All the code is fine in D13836, but the value of the constant got updated (from "open" to "active") and the migration still used the old value. Correct any affected dashboards to use the proper constant. This only affected old dashboards: newly created ones use the right constant. Test Plan: Ran migration, verified that all active dashboards appeared on "Active Dashboards". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9500 Differential Revision: https://secure.phabricator.com/D14223 --- resources/sql/autopatches/20151002.dashboard.status.1.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resources/sql/autopatches/20151002.dashboard.status.1.sql diff --git a/resources/sql/autopatches/20151002.dashboard.status.1.sql b/resources/sql/autopatches/20151002.dashboard.status.1.sql new file mode 100644 index 0000000000..09b19ef1d8 --- /dev/null +++ b/resources/sql/autopatches/20151002.dashboard.status.1.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_dashboard.dashboard + SET status = 'active' WHERE status = 'open'; From 14d6325394bb9c842e1bc5b8c082f8ec7fe23aa1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 09:17:51 -0700 Subject: [PATCH 42/43] Acccept any HTTP 2xx status as success in Harbormaster Summary: Ref T9478. This should probably be configurable eventually, but for now treat any 200-block status as success. Also show the result code. Test Plan: - Hit a bad URI, saw "HTTP 503" + failure. - Hit a good URI, saw "HTTP 200" + success. Reviewers: chad, hach-que Reviewed By: chad, hach-que Maniphest Tasks: T9478 Differential Revision: https://secure.phabricator.com/D14226 --- .../HarbormasterHTTPRequestBuildStepImplementation.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index bfaa5c6a56..10d2cb08b2 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -75,6 +75,12 @@ final class HarbormasterHTTPRequestBuildStepImplementation list($status, $body, $headers) = $future->resolve(); $header_lines = array(); + + // TODO: We don't currently preserve the entire "HTTP" response header, but + // should. Once we do, reproduce it here faithfully. + $status_code = $status->getStatusCode(); + $header_lines[] = "HTTP {$status_code}"; + foreach ($headers as $header) { list($head, $tail) = $header; $header_lines[] = "{$head}: {$tail}"; @@ -89,7 +95,7 @@ final class HarbormasterHTTPRequestBuildStepImplementation ->newLog($uri, 'http.body') ->append($body); - if ($status->getStatusCode() != 200) { + if ($status->isError()) { throw new HarbormasterBuildFailureException(); } } From 0db86cce7df74a7cc681386713d5705625c03129 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Oct 2015 16:11:03 -0700 Subject: [PATCH 43/43] Improve Diffusion behavior for no-longer-existing commits Summary: Ref T9028. When users push a commit, then later delete it (e.g., by deleting the branch which contained it) we currently explode when trying to view it. Instead, degrade gradually if some information is not available. Test Plan: - Looked at valid commits with parents, refs, branches and merges. - Looked at invalid commits. - Looked at a previously valid, now-deleted + gc'd commit: {F859273} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9028 Differential Revision: https://secure.phabricator.com/D14227 --- .../controller/DiffusionCommitController.php | 277 ++++++++++++------ 1 file changed, 188 insertions(+), 89 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 8a7d7d7d7c..fe2f539f09 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -7,6 +7,12 @@ final class DiffusionCommitController extends DiffusionController { private $auditAuthorityPHIDs; private $highlightedAudits; + private $commitParents; + private $commitRefs; + private $commitMerges; + private $commitErrors; + private $commitExists; + public function shouldAllowPublic() { return true; } @@ -17,6 +23,7 @@ final class DiffusionCommitController extends DiffusionController { protected function processDiffusionRequest(AphrontRequest $request) { $user = $request->getUser(); + // This controller doesn't use blob/path stuff, just pass the dictionary // in directly instead of using the AphrontRequest parsing mechanism. $data = $request->getURIMap(); @@ -45,10 +52,7 @@ final class DiffusionCommitController extends DiffusionController { )); if (!$commit) { - $exists = $this->callConduitWithDiffusionRequest( - 'diffusion.existsquery', - array('commit' => $drequest->getCommit())); - if (!$exists) { + if (!$this->getCommitExists()) { return new Aphront404Response(); } @@ -95,18 +99,6 @@ final class DiffusionCommitController extends DiffusionController { require_celerity_resource('phabricator-remarkup-css'); - $parents = $this->callConduitWithDiffusionRequest( - 'diffusion.commitparentsquery', - array('commit' => $drequest->getCommit())); - - if ($parents) { - $parents = id(new DiffusionCommitQuery()) - ->setViewer($user) - ->withRepository($repository) - ->withIdentifiers($parents) - ->execute(); - } - $headsup_view = id(new PHUIHeaderView()) ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))); @@ -115,7 +107,6 @@ final class DiffusionCommitController extends DiffusionController { $commit_properties = $this->loadCommitProperties( $commit, $commit_data, - $parents, $audit_requests); $property_list = id(new PHUIPropertyListView()) ->setHasKeyboardShortcuts(true) @@ -148,8 +139,10 @@ final class DiffusionCommitController extends DiffusionController { $message)); $headsup_view->setTall(true); + $object_box = id(new PHUIObjectBoxView()) ->setHeader($headsup_view) + ->setFormErrors($this->getCommitErrors()) ->addPropertyList($property_list) ->addPropertyList($detail_list); @@ -216,6 +209,10 @@ final class DiffusionCommitController extends DiffusionController { 'This commit is enormous, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); + } else if (!$this->getCommitExists()) { + $content[] = $this->renderStatusMessage( + pht('Commit No Longer Exists'), + pht('This commit no longer exists in the repository.')); } else { $show_changesets = true; @@ -382,10 +379,8 @@ final class DiffusionCommitController extends DiffusionController { private function loadCommitProperties( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, - array $parents, array $audit_requests) { - assert_instances_of($parents, 'PhabricatorRepositoryCommit'); $viewer = $this->getRequest()->getUser(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); @@ -423,11 +418,6 @@ final class DiffusionCommitController extends DiffusionController { if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); } - if ($parents) { - foreach ($parents as $parent) { - $phids[] = $parent->getPHID(); - } - } // NOTE: We should never normally have more than a single push log, but // it can occur naturally if a commit is pushed, then the branch it was @@ -564,39 +554,47 @@ final class DiffusionCommitController extends DiffusionController { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); } + $parents = $this->getCommitParents(); if ($parents) { - $parent_links = array(); - foreach ($parents as $parent) { - $parent_links[] = $handles[$parent->getPHID()]->renderLink(); - } - $props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links); + $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); } - $props['Branches'] = phutil_tag( - 'span', - array( - 'id' => 'commit-branches', - ), - pht('Unknown')); - $props['Tags'] = phutil_tag( - 'span', - array( - 'id' => 'commit-tags', - ), - pht('Unknown')); + if ($this->getCommitExists()) { + $props['Branches'] = phutil_tag( + 'span', + array( + 'id' => 'commit-branches', + ), + pht('Unknown')); + $props['Tags'] = phutil_tag( + 'span', + array( + 'id' => 'commit-tags', + ), + pht('Unknown')); - $callsign = $repository->getCallsign(); - $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); - Javelin::initBehavior( - 'diffusion-commit-branches', - array( - $root.'/branches/' => 'commit-branches', - $root.'/tags/' => 'commit-tags', - )); + $callsign = $repository->getCallsign(); + $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); + Javelin::initBehavior( + 'diffusion-commit-branches', + array( + $root.'/branches/' => 'commit-branches', + $root.'/tags/' => 'commit-tags', + )); + } - $refs = $this->buildRefs($drequest); + $refs = $this->getCommitRefs(); if ($refs) { - $props['References'] = $refs; + $ref_links = array(); + foreach ($refs as $ref_data) { + $ref_links[] = phutil_tag( + 'a', + array( + 'href' => $ref_data['href'], + ), + $ref_data['ref']); + } + $props['References'] = phutil_implode_html(', ', $ref_links); } if ($reverts_phids) { @@ -860,26 +858,12 @@ final class DiffusionCommitController extends DiffusionController { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $vcs = $repository->getVersionControlSystem(); - switch ($vcs) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - // These aren't supported under SVN. - return null; - } - - $limit = 50; - - $merges = $this->callConduitWithDiffusionRequest( - 'diffusion.mergedcommitsquery', - array( - 'commit' => $drequest->getCommit(), - 'limit' => $limit + 1, - )); - + $merges = $this->getCommitMerges(); if (!$merges) { return null; } - $merges = DiffusionPathChange::newFromConduit($merges); + + $limit = $this->getMergeDisplayLimit(); $caption = null; if (count($merges) > $limit) { @@ -961,27 +945,6 @@ final class DiffusionCommitController extends DiffusionController { return $actions; } - private function buildRefs(DiffusionRequest $request) { - // this is git-only, so save a conduit round trip and just get out of - // here if the repository isn't git - $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; - $repository = $request->getRepository(); - if ($repository->getVersionControlSystem() != $type_git) { - return null; - } - - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.refsquery', - array('commit' => $request->getCommit())); - $ref_links = array(); - foreach ($results as $ref_data) { - $ref_links[] = phutil_tag('a', - array('href' => $ref_data['href']), - $ref_data['ref']); - } - return phutil_implode_html(', ', $ref_links); - } - private function buildRawDiffResponse(DiffusionRequest $drequest) { $raw_diff = $this->callConduitWithDiffusionRequest( 'diffusion.rawdiffquery', @@ -1137,4 +1100,140 @@ final class DiffusionCommitController extends DiffusionController { return $toc_view; } + private function loadCommitState() { + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $commit = $drequest->getCommit(); + + // TODO: We could use futures here and resolve these calls in parallel. + + $exceptions = array(); + + try { + $parent_refs = $this->callConduitWithDiffusionRequest( + 'diffusion.commitparentsquery', + array( + 'commit' => $commit, + )); + + if ($parent_refs) { + $parents = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($repository) + ->withIdentifiers($parent_refs) + ->execute(); + } else { + $parents = array(); + } + + $this->commitParents = $parents; + } catch (Exception $ex) { + $this->commitParents = false; + $exceptions[] = $ex; + } + + $merge_limit = $this->getMergeDisplayLimit(); + + try { + $merges = $this->callConduitWithDiffusionRequest( + 'diffusion.mergedcommitsquery', + array( + 'commit' => $commit, + 'limit' => $merge_limit + 1, + )); + + $this->commitMerges = DiffusionPathChange::newFromConduit($merges); + } catch (Exception $ex) { + $this->commitMerges = false; + $exceptions[] = $ex; + } + + + try { + if ($repository->isGit()) { + $refs = $this->callConduitWithDiffusionRequest( + 'diffusion.refsquery', + array( + 'commit' => $commit, + )); + } else { + $refs = array(); + } + + $this->commitRefs = $refs; + } catch (Exception $ex) { + $this->commitRefs = false; + $exceptions[] = $ex; + } + + if ($exceptions) { + $exists = $this->callConduitWithDiffusionRequest( + 'diffusion.existsquery', + array( + 'commit' => $commit, + )); + + if ($exists) { + $this->commitExists = true; + foreach ($exceptions as $exception) { + $this->commitErrors[] = $exception->getMessage(); + } + } else { + $this->commitExists = false; + $this->commitErrors[] = pht( + 'This commit no longer exists in the repository. It may have '. + 'been part of a branch which was deleted.'); + } + } else { + $this->commitExists = true; + $this->commitErrors = array(); + } + } + + private function getMergeDisplayLimit() { + return 50; + } + + private function getCommitExists() { + if ($this->commitExists === null) { + $this->loadCommitState(); + } + + return $this->commitExists; + } + + private function getCommitParents() { + if ($this->commitParents === null) { + $this->loadCommitState(); + } + + return $this->commitParents; + } + + private function getCommitRefs() { + if ($this->commitRefs === null) { + $this->loadCommitState(); + } + + return $this->commitRefs; + } + + private function getCommitMerges() { + if ($this->commitMerges === null) { + $this->loadCommitState(); + } + + return $this->commitMerges; + } + + private function getCommitErrors() { + if ($this->commitErrors === null) { + $this->loadCommitState(); + } + + return $this->commitErrors; + } + + }