Пример #1
0
 def test_missing_zabbix_password(self):
     config = {
         'init_config': {},
         'instances': [
             {
                 'url': "http://host/zabbix/api_jsonrpc.php",
                 'user': '******'
             }
         ]
     }
     with self.assertRaises(ConfigurationError) as context:
         self.check = ZabbixCheck(CHECK_NAME, config, instances=[config["instances"]])
         self.check.check(config["instances"][0])
     self.assertEqual('Missing API password in configuration.', str(context.exception))
Пример #2
0
    def test_missing_zabbix_url(self):
        config = {
            'init_config': {},
            'instances': [
                {
                    'user': '******',
                    'password': '******'
                }
            ]
        }

        with self.assertRaises(ConfigurationError) as context:
            self.check = ZabbixCheck(CHECK_NAME, config, instances=[config["instances"]])
            self.check.get_instance_key(config["instances"][0])
        self.assertEqual('Missing API url in configuration.', str(context.exception))
Пример #3
0
class TestZabbixInvalidConfig(unittest.TestCase):

    def test_missing_zabbix_url(self):
        config = {
            'init_config': {},
            'instances': [
                {
                    'user': '******',
                    'password': '******'
                }
            ]
        }

        with self.assertRaises(ConfigurationError) as context:
            self.check = ZabbixCheck(CHECK_NAME, config, instances=[config["instances"]])
            self.check.get_instance_key(config["instances"][0])
        self.assertEqual('Missing API url in configuration.', str(context.exception))

    def test_missing_zabbix_user(self):
        config = {
            'init_config': {},
            'instances': [
                {
                    'url': "http://host/zabbix/api_jsonrpc.php",
                    'password': '******'
                }
            ]
        }

        with self.assertRaises(ConfigurationError) as context:
            self.check = ZabbixCheck(CHECK_NAME, config, instances=[config["instances"]])
            self.check.check(config["instances"][0])
        self.assertEqual('Missing API user in configuration.', str(context.exception))

    def test_missing_zabbix_password(self):
        config = {
            'init_config': {},
            'instances': [
                {
                    'url': "http://host/zabbix/api_jsonrpc.php",
                    'user': '******'
                }
            ]
        }
        with self.assertRaises(ConfigurationError) as context:
            self.check = ZabbixCheck(CHECK_NAME, config, instances=[config["instances"]])
            self.check.check(config["instances"][0])
        self.assertEqual('Missing API password in configuration.', str(context.exception))
