#    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_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel as uuids

from nova import conf
from nova import objects
from nova.objects import numa
from nova.scheduler import host_manager
from nova.tests.unit.objects import test_objects

CONF = conf.CONF

fake_numa_limit1 = numa.NUMATopologyLimits(cpu_allocation_ratio=1.0,
        ram_allocation_ratio=1.0)
fake_limit1 = {"memory_mb": 1024, "disk_gb": 100, "vcpus": 2,
        "numa_topology": fake_numa_limit1}
fake_limit_obj1 = objects.SchedulerLimits.from_dict(fake_limit1)
fake_host1 = {
        "uuid": uuids.host1,
        "host": "host1",
        "nodename": "node1",
        "cell_uuid": uuids.cell,
        "limits": fake_limit1,
        }
fake_host_state1 = host_manager.HostState("host1", "node1", uuids.cell)
fake_host_state1.uuid = uuids.host1
fake_host_state1.limits = fake_limit1.copy()
fake_alloc1 = {"allocations": [
        {"resource_provider": {"uuid": uuids.host1},
         "resources": {"VCPU": 1,
                       "MEMORY_MB": 1024,
                       "DISK_GB": 100}
        }]}
fake_alloc_version = "1.23"


class _TestSelectionObject(object):
    def test_create_with_values(self):
        json_alloc = jsonutils.dumps(fake_alloc1)
        dest = objects.Selection(service_host="host", nodename="node",
                compute_node_uuid=uuids.host1, cell_uuid=uuids.cell,
                limits=fake_limit_obj1, allocation_request=json_alloc,
                allocation_request_version=fake_alloc_version)
        self.assertEqual("host", dest.service_host)
        self.assertEqual(uuids.host1, dest.compute_node_uuid)
        self.assertEqual("node", dest.nodename)
        self.assertEqual(uuids.cell, dest.cell_uuid)
        self.assertEqual(fake_limit_obj1, dest.limits)
        self.assertEqual(json_alloc, dest.allocation_request)
        self.assertEqual(fake_alloc_version, dest.allocation_request_version)

    def test_passing_dict_allocation_fails(self):
        self.assertRaises(ValueError, objects.Selection, service_host="host",
                compute_node_uuid=uuids.host, nodename="node",
                cell_uuid=uuids.cell, allocation_request=fake_alloc1,
                allocation_request_version=fake_alloc_version)

    def test_passing_numeric_allocation_version_converts(self):
        json_alloc = jsonutils.dumps(fake_alloc1)
        dest = objects.Selection(service_host="host",
                compute_node_uuid=uuids.host, nodename="node",
                cell_uuid=uuids.cell, allocation_request=json_alloc,
                allocation_request_version=1.23)
        self.assertEqual("1.23", dest.allocation_request_version)

    def test_from_host_state(self):
        dest = objects.Selection.from_host_state(fake_host_state1, fake_alloc1,
                fake_alloc_version)
        self.assertEqual(dest.service_host, fake_host_state1.host)
        expected_alloc = jsonutils.dumps(fake_alloc1)
        self.assertEqual(dest.allocation_request, expected_alloc)
        self.assertEqual(dest.allocation_request_version, fake_alloc_version)

    def test_from_host_state_no_alloc_info(self):
        dest = objects.Selection.from_host_state(fake_host_state1)
        self.assertEqual(dest.service_host, fake_host_state1.host)
        expected_alloc = jsonutils.dumps(None)
        self.assertEqual(expected_alloc, dest.allocation_request)
        self.assertIsNone(dest.allocation_request_version)

    def test_selection_obj_to_dict(self):
        """Tests that to_dict() method properly converts a Selection object to
        the corresponding dict.
        """
        fake_network_metadata = objects.NetworkMetadata(
            physnets=set(['foo', 'bar']), tunneled=True)
        fake_numa_limit = objects.numa.NUMATopologyLimits(
            cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0,
            network_metadata=fake_network_metadata)
        fake_limit = {"memory_mb": 1024, "disk_gb": 100, "vcpus": 2,
                "numa_topology": fake_numa_limit}
        fake_limit_obj = objects.SchedulerLimits.from_dict(fake_limit)
        sel_obj = objects.Selection(service_host="fakehost",
                nodename="fakenode", compute_node_uuid=uuids.host,
                cell_uuid=uuids.cell, limits=fake_limit_obj,
                allocation_request="fake", allocation_request_version="99.9")

        result = sel_obj.to_dict()

        self.assertEqual(['host', 'limits', 'nodename'], sorted(result.keys()))
        self.assertEqual('fakehost', result['host'])
        self.assertEqual('fakenode', result['nodename'])

        limits = result['limits']
        self.assertEqual(['disk_gb', 'memory_mb', 'numa_topology'],
                         sorted(limits.keys()))
        self.assertEqual(100, limits['disk_gb'])
        self.assertEqual(1024, limits['memory_mb'])

        numa_topology = limits['numa_topology']['nova_object.data']
        self.assertEqual(1.0, numa_topology['cpu_allocation_ratio'])
        self.assertEqual(1.0, numa_topology['ram_allocation_ratio'])

        network_meta = numa_topology['network_metadata']['nova_object.data']
        # sets are unordered so we need to convert to a list
        self.assertEqual(['bar', 'foo'], sorted(network_meta['physnets']))
        self.assertTrue(network_meta['tunneled'])

    def test_selection_obj_to_dict_no_numa(self):
        """Tests that to_dict() method properly converts a
        Selection object to the corresponding dict when the numa_topology field
        is None.
        """
        fake_limit = {"memory_mb": 1024, "disk_gb": 100, "vcpus": 2,
                "numa_topology": None}
        fake_limit_obj = objects.SchedulerLimits.from_dict(fake_limit)
        sel_obj = objects.Selection(service_host="fakehost",
                nodename="fakenode", compute_node_uuid=uuids.host,
                cell_uuid=uuids.cell, limits=fake_limit_obj,
                allocation_request="fake", allocation_request_version="99.9")
        expected = {"host": "fakehost",
                "nodename": "fakenode",
                "limits": {
                    "disk_gb": 100,
                    "memory_mb": 1024}}
        result = sel_obj.to_dict()
        self.assertDictEqual(expected, result)


