def test_client_retries_and_returns_data_if_successful(self, _make_request, from_httplib, base_client):
        #  The third response here would normally be a httplib response object. It's only use is to be passed in to
        #  `from_httplib`, which we're mocking the return of below. `from_httplib` converts a httplib response into a
        #  urllib3 response. The mock object we're returning is a mock for that urllib3 response.
        _make_request.side_effect = [
            ProtocolError(mock.Mock(), '1st error'),
            ProtocolError(mock.Mock(), '2nd error'),
            ProtocolError(mock.Mock(), '3nd error'),
            'httplib_response - success!',
        ]

        from_httplib.return_value = self._from_httplib_response_mock(200, response_data=b'{"Success?": "Yes!"}')

        response = base_client._request("GET", '/')
        requests = _make_request.call_args_list

        assert len(requests) == 4
        assert all((request[0][1], request[0][2]) == ('GET', '/') for request in requests)
        assert response == {'Success?': 'Yes!'}
Пример #2
0
 def test_do_request_exception_args_2(self):
     self.mock_requests(
     ).put.side_effect = requests.exceptions.ConnectionError(
         ProtocolError('Connection broken: xyz'))
     with self.assertRaises(RequestException) as context:
         self.rest_client.do_request('PUT', '/test')
         self.assertEqual(
             'UnitTest REST API cannot be reached. Please '
             'check your configuration and that the API '
             'endpoint is accessible', context.exception.message)
Пример #3
0
def _error_catcher(self):
    """
    Catch low-level python exceptions, instead re-raising urllib3
    variants, so that low-level exceptions are not leaked in the
    high-level api.
    On exit, release the connection back to the pool.
    """
    try:
        try:
            yield

        except SocketTimeout:
            # FIXME: Ideally we'd like to include the url in the
            # ReadTimeoutError but there is yet no clean way to
            # get at it from this context.
            raise ReadTimeoutError(self._pool, None, 'Read timed out.')

        except BaseSSLError as e:
            # FIXME: Is there a better way to differentiate between SSLErrors?
            if 'read operation timed out' not in str(e):  # Defensive:
                # This shouldn't happen but just in case we're missing an edge
                # case, let's avoid swallowing SSL errors.
                raise

            raise ReadTimeoutError(self._pool, None, 'Read timed out.')

        except HTTPException as e:
            # This includes IncompleteRead.
            raise ProtocolError('Connection broken: %r' % e, e)
    except Exception:
        # The response may not be closed but we're not going to use it anymore
        # so close it now to ensure that the connection is released back to the
        #  pool.
        if self._original_response and not self._original_response.isclosed():
            self._original_response.close()

        # Before returning the socket, close it.  From the server's
        # point of view,
        # this socket is in the middle of handling an SSL handshake/HTTP
        # request so it we were to try and re-use the connection later,
        #  we'd see undefined behaviour.
        #
        # Still return the connection to the pool (it will be
        # re-established next time it is used).
        self._connection.close()

        raise
    finally:
        if self._original_response and self._original_response.isclosed():
            self.release_conn()
Пример #4
0
    def exec(self, name, *args, api=None, return_with_args=None, _ret_cnt=0):
        """ Execute a method against steemd RPC.

        Warnings:
            This command will auto-retry in case of node failure, as well as handle
            node fail-over, unless we are broadcasting a transaction.
            In latter case, the exception is **re-raised**.
        """
        body = HttpClient.json_rpc_body(name, *args, api=api)
        response = None
        try:
            response = self.request(body=body)
            if response.status in (502, 503, 504):
                raise ProtocolError('Bad status code: %s' % response.status)
        except (MaxRetryError, ConnectionResetError, ReadTimeoutError,
                RemoteDisconnected, ProtocolError) as e:
            # if we broadcasted a transaction, always raise
            # this is to prevent potential for double spend scenario
            if api == 'network_broadcast_api':
                raise e

            # try switching nodes before giving up
            if _ret_cnt > 2:
                time.sleep(5 * _ret_cnt)
            elif _ret_cnt > 10:
                raise e
            self.next_node()
            logging.debug('Switched node to %s due to exception: %s' %
                          (self.hostname, e.__class__.__name__))
            return self.exec(name,
                             *args,
                             return_with_args=return_with_args,
                             _ret_cnt=_ret_cnt + 1)
        except Exception as e:
            if self.re_raise:
                raise e
            else:
                extra = dict(err=e, request=self.request)
                logger.info('Request error', extra=extra)
                return self._return(response=response,
                                    args=args,
                                    return_with_args=return_with_args)
        else:
            if response.status not in tuple([*response.REDIRECT_STATUSES,
                                             200]):
                logger.info('non 200 response:%s', response.status)

            return self._return(response=response,
                                args=args,
                                return_with_args=return_with_args)
