From 30473549acfbcaecb822d0d7fffb3d3ffd7c7a0e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Jan 2016 05:21:38 -0800 Subject: [PATCH] Add a basic pull event log for debugging repository cloning Summary: Ref T10228. This is currently quite limited: - No UI. - No SSH support. My primary goal is to debug the issue in T10228. In the long run we can expand this to be a bit fancier. Test Plan: Made various valid and invalid clones, got sucess responses and not-so-successful responses, viewed the log table for general corresponding messages and broad sanity. Ran GC via `bin/phd debug trigger`, no issues. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10228 Differential Revision: https://secure.phabricator.com/D15127 --- .../sql/autopatches/20160128.repo.1.pull.sql | 14 +++ src/__phutil_library_map__.php | 11 +++ .../DiffusionPullEventGarbageCollector.php | 29 ++++++ .../controller/DiffusionServeController.php | 94 ++++++++++++++++++ ...PhabricatorRepositoryPullEventPHIDType.php | 39 ++++++++ .../PhabricatorRepositoryPullEventQuery.php | 97 +++++++++++++++++++ .../PhabricatorRepositoryPullEvent.php | 87 +++++++++++++++++ 7 files changed, 371 insertions(+) create mode 100644 resources/sql/autopatches/20160128.repo.1.pull.sql create mode 100644 src/applications/diffusion/DiffusionPullEventGarbageCollector.php create mode 100644 src/applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php create mode 100644 src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php create mode 100644 src/applications/repository/storage/PhabricatorRepositoryPullEvent.php diff --git a/resources/sql/autopatches/20160128.repo.1.pull.sql b/resources/sql/autopatches/20160128.repo.1.pull.sql new file mode 100644 index 0000000000..4a8ec89480 --- /dev/null +++ b/resources/sql/autopatches/20160128.repo.1.pull.sql @@ -0,0 +1,14 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_pullevent ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + repositoryPHID VARBINARY(64), + epoch INT UNSIGNED NOT NULL, + pullerPHID VARBINARY(64), + remoteAddress INT UNSIGNED, + remoteProtocol VARCHAR(32) COLLATE {$COLLATE_TEXT}, + resultType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + resultCode INT UNSIGNED NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_repository` (repositoryPHID), + KEY `key_epoch` (epoch) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a54459c690..3ac894a406 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -688,6 +688,7 @@ phutil_register_library_map(array( 'DiffusionPreCommitRefRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryHeraldField.php', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryProjectsHeraldField.php', 'DiffusionPreCommitRefTypeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php', + 'DiffusionPullEventGarbageCollector' => 'applications/diffusion/DiffusionPullEventGarbageCollector.php', 'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php', 'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php', 'DiffusionPushLogController' => 'applications/diffusion/controller/DiffusionPushLogController.php', @@ -3028,6 +3029,9 @@ phutil_register_library_map(array( 'PhabricatorRepositoryMirrorQuery' => 'applications/repository/query/PhabricatorRepositoryMirrorQuery.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', + 'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php', + 'PhabricatorRepositoryPullEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php', + 'PhabricatorRepositoryPullEventQuery' => 'applications/repository/query/PhabricatorRepositoryPullEventQuery.php', 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php', 'PhabricatorRepositoryPushEvent' => 'applications/repository/storage/PhabricatorRepositoryPushEvent.php', 'PhabricatorRepositoryPushEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushEventPHIDType.php', @@ -4708,6 +4712,7 @@ phutil_register_library_map(array( 'DiffusionPreCommitRefRepositoryHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefTypeHeraldField' => 'DiffusionPreCommitRefHeraldField', + 'DiffusionPullEventGarbageCollector' => 'PhabricatorGarbageCollector', 'DiffusionPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionPushEventViewController' => 'DiffusionPushLogController', 'DiffusionPushLogController' => 'DiffusionController', @@ -7476,6 +7481,12 @@ phutil_register_library_map(array( 'PhabricatorRepositoryMirrorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryParsedChange' => 'Phobject', 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', + 'PhabricatorRepositoryPullEvent' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorRepositoryPullEventPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorRepositoryPullEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryPushEvent' => array( 'PhabricatorRepositoryDAO', diff --git a/src/applications/diffusion/DiffusionPullEventGarbageCollector.php b/src/applications/diffusion/DiffusionPullEventGarbageCollector.php new file mode 100644 index 0000000000..9407b95070 --- /dev/null +++ b/src/applications/diffusion/DiffusionPullEventGarbageCollector.php @@ -0,0 +1,29 @@ +establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE epoch < %d LIMIT 100', + $table->getTableName(), + $this->getGarbageEpoch()); + + return ($conn_w->getAffectedRows() == 100); + } + +} diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index ea5d4c209d..1d41d62efa 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -2,6 +2,27 @@ final class DiffusionServeController extends DiffusionController { + private $serviceViewer; + private $serviceRepository; + + public function setServiceViewer(PhabricatorUser $viewer) { + $this->serviceViewer = $viewer; + return $this; + } + + public function getServiceViewer() { + return $this->serviceViewer; + } + + public function setServiceRepository(PhabricatorRepository $repository) { + $this->serviceRepository = $repository; + return $this; + } + + public function getServiceRepository() { + return $this->serviceRepository; + } + public function isVCSRequest(AphrontRequest $request) { $identifier = $this->getRepositoryIdentifierFromRequest($request); if ($identifier === null) { @@ -45,6 +66,75 @@ final class DiffusionServeController extends DiffusionController { } public function handleRequest(AphrontRequest $request) { + $service_exception = null; + $response = null; + + try { + $response = $this->serveRequest($request); + } catch (Exception $ex) { + $service_exception = $ex; + } + + try { + $remote_addr = $request->getRemoteAddr(); + $remote_addr = ip2long($remote_addr); + + $pull_event = id(new PhabricatorRepositoryPullEvent()) + ->setEpoch(PhabricatorTime::getNow()) + ->setRemoteAddress($remote_addr) + ->setRemoteProtocol('http'); + + if ($response) { + $pull_event + ->setResultType('wild') + ->setResultCode($response->getHTTPResponseCode()); + + if ($response instanceof PhabricatorVCSResponse) { + $pull_event->setProperties( + array( + 'response.message' => $response->getMessage(), + )); + } + } else { + $pull_event + ->setResultType('exception') + ->setResultCode(500) + ->setProperties( + array( + 'exception.class' => $ex->getClass(), + 'exception.message' => $ex->getMessage(), + )); + } + + $viewer = $this->getServiceViewer(); + if ($viewer) { + $pull_event->setPullerPHID($viewer->getPHID()); + } + + $repository = $this->getServiceRepository(); + if ($repository) { + $pull_event->setRepositoryPHID($repository->getPHID()); + } + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $pull_event->save(); + unset($unguarded); + + } catch (Exception $ex) { + if ($service_exception) { + throw $service_exception; + } + throw $ex; + } + + if ($service_exception) { + throw $service_exception; + } + + return $response; + } + + private function serveRequest(AphrontRequest $request) { $identifier = $this->getRepositoryIdentifierFromRequest($request); // If authentication credentials have been provided, try to find a user @@ -65,6 +155,8 @@ final class DiffusionServeController extends DiffusionController { $viewer = new PhabricatorUser(); } + $this->setServiceViewer($viewer); + $allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public'); $allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); if (!$allow_public) { @@ -111,6 +203,8 @@ final class DiffusionServeController extends DiffusionController { } } + $this->setServiceRepository($repository); + if (!$repository->isTracked()) { return new PhabricatorVCSResponse( 403, diff --git a/src/applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php new file mode 100644 index 0000000000..45ed2819fb --- /dev/null +++ b/src/applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php @@ -0,0 +1,39 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $event = $objects[$phid]; + + $handle->setName(pht('Pull Event %d', $event->getID())); + } + } + +} diff --git a/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php b/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php new file mode 100644 index 0000000000..1063cdb48c --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php @@ -0,0 +1,97 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withRepositoryPHIDs(array $repository_phids) { + $this->repositoryPHIDs = $repository_phids; + return $this; + } + + public function withPullerPHIDs(array $puller_phids) { + $this->pullerPHIDs = $puller_phids; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryPullEvent(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function willFilterPage(array $events) { + $repository_phids = mpull($events, 'getRepositoryPHID'); + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($repository_phids) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($events as $key => $event) { + $phid = $event->getRepositoryPHID(); + if (empty($repositories[$phid])) { + unset($events[$key]); + continue; + } + $event->attachRepository($repositories[$phid]); + } + + return $events; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->pullerPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'pullerPHID in (%Ls)', + $this->pullerPHIDs); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php b/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php new file mode 100644 index 0000000000..d17fded9a8 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php @@ -0,0 +1,87 @@ +setPusherPHID($viewer->getPHID()); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'repositoryPHID' => 'phid?', + 'pullerPHID' => 'phid?', + 'remoteAddress' => 'uint32?', + 'remoteProtocol' => 'text32?', + 'resultType' => 'text32', + 'resultCode' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_repository' => array( + 'columns' => array('repositoryPHID'), + ), + 'key_epoch' => array( + 'columns' => array('epoch'), + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorRepositoryPullEventPHIDType::TYPECONST); + } + + public function attachRepository(PhabricatorRepository $repository) { + $this->repository = $repository; + return $this; + } + + public function getRepository() { + return $this->assertAttached($this->repository); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getRepository()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getRepository()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + "A repository's pull events are visible to users who can see the ". + "repository."); + } + +}