class TestSelectionObject(test_objects._LocalTest,
                            _TestSelectionObject):

    # NOTE(mriedem): The tests below are for methods which are not remotable
    # so they can go in the local-only test class rather than the mixin above.

    def test_obj_make_compatible(self):
        selection = objects.Selection(service_host='host1',
                                      availability_zone='zone1')
        primitive = selection.obj_to_primitive(
            target_version='1.1')['nova_object.data']
        self.assertIn('availability_zone', primitive)
        primitive = selection.obj_to_primitive(
            target_version='1.0')['nova_object.data']
        self.assertNotIn('availability_zone', primitive)
        self.assertIn('service_host', primitive)

    def test_from_host_state_az_via_aggregate_metadata(self):
        """Tests the scenario that the host is in multiple aggregates and
        one has the availability_zone aggregate metadata key which is used
        on the selection object.
        """
        host_state = host_manager.HostState('host', 'node', uuids.cell_uuid)
        host_state.uuid = uuids.compute_node_uuid
        host_state.limits = {}
        host_state.aggregates = [
            objects.Aggregate(metadata={'foo': 'bar'}),
            objects.Aggregate(metadata={'availability_zone': 'zone1'})
        ]
        selection = objects.Selection.from_host_state(host_state)
        self.assertEqual('zone1', selection.availability_zone)

    def test_from_host_state_az_via_config(self):
        """Tests the scenario that the host is not in an aggregate with the
        availability_zone metadata key so the AZ comes from config.
        """
        host_state = host_manager.HostState('host', 'node', uuids.cell_uuid)
        host_state.uuid = uuids.compute_node_uuid
        host_state.limits = {}
        host_state.aggregates = []
        selection = objects.Selection.from_host_state(host_state)
        self.assertEqual(CONF.default_availability_zone,
                         selection.availability_zone)


class TestRemoteSelectionObject(test_objects._RemoteTest,
                                  _TestSelectionObject):
    pass