Пример #5
0
def _mock_incomplete_read_session_response(data: bytes):
    data_stream = BytesIO(data)
    mock = MagicMock(name='session_response')
    mock.raw.read.side_effect = _sequential_side_effects(
        [data_stream.read, ProtocolError('IncompleteRead')])
    return mock
Пример #6
0
    def urlopen(self, method, url, redirect=True, **kw):
        """
        Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen`
        with custom cross-host redirect logic and only sends the request-uri
        portion of the ``url``.

        The given ``url`` parameter must be absolute, such that an appropriate
        :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
        """
        #===============================================================================================================
        # add by mz
        error_type = kw.get('error_type')

        if error_type:

            from urllib3.exceptions import LocationValueError, HostChangedError, LocationParseError, ConnectTimeoutError
            from urllib3.exceptions import ProxyError, TimeoutError, ReadTimeoutError, ProtocolError, DecodeError
            from urllib3.exceptions import ResponseError, ResponseNotChunked, SSLError, HTTPError, HTTPWarning, PoolError
            from urllib3.exceptions import RequestError, MaxRetryError, TimeoutStateError, NewConnectionError
            from urllib3.exceptions import EmptyPoolError, ClosedPoolError, SecurityWarning, SubjectAltNameWarning
            from urllib3.exceptions import InsecureRequestWarning, SystemTimeWarning, InsecurePlatformWarning
            from urllib3.exceptions import SNIMissingWarning, DependencyWarning, ProxySchemeUnknown, HeaderParsingError
            get_error = {
                "LocationValueError":
                LocationValueError(),
                "HostChangedError":
                HostChangedError(pool=1, url=2),
                "LocationParseError":
                LocationParseError(url),
                "ConnectTimeoutError":
                ConnectTimeoutError(),
                "ProxyError":
                ProxyError(),
                "TimeoutError":
                TimeoutError(),
                "ReadTimeoutError":
                ReadTimeoutError(pool=1, url=2, message="ReadTimeoutError"),
                "ProtocolError":
                ProtocolError(),
                "DecodeError":
                DecodeError(),
                "ResponseError":
                ResponseError(),
                "ResponseNotChunked":
                ResponseNotChunked(),
                "SSLError":
                SSLError(),
                "HTTPError":
                HTTPError(),
                "HTTPWarning":
                HTTPWarning(),
                "PoolError":
                PoolError(pool=1, message=2),
                "RequestError":
                RequestError(pool=1, url=2, message="RequestError"),
                "MaxRetryError":
                MaxRetryError(pool=1, url=2, reason=None),
                "TimeoutStateError":
                TimeoutStateError(),
                "NewConnectionError":
                NewConnectionError(pool=1, message="NewConnectionError"),
                "EmptyPoolError":
                EmptyPoolError(pool=1, message="EmptyPoolError"),
                "ClosedPoolError":
                ClosedPoolError(pool=1, message="ClosedPoolError"),
                "SecurityWarning":
                SecurityWarning(),
                "SubjectAltNameWarning":
                SubjectAltNameWarning(),
                "InsecureRequestWarning":
                InsecureRequestWarning(),
                "SystemTimeWarning":
                SystemTimeWarning(),
                "InsecurePlatformWarning":
                InsecurePlatformWarning(),
                "SNIMissingWarning":
                SNIMissingWarning(),
                "DependencyWarning":
                DependencyWarning(),
                "ProxySchemeUnknown":
                ProxySchemeUnknown(scheme=1),
                "HeaderParsingError":
                HeaderParsingError(defects=1, unparsed_data=2)
            }
            error_ = get_error[error_type]
            raise error_
        #===============================================================================================================

        u = parse_url(url)
        conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)

        kw['assert_same_host'] = False
        kw['redirect'] = False
        if 'headers' not in kw:
            kw['headers'] = self.headers

        if self.proxy is not None and u.scheme == "http":
            response = conn.urlopen(method, url, **kw)
        else:
            response = conn.urlopen(method, u.request_uri, **kw)

        redirect_location = redirect and response.get_redirect_location()
        if not redirect_location:
            return response

        # Support relative URLs for redirecting.
        redirect_location = urljoin(url, redirect_location)

        # RFC 2616, Section 10.3.4
        if response.status == 303:
            method = 'GET'

        log.info("Redirecting %s -> %s" % (url, redirect_location))
        kw['retries'] = kw.get('retries', 3) - 1  # Persist retries countdown
        kw['redirect'] = redirect
        return self.urlopen(method, redirect_location, **kw)
