mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 09:17:30 +03:00
33b2fbc0bc
Summary: Don't let errors propagate out of the gitnode() revset. Always report errors in gitnode() as a translation failure, rather than letting exceptions propagate up and crash mercurial. The code was previously only catching internally generated ConduitError exceptions, but it can also throw HttpError exceptions, and the underlying httplib code can throw its own exceptions as well as socket.error exceptions. This also fixes the test code to use an ephemeral TCP port rather than assuming port 8543 will always be available. Using a fixed TCP port in test code is a very common way to cause bogus failures if the tests are run in parallel. (For instance, testing multiple repositories in parallel on the same build host.) Test Plan: Added unit tests that check the behavior when the server returns a 500 error, and when the server refuses the connection entirely. Reviewers: quark, durham, rmcelroy Reviewed By: rmcelroy Subscribers: net-systems-diffs@fb.com, yogeshwer, mjpieters Differential Revision: https://phabricator.intern.facebook.com/D4556871 Signature: t1:4556871:1487062142:b58d770d46c975d44933bec08cfce8acb25ff16b
165 lines
5.1 KiB
Python
Executable File
165 lines
5.1 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""
|
|
HTTP server for use in conduit tests.
|
|
"""
|
|
|
|
# no-check-code
|
|
from optparse import OptionParser
|
|
from StringIO import StringIO
|
|
import BaseHTTPServer, json, signal, sys, urlparse
|
|
|
|
try:
|
|
from mercurial.server import runservice
|
|
except ImportError:
|
|
from mercurial.cmdutil import service as runservice
|
|
|
|
known_translations = {}
|
|
next_error_message = []
|
|
|
|
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
def handle_request(self, param):
|
|
from_repo = param['from_repo']
|
|
from_scm = param['from_scm']
|
|
to_repo = param['to_repo']
|
|
to_scm = param['to_scm']
|
|
revs = param['revs']
|
|
|
|
if next_error_message:
|
|
self.send_response(500, next_error_message[0])
|
|
self.end_headers()
|
|
del next_error_message[0]
|
|
|
|
translations = known_translations.get(
|
|
(from_repo, from_scm, to_repo, to_scm), {})
|
|
translated_revs = {}
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
|
|
f = StringIO()
|
|
f.write("for(;;);")
|
|
|
|
response = {}
|
|
|
|
for rev in revs:
|
|
if rev in translations:
|
|
translated_revs[rev] = translations[rev]
|
|
else:
|
|
translated_revs[rev] = ""
|
|
else:
|
|
response['result'] = translated_revs
|
|
response['error_code'] = None
|
|
response['error_info'] = None
|
|
|
|
f.write(json.dumps(response))
|
|
self.wfile.write(f.getvalue())
|
|
|
|
|
|
def do_POST(self):
|
|
content_len = int(self.headers.getheader('content-length', 0))
|
|
data = self.rfile.read(content_len)
|
|
params = urlparse.parse_qs(data)
|
|
if self.path.startswith('/intern/conduit/scmquery.get.mirrored.revs'):
|
|
param = json.loads(params['params'][0])
|
|
self.handle_request(param)
|
|
return
|
|
|
|
self.send_response(500)
|
|
self.end_headers()
|
|
|
|
|
|
def get_path_comps(self):
|
|
assert self.path.startswith("/")
|
|
return self.path[1:].split("/")
|
|
|
|
|
|
def update(self, cmd, comps):
|
|
(from_repo, from_scm,
|
|
to_repo, to_scm,
|
|
from_rev, to_rev) = comps
|
|
key = (from_repo, from_scm, to_repo, to_scm)
|
|
translations = known_translations.setdefault(key, {})
|
|
|
|
if cmd == "PUT":
|
|
translations[from_rev] = to_rev
|
|
self.send_response(201)
|
|
self.end_headers()
|
|
elif cmd == "DELETE":
|
|
translations.pop(from_rev, None)
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
|
|
def do_PUT(self):
|
|
path_comps = self.get_path_comps()
|
|
self.log_message("%s", path_comps)
|
|
if len(path_comps) == 6:
|
|
self.update("PUT", path_comps)
|
|
return
|
|
elif len(path_comps) == 2 and path_comps[0] == 'fail_next':
|
|
# This allows tests to ask us to fail the next HTTP request
|
|
next_error_message.append(path_comps[1])
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
return
|
|
|
|
self.send_response(500)
|
|
self.end_headers()
|
|
|
|
|
|
def do_DELETE(self):
|
|
path_comps = self.get_path_comps()
|
|
if len(path_comps) == 6:
|
|
self.update("DELETE", path_comps)
|
|
return
|
|
|
|
self.send_response(500)
|
|
self.end_headers()
|
|
|
|
|
|
def do_GET(self):
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(known_translations)
|
|
|
|
|
|
class simplehttpservice(object):
|
|
def __init__(self, host, port, port_file):
|
|
self.address = (host, port)
|
|
self.port_file = port_file
|
|
def init(self):
|
|
self.httpd = BaseHTTPServer.HTTPServer(self.address, RequestHandler)
|
|
if self.port_file:
|
|
with open(self.port_file, 'w') as f:
|
|
f.write('%d\n' % self.httpd.server_port)
|
|
def run(self):
|
|
self.httpd.serve_forever()
|
|
|
|
if __name__ == '__main__':
|
|
parser = OptionParser()
|
|
parser.add_option('-p', '--port', dest='port', type='int', default=0,
|
|
help='TCP port to listen on', metavar='PORT')
|
|
parser.add_option('--port-file', dest='port_file',
|
|
help='file name where the server port should be written')
|
|
parser.add_option('-H', '--host', dest='host', default='localhost',
|
|
help='hostname or IP to listen on', metavar='HOST')
|
|
parser.add_option('--pid', dest='pid',
|
|
help='file name where the PID of the server is stored')
|
|
parser.add_option('-f', '--foreground', dest='foreground',
|
|
action='store_true',
|
|
help='do not start the HTTP server in the background')
|
|
parser.add_option('--daemon-postexec', action='append')
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
|
|
|
|
if options.foreground and options.pid:
|
|
parser.error("options --pid and --foreground are mutually exclusive")
|
|
|
|
opts = {'pid_file': options.pid,
|
|
'daemon': not options.foreground,
|
|
'daemon_postexec': options.daemon_postexec}
|
|
service = simplehttpservice(options.host, options.port, options.port_file)
|
|
runservice(opts, initfn=service.init, runfn=service.run,
|
|
runargs=[sys.executable, __file__] + sys.argv[1:])
|