#!/usr/bin/python3 import os import sys import tempfile import glob import shutil import shlex import subprocess import select sys.path.append('/opt/lib/confluent/python') import confluent.sortutil as sortutil import confluent.client as client def prep_outdir(node): tmpdir = tempfile.mkdtemp() for certfile in glob.glob('/var/lib/confluent/public/site/tls/*.pem'): basename = os.path.basename(certfile) destfile = os.path.join(tmpdir, basename) shutil.copy2(certfile, destfile) subprocess.check_call(shlex.split(f'confetty set /nodes/{node}/deployment/ident_image=create')) shutil.copy2(f'/var/lib/confluent/private/identity_files/{node}.json', os.path.join(tmpdir, 'identity.json')) return tmpdir def exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller): remotedir = subprocess.check_output(shlex.split(f'ssh {host} mktemp -d /tmp/bfb.XXXXXX')).decode().strip() bfbbasename = os.path.basename(bfbfile) subprocess.check_call(shlex.split(f'rsync -avz --info=progress2 {bfbfile} {host}:{remotedir}/{bfbbasename}')) subprocess.check_call(shlex.split(f'rsync -avc --info=progress2 /opt/lib/confluent/osdeploy/bluefield/hostscripts/ {host}:{remotedir}/')) for node in nodetorshim: rshim = nodetorshim[node] nodeoutdir = prep_outdir(node) nodeprofile = subprocess.check_output(shlex.split(f'nodeattrib {node} deployment.pendingprofile')).decode().strip().split(':', 2)[2].strip() shutil.copy2(f'/var/lib/confluent/public/os/{nodeprofile}/bfb.cfg.template', os.path.join(nodeoutdir, 'bfb.cfg.template')) subprocess.check_call(shlex.split(f'rsync -avz {nodeoutdir}/ {host}:{remotedir}/{node}/')) shutil.rmtree(nodeoutdir) run_cmdv(node, shlex.split(f'ssh {host} sh /etc/confluent/functions confluentpython {remotedir}/bfb-autoinstall {node} {remotedir}/{bfbbasename} {rshim}'), all, poller, pipedesc) def run_cmdv(node, cmdv, all, poller, pipedesc): try: nopen = subprocess.Popen( cmdv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError as e: if e.errno == 2: sys.stderr.write('{0}: Unable to find local executable file "{1}"\n'.format(node, cmdv[0])) return raise pipedesc[nopen.stdout.fileno()] = {'node': node, 'popen': nopen, 'type': 'stdout', 'file': nopen.stdout} pipedesc[nopen.stderr.fileno()] = {'node': node, 'popen': nopen, 'type': 'stderr', 'file': nopen.stderr} all.add(nopen.stdout) poller.register(nopen.stdout, select.EPOLLIN) all.add(nopen.stderr) poller.register(nopen.stderr, select.EPOLLIN) if __name__ == '__main__': if len(sys.argv) < 3: print(f'Usage: {sys.argv[0]} [ ...]') sys.exit(1) host = sys.argv[1] bfbfile = sys.argv[2] nodetorshim = {} for arg in sys.argv[3:]: node, rshim = arg.split(':') nodetorshim[node] = rshim installprocs = {} pipedesc = {} all = set() poller = select.epoll() exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller) rdy = poller.poll(10) pendingexecs = [] exitcode = 0 while all: pernodeout = {} for r in rdy: r = r[0] desc = pipedesc[r] r = desc['file'] node = desc['node'] data = True singlepoller = select.epoll() singlepoller.register(r, select.EPOLLIN) while data and singlepoller.poll(0): data = r.readline() if data: if desc['type'] == 'stdout': if node not in pernodeout: pernodeout[node] = [] pernodeout[node].append(data) else: data = client.stringify(data) sys.stderr.write('{0}: {1}'.format(node, data)) sys.stderr.flush() else: pop = desc['popen'] ret = pop.poll() if ret is not None: exitcode = exitcode | ret all.discard(r) poller.unregister(r) r.close() if desc['type'] == 'stdout' and pendingexecs: node, cmdv = pendingexecs.popleft() run_cmdv(node, cmdv, all, poller, pipedesc) singlepoller.close() for node in sortutil.natural_sort(pernodeout): for line in pernodeout[node]: line = client.stringify(line) sys.stdout.write('{0}: {1}'.format(node, line)) sys.stdout.flush() if all: rdy = poller.poll(10)