def _rest_op(self, operation, suburi, request_headers, request_body):
        """Generic REST Operation handler."""

        url = urlparse.urlparse('https://' + self.host + suburi)
        # Used for logging on redirection error.
        start_url = url.geturl()

        LOG.debug(self._("%(operation)s %(url)s"),
                  {'operation': operation, 'url': start_url})

        if request_headers is None or not isinstance(request_headers, dict):
            request_headers = {}

        # Use self.login/self.password and Basic Auth
        if self.login is not None and self.password is not None:
            auth_data = self.login + ":" + self.password
            hr = "BASIC " + base64.b64encode(
                auth_data.encode('ascii')).decode("utf-8")
            request_headers['Authorization'] = hr

        if request_body is not None:
            if (isinstance(request_body, dict)
                    or isinstance(request_body, list)):
                request_headers['Content-Type'] = 'application/json'
            else:
                request_headers['Content-Type'] = ('application/'
                                                   'x-www-form-urlencoded')

        """Helper methods to retry and keep retrying on redirection - START"""

        def retry_if_response_asks_for_redirection(response):
            # NOTE:Do not assume every HTTP operation will return a JSON
            # request_body. For example, ExtendedError structures are only
            # required for HTTP 400 errors and are optional elsewhere as they
            # are mostly redundant for many of the other HTTP status code.
            # In particular, 200 OK responses should not have to return any
            # request_body.

            # NOTE:  this makes sure the headers names are all lower cases
            # because HTTP says they are case insensitive
            # Follow HTTP redirect
            if response.status_code == 301 and 'location' in response.headers:
                retry_if_response_asks_for_redirection.url = (
                    urlparse.urlparse(response.headers['location']))
                LOG.debug(self._("Request redirected to %s."),
                          retry_if_response_asks_for_redirection.url.geturl())
                return True
            return False

        @retrying.retry(
            # Note(deray): Return True if we should retry, False otherwise.
            # In our case, when the url response we receive asks for
            # redirection then we retry.
            retry_on_result=retry_if_response_asks_for_redirection,
            # Note(deray): Return True if we should retry, False otherwise.
            # In our case, when it's an IloConnectionError we don't retry.
            # ``requests`` already takes care of issuing max number of
            # retries if the URL service is unavailable.
            retry_on_exception=(
                lambda e: not isinstance(e, exception.IloConnectionError)),
            stop_max_attempt_number=REDIRECTION_ATTEMPTS)
        def _fetch_response():

            url = retry_if_response_asks_for_redirection.url

            kwargs = {'headers': request_headers,
                      'data': json.dumps(request_body)}
            if self.cacert is not None:
                kwargs['verify'] = self.cacert
            else:
                kwargs['verify'] = False

            LOG.debug(self._('\n\tHTTP REQUEST: %(restreq_method)s'
                             '\n\tPATH: %(restreq_path)s'
                             '\n\tBODY: %(restreq_body)s'
                             '\n'),
                      {'restreq_method': operation,
                       'restreq_path': url.geturl(),
                       'restreq_body': request_body})

            request_method = getattr(requests, operation.lower())
            try:
                response = request_method(url.geturl(), **kwargs)
            except Exception as e:
                LOG.debug(self._("Unable to connect to iLO. %s"), e)
                raise exception.IloConnectionError(e)

            return response

        """Helper methods to retry and keep retrying on redirection - END"""

        try:
            # Note(deray): This is a trick to use the function attributes
            # to overwrite variable/s (in our case ``url``) and use the
            # modified one in nested functions, i.e. :func:`_fetch_response`
            # and :func:`retry_if_response_asks_for_redirection`
            retry_if_response_asks_for_redirection.url = url

            response = _fetch_response()
        except retrying.RetryError as e:
            # Redirected for REDIRECTION_ATTEMPTS - th time. Throw error
            msg = (self._("URL Redirected %(times)s times continuously. "
                          "URL used: %(start_url)s More info: %(error)s") %
                   {'start_url': start_url, 'times': REDIRECTION_ATTEMPTS,
                    'error': str(e)})
            LOG.debug(msg)
            raise exception.IloConnectionError(msg)

        response_body = {}
        if response.text:
            try:
                response_body = json.loads(response.text)
            except (TypeError, ValueError):
                # Note(deray): If it doesn't decode as json, then
                # resources may return gzipped content.
                # ``json.loads`` on python3 raises TypeError when
                # ``response.text`` is gzipped one.
                response_body = (
                    self._get_response_body_from_gzipped_content(url,
                                                                 response))

        LOG.debug(self._('\n\tHTTP RESPONSE for %(restreq_path)s:'
                         '\n\tCode: %(status_code)s'
                         '\n\tResponse Body: %(response_body)s'
                         '\n'),
                  {'restreq_path': url.geturl(),
                   'status_code': response.status_code,
                   'response_body': response_body})
        return response.status_code, response.headers, response_body