def test_request_error(self, data, config, collectd, ClientV3, post):
        """Test error raised by underlying requests module"""

        # tell POST request to raise an exception
        post.side_effect = requests.RequestException('Test POST exception')

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # write the value
        self.assertRaises(requests.RequestException, instance.write, data)
    def test_exceptions(self, data, config, collectd, LOGGER, Writer, ClientV3,
                        post):
        """Test exception raised during write and shutdown"""

        writer = Writer.return_value
        writer.write.side_effect = ValueError('Test write error')
        writer.flush.side_effect = RuntimeError('Test shutdown error')

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        self.assertRaises(ValueError, instance.write, data)
        self.assertRaises(RuntimeError, instance.shutdown)
    def test_authentication_in_multiple_threads(self, data, config, collectd,
                                                LOGGER, ClientV3, post):
        """Test authentication in muliple threads

        This test simulates the authentication performed from different thread
        after the authentication lock has been acquired. The sender is not
        authenticated, the lock is acquired, the authentication token exists
        (i.e. it has been set by different thread) and it is used.
        """
        # pylint: disable=protected-access

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # the sender used by the instance
        sender = instance._writer._sender

        # create a dummy lock
        class DummyLock(namedtuple('LockBase',
                                   ['sender', 'token', 'urlbase'])):
            """Lock simulation, which sets the auth token when locked"""
            def __enter__(self, *args, **kwargs):
                # pylint: disable=protected-access
                self.sender._auth_token = self.token
                self.sender._url_base = self.urlbase

            def __exit__(self, *args, **kwargs):
                pass

        # replace the sender's lock by the dummy lock
        sender._auth_lock = DummyLock(sender, 'TOKEN', 'URLBASE/%s')

        # write the value
        instance.write(data)

        # No errors has been registered
        LOGGER.exception.assert_not_called()

        # client has not been called at all
        ClientV3.assert_not_called()

        # verify the auth token
        post.assert_called_once_with('URLBASE/cpu.freq',
                                     data=mock.ANY,
                                     headers={
                                         'Content-type': 'application/json',
                                         'X-Auth-Token': 'TOKEN'
                                     },
                                     timeout=1.0)
    def test_write_auth_failed(self, data, config, collectd, LOGGER, ClientV3,
                               post):
        """Test authentication failure"""

        ClientV3.auth_url = "http://tst-url"
        # tell the auth client to rise an exception
        ClientV3.side_effect = RuntimeError('Test Client() exception')

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # write the value
        self.assertRaises(RuntimeError, instance.write, data)

        # no requests method has been called
        post.assert_not_called()
    def test_reauthentication(self, data, config, collectd, ClientV3, post,
                              perf_req):
        """Test re-authentication"""

        # response returned on success
        response_ok = requests.Response()
        response_ok.status_code = requests.codes["OK"]

        # response returned on failure
        response_unauthorized = requests.Response()
        response_unauthorized.status_code = requests.codes["UNAUTHORIZED"]

        # write the first value with success
        # subsequent call of POST method will fail due to the authentication
        perf_req.return_value = response_ok

        client = ClientV3.return_value
        client.auth_url = "http://tst-url"
        client.auth_token = 'Test auth token'

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # write the value
        instance.write(data)

        # verify the auth token
        perf_req.assert_called_once_with(mock.ANY, mock.ANY, 'Test auth token')

        # set a new auth token
        client.auth_token = 'New test auth token'
        perf_req.side_effect = \
            [requests.exceptions.HTTPError(response=response_unauthorized),
             response_ok]

        # write the value
        instance.write(data)

        # verify the auth token
        perf_req.assert_has_calls([
            mock.call(mock.ANY, mock.ANY, 'Test auth token'),
            mock.call(mock.ANY, mock.ANY, 'New test auth token')
        ])
    def test_write_auth_failed2(self, data, config, collectd, LOGGER, ClientV3,
                                post):
        """Test authentication failure2"""

        ClientV3.side_effect = keystone_light.KeystoneException(
            "Missing name 'xxx' in received services", "exception",
            "services list")

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # write the value
        instance.write(data)

        LOGGER.error.assert_called_once_with(
            "Suspending error logs until successful auth")
        LOGGER.log.assert_called_once_with(
            logging.ERROR,
            "Authentication error: %s",
            "Missing name 'xxx' in received services\nReason: exception",
            exc_info=0)

        # no requests method has been called
        post.assert_not_called()
    def test_write(self, data, config, collectd, ClientV3, post):
        """Test collectd data writing"""

        auth_client = ClientV3.return_value
        auth_client.get_service_endpoint.return_value =\
            'https://test-ceilometer.tld'

        post.return_value.status_code = common_sender.Sender.HTTP_CREATED
        post.return_value.text = 'Created'

        # init instance
        instance = plugin.Plugin(collectd=collectd, config=config)

        # no authentication has been performed so far
        ClientV3.assert_not_called()

        # write first value
        instance.write(data)
        collectd.error.assert_not_called()

        # no value has been sent to ceilometer
        post.assert_not_called()

        # send the second value
        instance.write(data)
        collectd.error.assert_not_called()

        # authentication client has been created
        ClientV3.assert_called_once()

        # and values has been sent
        post.assert_called_once_with(
            'https://test-ceilometer.tld/v2/meters/cpu.freq',
            data=match.json([{
                "source": "collectd",
                "counter_name": "cpu.freq",
                "counter_unit": "jiffies",
                "counter_volume": 1234,
                "timestamp": "Thu Nov 29 21:33:09 1973",
                "resource_id": "localhost-0",
                "resource_metadata": None,
                "counter_type": "gauge"
            }, {
                "source": "collectd",
                "counter_name": "cpu.freq",
                "counter_unit": "jiffies",
                "counter_volume": 1234,
                "timestamp": "Thu Nov 29 21:33:09 1973",
                "resource_id": "localhost-0",
                "resource_metadata": None,
                "counter_type": "gauge"
            }]),
            headers={
                'Content-type': 'application/json',
                'X-Auth-Token': auth_client.auth_token
            },
            timeout=1.0)

        # reset post method
        post.reset_mock()

        # write another values
        instance.write(data)
        collectd.error.assert_not_called()

        # nothing has been sent
        post.assert_not_called()

        # call shutdown
        instance.shutdown()

        # no errors
        collectd.error.assert_not_called()

        # previously written value has been sent
        post.assert_called_once_with(
            'https://test-ceilometer.tld/v2/meters/cpu.freq',
            data=match.json([{
                "source": "collectd",
                "counter_name": "cpu.freq",
                "counter_unit": "jiffies",
                "counter_volume": 1234,
                "timestamp": "Thu Nov 29 21:33:09 1973",
                "resource_id": "localhost-0",
                "resource_metadata": None,
                "counter_type": "gauge"
            }]),
            headers={
                'Content-type': 'application/json',
                'X-Auth-Token': auth_client.auth_token
            },
            timeout=1.0)