# Copyright 2016 OpenStack Foundation
# 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.

import inspect
import os

from oslo_utils import importutils
import osprofiler.opts as profiler
import six.moves as six

from nova import conf
from nova import test


class TestProfiler(test.NoDBTestCase):
    def test_all_public_methods_are_traced(self):
        # NOTE(rpodolyaka): osprofiler only wraps class methods when option
        # CONF.profiler.enabled is set to True and the default value is False,
        # which means in our usual test run we use original, not patched
        # classes. In order to test, that we actually properly wrap methods
        # we are interested in, this test case sets CONF.profiler.enabled to
        # True and reloads all the affected Python modules (application of
        # decorators and metaclasses is performed at module import time).
        # Unfortunately, this leads to subtle failures of other test cases
        # (e.g. super() is performed on a "new" version of a class instance
        # created after a module reload while the class name is a reference to
        # an "old" version of the class). Thus, this test is run in isolation.
        if not os.getenv('TEST_OSPROFILER', False):
            self.skipTest('TEST_OSPROFILER env variable is not set. '
                          'Skipping osprofiler tests...')

        # reinitialize the metaclass after enabling osprofiler
        profiler.set_defaults(conf.CONF)
        self.flags(enabled=True, group='profiler')
        six.reload_module(importutils.import_module('nova.manager'))

        classes = [
            'nova.compute.api.API',
            'nova.compute.manager.ComputeManager',
            'nova.compute.rpcapi.ComputeAPI',
            'nova.conductor.manager.ComputeTaskManager',
            'nova.conductor.manager.ConductorManager',
            'nova.conductor.rpcapi.ComputeTaskAPI',
            'nova.conductor.rpcapi.ConductorAPI',
            'nova.image.glance.API',
            'nova.network.neutron.ClientWrapper',
            'nova.scheduler.manager.SchedulerManager',
            'nova.scheduler.rpcapi.SchedulerAPI',
            'nova.virt.libvirt.vif.LibvirtGenericVIFDriver',
            'nova.virt.libvirt.volume.volume.LibvirtBaseVolumeDriver',
        ]
        for clsname in classes:
            # give the metaclass and trace_cls() decorator a chance to patch
            # methods of the classes above
            six.reload_module(
                importutils.import_module(clsname.rsplit('.', 1)[0]))
            cls = importutils.import_class(clsname)

            for attr, obj in cls.__dict__.items():
                # only public methods are traced
                if attr.startswith('_'):
                    continue
                # only checks callables
                if not (inspect.ismethod(obj) or inspect.isfunction(obj)):
                    continue
                # osprofiler skips static methods
                if isinstance(obj, staticmethod):
                    continue

                self.assertTrue(getattr(obj, '__traced__', False), obj)
