def test_services_config_parsing_errors(conf): """ Test ``announcer.service.Service`` initialization - Consul config parsing errors. :param str conf: custom test function parameter: config file path or JSON """ if conf.startswith('@'): with pytest.raises(AnnouncerImproperlyConfigured): Service('localhost', conf, ['...']) else: with pytest.raises(ValueError): Service('localhost', conf, ['...'])
def test_subprocess_alive(fake_consul): """ Test ``announcer.service.Service`` subprocess spawning. :param fake_consul: custom fixture to disable calls to Consul API """ service = Service('localhost', '@tests/config/correct.json', ['sleep', '0.5'], None, 0.2) service.poll = lambda: None service.run() assert service.process.poll() is None time.sleep(1) assert service.process.poll() == 0
def test_services_config_parsing_success(fake_service): """ Test ``announcer.service.Service`` initialization - Consul config parsing success. :param fake_service: custom fixture to disable calls to Consul API and subprocess spawning """ service = Service('localhost', '@tests/config/correct.json', ['...']) assert len(service.services) == 3 assert len(service.ttl_checks) == 1 service = Service( 'localhost', '{"service": {"name": "simple service", "check": {"ttl": "8s"}}}', ['...'] ) assert len(service.services) == 1 assert len(service.ttl_checks) == 1
def test_consul_interaction(): """ Test ``announcer.service.Service`` interaction with Consul. """ api_url = 'http://*****:*****@tests/config/correct.json', ['sleep', '0.2'], None, 0.1).run()
def test_interval_parsing(fake_service, caplog): """ Test ``announcer.service.Service`` initialization - interval parsing. """ # Interval is provided assert Service('localhost', '@tests/config/correct.json', ['...'], None, 3).interval == 3 # Default interval is 1 sec assert Service('localhost', '@tests/config/correct.json', ['...']).interval == 1 # Interval is auto-calculated as min TTL / 10 assert Service('localhost', '@tests/config/correct.json', ['...'], None, None).interval == 1.5 # No TTL specified with pytest.raises(AnnouncerImproperlyConfigured): Service('localhost', '@tests/config/correct-no-ttl.json', ['...'], None, None) Service('localhost', '@tests/config/correct.json', ['...'], None, 20.0) log_record = caplog.records[-1] assert log_record.levelname == 'WARNING' assert log_record.message == 'Polling interval (20.0 sec) is greater than min TTL (15.0 sec)'
def test_subprocess_cleanup(fake_consul): """ Test ``announcer.service.Service`` subprocess termination when Python process has exited. :param fake_consul: custom fixture to disable calls to Consul API """ service = Service('localhost', '@tests/config/correct.json', ['tail', '-f', '/dev/null']) service.poll = lambda: None service.run() service.__del__() # this method is called by Python on garbage collection time.sleep(0.5) # we need to wait some time until the process is killed assert service.process.poll() is not None # subprocess was killed
def test_subprocess_polling(fake_consul, caplog, monkeypatch): """ Test ``announcer.service.Service`` subprocess keeping alive. :param fake_consul: custom fixture to disable calls to Consul API :param caplog: ``pytest-catchlog`` fixture to catch Python logs :param monkeypatch: pytest "patching" fixture """ monkeypatch.setattr(root_logger, 'level', logging.DEBUG) service = Service('localhost', '@tests/config/correct.json', ['sleep', '0.2'], None, 0.1) service.run() assert service.process.poll() == 0 # No TTL checks - log a message service = Service('localhost', '@tests/config/correct-no-ttl.json', ['sleep', '0.2'], None, 0.1) service.run() assert service.process.poll() == 0 assert caplog.records[-1].message == "No TTL checks registered"
def main(): parser = argparse.ArgumentParser( 'consul-announcer', description="Service announcer for Consul.", formatter_class=ArgsFormatter) parser.add_argument( '--agent', default=os.getenv('CONSUL_ANNOUNCER_AGENT', 'localhost'), help="Consul agent address: hostname[:port]. " "Default: localhost (default port is 8500). " "You can also use CONSUL_ANNOUNCER_AGENT env variable.", metavar='hostname[:port]') parser.add_argument( '--config', required='CONSUL_ANNOUNCER_CONFIG' not in os.environ, default=os.getenv('CONSUL_ANNOUNCER_CONFIG'), help="Consul configuration JSON (required). " "If starts with @ - considered as file path. " "You can also use CONSUL_ANNOUNCER_CONFIG env variable.", metavar='"JSON or @path"') parser.add_argument( '--token', default=os.getenv('CONSUL_ANNOUNCER_TOKEN'), help="Consul ACL token. " "You can also use CONSUL_ANNOUNCER_TOKEN env variable.", metavar='acl-token') parser.add_argument( '--interval', default=os.getenv('CONSUL_ANNOUNCER_INTERVAL'), help= "interval for periodic marking all TTL checks as passed, in seconds. " "Should be less than min TTL. " "You can also use CONSUL_ANNOUNCER_INTERVAL env variable.", metavar='seconds', type=float) parser.add_argument('--verbose', '-v', action='count', help="verbose output. You can specify -v or -vv") if '--' not in sys.argv: if "--help" in sys.argv or "-h" in sys.argv or len(sys.argv) == 1: parser.print_help() sys.exit() else: parser.print_usage() sys.stderr.write("{}: error: command is not specified".format( parser.prog)) sys.exit(2) split_at = sys.argv.index('--') args = parser.parse_args(sys.argv[1:split_at]) cmd = sys.argv[split_at + 1:] if not args.verbose: root_logger.setLevel(logging.WARNING) elif args.verbose == 1: root_logger.setLevel(logging.INFO) elif args.verbose >= 2: root_logger.setLevel(logging.DEBUG) try: Service(agent_address=args.agent, config=args.config, cmd=cmd, token=args.token, interval=args.interval).run() except ConnectionError as e: logger.error("Can't connect to \"{}\"".format(e.request.url)) sys.exit(1) except (AnnouncerImproperlyConfigured, OSError, ValueError) as e: logger.error(e) sys.exit(1)