"""A utility for waiting for services to be up and running.""" import argparse import socket import time import requests import urllib3 class WaitingTimeoutException(RuntimeError): """Thrown when the number of tries has reached a specified limit.""" pass def wait_for_1_sec(): """Wait for 1 second.""" time.sleep(1) def tcp_available(url): """Check if the specified socket is listening.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((url.host, url.port)) return True except socket.error: return False finally: s.close() def http_available(url): """Check if the specified url responds.""" try: return requests.get(url) except requests.exceptions.ConnectionError: return False strategies = dict( tcp=tcp_available, http=http_available, https=http_available ) def wait(services, num_tries): """Wait num_tries times for all the services to be up.""" tries = 0 available = set() while tries < num_tries and services - available: for service in services - available: print(f'Trying {service}... ', end='') up = strategies[service.scheme](service) if up: print('SUCCEEDED') available.add(service) else: print('UNAVAILABLE') wait_for_1_sec() tries += 1 if services - available: remaining = {str(service) for service in services - available} raise WaitingTimeoutException( f'The following services did not respond in time: {remaining}') def validate(services): """Parse the service urls and check that the scheme is supported.""" urls = {urllib3.util.parse_url(service) for service in services} for url in urls: if url.scheme not in strategies: raise ValueError('Unsupported scheme', url.scheme) return urls def main(): """Do the thing.""" parser = argparse.ArgumentParser( description='Wait for services to be available.' ) parser.add_argument( '--services', nargs='+', help='services to wait for', required=True ) parser.add_argument( '--tries', type=int, help='number of times to poll (default 120)', default=120 ) args = parser.parse_args() wait(validate(args.services), args.tries)