From ae65aed40a1111514d6918619f1d3da2f5247216 Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Sat, 4 Apr 2015 23:33:09 +0300 Subject: [PATCH 1/4] More verbose check nodes for being alive --- dev/run | 64 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/dev/run b/dev/run index fcf9514c59d..501c3cf4fc0 100755 --- a/dev/run +++ b/dev/run @@ -50,13 +50,13 @@ def log(msg): sys.stdout.flush() callargs = dict(zip(inspect.getargspec(func).args, args)) callargs.update(kwargs) - print_(msg.format(**callargs) + '... ') + print_(msg.format(**callargs) + ' ... ') try: res = func(*args, **kwargs) except KeyboardInterrupt: print_('ok\n') - except: - print_('failed\n') + except Exception as err: + print_('failed: %s\n' % err) raise else: print_('ok\n') @@ -230,6 +230,7 @@ def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=20): def startup(ctx): atexit.register(kill_processes, ctx) boot_nodes(ctx) + ensure_all_nodes_alive(ctx) join_nodes(ctx, "127.0.0.1", 15986) @@ -243,30 +244,44 @@ def boot_nodes(ctx): for node in ctx['nodes']: ctx['procs'].append(boot_node(ctx, node)) - ensure_nodes_stated(ctx) - - -@log('Ensure all nodes are run') -def ensure_nodes_stated(ctx): - for _ in range(30): - if all_nodes_alive(ctx): - break - time.sleep(1) - -def all_nodes_alive(ctx): - for num in range(ctx['N']): - local_port, _ = get_ports(num + 1) - url = "http://127.0.0.1:{0}/".format(local_port) - while True: - try: - with contextlib.closing(urlopen(url)): - pass - except IOError: - time.sleep(0.25) +def ensure_all_nodes_alive(ctx): + status = dict((num, False) for num in range(ctx['N'])) + for _ in range(10): + for num in range(ctx['N']): + if status[num]: continue + local_port, _ = get_ports(num + 1) + url = "http://127.0.0.1:{0}/".format(local_port) + try: + check_node_alive(url) + except: + pass + else: + status[num] = True + if all(status.values()): + return + time.sleep(1) + if not all(status.values()): + print('Failed to start all the nodes.' + ' Check the dev/logs/*.log for errors.') + sys.exit(1) + + +@log('Check node at {url}') +def check_node_alive(url): + error = None + for _ in range(10): + try: + with contextlib.closing(urlopen(url)): + pass + except Exception as exc: + error = exc + time.sleep(1) + else: break - return True + if error is not None: + raise error @log('Start node {node}') @@ -332,6 +347,7 @@ def run_command(ctx, cmd): def reboot_nodes(ctx): kill_processes(ctx) boot_nodes(ctx) + ensure_all_nodes_alive(ctx) if __name__ == "__main__": From e52e00c8676de57c8f477ff18707406c05ac40f5 Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Sun, 5 Apr 2015 00:08:00 +0300 Subject: [PATCH 2/4] Use /_cluster_setup --- dev/run | 119 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 22 deletions(-) diff --git a/dev/run b/dev/run index 501c3cf4fc0..7856addefe4 100755 --- a/dev/run +++ b/dev/run @@ -13,10 +13,12 @@ # the License. import atexit +import base64 import contextlib import functools import glob import inspect +import json import optparse import os import re @@ -72,7 +74,7 @@ def main(): if ctx['cmd']: run_command(ctx, ctx['cmd']) else: - join(ctx) + join(ctx, 15984, *ctx['admin']) def setup(): @@ -105,7 +107,7 @@ def setup_argparse(): def setup_context(opts, args): fpath = os.path.abspath(__file__) return {'N': opts.nodes, - 'admin': opts.admin, + 'admin': opts.admin.split(':', 1) if opts.admin else None, 'nodes': ['node%d' % (i + 1) for i in range(opts.nodes)], 'devdir': os.path.dirname(fpath), 'rootdir': os.path.dirname(os.path.dirname(fpath)), @@ -204,12 +206,19 @@ def hack_local_ini(ctx, contents): secret_line = "secret = %s\n" % COMMON_SALT previous_line = "; require_valid_user = false\n" contents = contents.replace(previous_line, previous_line + secret_line) - # if --admin user:password on invocation, make sure all three nodes - # have the same hashed password + + # handle admin credentials passed from cli or generate own one if ctx['admin'] is None: - return contents - usr, pwd = ctx['admin'].split(":", 1) - return contents + "\n%s = %s" % (usr, hashify(pwd)) + ctx['admin'] = user, pswd = 'root', gen_password() + else: + user, pswd = ctx['admin'] + + return contents + "\n%s = %s" % (user, hashify(pswd)) + + +def gen_password(): + # TODO: figure how to generate something more friendly here + return base64.b64encode(os.urandom(6)).decode() def hashify(pwd, salt=COMMON_SALT, iterations=10, keylen=20): @@ -231,7 +240,7 @@ def startup(ctx): atexit.register(kill_processes, ctx) boot_nodes(ctx) ensure_all_nodes_alive(ctx) - join_nodes(ctx, "127.0.0.1", 15986) + cluster_setup(ctx) def kill_processes(ctx): @@ -310,20 +319,86 @@ def boot_node(ctx, node): return sp.Popen(cmd, stdin=sp.PIPE, stdout=log, stderr=sp.STDOUT, env=env) -@log('Join nodes into cluster') -def join_nodes(ctx, host, port): - for node in ctx['nodes']: - body = "{}" - conn = httpclient.HTTPConnection(host, port) - conn.request("PUT", "/_nodes/%s@127.0.0.1" % node, body) - resp = conn.getresponse() - if resp.status not in (200, 201, 202, 409): - print('Failed to join %s into cluster' % node, resp.reason) - exit(1) - - -@log('Developers cluster is set up at http://127.0.0.1:15984. Time to hack!') -def join(ctx): +@log('Running cluster setup') +def cluster_setup(ctx): + lead_port, _ = get_ports(1) + if enable_cluster(lead_port, *ctx['admin']): + for num in range(1, ctx['N']): + node_port, _ = get_ports(num + 1) + enable_cluster(node_port, *ctx['admin']) + add_node(lead_port, node_port, *ctx['admin']) + finish_cluster(lead_port, *ctx['admin']) + return lead_port + + +def enable_cluster(port, user, pswd): + conn = httpclient.HTTPConnection('127.0.0.1', port) + conn.request('POST', '/_cluster_setup', + json.dumps({'action': 'enable_cluster', + 'bind_address': '0.0.0.0', + 'username': user, + 'password': pswd}), + {'Authorization': basic_auth_header(user, pswd), + 'Content-Type': 'application/json'}) + resp = conn.getresponse() + if resp.status == 400: + resp.close() + return False + assert resp.status == 201, resp.read() + resp.close() + + +def add_node(lead_port, node_port, user, pswd): + conn = httpclient.HTTPConnection('127.0.0.1', lead_port) + conn.request('POST', '/_cluster_setup', + json.dumps({'action': 'add_node', + 'host': '127.0.0.1', + 'port': node_port, + 'username': user, + 'password': pswd}), + {'Authorization': basic_auth_header(user, pswd), + 'Content-Type': 'application/json'}) + resp = conn.getresponse() + assert resp.status in (201, 409), resp.read() + resp.close() + + +def set_cookie(port, user, pswd): + conn = httpclient.HTTPConnection('127.0.0.1', port) + conn.request('POST', '/_cluster_setup', + json.dumps({'action': 'receive_cookie', + 'cookie': generate_cookie()}), + {'Authorization': basic_auth_header(user, pswd), + 'Content-Type': 'application/json'}) + resp = conn.getresponse() + assert resp.status == 201, resp.read() + resp.close() + + +def finish_cluster(port, user, pswd): + conn = httpclient.HTTPConnection('127.0.0.1', port) + conn.request('POST', '/_cluster_setup', + json.dumps({'action': 'finish_cluster'}), + {'Authorization': basic_auth_header(user, pswd), + 'Content-Type': 'application/json'}) + resp = conn.getresponse() + assert resp.status == 201, resp.read() + resp.close() + + +def basic_auth_header(user, pswd): + return 'Basic ' + base64.b64encode((user + ':' + pswd).encode()).decode() + + +def generate_cookie(): + return base64.b64encode(os.urandom(12)).decode() + + +@log('Developers cluster is set up at http://127.0.0.1:{lead_port}.\n' + 'Admin username: {user}\n' + 'Password: {password}\n' + 'Time to hack!') +def join(ctx, lead_port, user, password): while True: for proc in ctx['procs']: if proc.returncode is not None: From 3c305077a54e8fb8eabc784eb36df8653ba798fc Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Sun, 5 Apr 2015 01:18:01 +0300 Subject: [PATCH 3/4] Print dev/run steps nicer --- dev/run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/run b/dev/run index 7856addefe4..bc8286cc0e1 100755 --- a/dev/run +++ b/dev/run @@ -52,7 +52,7 @@ def log(msg): sys.stdout.flush() callargs = dict(zip(inspect.getargspec(func).args, args)) callargs.update(kwargs) - print_(msg.format(**callargs) + ' ... ') + print_('[ * ] ' + msg.format(**callargs) + ' ... ') try: res = func(*args, **kwargs) except KeyboardInterrupt: From 9f5ae101b30b3df6ada11edaa7efeb0923057222 Mon Sep 17 00:00:00 2001 From: Alexander Shorin Date: Sun, 5 Apr 2015 01:54:51 +0300 Subject: [PATCH 4/4] Restore ability to make a cluster in Admin Party state JavaScript tests are need it to run. --- Makefile | 2 +- dev/run | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 534cdae8004..2f49a6e7ee9 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ eunit: couch javascript: all @mkdir -p share/www/script/test @cp test/javascript/tests/lorem*.txt share/www/script/test/ - @dev/run -q test/javascript/run + @dev/run -q --with-admin-party-please test/javascript/run fauxton: share/www diff --git a/dev/run b/dev/run index bc8286cc0e1..6087f144084 100755 --- a/dev/run +++ b/dev/run @@ -101,12 +101,17 @@ def setup_argparse(): parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="Don't print anything to STDOUT") + parser.add_option('--with-admin-party-please', + dest='with_admin_party', default=False, + action='store_true', + help='Runs a dev cluster with admin party mode on') return parser.parse_args() def setup_context(opts, args): fpath = os.path.abspath(__file__) return {'N': opts.nodes, + 'with_admin_party': opts.with_admin_party, 'admin': opts.admin.split(':', 1) if opts.admin else None, 'nodes': ['node%d' % (i + 1) for i in range(opts.nodes)], 'devdir': os.path.dirname(fpath), @@ -207,6 +212,10 @@ def hack_local_ini(ctx, contents): previous_line = "; require_valid_user = false\n" contents = contents.replace(previous_line, previous_line + secret_line) + if ctx['with_admin_party']: + ctx['admin'] = ('Admin Party!', 'You do not need any password.') + return contents + # handle admin credentials passed from cli or generate own one if ctx['admin'] is None: ctx['admin'] = user, pswd = 'root', gen_password() @@ -240,7 +249,10 @@ def startup(ctx): atexit.register(kill_processes, ctx) boot_nodes(ctx) ensure_all_nodes_alive(ctx) - cluster_setup(ctx) + if ctx['with_admin_party']: + cluster_setup_with_admin_party(ctx) + else: + cluster_setup(ctx) def kill_processes(ctx): @@ -394,6 +406,31 @@ def generate_cookie(): return base64.b64encode(os.urandom(12)).decode() +def cluster_setup_with_admin_party(ctx): + host, port = '127.0.0.1', 15986 + for node in ctx['nodes']: + body = '{}' + conn = httpclient.HTTPConnection(host, port) + conn.request('PUT', "/_nodes/%s@127.0.0.1" % node, body) + resp = conn.getresponse() + if resp.status not in (200, 201, 202, 409): + print('Failed to join %s into cluster: %s' % (node, resp.read())) + sys.exit(1) + create_system_databases(host, 15984) + + +def create_system_databases(host, port): + for dbname in ['_users', '_replicator', '_metadata']: + conn = httpclient.HTTPConnection(host, port) + conn.request('HEAD', '/' + dbname) + resp = conn.getresponse() + if resp.status == 404: + conn = httpclient.HTTPConnection(host, port) + conn.request('PUT', '/' + dbname) + resp = conn.getresponse() + assert resp.status == 201, resp.read() + + @log('Developers cluster is set up at http://127.0.0.1:{lead_port}.\n' 'Admin username: {user}\n' 'Password: {password}\n'