# Copyright 2014 NEC Corporation.  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_utils import timeutils
from tempest.lib import decorators
from tempest.lib import exceptions

from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
from ironic_tempest_plugin.tests.api import base


class TestNodeStatesMixin(object):
    """Mixin for baremetal node states tests."""

    @classmethod
    def resource_setup(cls):
        super(TestNodeStatesMixin, cls).resource_setup()
        _, cls.chassis = cls.create_chassis()

    def _validate_power_state(self, node_uuid, power_state):
        # Validate that power state is set within timeout
        if power_state == 'rebooting':
            power_state = 'power on'
        start = timeutils.utcnow()
        while timeutils.delta_seconds(
                start, timeutils.utcnow()) < self.power_timeout:
            _, node = self.client.show_node(node_uuid)
            if node['power_state'] == power_state:
                return
        message = ('Failed to set power state within '
                   'the required time: %s sec.' % self.power_timeout)
        raise exceptions.TimeoutException(message)

    def _validate_provision_state(self, node_uuid, target_state):
        # Validate that provision state is set within timeout
        start = timeutils.utcnow()
        while timeutils.delta_seconds(
                start, timeutils.utcnow()) < self.unprovision_timeout:
            _, node = self.client.show_node(node_uuid)
            if node['provision_state'] == target_state:
                return
        message = ('Failed to set provision state %(state)s within '
                   'the required time: %(timeout)s sec.',
                   {'state': target_state,
                    'timeout': self.unprovision_timeout})
        raise exceptions.TimeoutException(message)

    @decorators.idempotent_id('cd8afa5e-3f57-4e43-8185-beb83d3c9015')
    def test_list_nodestates(self):
        _, node = self.create_node(self.chassis['uuid'])
        _, nodestates = self.client.list_nodestates(node['uuid'])
        for key in nodestates:
            self.assertEqual(nodestates[key], node[key])

    @decorators.idempotent_id('fc5b9320-0c98-4e5a-8848-877fe5a0322c')
    def test_set_node_power_state(self):
        _, node = self.create_node(self.chassis['uuid'])
        states = ["power on", "rebooting", "power off"]
        for state in states:
            # Set power state
            self.client.set_node_power_state(node['uuid'], state)
            # Check power state after state is set
            self._validate_power_state(node['uuid'], state)


class TestNodeStatesV1_1(TestNodeStatesMixin, base.BaseBaremetalTest):

    @decorators.idempotent_id('ccb8fca9-2ba0-480c-a037-34c3bd09dc74')
    def test_set_node_provision_state(self):
        _, node = self.create_node(self.chassis['uuid'])
        # Nodes appear in NONE state by default until v1.1
        self.assertIsNone(node['provision_state'])
        provision_states_list = ['active', 'deleted']
        target_states_list = ['active', None]
        for (provision_state, target_state) in zip(provision_states_list,
                                                   target_states_list):
            self.client.set_node_provision_state(node['uuid'], provision_state)
            self._validate_provision_state(node['uuid'], target_state)


class TestNodeStatesV1_2(TestNodeStatesMixin, base.BaseBaremetalTest):

    def setUp(self):
        super(TestNodeStatesV1_2, self).setUp()
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.2'))
        # Make node with 1.2, so the start state written to the DB as expected.
        _, self.node = self.create_node(self.chassis['uuid'])
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.31'))
        # Now with a 1.31 microversion, swap the deploy and network
        # interfaces into place so the test doesn't break depending on
        # the environment's default state.
        self.client.update_node(self.node['uuid'],
                                [{'path': '/deploy_interface',
                                  'op': 'replace',
                                  'value': 'fake'},
                                 {'path': '/network_interface',
                                  'op': 'replace',
                                  'value': 'noop'}])
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.2'))

    @decorators.idempotent_id('9c414984-f3b6-4b3d-81da-93b60d4662fb')
    def test_set_node_provision_state(self):
        _, node = self.client.show_node(self.node['uuid'])
        # Nodes appear in AVAILABLE state by default from v1.2 to v1.10
        self.assertEqual('available', self.node['provision_state'])
        provision_states_list = ['active', 'deleted']
        target_states_list = ['active', 'available']
        for (provision_state, target_state) in zip(provision_states_list,
                                                   target_states_list):
            self.client.set_node_provision_state(node['uuid'],
                                                 provision_state)
            self._validate_provision_state(node['uuid'], target_state)


