def __init__(self, config_file): self.logger = logging.getLogger( 'cachet_url_monitor.scheduler.Scheduler') self.configuration = Configuration(config_file) self.agent = self.get_agent() self.stop = False
def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger def get(url, headers): get_return = mock.Mock() get_return.ok = True get_return.json = mock.Mock() get_return.json.return_value = { 'data': { 'status': 1, 'default_value': 0.5 } } return get_return sys.modules['requests'].get = get self.env = EnvironmentVarGuard() self.env.set('CACHET_TOKEN', 'token2') self.configuration = Configuration('config.yml') sys.modules['requests'].Timeout = Timeout sys.modules['requests'].ConnectionError = ConnectionError sys.modules['requests'].HTTPError = HTTPError
def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger self.client = mock.Mock() # We set the initial status to OPERATIONAL. self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL self.configuration = Configuration( load(open(os.path.join(os.path.dirname(__file__), 'configs/config.yml'), 'rt'), SafeLoader), 0, self.client, 'token2')
class Scheduler(object): def __init__(self, config_file): self.logger = logging.getLogger('cachet_url_monitor.scheduler.Scheduler') self.configuration = Configuration(config_file) self.agent = self.get_agent() self.stop = False def get_agent(self): action_names = { 'CREATE_INCIDENT': CreateIncidentDecorator, 'UPDATE_STATUS': UpdateStatusDecorator, } actions = [] for action in self.configuration.get_action(): self.logger.info('Registering action %s' % (action)) actions.append(action_names[action]()) return Agent(self.configuration, decorators=actions) def start(self): self.agent.start() self.logger.info('Starting monitor agent...') while not self.stop: schedule.run_pending() time.sleep(self.configuration.data['frequency'])
class Scheduler(object): def __init__(self, config_file): self.logger = logging.getLogger( 'cachet_url_monitor.scheduler.Scheduler') self.configuration = Configuration(config_file) self.agent = self.get_agent() self.stop = False def get_agent(self): action_names = { 'CREATE_INCIDENT': CreateIncidentDecorator, 'UPDATE_STATUS': UpdateStatusDecorator, } actions = [] for action in self.configuration.get_action(): self.logger.info('Registering action %s' % (action)) actions.append(action_names[action]()) return Agent(self.configuration, decorators=actions) def start(self): self.agent.start() self.logger.info('Starting monitor agent...') while not self.stop: schedule.run_pending() time.sleep(self.configuration.data['frequency'])
def test_init_with_metric_id(metric_config_file, mock_client): mock_client.get_default_metric_value.return_value = 0.456 configuration = Configuration(metric_config_file, 0, mock_client) assert (configuration.default_metric_value == 0.456 ), "Default metric was not set during init" mock_client.get_default_metric_value.assert_called_once_with(3)
def setUp(self): config_yaml = load(open(os.path.join(os.path.dirname(__file__), 'configs/config_multiple_urls.yml'), 'rt'), SafeLoader) self.client = [] self.configuration = [] for index in range(len(config_yaml['endpoints'])): client = mock.Mock() self.client.append(client) self.configuration.append(Configuration(config_yaml, index, client, 'token2'))
def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger def get(url, headers): get_return = mock.Mock() get_return.ok = True get_return.json = mock.Mock() get_return.json.return_value = {'data': {'status': 1, 'default_value': 0.5}} return get_return sys.modules['requests'].get = get self.configuration = Configuration('config.yml') sys.modules['requests'].Timeout = Timeout sys.modules['requests'].ConnectionError = ConnectionError sys.modules['requests'].HTTPError = HTTPError
def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger def get(url, headers): get_return = mock.Mock() get_return.ok = True get_return.json = mock.Mock() get_return.json.return_value = {'data': {'status': 1}} return get_return sys.modules['requests'].get = get self.env = EnvironmentVarGuard() self.env.set('CACHET_TOKEN', 'token2') self.configuration = Configuration('config.yml') sys.modules['requests'].Timeout = Timeout sys.modules['requests'].ConnectionError = ConnectionError sys.modules['requests'].HTTPError = HTTPError
def test_init_unknown_status(config_file, mock_client): mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.UNKNOWN configuration = Configuration(config_file, 0, mock_client) assert configuration.previous_status == cachet_url_monitor.status.ComponentStatus.UNKNOWN
def multiple_urls_configuration(multiple_urls_config_file, mock_client, mock_logger): yield [ Configuration(multiple_urls_config_file, index, mock_client) for index in range(len(multiple_urls_config_file["endpoints"])) ]
def test_init_missing_latency_unit(missing_latency_unit_config_file, mock_client): configuration = Configuration(missing_latency_unit_config_file, 0, mock_client) assert configuration.latency_unit == "s"
def header_configuration(header_config_file, mock_client, mock_logger): yield Configuration(header_config_file, 0, mock_client)
def webhooks_configuration(webhooks_config_file, mock_client, mock_logger): webhooks = [] for webhook in webhooks_config_file.get("webhooks", []): webhooks.append(Webhook(webhook["url"], webhook.get("params", {}))) yield Configuration(webhooks_config_file, 0, mock_client, webhooks)
def configuration(config_file, mock_client, mock_logger): yield Configuration(config_file, 0, mock_client)
def __init__(self, config_file): self.logger = logging.getLogger('cachet_url_monitor.scheduler.Scheduler') self.configuration = Configuration(config_file) self.agent = self.get_agent() self.stop = False
def webhooks_configuration(webhooks_config_file, mock_client, mock_logger): webhooks = [] for webhook in webhooks_config_file.get('webhooks', []): webhooks.append(Webhook(webhook['url'], webhook.get('params', {}))) yield Configuration(webhooks_config_file, 0, mock_client, webhooks)
class ConfigurationTest(unittest.TestCase): def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger def get(url, headers): get_return = mock.Mock() get_return.ok = True get_return.json = mock.Mock() get_return.json.return_value = {'data': {'status': 1}} return get_return sys.modules['requests'].get = get self.env = EnvironmentVarGuard() self.env.set('CACHET_TOKEN', 'token2') self.configuration = Configuration('config.yml') sys.modules['requests'].Timeout = Timeout sys.modules['requests'].ConnectionError = ConnectionError sys.modules['requests'].HTTPError = HTTPError def test_init(self): self.assertEqual(len(self.configuration.data), 3, 'Configuration data size is incorrect') self.assertEquals(len(self.configuration.expectations), 3, 'Number of expectations read from file is incorrect') self.assertDictEqual(self.configuration.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') self.assertEquals(self.configuration.api_url, 'https://demo.cachethq.io/api/v1', 'Cachet API URL was set incorrectly') def test_evaluate(self): def total_seconds(): return 0.1 def request(method, url, timeout=None): response = mock.Mock() response.status_code = 200 response.elapsed = mock.Mock() response.elapsed.total_seconds = total_seconds response.text = '<body>' return response sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Component status set incorrectly') def test_evaluate_with_failure(self): def total_seconds(): return 0.1 def request(method, url, timeout=None): response = mock.Mock() # We are expecting a 200 response, so this will fail the expectation. response.status_code = 400 response.elapsed = mock.Mock() response.elapsed.total_seconds = total_seconds response.text = '<body>' return response sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') def test_evaluate_with_timeout(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise Timeout() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PERFORMANCE_ISSUES, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with('Request timed out') def test_evaluate_with_connection_error(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise ConnectionError() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with('The URL is unreachable: GET http://localhost:8080/swagger') def test_evaluate_with_http_error(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise HTTPError() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.exception.assert_called_with('Unexpected HTTP response') def test_push_status(self): def put(url, params=None, headers=None): self.assertEquals(url, 'https://demo.cachethq.io/api/v1/components/1', 'Incorrect cachet API URL') self.assertDictEqual(params, {'id': 1, 'status': 1}, 'Incorrect component update parameters') self.assertDictEqual(headers, {'X-Cachet-Token': 'token2'}, 'Incorrect component update parameters') response = mock.Mock() response.status_code = 200 return response sys.modules['requests'].put = put self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Incorrect component update parameters') self.configuration.push_status() def test_push_status_with_failure(self): def put(url, params=None, headers=None): self.assertEquals(url, 'https://demo.cachethq.io/api/v1/components/1', 'Incorrect cachet API URL') self.assertDictEqual(params, {'id': 1, 'status': 1}, 'Incorrect component update parameters') self.assertDictEqual(headers, {'X-Cachet-Token': 'token2'}, 'Incorrect component update parameters') response = mock.Mock() response.status_code = 400 return response sys.modules['requests'].put = put self.assertEquals(self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Incorrect component update parameters') self.configuration.push_status()
def test_init(self): with pytest.raises(cachet_url_monitor.configuration.ConfigurationValidationError): self.configuration = Configuration( load(open(os.path.join(os.path.dirname(__file__), 'configs/config_invalid_type.yml'), 'rt'), SafeLoader), 0, mock.Mock(), 'token2')
def build_agent(configuration: Configuration, logger: logging.Logger): actions: List[Decorator] = [] for action in configuration.get_action(): logger.info(f"Registering action {action}") actions.append(ACTION_NAMES_DECORATOR_MAP[action]()) return Agent(configuration, decorators=actions)
if len(sys.argv) <= 1: fatal_error("Missing configuration file argument") sys.exit(1) try: config_data = load(open(sys.argv[1], "r"), SafeLoader) except FileNotFoundError: fatal_error(f"File not found: {sys.argv[1]}") sys.exit(1) validate_config() webhooks: List[Webhook] = [] for webhook in config_data.get("webhooks", []): webhooks.append(Webhook(webhook["url"], webhook.get("params", {}))) token: str = get_token(config_data["cachet"]["token"]) api_url: str = os.environ.get( "CACHET_API_URL") or config_data["cachet"]["api_url"] client: CachetClient = CachetClient(api_url, token) for endpoint_index in range(len(config_data["endpoints"])): configuration = Configuration(config_data, endpoint_index, client, webhooks) NewThread( Scheduler( configuration, build_agent(configuration, logging.getLogger( "cachet_url_monitor.scheduler")))).start()
def test_init_invalid_configuration(invalid_config_file, mock_client): with pytest.raises( cachet_url_monitor.configuration.ConfigurationValidationError): Configuration(invalid_config_file, 0, mock_client)
def insecure_configuration(insecure_config_file, mock_client, mock_logger): yield Configuration(insecure_config_file, 0, mock_client)
def execute(self, configuration: Configuration): configuration.push_status()
def fatal_error(message): logging.getLogger('cachet_url_monitor.scheduler').fatal("%s", message) sys.exit(1) if __name__ == "__main__": FORMAT = "%(levelname)9s [%(asctime)-15s] %(name)s - %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) for handler in logging.root.handlers: handler.addFilter(logging.Filter('cachet_url_monitor')) if len(sys.argv) <= 1: logging.getLogger('cachet_url_monitor.scheduler').fatal('Missing configuration file argument') sys.exit(1) try: config_data = load(open(sys.argv[1], 'r'), SafeLoader) except FileNotFoundError: logging.getLogger('cachet_url_monitor.scheduler').fatal(f'File not found: {sys.argv[1]}') sys.exit(1) validate_config() for endpoint_index in range(len(config_data['endpoints'])): token = os.environ.get('CACHET_TOKEN') or config_data['cachet']['token'] api_url = os.environ.get('CACHET_API_URL') or config_data['cachet']['api_url'] configuration = Configuration(config_data, endpoint_index, CachetClient(api_url, token), token) NewThread(Scheduler(configuration, build_agent(configuration, logging.getLogger('cachet_url_monitor.scheduler')))).start()
class ConfigurationTest(unittest.TestCase): client: CachetClient configuration: Configuration def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger self.client = mock.Mock() # We set the initial status to OPERATIONAL. self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL self.configuration = Configuration( load(open(os.path.join(os.path.dirname(__file__), 'configs/config.yml'), 'rt'), SafeLoader), 0, self.client, 'token2') def test_init(self): self.assertEqual(len(self.configuration.data), 2, 'Number of root elements in config.yml is incorrect') self.assertEqual(len(self.configuration.expectations), 3, 'Number of expectations read from file is incorrect') self.assertDictEqual(self.configuration.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') self.assertDictEqual(self.configuration.endpoint_header, {'SOME-HEADER': 'SOME-VALUE'}, 'Header is incorrect') @requests_mock.mock() def test_evaluate(self, m): m.get('http://localhost:8080/swagger', text='<body>') self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly') @requests_mock.mock() def test_evaluate_without_header(self, m): m.get('http://localhost:8080/swagger', text='<body>') self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly') @requests_mock.mock() def test_evaluate_with_failure(self, m): m.get('http://localhost:8080/swagger', text='<body>', status_code=400) self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.MAJOR_OUTAGE, 'Component status set incorrectly or custom incident status is incorrectly parsed') @requests_mock.mock() def test_evaluate_with_timeout(self, m): m.get('http://localhost:8080/swagger', exc=requests.Timeout) self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PERFORMANCE_ISSUES, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with('Request timed out') @requests_mock.mock() def test_evaluate_with_connection_error(self, m): m.get('http://localhost:8080/swagger', exc=requests.ConnectionError) self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with('The URL is unreachable: GET http://localhost:8080/swagger') @requests_mock.mock() def test_evaluate_with_http_error(self, m): m.get('http://localhost:8080/swagger', exc=requests.HTTPError) self.configuration.evaluate() self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.exception.assert_called_with('Unexpected HTTP response') def test_push_status(self): self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL push_status_response = mock.Mock() self.client.push_status.return_value = push_status_response push_status_response.ok = True self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE self.configuration.push_status() self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) def test_push_status_with_failure(self): self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL push_status_response = mock.Mock() self.client.push_status.return_value = push_status_response push_status_response.ok = False self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE self.configuration.push_status() self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) def test_push_status_same_status(self): self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL self.configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL self.configuration.push_status() self.client.push_status.assert_not_called()
def execute(self, configuration: Configuration): configuration.push_incident()
class ConfigurationTest(unittest.TestCase): def setUp(self): def getLogger(name): self.mock_logger = mock.Mock() return self.mock_logger sys.modules['logging'].getLogger = getLogger def get(url, headers): get_return = mock.Mock() get_return.ok = True get_return.json = mock.Mock() get_return.json.return_value = {'data': {'status': 1}} return get_return sys.modules['requests'].get = get self.env = EnvironmentVarGuard() self.env.set('CACHET_TOKEN', 'token2') self.configuration = Configuration('config.yml') sys.modules['requests'].Timeout = Timeout sys.modules['requests'].ConnectionError = ConnectionError sys.modules['requests'].HTTPError = HTTPError def test_init(self): self.assertEqual(len(self.configuration.data), 3, 'Configuration data size is incorrect') self.assertEquals( len(self.configuration.expectations), 3, 'Number of expectations read from file is incorrect') self.assertDictEqual(self.configuration.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') self.assertEquals(self.configuration.api_url, 'https://demo.cachethq.io/api/v1', 'Cachet API URL was set incorrectly') def test_evaluate(self): def total_seconds(): return 0.1 def request(method, url, timeout=None): response = mock.Mock() response.status_code = 200 response.elapsed = mock.Mock() response.elapsed.total_seconds = total_seconds response.text = '<body>' return response sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Component status set incorrectly') def test_evaluate_with_failure(self): def total_seconds(): return 0.1 def request(method, url, timeout=None): response = mock.Mock() # We are expecting a 200 response, so this will fail the expectation. response.status_code = 400 response.elapsed = mock.Mock() response.elapsed.total_seconds = total_seconds response.text = '<body>' return response sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') def test_evaluate_with_timeout(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise Timeout() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PERFORMANCE_ISSUES, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with('Request timed out') def test_evaluate_with_connection_error(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise ConnectionError() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.warning.assert_called_with( 'The URL is unreachable: GET http://localhost:8080/swagger') def test_evaluate_with_http_error(self): def request(method, url, timeout=None): self.assertEquals(method, 'GET', 'Incorrect HTTP method') self.assertEquals(url, 'http://localhost:8080/swagger', 'Monitored URL is incorrect') self.assertEquals(timeout, 0.010) raise HTTPError() sys.modules['requests'].request = request self.configuration.evaluate() self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_PARTIAL_OUTAGE, 'Component status set incorrectly') self.mock_logger.exception.assert_called_with( 'Unexpected HTTP response') def test_push_status(self): def put(url, params=None, headers=None): self.assertEquals(url, 'https://demo.cachethq.io/api/v1/components/1', 'Incorrect cachet API URL') self.assertDictEqual(params, { 'id': 1, 'status': 1 }, 'Incorrect component update parameters') self.assertDictEqual(headers, {'X-Cachet-Token': 'token2'}, 'Incorrect component update parameters') response = mock.Mock() response.status_code = 200 return response sys.modules['requests'].put = put self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Incorrect component update parameters') self.configuration.push_status() def test_push_status_with_failure(self): def put(url, params=None, headers=None): self.assertEquals(url, 'https://demo.cachethq.io/api/v1/components/1', 'Incorrect cachet API URL') self.assertDictEqual(params, { 'id': 1, 'status': 1 }, 'Incorrect component update parameters') self.assertDictEqual(headers, {'X-Cachet-Token': 'token2'}, 'Incorrect component update parameters') response = mock.Mock() response.status_code = 400 return response sys.modules['requests'].put = put self.assertEquals( self.configuration.status, cachet_url_monitor.status.COMPONENT_STATUS_OPERATIONAL, 'Incorrect component update parameters') self.configuration.push_status()
def execute(self, configuration: Configuration): configuration.push_metrics()