# Copyright (c) 2011 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.
"""
Tests For HostManager
"""

from datetime import datetime
from datetime import timedelta
import ddt

import mock
from oslo_serialization import jsonutils
from oslo_utils import timeutils

from cinder.common import constants
from cinder import context
from cinder import db
from cinder import exception
from cinder import objects
from cinder.scheduler import filters
from cinder.scheduler import host_manager
from cinder import test
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit.objects import test_service


class FakeFilterClass1(filters.BaseBackendFilter):
    def backend_passes(self, host_state, filter_properties):
        pass


class FakeFilterClass2(filters.BaseBackendFilter):
    def backend_passes(self, host_state, filter_properties):
        pass


class FakeFilterClass3(filters.BaseHostFilter):
    def host_passes(self, host_state, filter_properties):
        return host_state.get('volume_backend_name') == \
            filter_properties.get('volume_type')['volume_backend_name']


@ddt.ddt
class HostManagerTestCase(test.TestCase):
    """Test case for HostManager class."""

    def setUp(self):
        super(HostManagerTestCase, self).setUp()
        self.host_manager = host_manager.HostManager()
        self.fake_backends = [host_manager.BackendState('fake_be%s' % x, None)
                              for x in range(1, 5)]
        # For a second scheduler service.
        self.host_manager_1 = host_manager.HostManager()

    @mock.patch(
        'cinder.scheduler.filters.BackendFilterHandler.get_all_classes')
    def test_initialzie_with_default_filters(self, mock_get_all_classes):
        self.flags(scheduler_default_filters=
                   'FakeFilterClass1,FakeFilterClass2')
        mock_get_all_classes.return_value = [
            FakeFilterClass1, FakeFilterClass2, FakeFilterClass3]
        h_manager = host_manager.HostManager()
        self.assertListEqual([FakeFilterClass1, FakeFilterClass2],
                             h_manager.enabled_filters)

    def test_choose_backend_filters_not_found(self):
        self.host_manager.filter_classes = [FakeFilterClass1,
                                            FakeFilterClass2]
        self.assertRaises(exception.SchedulerHostFilterNotFound,
                          self.host_manager._choose_backend_filters,
                          'FakeFilterClass3')

    def test_choose_backend_filters(self):
        self.host_manager.filter_classes = [FakeFilterClass1,
                                            FakeFilterClass2]

        # Test 'volume' returns 1 correct function
        filter_classes = self.host_manager._choose_backend_filters(
            'FakeFilterClass2')
        self.assertEqual(1, len(filter_classes))
        self.assertEqual('FakeFilterClass2', filter_classes[0].__name__)

    @mock.patch('cinder.scheduler.host_manager.HostManager.'
                '_choose_backend_filters')
    def test_get_filtered_backends(self, _mock_choose_backend_filters):
        filter_class = FakeFilterClass1
        mock_func = mock.Mock()
        mock_func.return_value = True
        filter_class._filter_one = mock_func
        _mock_choose_backend_filters.return_value = [filter_class]

        fake_properties = {'moo': 1, 'cow': 2}
        expected = []
        for fake_backend in self.fake_backends:
            expected.append(mock.call(fake_backend, fake_properties))
        host_manager1 = host_manager.HostManager()
        result = host_manager1.get_filtered_backends(self.fake_backends,
                                                     fake_properties)
        self.assertEqual(expected, mock_func.call_args_list)
        self.assertEqual(set(self.fake_backends), set(result))

    @mock.patch('cinder.scheduler.host_manager.HostManager._get_updated_pools')
    @mock.patch('oslo_utils.timeutils.utcnow')
    def test_update_service_capabilities(self, _mock_utcnow,
                                         _mock_get_updated_pools):
        service_states = self.host_manager.service_states
        self.assertDictEqual({}, service_states)
        _mock_utcnow.side_effect = [31338, 31339]

        _mock_get_updated_pools.return_value = []
        timestamp = jsonutils.to_primitive(datetime.utcnow())
        host1_volume_capabs = dict(free_capacity_gb=4321, timestamp=timestamp)
        host1_old_volume_capabs = dict(free_capacity_gb=1, timestamp=timestamp)
        host2_volume_capabs = dict(free_capacity_gb=5432)
        host3_volume_capabs = dict(free_capacity_gb=6543)

        service_name = 'volume'
        # The host manager receives a deserialized timestamp
        timestamp = datetime.strptime(timestamp, timeutils.PERFECT_TIME_FORMAT)
        self.host_manager.update_service_capabilities(service_name, 'host1',
                                                      host1_volume_capabs,
                                                      None, timestamp)
        # It'll ignore older updates
        old_timestamp = timestamp - timedelta(hours=1)
        self.host_manager.update_service_capabilities(service_name, 'host1',
                                                      host1_old_volume_capabs,
                                                      None, old_timestamp)
        self.host_manager.update_service_capabilities(service_name, 'host2',
                                                      host2_volume_capabs,
                                                      None, None)
        self.host_manager.update_service_capabilities(service_name, 'host3',
                                                      host3_volume_capabs,
                                                      None, None)

        # Make sure dictionary isn't re-assigned
        self.assertEqual(service_states, self.host_manager.service_states)

        host1_volume_capa
bs['timestamp'] = timestamp
        host2_volume_capabs['timestamp'] = 31338
        host3_volume_capabs['timestamp
'] = 31339

        expected = {'host1': host1_volume_capabs,
                    'host2': host2_volume_capabs,
                    'host3': host3_volume_capabs}
        self.assertDictEqual(expected, service_states)

    @mock.patch(
        'cinder.scheduler.host_manager.HostManager.get_usage_and_notify')
    @mock.patch('oslo_utils.timeutils.utcnow')
    def test_update_and_notify_service_capabilities_case1(
            self, _mock_utcnow,
            _mock_get_usage_and_notify):

        _mock_utcnow.side_effect = [31337, 31338, 31339]
        service_name = 'volume'

        capab1 = {'pools': [{
                  'pool_name': 'pool1', 'thick_provisioning_support': True,
                  'thin_provisioning_support': False, 'total_capacity_gb': 10,
                  'free_capacity_gb': 10, 'max_over_subscription_ratio': '1',
                  'provisioned_capacity_gb': 0, 'allocated_capacity_gb': 0,
                  'reserved_percentage': 0}]}

        # Run 1:
        # capa: capa1
        # S0: update_service_capabilities()
        # S0: notify_service_capabilities()
        # S1: update_service_capabilities()
        #
        # notify capab1 to ceilometer by S0
        #

        # S0: update_service_capabilities()
        self.host_manager.update_service_capabilities(service_name, 'host1',
                                                      capab1, None, None)
        self.assertDictEqual(dict(dict(timestamp=31337), **capab1),
                             self.host_manager.service_states['host1'])

        # S0: notify_service_capabilities()