Пример #7
0
 def test_catches_bad_status_line(self):
     error = ProtocolError(None)
     self.make_request_with_error(error)
class TestBaseApiClient(object):
    def _from_httplib_response_mock(self, status, response_data=None):
        response_mock = mock.Mock(status=status,
                                  headers={},
                                  spec=[
                                      'get_redirect_location', 'getheader',
                                      'read', 'reason', 'drain_conn'
                                  ])
        response_mock.get_redirect_location.return_value = None
        response_mock.getheader.return_value = None
        response_mock.read.side_effect = [response_data, None, None, None]
        response_mock.reason = f'Mocked {status} response'

        return response_mock

    @pytest.mark.parametrize(
        "method,exc_factory",
        chain(
            ((m, lambda: NewConnectionError(mock.Mock(), "I'm a message"))
             for m in (
                 "GET",
                 "PUT",
                 "POST",
                 "PATCH",
             )),
            ((m, lambda: ProtocolError(mock.Mock(), "I'm a message"))
             for m in (
                 "GET",
                 "PUT",
             )),
            ((m, lambda: ReadTimeoutError(mock.Mock(), mock.Mock(),
                                          "I'm a message")) for m in (
                                              "GET",
                                              "PUT",
                                          )),
        ))
    @pytest.mark.parametrize('retry_count', range(1, 4))
    @mock.patch('urllib3.connectionpool.HTTPConnectionPool._make_request')
    @mock.patch('dmapiclient.base.BaseAPIClient._RETRIES_BACKOFF_FACTOR', 0)
    def test_client_retries_on_httperror_and_raises_api_error(
        self,
        _make_request,
        base_client,
        retry_count,
        exc_factory,
        method,
    ):
        _make_request.side_effect = exc_factory()

        with mock.patch('dmapiclient.base.BaseAPIClient._RETRIES',
                        retry_count):
            with pytest.raises(HTTPError) as e:
                base_client._request(method, '/')

        requests = _make_request.call_args_list

        assert len(requests) == retry_count + 1
        assert all((request[0][1], request[0][2]) == (method, '/')
                   for request in requests)

        assert type(_make_request.side_effect).__name__ in e.value.message
        assert e.value.status_code == REQUEST_ERROR_STATUS_CODE

    @pytest.mark.parametrize("exc_factory", (
        lambda: ProtocolError(mock.Mock(), "I'm a message"),
        lambda: ReadTimeoutError(mock.Mock(), mock.Mock(), "I'm a message"),
    ))
    @pytest.mark.parametrize("method", (
        "POST",
        "PATCH",
    ))
    @pytest.mark.parametrize('retry_count', range(1, 4))
    @mock.patch('urllib3.connectionpool.HTTPConnectionPool._make_request')
    @mock.patch('dmapiclient.base.BaseAPIClient._RETRIES_BACKOFF_FACTOR', 0)
    def test_client_doesnt_retry_non_whitelisted_methods_on_unsafe_errors(
        self,
        _make_request,
        base_client,
        retry_count,
        exc_factory,
        method,
    ):
        _make_request.side_effect = exc_factory()

        with mock.patch('dmapiclient.base.BaseAPIClient._RETRIES',
                        retry_count):
            with pytest.raises(HTTPError) as e:
                base_client._request(method, '/')

        requests = _make_request.call_args_list

        assert len(requests) == 1
        assert requests[0][0][1] == method
        assert requests[0][0][2] == "/"

        assert type(_make_request.side_effect).__name__ in e.value.message
        assert e.value.status_code == REQUEST_ERROR_STATUS_CODE

    @pytest.mark.parametrize(('retry_count'), range(1, 4))
    @pytest.mark.parametrize(('status'),
                             BaseAPIClient.RETRIES_FORCE_STATUS_CODES)
    @mock.patch(
        'urllib3.connectionpool.HTTPConnectionPool.ResponseCls.from_httplib')
    @mock.patch('urllib3.connectionpool.HTTPConnectionPool._make_request')
    @mock.patch('dmapiclient.base.BaseAPIClient._RETRIES_BACKOFF_FACTOR', 0)
    def test_client_retries_on_status_error_and_raises_api_error(
            self, _make_request, from_httplib, base_client, status,
            retry_count):
        response_mock = self._from_httplib_response_mock(status)
        from_httplib.return_value = response_mock

        with mock.patch('dmapiclient.base.BaseAPIClient._RETRIES',
                        retry_count):
            with pytest.raises(HTTPError) as e:
                base_client._request("GET", '/')

        requests = _make_request.call_args_list

        assert len(requests) == retry_count + 1
        assert all((request[0][1], request[0][2]) == ('GET', '/')
                   for request in requests)

        assert f'{status} Server Error: {response_mock.reason} for url: http://baseurl/\n' in e.value.message
        assert e.value.status_code == status

    @mock.patch(
        'urllib3.connectionpool.HTTPConnectionPool.ResponseCls.from_httplib')
    @mock.patch('urllib3.connectionpool.HTTPConnectionPool._make_request')
    @mock.patch('dmapiclient.base.BaseAPIClient._RETRIES_BACKOFF_FACTOR', 0)
    def test_client_retries_and_returns_data_if_successful(
            self, _make_request, from_httplib, base_client):
        #  The third response here would normally be a httplib response object. It's only use is to be passed in to
        #  `from_httplib`, which we're mocking the return of below. `from_httplib` converts a httplib response into a
        #  urllib3 response. The mock object we're returning is a mock for that urllib3 response.
        _make_request.side_effect = [
            ProtocolError(mock.Mock(), '1st error'),
            ProtocolError(mock.Mock(), '2nd error'),
            ProtocolError(mock.Mock(), '3nd error'),
            'httplib_response - success!',
        ]

        from_httplib.return_value = self._from_httplib_response_mock(
            200, response_data=b'{"Success?": "Yes!"}')

        response = base_client._request("GET", '/')
        requests = _make_request.call_args_list

        assert len(requests) == 4
        assert all((request[0][1], request[0][2]) == ('GET', '/')
                   for request in requests)
        assert response == {'Success?': 'Yes!'}

    def test_non_2xx_response_raises_api_error(self, base_client, rmock):
        rmock.request("GET",
                      "http://baseurl/",
                      json={"error": "Not found"},
                      status_code=404)

        with pytest.raises(HTTPError) as e:
            base_client._request("GET", '/')

        assert e.value.message == "Not found"
        assert e.value.status_code == 404

    def test_base_error_is_logged(self, base_client):
        with requests_mock.Mocker() as m:
            m.register_uri('GET', '/', exc=requests.RequestException())

            with pytest.raises(HTTPError) as e:
                base_client._request("GET", "/")

            assert e.value.message == "\nRequestException()"
            assert e.value.status_code == 503

    def test_invalid_json_raises_api_error(self, base_client, rmock):
        rmock.request("GET",
                      "http://baseurl/",
                      text="Internal Error",
                      status_code=200)

        with pytest.raises(InvalidResponse) as e:
            base_client._request("GET", '/')

        assert e.value.message == "No JSON object could be decoded"
        assert e.value.status_code == 200

    def test_user_agent_is_set(self, base_client, rmock):
        rmock.request("GET", "http://baseurl/", json={}, status_code=200)

        base_client._request('GET', '/')

        assert rmock.last_request.headers.get("User-Agent").startswith(
            "DM-API-Client/")

    def test_request_always_uses_base_url_scheme(self, base_client, rmock):
        rmock.request("GET", "http://baseurl/path/", json={}, status_code=200)

        base_client._request('GET', 'https://host/path/')
        assert rmock.called

    def test_null_api_throws(self):
        bad_client = BaseAPIClient(None, 'auth-token', True)
        with pytest.raises(ImproperlyConfigured):
            bad_client._request('GET', '/anything')

    def test_onwards_request_headers_added_if_available(
            self, base_client, rmock, app):
        rmock.get("http://baseurl/_status",
                  json={"status": "ok"},
                  status_code=200)
        with app.test_request_context('/'):
            # add a simple mock callable instead of using a full request implementation
            request.get_onwards_request_headers = mock.Mock()
            request.get_onwards_request_headers.return_value = {
                "Douce": "bronze",
                "Kennedy": "gold",
            }

            base_client.get_status()

            assert rmock.last_request.headers["Douce"] == "bronze"
            assert rmock.last_request.headers["kennedy"] == "gold"

            assert request.get_onwards_request_headers.call_args_list == [
                # just a single, arg-less call
                (),
            ]

    def test_onwards_request_headers_not_available(self, base_client, rmock,
                                                   app):
        rmock.get("http://baseurl/_status",
                  json={"status": "ok"},
                  status_code=200)
        with app.test_request_context('/'):
            # really just asserting no exception arose from performing a call without get_onwards_request_headers being
            # available
            base_client.get_status()

    def test_request_id_fallback(self, base_client, rmock, app):
        # request.request_id is an old interface which we're still supporting here just for compatibility
        rmock.get("http://baseurl/_status",
                  json={"status": "ok"},
                  status_code=200)
        app.config["DM_REQUEST_ID_HEADER"] = "Bar"
        with app.test_request_context('/'):
            request.request_id = "Ormond"

            base_client.get_status()

            assert rmock.last_request.headers["bar"] == "Ormond"

    @pytest.mark.parametrize("dm_span_id_headers_setting", (
        None,
        (
            "X-Brian-Tweedy",
            "Major-Tweedy",
        ),
    ))
    @pytest.mark.parametrize(
        "has_request_context",
        (False, True),
    )
    @mock.patch("dmapiclient.base.logger")
    def test_child_span_id_not_provided(
        self,
        logger,
        dm_span_id_headers_setting,
        has_request_context,
        base_client,
        rmock,
        app,
    ):
        rmock.get("http://baseurl/_status",
                  json={"status": "ok"},
                  status_code=200)
        app.config["DM_SPAN_ID_HEADERS"] = dm_span_id_headers_setting
        with (app.test_request_context('/')
              if has_request_context else _empty_context_manager()):
            if has_request_context:
                request.get_onwards_request_headers = mock.Mock(
                    return_value={
                        "impression": "arrested",
                    })

            base_client.get_status()

            assert rmock.called
            assert logger.log.call_args_list == [
                mock.call(
                    logging.DEBUG,
                    "API request {method} {url}",
                    extra={
                        "method": "GET",
                        "url": "http://baseurl/_status",
                        # childSpanId NOT provided
                    }),
                mock.call(
                    logging.INFO,
                    "API {api_method} request on {api_url} finished in {api_time}",
                    extra={
                        "api_method": "GET",
                        "api_url": "http://baseurl/_status",
                        "api_status": 200,
                        "api_time": mock.ANY,
                        # childSpanId NOT provided
                    }),
            ]

    @pytest.mark.parametrize(
        "onwards_request_headers",
        (
            {
                "X-Brian-Tweedy": "Amiens Street",
            },
            {
                "major-TWEEDY": "Amiens Street",
            },
            {
                "Major-Tweedy": "terminus",
                "x-brian-tweedy": "Amiens Street",
            },
            {
                # note same header name, different capitalizations
                "X-BRIAN-TWEEDY": "great northern",
                "x-brian-tweedy": "Amiens Street",
            },
        ))
    @pytest.mark.parametrize("response_status", (
        200,
        500,
    ))
    @mock.patch("dmapiclient.base.logger")
    def test_child_span_id_provided(
        self,
        mock_logger,
        onwards_request_headers,
        response_status,
        base_client,
        rmock,
        app,
    ):
        rmock.get("http://baseurl/_status",
                  json={"status": "foobar"},
                  status_code=response_status)
        app.config["DM_SPAN_ID_HEADERS"] = (
            "X-Brian-Tweedy",
            "major-tweedy",
        )
        with app.test_request_context('/'):
            request.get_onwards_request_headers = mock.Mock(
                return_value=onwards_request_headers)

            try:
                base_client.get_status()
            except HTTPError:
                # it is tested elsewhere whether this exception is raised in the *right* circumstances or not
                pass

            assert rmock.called

            # some of our scenarios test multiple header names differing only by capitalization - we care that the same
            # span id that was chosen for the log message is the same one that was sent in the onwards request header,
            # so we need two distinct values which are acceptable
            either_span_id = RestrictedAny(
                lambda value: value == "Amiens Street" or value ==
                "great northern")

            assert mock_logger.log.call_args_list == [
                mock.call(logging.DEBUG,
                          "API request {method} {url}",
                          extra={
                              "method": "GET",
                              "url": "http://baseurl/_status",
                              "childSpanId": either_span_id,
                          }),
                (mock.call(
                    logging.INFO,
                    "API {api_method} request on {api_url} finished in {api_time}",
                    extra={
                        "api_method": "GET",
                        "api_url": "http://baseurl/_status",
                        "api_status": response_status,
                        "api_time": mock.ANY,
                        "childSpanId": either_span_id,
                    }
                ) if response_status == 200 else mock.call(
                    logging.WARNING,
                    "API {api_method} request on {api_url} failed with {api_status} '{api_error}'",
                    extra={
                        "api_method": "GET",
                        "api_url": "http://baseurl/_status",
                        "api_status": response_status,
                        "api_time": mock.ANY,
                        "api_error": mock.ANY,
                        "childSpanId": either_span_id,
                    },
                ))
            ]
            # both logging calls should have had the *same* childSpanId value
            assert mock_logger.log.call_args_list[0][1]["extra"]["childSpanId"] \
                == mock_logger.log.call_args_list[1][1]["extra"]["childSpanId"]

            # that value should be the same one that was sent in the onwards request header
            assert (
                rmock.last_request.headers.get("x-brian-tweedy")
                or rmock.last_request.headers.get("major-tweedy")
            ) == mock_logger.log.call_args_list[0][1]["extra"]["childSpanId"]

    @pytest.mark.parametrize(
        "thrown_exception",
        (
            # requests can be slightly unpredictable in the exceptions it raises
            requests.exceptions.ConnectionError(
                MaxRetryError(
                    mock.Mock(), "http://abc.net",
                    ReadTimeoutError(mock.Mock(), mock.Mock(), mock.Mock()))),
            requests.exceptions.ConnectionError(
                ReadTimeoutError(mock.Mock(), mock.Mock(), mock.Mock())),
            requests.exceptions.ReadTimeout,
        ))
    @mock.patch("dmapiclient.base.logger")
    def test_nowait_times_out(
        self,
        mock_logger,
        base_client,
        rmock,
        app,
        thrown_exception,
    ):
        "test the case when a request with client_wait_for_response=False does indeed time out"
        rmock.post("http://baseurl/services/10000", exc=thrown_exception)

        retval = base_client._request(
            "POST",
            "/services/10000",
            {"serviceName": "Postcard"},
            client_wait_for_response=False,
        )

        assert retval is None

        assert rmock.called
        assert tuple(
            req.timeout
            for req in rmock.request_history) == (base_client.nowait_timeout, )

        assert mock_logger.log.call_args_list == [
            mock.call(logging.DEBUG,
                      "API request {method} {url}",
                      extra={
                          "method": "POST",
                          "url": "http://baseurl/services/10000",
                      }),
            mock.call(
                logging.INFO,
                "API {api_method} request on {api_url} dispatched but ignoring response",
                extra={
                    "api_method": "POST",
                    "api_url": "http://baseurl/services/10000",
                    "api_time": mock.ANY,
                    "api_time_incomplete": True,
                }),
        ]

    @mock.patch("dmapiclient.base.logger")
    def test_nowait_completes(
        self,
        mock_logger,
        base_client,
        rmock,
        app,
    ):
        "test the case when a request with client_wait_for_response=False completes before it can time out"
        rmock.post("http://baseurl/services/10000",
                   json={"services": {
                       "id": "10000"
                   }},
                   status_code=200)

        retval = base_client._request(
            "POST",
            "/services/10000",
            {"serviceName": "Postcard"},
            client_wait_for_response=False,
        )

        assert retval == {"services": {"id": "10000"}}

        assert rmock.called
        assert tuple(
            req.timeout
            for req in rmock.request_history) == (base_client.nowait_timeout, )

        assert mock_logger.log.call_args_list == [
            mock.call(logging.DEBUG,
                      "API request {method} {url}",
                      extra={
                          "method": "POST",
                          "url": "http://baseurl/services/10000",
                      }),
            mock.call(
                logging.INFO,
                "API {api_method} request on {api_url} finished in {api_time}",
                extra={
                    "api_method": "POST",
                    "api_url": "http://baseurl/services/10000",
                    "api_time": mock.ANY,
                    "api_status": 200,
                }),
        ]