class TestNodeStatesV1_4(TestNodeStatesMixin, base.BaseBaremetalTest):

    def setUp(self):
        super(TestNodeStatesV1_4, self).setUp()
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.4'))
        _, self.node = self.create_node(self.chassis['uuid'])
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.31'))
        # Now with a 1.31 microversion, swap the deploy and network
        # interfaces into place so the test doesn't break depending on
        # the environment's default state.
        self.client.update_node(self.node['uuid'],
                                [{'path': '/deploy_interface',
                                  'op': 'replace',
                                  'value': 'fake'},
                                 {'path': '/network_interface',
                                  'op': 'replace',
                                  'value': 'noop'}])
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.4'))

    @decorators.idempotent_id('3d606003-05ce-4b5a-964d-bdee382fafe9')
    def test_set_node_provision_state(self):
        # Make node with 1.2, so the start state written to the DB as expected.
        _, node = self.client.show_node(self.node['uuid'])
        # Nodes appear in AVAILABLE state by default from v1.2 to v1.10
        self.assertEqual('available', node['provision_state'])
        # MANAGEABLE state and PROVIDE transition have been added in v1.4
        provision_states_list = [
            'manage', 'provide', 'active', 'deleted']
        target_states_list = [
            'manageable', 'available', 'active', 'available']
        for (provision_state, target_state) in zip(provision_states_list,
                                                   target_states_list):
            self.client.set_node_provision_state(node['uuid'],
                                                 provision_state)
            self._validate_provision_state(node['uuid'], target_state)


class TestNodeStatesV1_6(TestNodeStatesMixin, base.BaseBaremetalTest):

    def setUp(self):
        super(TestNodeStatesV1_6, self).setUp()
        # Creates a node with 1.31, and is later reset for the rest of the test
        # due to Ironic's evolution of drivers.
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.6'))
        _, self.node = self.create_node(self.chassis['uuid'])
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.31'))
        # Now with a 1.31 microversion, swap the deploy and network
        # interfaces into place so the test doesn't break depending on
        # the environment's default state.
        self.client.update_node(self.node['uuid'],
                                [{'path': '/deploy_interface',
                                  'op': 'replace',
                                  'value': 'fake'},
                                 {'path': '/network_interface',
                                  'op': 'replace',
                                  'value': 'noop'}])
        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.6'))

    @decorators.idempotent_id('6c9ce4a3-713b-4c76-91af-18c48d01f1bb')
    def test_set_node_provision_state(self):
        _, node = self.client.show_node(self.node['uuid'])
        # Nodes appear in AVAILABLE state by default from v1.2 to v1.10
        self.assertEqual('available', node['provision_state'])
        # INSPECT* states have been added in v1.6
        provision_states_list = [
            'manage', 'inspect', 'provide', 'active', 'deleted']
        target_states_list = [
            'manageable', 'manageable', 'available', 'active', 'available']
        for (provision_state, target_state) in zip(provision_states_list,
                                                   target_states_list):
            self.client.set_node_provision_state(node['uuid'],
                                                 provision_state)
            self._validate_provision_state(node['uuid'],
                                           target_state)


class TestNodeStatesV1_11(TestNodeStatesMixin, base.BaseBaremetalTest):

    def setUp(self):
        super(TestNodeStatesV1_11, self).setUp()
        # Creates a node with 1.31, and is later reset for the rest of the test
        # due to Ironic's evolution of drivers.
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.31'))
        _, self.node = self.create_node(self.chassis['uuid'],
                                        deploy_interface='fake',
                                        network_interface='noop',
                                        inspect_interface='fake')
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.11')
        )

    @decorators.idempotent_id('31f53828-b83d-40c7-98e5-843e28a1b6b9')
    def test_set_node_provision_state(self):
        # Nodes appear in ENROLL state by default from v1.11
        self.assertEqual('enroll', self.node['provision_state'])
        provision_states_list = [
            'manage', 'inspect', 'provide', 'active', 'deleted']
        target_states_list = [
            'manageable', 'manageable', 'available', 'active', 'available']
        for (provision_state, target_state) in zip(provision_states_list,
                                                   target_states_list):
            self.client.set_node_provision_state(self.node['uuid'],
                                                 provision_state)
            self._validate_provision_state(self.node['uuid'],
                                           target_state)


class TestNodeStatesV1_12(TestNodeStatesMixin, base.BaseBaremetalTest):

    def setUp(self):
        super(TestNodeStatesV1_12, self).setUp()
        self.useFixture(
            api_microversion_fixture.APIMicroversionFixture('1.12')
        )

    @decorators.idempotent_id('4427b1ca-8e79-4139-83d6-77dfac03e61e')
    def test_set_node_raid_config(self):
        _, node = self.create_node(self.chassis['uuid'])
        target_raid_config = {'logical_disks': [{'size_gb': 100,
                                                 'raid_level': '1'}]}
        self.client.set_node_raid_config(node['uuid'], target_raid_config)
        _, ret = self.client.show_node(node['uuid'])
        self.assertEqual(target_raid_config, ret['target_raid_config'])
