@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow repository cluster bindings to be marked as not "writable", making them read-only

Summary:
Depends on D19356. Fixes T10883. Ref T13120.

- Add a "writable" property to the bindings, defaulting to "true" with a nice dropdown.
- When selecting hosts, allow callers to request a writable host.
- If the caller wants a writable host, only return hosts if they're writable.
- In SVN and Mercurial, we sometimes return only writable hosts when we //could// return read-only hosts, but figuring out if these request are read-only or read-write is currently tricky. Since these repositories can't really cluster yet, this shouldn't matter too much today.

Test Plan:
- Without any config changes, viewed repositories via web UI and pushed/pulled via SSH and HTTP.
- Made all nodes in the cluster read-only by disabling "writable", pulled and hit the web UI (worked), tried to push via SSH and HTTP (got errors about read-only).
- Put everything back, pulled and pushed.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13120, T10883

Differential Revision: https://secure.phabricator.com/D19357

+114 -25
+5
src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
··· 57 57 'protocol' => id(new PhabricatorSelectEditField()) 58 58 ->setOptions(ipull($protocols, 'value', 'value')) 59 59 ->setValue($default_value), 60 + 'writable' => id(new PhabricatorBoolEditField()) 61 + ->setOptions( 62 + pht('Prevent Writes'), 63 + pht('Allow Writes')) 64 + ->setValue(true), 60 65 ); 61 66 } 62 67
+1
src/applications/diffusion/controller/DiffusionServeController.php
··· 437 437 'http', 438 438 'https', 439 439 ), 440 + 'writable' => !$this->isReadOnlyRequest($repository), 440 441 )); 441 442 if ($uri) { 442 443 $future = $this->getRequest()->newClusterProxyFuture($uri);
+1 -1
src/applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php
··· 29 29 ->setLog($this); 30 30 31 31 if ($this->shouldProxy()) { 32 - $command = $this->getProxyCommand(); 32 + $command = $this->getProxyCommand(true); 33 33 $did_synchronize = false; 34 34 35 35 if ($device) {
+1 -1
src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php
··· 22 22 $is_proxy = $this->shouldProxy(); 23 23 24 24 if ($is_proxy) { 25 - $command = $this->getProxyCommand(); 25 + $command = $this->getProxyCommand(false); 26 26 27 27 if ($device) { 28 28 $this->writeClusterEngineLogMessage(
+4 -1
src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php
··· 45 45 } 46 46 47 47 if ($this->shouldProxy()) { 48 - $command = $this->getProxyCommand(); 48 + // NOTE: For now, we're always requesting a writable node. The request 49 + // may not actually need one, but we can't currently determine whether 50 + // it is read-only or not at this phase of evaluation. 51 + $command = $this->getProxyCommand(true); 49 52 } else { 50 53 $command = csprintf( 51 54 'hg -R %s serve --stdio',
+36 -14
src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
··· 5 5 private $args; 6 6 private $repository; 7 7 private $hasWriteAccess; 8 - private $proxyURI; 8 + private $shouldProxy; 9 9 private $baseRequestPath; 10 10 11 11 public function getRepository() { ··· 69 69 return php_uname('n'); 70 70 } 71 71 72 - protected function getTargetDeviceName() { 73 - // TODO: This should use the correct device identity. 74 - $uri = new PhutilURI($this->proxyURI); 75 - return $uri->getDomain(); 76 - } 77 - 78 72 protected function shouldProxy() { 79 - return (bool)$this->proxyURI; 73 + return $this->shouldProxy; 80 74 } 81 75 82 - protected function getProxyCommand() { 83 - $uri = new PhutilURI($this->proxyURI); 76 + protected function getProxyCommand($for_write) { 77 + $viewer = $this->getSSHUser(); 78 + $repository = $this->getRepository(); 79 + 80 + $is_cluster_request = $this->getIsClusterRequest(); 81 + 82 + $uri = $repository->getAlmanacServiceURI( 83 + $viewer, 84 + array( 85 + 'neverProxy' => $is_cluster_request, 86 + 'protocols' => array( 87 + 'ssh', 88 + ), 89 + 'writable' => $for_write, 90 + )); 91 + 92 + if (!$uri) { 93 + throw new Exception( 94 + pht( 95 + 'Failed to generate an intracluster proxy URI even though this '. 96 + 'request was routed as a proxy request.')); 97 + } 98 + 99 + $uri = new PhutilURI($uri); 84 100 85 101 $username = AlmanacKeys::getClusterSSHUser(); 86 102 if ($username === null) { ··· 148 164 $repository = $this->identifyRepository(); 149 165 $this->setRepository($repository); 150 166 167 + // NOTE: Here, we're just figuring out if this is a proxyable request to 168 + // a clusterized repository or not. We don't (and can't) use the URI we get 169 + // back directly. 170 + 171 + // For example, we may get a read-only URI here but be handling a write 172 + // request. We only care if we get back `null` (which means we should 173 + // handle the request locally) or anything else (which means we should 174 + // proxy it to an appropriate device). 175 + 151 176 $is_cluster_request = $this->getIsClusterRequest(); 152 177 $uri = $repository->getAlmanacServiceURI( 153 178 $viewer, ··· 157 182 'ssh', 158 183 ), 159 184 )); 160 - 161 - if ($uri) { 162 - $this->proxyURI = $uri; 163 - } 185 + $this->shouldProxy = (bool)$uri; 164 186 165 187 try { 166 188 return $this->executeRepositoryOperations();
+4 -1
src/applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php
··· 148 148 } 149 149 150 150 if ($this->shouldProxy()) { 151 - $command = $this->getProxyCommand(); 151 + // NOTE: We're always requesting a writable device here. The request 152 + // might be read-only, but we can't currently tell, and SVN requests 153 + // can mix reads and writes. 154 + $command = $this->getProxyCommand(true); 152 155 $this->isProxying = true; 153 156 $cwd = null; 154 157 } else {
+38 -3
src/applications/repository/storage/PhabricatorRepository.php
··· 1909 1909 array( 1910 1910 'neverProxy' => 'bool', 1911 1911 'protocols' => 'list<string>', 1912 + 'writable' => 'optional bool', 1912 1913 )); 1913 1914 1914 1915 $never_proxy = $options['neverProxy']; 1915 1916 $protocols = $options['protocols']; 1917 + $writable = idx($options, 'writable', false); 1916 1918 1917 1919 $cache_key = $this->getAlmanacServiceCacheKey(); 1918 1920 if (!$cache_key) { ··· 1958 1960 } 1959 1961 1960 1962 if (isset($protocol_map[$uri['protocol']])) { 1961 - $results[] = new PhutilURI($uri['uri']); 1963 + $results[] = $uri; 1962 1964 } 1963 1965 } 1964 1966 ··· 1989 1991 } 1990 1992 } 1991 1993 1994 + // If we require a writable device, remove URIs which aren't writable. 1995 + if ($writable) { 1996 + foreach ($results as $key => $uri) { 1997 + if (!$uri['writable']) { 1998 + unset($results[$key]); 1999 + } 2000 + } 2001 + 2002 + if (!$results) { 2003 + throw new Exception( 2004 + pht( 2005 + 'This repository ("%s") is not writable with the given '. 2006 + 'protocols (%s). The Almanac service for this repository has no '. 2007 + 'writable bindings that support these protocols.', 2008 + $this->getDisplayName(), 2009 + implode(', ', $protocols))); 2010 + } 2011 + } 2012 + 1992 2013 shuffle($results); 1993 - return head($results); 2014 + 2015 + $result = head($results); 2016 + return $result['uri']; 1994 2017 } 1995 2018 1996 2019 public function supportsSynchronization() { ··· 2009 2032 } 2010 2033 2011 2034 $repository_phid = $this->getPHID(); 2012 - return "diffusion.repository({$repository_phid}).service({$service_phid})"; 2035 + 2036 + $parts = array( 2037 + "repo({$repository_phid})", 2038 + "serv({$service_phid})", 2039 + 'v2', 2040 + ); 2041 + 2042 + return implode('.', $parts); 2013 2043 } 2014 2044 2015 2045 private function buildAlmanacServiceURIs() { ··· 2038 2068 'protocol' => $protocol, 2039 2069 'uri' => (string)$uri, 2040 2070 'device' => $device_name, 2071 + 'writable' => (bool)$binding->getAlmanacPropertyValue('writable'), 2041 2072 ); 2042 2073 } 2043 2074 ··· 2091 2122 'http', 2092 2123 'https', 2093 2124 ), 2125 + 2126 + // At least today, no Conduit call can ever write to a repository, 2127 + // so it's fine to send anything to a read-only node. 2128 + 'writable' => false, 2094 2129 )); 2095 2130 if ($uri === null) { 2096 2131 return null;
+24 -4
src/docs/user/cluster/cluster_repositories.diviner
··· 221 221 Contracting a Cluster 222 222 ===================== 223 223 224 - To reduce the size of an existing cluster, follow these general steps: 224 + If you want to remove working devices from a cluster (for example, to take 225 + hosts down for maintenance), first do this for each device: 225 226 226 - - Disable the bindings from the service to the dead device in Almanac. 227 + - Change the `writable` property on the bindings to "Prevent Writes". 228 + - Wait a few moments until the cluster synchronizes (see 229 + "Monitoring Services" below). 230 + 231 + This will ensure that the device you're about to remove is not the only cluster 232 + leader, even if the cluster is receiving a high write volume. You can skip this 233 + step if the device isn't working property to start with. 234 + 235 + Once you've stopped writes and waited for synchronization (or if the hosts are 236 + not working in the first place) do this for each device: 237 + 238 + - Disable the bindings from the service to the device in Almanac. 227 239 228 240 If you are removing a device because it failed abruptly (or removing several 229 - devices at once) it is possible that some repositories will have lost all their 230 - leaders. See "Loss of Leaders" below to understand and resolve this. 241 + devices at once; or you skip the "Prevent Writes" step), it is possible that 242 + some repositories will have lost all their leaders. See "Loss of Leaders" below 243 + to understand and resolve this. 244 + 245 + If you want to put the hosts back in service later: 246 + 247 + - Enable the bindings again. 248 + - Change `writable` back to "Allow Writes". 249 + 250 + This will restore the cluster to the original state. 231 251 232 252 233 253 Monitoring Services