#    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 nova.cmd import manage
from nova import context
from nova import objects
from nova import test


class NovaManageDBIronicTest(test.TestCase):
    def setUp(self):
        super(NovaManageDBIronicTest, self).setUp()
        self.commands = manage.DbCommands()
        self.context = context.RequestContext('fake-user', 'fake-project')

        self.service1 = objects.Service(context=self.context,
                                       host='fake-host1',
                                       binary='nova-compute',
                                       topic='fake-host1',
                                       report_count=1,
                                       disabled=False,
                                       disabled_reason=None,
                                       availability_zone='nova',
                                       forced_down=False)
        self.service1.create()

        self.service2 = objects.Service(context=self.context,
                                       host='fake-host2',
                                       binary='nova-compute',
                                       topic='fake-host2',
                                       report_count=1,
                                       disabled=False,
                                       disabled_reason=None,
                                       availability_zone='nova',
                                       forced_down=False)
        self.service2.create()

        self.service3 = objects.Service(context=self.context,
                                       host='fake-host3',
                                       binary='nova-compute',
                                       topic='fake-host3',
                                       report_count=1,
                                       disabled=False,
                                       disabled_reason=None,
                                       availability_zone='nova',
                                       forced_down=False)
        self.service3.create()

        self.cn1 = objects.ComputeNode(context=self.context,
                                       service_id=self.service1.id,
                                       host='fake-host1',
                                       hypervisor_type='ironic',
                                       vcpus=1,
                                       memory_mb=1024,
                                       local_gb=10,
                                       vcpus_used=1,
                                       memory_mb_used=1024,
                                       local_gb_used=10,
                                       hypervisor_version=0,
                                       hypervisor_hostname='fake-node1',
                                       cpu_info='{}')
        self.cn1.create()

        self.cn2 = objects.ComputeNode(context=self.context,
                                       service_id=self.service1.id,
                                       host='fake-host1',
                                       hypervisor_type='ironic',
                                       vcpus=1,
                                       memory_mb=1024,
                                       local_gb=10,
                                       vcpus_used=1,
                                       memory_mb_used=1024,
                                       local_gb_used=10,
                                       hypervisor_version=0,
                                       hypervisor_hostname='fake-node2',
                                       cpu_info='{}')
        self.cn2.create()

        self.cn3 = objects.ComputeNode(context=self.context,
                                       service_id=self.service2.id,
                                       host='fake-host2',
                                       hypervisor_type='ironic',
                                       vcpus=1,
                                       memory_mb=1024,
                                       local_gb=10,
                                       vcpus_used=1,
                                       memory_mb_used=1024,
                                       local_gb_used=10,
                                       hypervisor_version=0,
                                       hypervisor_hostname='fake-node3',
                                       cpu_info='{}')
        self.cn3.create()

        self.cn4 = objects.ComputeNode(context=self.context,
                                       service_id=self.service3.id,
                                       host='fake-host3',
                                       hypervisor_type='libvirt',
                                       vcpus=1,
                                       memory_mb=1024,
                                       local_gb=10,
                                       vcpus_used=1,
                                       memory_mb_used=1024,
                                       local_gb_used=10,
                                       hypervisor_version=0,
                                       hypervisor_hostname='fake-node4',
                                       cpu_info='{}')
        self.cn4.create()

        self.cn5 = objects.ComputeNode(context=self.context,
                                       service_id=self.service2.id,
                                       host='fake-host2',
                                       hypervisor_type='ironic',
                                       vcpus=1,
                                       memory_mb=1024,
                                       local_gb=10,
                                       vcpus_used=1,
                                       memory_mb_used=1024,
                                       local_gb_used=10,
                                       hypervisor_version=0,
                                       hypervisor_hostname='fake-node5',
                                       cpu_info='{}')
        self.cn5.create()

        self.insts = []
        for cn in (self.cn1, self.cn2, self.cn3, self.cn4, self.cn4, self.cn5):
            flavor = objects.Flavor(extra_specs={})
            inst = objects.Instance(context=self.context,
                                    user_id=self.context.user_id,
                                    project_id=self.context.project_id,
                                    flavor=flavor,
                                    node=cn.hypervisor_hostname)
            inst.create()
            self.insts.append(inst)

        self.ironic_insts = [i for i in self.insts
                             if i.node != self.cn4.hypervisor_hostname]
        self.virt_insts = [i for i in self.insts
                           if i.node == self.cn4.hypervisor_hostname]

    def test_ironic_flavor_migration_by_host_and_node(self):
        ret = self.commands.ironic_flavor_migration('test', 'fake-host1',
                                                    'fake-node2', False, False)
        self.assertEqual(0, ret)
        k = 'resources:CUSTOM_TEST'

        for inst in self.ironic_insts:
            inst.refresh()
            if inst.node == 'fake-node2':
                self.assertIn(k, inst.flavor.extra_specs)
                self.assertEqual('1', inst.flavor.extra_specs[k])
            else:
                self.assertNotIn(k, inst.flavor.extra_specs)

        for inst in self.virt_insts:
            inst.refresh()
            self.assertNotIn(k, inst.flavor.extra_specs)

    def test_ironic_flavor_migration_by_host(self):
        ret = self.commands.ironic_flavor_migration('test', 'fake-host1', None,
                                                    False, False)
        self.assertEqual(0, ret)
        k = 'resources:CUSTOM_TEST'

        for inst in self.ironic_insts:
            inst.refresh()
            if inst.node in ('fake-node1', 'fake-node2'):
                self.assertIn(k, inst.flavor.extra_specs)
                self.assertEqual('1', inst.flavor.extra_specs[k])
            else:
                self.assertNotIn(k, inst.flavor.extra_specs)

        for inst in self.virt_insts:
            inst.refresh()
            self.assertNotIn(k, inst.flavor.extra_specs)

    def test_ironic_flavor_migration_by_host_not_ironic(self):
        ret = self.commands.ironic_flavor_migration('test', 'fake-host3', None,
                                                    False, False)
        self.assertEqual(1, ret)
        k = 'resources:CUSTOM_TEST'

        for inst in self.ironic_insts:
            inst.refresh()
            self.assertNotIn(k, inst.flavor.extra_specs)

        for inst in self.virt_insts:
            inst.refresh()
            self.assertNotIn(k, inst.flavor.extra_specs)

    def test_ironic_flavor_migration_all_hosts(self):
        ret = self.commands.ironic_flavor_migration('test', None, None,
                                                    True, False)
        self.assertEqual(0, ret)
        k = 'resources:CUSTOM_TEST'

        for inst in self.ironic_insts:
            inst.refresh()
            self.assertIn(k, inst.flavor.extra_specs)
            self.assertEqual('1', inst.flavor.extra_specs[k])

        for inst in self.virt_insts:
            inst.refresh()
            self.assertNotIn(k, inst.flavor.extra_specs)

    def test_ironic_flavor_migration_invalid(self):
        # No host or node and not "all"
        ret = self.commands.ironic_flavor_migration('test', None, None,
                                                    False, False)
        self.assertEqual(3, ret)

        # No host, only node
        ret = self.commands.ironic_flavor_migration('test', None, 'fake-node',
                                                    False, False)
        self.assertEqual(3, ret)

        # Asked for all but provided a node
        ret = self.commands.ironic_flavor_migration('test', None, 'fake-node',
                                                    True, False)
        self.assertEqual(3, ret)

        # Asked for all but provided a host
        ret = self.commands.ironic_flavor_migration('test', 'fake-host', None,
                                                    True, False)
        self.assertEqual(3, ret)

        # Asked for all but provided a host and node
        ret = self.commands.ironic_flavor_migration('test', 'fake-host',
                                                    'fake-node', True, False)
        self.assertEqual(3, ret)

        # Did not provide a resource_class
        ret = self.commands.ironic_flavor_migration(None, 'fake-host',
                                                    'fake-node', False, False)
        self.assertEqual(3, ret)

    def test_ironic_flavor_migration_no_match(self):
        ret = self.commands.ironic_flavor_migration('test', 'fake-nonexist',
                                                    None, False, False)
        self.assertEqual(1, ret)

        ret = self.commands.ironic_flavor_migration('test', 'fake-nonexist',
                                                    'fake-node', False, False)
        self.assertEqual(1, ret)

    def test_ironic_two_instances(self):
        # NOTE(danms): This shouldn't be possible, but simulate it like
        # someone hacked the database, which should also cover any other
        # way this could happen.

        # Since we created two instances on cn4 in setUp() we can convert that
        # to an ironic host and cause the two-instances-on-one-ironic paradox
        # to happen.
        self.cn4.hypervisor_type = 'ironic'
        self.cn4.save()
        ret = self.commands.ironic_flavor_migration('test', 'fake-host3',
                                                    'fake-node4', False, False)
        self.assertEqual(2, ret)
