# Copyright 2022 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from oslo_log import log as logging
from oslo_middleware import request_id as oslo_req_id
from oslo_utils import timeutils

from cinder import context as cinder_context


LOG = logging.getLogger(__name__)


class RequestId(oslo_req_id.RequestId):
    def _context_setter(self, environ, *args, **kwargs):
        """Wrapper to set a temporary context.

        It is necessary to replace the previous request's context, but at this
        point when we are generating the new request ID we don't have the
        keystone info, so use a placeholder with the information we have:

        - global_request_id ==> Extracted from the headers by the parent class
        - request_id => Generated by the parent class __call__ method
        - timestamp => The moment Cinder API starts processing the request

        This middleware needs to be the first in ALL the pipelines for things
        to work as expected, otherwise we'll have the following issues:

        - Logs from other filters reuse a context from a previous request,
          presenting the wrong request id and project and user info, and then
          after the request passes the auth filter the request id will change
          to the right one.  We'll see this when enabling debug mode in the
          keystonemiddleware module.

        - Requests that don't require authorization (/ and /healthcheck) won't
          return a request ID in the headers.
        """
        # Replace previous request's context with all the info we have now
        placeholder_ctx = cinder_context.RequestContext(
            request_id=environ[oslo_req_id.ENV_REQUEST_ID],
            global_request_id=environ.get(oslo_req_id.GLOBAL_REQ_ID),
            timestamp=timeutils.utcnow(),
        )

        # Only update environ, oslo_context local store was updated
        # automatically when instantiating the request context.
        environ['cinder.context'] = placeholder_ctx

        # Have a timestamped log with the start of the pipeline processing.
        LOG.debug('RequestId filter calling following filter/app')
        return self._application(environ, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        # Parent __call__ method creates the request id and makes the call to
        # the chained app one after the other, so we need a wrapper on the app
        # to set the context.
        super().__init__(*args, **kwargs)
        self._application = self.application
        self.application = self._context_setter