Пример #9
0
def test_download_raises_retriable_exception(bucket):
    with mock.patch('google.cloud.storage.blob.RawDownload') as download_mock:
        # Some random urllib3 exception
        download_mock.return_value.consume.side_effect = ProtocolError()
        with pytest.raises(RetriableError):
            ret = download_file(bucket, 'foo')
Пример #10
0
    def test_monitor_sends_exception_data_and_hb_on_expected_exceptions(
            self, mock_get_data) -> None:
        json_decode_error = json.JSONDecodeError(msg='test error',
                                                 doc='test',
                                                 pos=2)
        errors_exceptions_dict = {
            ReqConnectionError('test'):
            CannotAccessGitHubPageException(self.repo_config.releases_page),
            ReadTimeout('test'):
            CannotAccessGitHubPageException(self.repo_config.releases_page),
            IncompleteRead('test'):
            DataReadingException(self.monitor_name,
                                 self.repo_config.releases_page),
            ChunkedEncodingError('test'):
            DataReadingException(self.monitor_name,
                                 self.repo_config.releases_page),
            ProtocolError('test'):
            DataReadingException(self.monitor_name,
                                 self.repo_config.releases_page),
            json_decode_error:
            JSONDecodeException(json_decode_error)
        }
        try:
            self.test_monitor._initialise_rabbitmq()
            for error, data_ret_exception in errors_exceptions_dict.items():
                mock_get_data.side_effect = error
                expected_output_data = {
                    'error': {
                        'meta_data': {
                            'monitor_name': self.test_monitor.monitor_name,
                            'repo_name':
                            self.test_monitor.repo_config.repo_name,
                            'repo_id': self.test_monitor.repo_config.repo_id,
                            'repo_parent_id':
                            self.test_monitor.repo_config.parent_id,
                            'time': datetime(2012, 1, 1).timestamp()
                        },
                        'message': data_ret_exception.message,
                        'code': data_ret_exception.code,
                    }
                }
                expected_output_hb = {
                    'component_name': self.test_monitor.monitor_name,
                    'is_alive': True,
                    'timestamp': datetime(2012, 1, 1).timestamp()
                }
                # Delete the queue before to avoid messages in the queue on
                # error.
                self.test_monitor.rabbitmq.queue_delete(self.test_queue_name)

                res = self.test_monitor.rabbitmq.queue_declare(
                    queue=self.test_queue_name,
                    durable=True,
                    exclusive=False,
                    auto_delete=False,
                    passive=False)
                self.assertEqual(0, res.method.message_count)
                self.test_monitor.rabbitmq.queue_bind(
                    queue=self.test_queue_name,
                    exchange=RAW_DATA_EXCHANGE,
                    routing_key='github')
                self.test_monitor.rabbitmq.queue_bind(
                    queue=self.test_queue_name,
                    exchange=HEALTH_CHECK_EXCHANGE,
                    routing_key='heartbeat.worker')

                self.test_monitor._monitor()

                # By re-declaring the queue again we can get the number of
                # messages in the queue.
                res = self.test_monitor.rabbitmq.queue_declare(
                    queue=self.test_queue_name,
                    durable=True,
                    exclusive=False,
                    auto_delete=False,
                    passive=True)
                # There must be 2 messages in the queue, the heartbeat and the
                # processed data
                self.assertEqual(2, res.method.message_count)

                # Check that the message received is actually the processed data
                _, _, body = self.test_monitor.rabbitmq.basic_get(
                    self.test_queue_name)
                self.assertEqual(expected_output_data, json.loads(body))

                # Check that the message received is actually the HB
                _, _, body = self.test_monitor.rabbitmq.basic_get(
                    self.test_queue_name)
                self.assertEqual(expected_output_hb, json.loads(body))
        except Exception as e:
            self.fail("Test failed: {}".format(e))
Пример #11
0
 def test_catches_bad_status_line(self):
     error = ProtocolError(None)
     with pytest.raises(ConnectionClosedError):
         self.make_request_with_error(error)