Пример #4
0
class TestZabbix(unittest.TestCase):

    _config = {
        'init_config': {},
        'instances': [
            {
                'url': "http://host/zabbix/api_jsonrpc.php",
                'user': '******',
                'password': '******'
            }
        ]
    }

    @staticmethod
    def _apiinfo_response():
        return {
            "jsonrpc": "2.0",
            "result": ["4.0.4"],
            "id": 1
        }

    @staticmethod
    def _zabbix_host_response(maintenance_mode="0", tag=False):
        tags = []
        if tag:
            tags = [{"tag": "stackstate-layer", "value": "stackstate"},
                    {"tag": "stackstate-identifier", "value": "common-identifier"}]
        return {
            "jsonrpc": "2.0",
            "result": [
                {
                    "hostid": "10084",
                    "host": "zabbix01.example.com",
                    "name": "Zabbix server",
                    "maintenance_status": maintenance_mode,
                    "groups": [
                        {
                            "groupid": "4",
                            "name": "Zabbix servers"
                        }
                    ],
                    "tags": tags
                }
            ],
            "id": 1
        }

    @staticmethod
    def _zabbix_problem():
        return {
            "jsonrpc": "2.0",
            "result": [
                {
                    "eventid": "14",
                    "source": "0",
                    "object": "0",
                    "objectid": "13491",
                    "clock": "1549878981",
                    "ns": "221836547",
                    "r_eventid": "0",
                    "r_clock": "0",
                    "r_ns": "0",
                    "correlationid": "0",
                    "userid": "0",
                    "name": "Zabbix agent on Zabbix server is unreachable for 5 minutes",
                    "acknowledged": "0",
                    "severity": "3",
                    "acknowledges": [],
                    "suppressed": 0
                }
            ],
            "id": 1
        }

    @staticmethod
    def _zabbix_trigger():
        return {
            "jsonrpc": "2.0",
            "result": [
                {
                    "triggerid": "13491",
                    "expression": "{12900}=1",
                    "description": "Zabbix agent on {HOST.NAME} is unreachable for 5 minutes",
                    "url": "",
                    "status": "0",
                    "value": "1",
                    "priority": "3",
                    "lastchange": "1549878981",
                    "comments": "",
                    "error": "",
                    "templateid": "10047",
                    "type": "0",
                    "state": "0",
                    "flags": "0",
                    "recovery_mode": "0",
                    "recovery_expression": "",
                    "correlation_mode": "0",
                    "correlation_tag": "",
                    "manual_close": "0"
                }
            ],
            "id": 1
        }

    @staticmethod
    def _zabbix_event():
        return {
            "jsonrpc": "2.0",
            "result": [
                {
                    "eventid": "14",
                    "value": "1",
                    "severity": "3",
                    "acknowledged": "0",
                    "hosts": [
                        {
                            "hostid": "10084"
                        }
                    ],
                    "relatedObject": {
                        "triggerid": "13491",
                        "description": "Zabbix agent on {HOST.NAME} is unreachable for 5 minutes",
                        "priority": "3"
                    }
                }
            ],
            "id": 1
        }

    def setUp(self):
        """
        Initialize and patch the check, i.e.
        """
        config = {}
        self.check = ZabbixCheck(CHECK_NAME, config, instances=[self.instance])

    def test_zabbix_topology_hosts(self):

        # TODO this is needed because the topology retains data across tests
        topology.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response(tag=True)
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"
        self.check.retrieve_problems = lambda url, auth: []
        self.check.retrieve_events = lambda url, auth, event_ids: []

        self.check.check(self.instance)

        topo_instances = topology.get_snapshot(self.check.check_id)
        self.assertEqual(len(topo_instances['components']), 1)
        self.assertEqual(len(topo_instances['relations']), 0)

        component = topo_instances['components'][0]
        expected_identifier = ["urn:host:/zabbix01.example.com", "zabbix01.example.com", "common-identifier"]
        self.assertEqual(component['id'], 'urn:host:/zabbix01.example.com')
        self.assertEqual(component['type'], 'zabbix_host')
        self.assertEqual(component['data']['name'], 'Zabbix server')
        self.assertEqual(component['data']['host_id'], '10084')
        self.assertEqual(component['data']['host'], 'zabbix01.example.com')
        self.assertEqual(component['data']['layer'], 'stackstate')
        self.assertEqual(component['data']['domain'], 'Zabbix servers')
        self.assertEqual(component['data']['identifiers'], expected_identifier)
        self.assertEqual(component['data']['environment'], 'Production')
        self.assertEqual(component['data']['host_groups'], ['Zabbix servers'])

        labels = component['data']['labels']
        for label in ['zabbix', 'host group:Zabbix servers']:
            if label not in labels:
                self.fail("Component does not have label '%s'." % label)

    def test_zabbix_topology_hosts_no_component(self):
        """
        Test should not return any hosts as host is in maintenance mode
        """

        # TODO this is needed because the topology retains data across tests
        topology.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response(maintenance_mode="1")
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"
        self.check.retrieve_problems = lambda url, auth: []
        self.check.retrieve_events = lambda url, auth, event_ids: []

        self.check.check(self.instance)

        topo_instances = topology.get_snapshot(self.check.check_id)
        self.assertEqual(len(topo_instances['components']), 0)
        self.assertEqual(len(topo_instances['relations']), 0)

    def test_zabbix_topology_non_default_environment(self):

        # TODO this is needed because the topology retains data across tests
        topology.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            else:
                self.fail("TEST FAILED on making invalid request")

        self.instance['stackstate_environment'] = 'MyTestEnvironment'
        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"
        self.check.retrieve_problems = lambda url, auth: []
        self.check.retrieve_events = lambda url, auth, event_ids: []

        self.check.check(self.instance)

        topo_instances = topology.get_snapshot(self.check.check_id)
        self.assertEqual(len(topo_instances['components']), 1)
        self.assertEqual(len(topo_instances['relations']), 0)

        component = topo_instances['components'][0]
        self.assertEqual(component['data']['environment'], 'MyTestEnvironment')

        labels = component['data']['labels']
        for label in ['zabbix', 'host group:Zabbix servers']:
            if label not in labels:
                self.fail("Component does not have label '%s'." % label)

    def test_zabbix_topology_multiple_host_groups(self):
        """
        Zabbix hosts can be placed in multiple host groups.
        When there is only one host group we place the host component in the StackState domain with the
        host group's name.
        However, when there are multiple host groups we use StackState domain 'Zabbix'
        """

        # TODO this is needed because the topology retains data across tests
        topology.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                response = self._zabbix_host_response()
                response['result'][0]['groups'].append(
                    {
                        "groupid": "5",
                        "name": "MyHostGroup"
                    }
                )
                return response
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"
        self.check.retrieve_problems = lambda url, auth: []
        self.check.retrieve_events = lambda url, auth, event_ids: []

        self.check.check(self.instance)

        topo_instances = topology.get_snapshot(self.check.check_id)

        self.assertEqual(len(topo_instances['components']), 1)
        self.assertEqual(len(topo_instances['relations']), 0)
        # Start and Stop Snapshot should be True
        self.assertEqual(topo_instances.get("start_snapshot"), True)
        self.assertEqual(topo_instances.get("stop_snapshot"), True)

        component = topo_instances['components'][0]
        self.assertEqual(component['data']['domain'], 'Zabbix')
        self.assertEqual(component['data']['layer'], 'machines')
        labels = component['data']['labels']
        for label in ['zabbix', 'host group:Zabbix servers', 'host group:MyHostGroup']:
            if label not in labels:
                self.fail("Component does not have label '%s'." % label)

        # check if OK service check generated
        service_checks = aggregator.service_checks('Zabbix')
        self.assertEqual(AgentCheck.OK, service_checks[0].status)

    def test_zabbix_problems(self):

        # TODO this is needed because the aggregator retains events data across tests
        aggregator.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            elif name == "problem.get":
                return self._zabbix_problem()
            elif name == "trigger.get":
                return self._zabbix_trigger()
            elif name == "event.get":
                return self._zabbix_event()
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"

        self.check.check(self.instance)

        events = aggregator.events

        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['source_type_name'], 'Zabbix')
        tags = events[0]['tags']

        for tag in ['host_id:10084', 'severity:3',
                    "triggers:['Zabbix agent on {HOST.NAME} is unreachable for 5 minutes']",
                    "host:zabbix01.example.com", "host_name:Zabbix server"]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

    def test_zabbix_no_problems(self):
        """
        When there are no problems, we are expecting all host components to go to green.
        To make this happen we need to send an event that says all is OK.
        """
        # TODO this is needed because the aggregator retains events data across tests
        aggregator.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            elif name == "problem.get":
                response = self._zabbix_problem()
                response['result'] = []
                return response
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"

        self.check.check(self.instance)
        events = aggregator.events
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['source_type_name'], 'Zabbix')
        tags = events[0]['tags']

        for tag in ['host_id:10084', 'severity:0', "triggers:[]",
                    "host:zabbix01.example.com", "host_name:Zabbix server"]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

    def test_zabbix_determine_most_severe_state(self):
        """
            A host can have multiple active problems.
            From the active problems we determine the most severe state and send that to StackState
        """
        # TODO this is needed because the aggregator retains events data across tests
        aggregator.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            elif name == "problem.get":
                response = self._zabbix_problem()
                response['result'].append({
                    "eventid": "100",
                    "source": "0",
                    "object": "0",
                    "objectid": "111",
                    "clock": "1549878981",
                    "ns": "221836547",
                    "r_eventid": "0",
                    "r_clock": "0",
                    "r_ns": "0",
                    "correlationid": "0",
                    "userid": "0",
                    "name": "My very own problem",
                    "acknowledged": "0",
                    "severity": "5",
                    "acknowledges": [],
                    "suppressed": 0
                })
                return response
            elif name == "trigger.get":
                return self._zabbix_trigger()
            elif name == "event.get":
                response = self._zabbix_event()
                response['result'].append({
                    "eventid": "100",
                    "value": "1",
                    "severity": "5",
                    "acknowledged": "0",
                    "hosts": [
                        {
                            "hostid": "10084"
                        }
                    ],
                    "relatedObject": {
                        "triggerid": "111",
                        "description": "My very own problem",
                        "priority": "5"
                    }
                })
                return response
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"

        self.check.check(self.instance)
        events = aggregator.events

        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['source_type_name'], 'Zabbix')
        tags = events[0]['tags']

        for tag in [
            'host_id:10084',
            'severity:5',
            "triggers:['Zabbix agent on {HOST.NAME} is unreachable for 5 minutes', 'My very own problem']",
            "host:zabbix01.example.com",
            "host_name:Zabbix server"
        ]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

    def validate_requests_ssl_verify_setting(self, instance_to_use, expected_verify_value):
        """
        Helper for testing whether the yaml setting ssl_verify is respected by mocking requests.get
        Mocking all the Zabbix functions that talk HTTP via requests.get, excluding the function `check_connection`
        Function check_connection is the first function that talks HTTP.
        """
        with mock.patch('requests.get') as mock_get:
            with mock.patch('yaml.safe_load'):
                self.check.login = lambda url, user, password: "******"
                self.check.retrieve_hosts = lambda x, y: []
                self.check.retrieve_problems = lambda url, auth: []
                self.check.retrieve_events = lambda url, auth, event_ids: []
                self.check.check(instance_to_use)
                mock_get.assert_called_once_with('http://10.0.0.1/zabbix/api_jsonrpc.php',
                                                 json={'params': {}, 'jsonrpc': '2.0', 'method': 'apiinfo.version',
                                                       'id': 1},
                                                 verify=expected_verify_value)

    def test_zabbix_respect_false_ssl_verify(self):
        config = self.instance
        config['ssl_verify'] = False
        self.validate_requests_ssl_verify_setting(config, False)

    def test_zabbix_respect_true_ssl_verify(self):
        config = self.instance
        config['ssl_verify'] = True
        self.validate_requests_ssl_verify_setting(config, True)

    def test_zabbix_respect_default_ssl_verify(self):
        self.validate_requests_ssl_verify_setting(self.instance, True)

    def test_zabbix_disabled_triggers(self):
        """
        When there are no triggers enabled and return empty list for `trigger.get` call, then we are expecting no
        problems and host will become green if it was red because there will be an event without any severity/problems.
        """
        # TODO this is needed because the aggregator retains events data across tests
        aggregator.reset()

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            elif name == "problem.get":
                response = self._zabbix_problem()
                return response
            elif name == "trigger.get":
                response = self._zabbix_trigger()
                response['result'] = []
                return response
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"

        self.check.check(self.instance)
        events = aggregator.events
        self.assertEqual(len(events), 1)
        tags = events[0]['tags']
        for tag in [
            'host_id:10084',
            "host:zabbix01.example.com",
            "host_name:Zabbix server",
            "severity:0",
            "triggers:[]"
        ]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

        # check if OK service check generated
        service_checks = aggregator.service_checks('Zabbix')
        self.assertEqual(AgentCheck.OK, service_checks[0].status)

    def test_zabbix_acknowledge_problem(self):
        """
        When there are problems, we are expecting all host components to go to yellow/red.
        But acknowledging the problem should make the host components go back to green
        To make this happen we need to send an event that says the problem is `acknowledged`.
        """
        # TODO this is needed because the aggregator retains events data across tests
        aggregator.reset()

        self.event_response = {}
        self.second_run = False

        def _mocked_method_request(url, name, auth=None, params={}, request_id=1):
            if name == "apiinfo.version":
                return self._apiinfo_response()
            elif name == "host.get":
                return self._zabbix_host_response()
            elif name == "problem.get":
                response = self._zabbix_problem()
                return response
            elif name == "trigger.get":
                response = self._zabbix_trigger()
                return response
            elif name == "event.get":
                self.event_response = self._zabbix_event()
                if not self.second_run:
                    # acknowledge the problem
                    self.event_response['result'][0]['acknowledged'] = '1'
                    self.second_run = True
                else:
                    self.event_response['result'][0]['acknowledged'] = '0'
                return self.event_response
            else:
                self.fail("TEST FAILED on making invalid request")

        self.check.method_request = _mocked_method_request
        self.check.login = lambda url, user, password: "******"

        self.check.check(self.instance)
        events = aggregator.events
        self.assertEqual(len(events), 1)
        tags = events[0]['tags']
        for tag in [
            'host_id:10084',
            "host:zabbix01.example.com",
            "host_name:Zabbix server",
            "severity:0",
            "triggers:[]"
        ]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

        aggregator.reset()
        # second run to revert the acknowledged problem to create the event again
        self.check.check(self.instance)
        events = aggregator.events
        self.assertEqual(len(events), 1)
        tags = events[0]['tags']
        for tag in [
            'host_id:10084',
            "host:zabbix01.example.com",
            "host_name:Zabbix server",
            "severity:3",
            "triggers:['Zabbix agent on {HOST.NAME} is unreachable for 5 minutes']"
        ]:
            if tag not in tags:
                self.fail("Event does not have tag '%s', got: %s." % (tag, tags))
        self.assertEqual(len(tags), 5)

    @mock.patch('requests.get')
    def test_method_request(self, mocked_get):
        event_resp = {
            "jsonrpc": "2.0",
            "result": [
                {
                    "triggerid": "13491",
                    "expression": u"{12900}=1",
                    "description": u"Zabbix agent on {HOST.NAME} is unreachable for 5 minutes",
                    "url": "",
                    "status": "0",
                    "value": "1",
                    "priority": "3",
                    "lastchange": "1549878981",
                    "comments": "",
                    "error": "",
                    "templateid": "10047",
                    "type": "0",
                    "state": "0",
                    "flags": "0",
                    "recovery_mode": "0",
                    "recovery_expression": "",
                    "correlation_mode": "0",
                    "correlation_tag": "",
                    "manual_close": "0"
                }
            ],
            "id": 1
        }
        mocked_get.return_value = mock.MagicMock(status_code=200, text=json.dumps(event_resp))
        self.check.ssl_verify = False
        resp = self.check.method_request("http://host/zabbix/api_jsonrpc.php", "events.get")
        self.assertEqual(u"{12900}=1", resp.get("result")[0].get("expression"))
Пример #5
0
 def setUp(self):
     """
     Initialize and patch the check, i.e.
     """
     config = {}
     self.check = ZabbixCheck(CHECK_NAME, config, instances=[self.instance])