# Copyright 2013: Mirantis Inc.
# 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 copy

import mock

from rally.deploy.fuel import fuelclient
from tests import test


class FuelNodeTestCase(test.TestCase):

    def test_check(self):

        node = {
            "cluster": None,
            "mac": "00:01:02:0a:0b:0c",
            "meta": {
                "memory": {"total": 42},
                "cpu": {"total": 2},
                "disks": [{"size": 22}, {"size": 33}]  # total 55
            },
        }

        n = fuelclient.FuelNode(node)

        self.assertFalse(n.check('ram==41'))
        self.assertFalse(n.check('ram!=42'))
        self.assertFalse(n.check('ram<=41'))
        self.assertFalse(n.check('ram>=43'))
        self.assertFalse(n.check('ram>43'))
        self.assertFalse(n.check('ram<41'))
        self.assertFalse(n.check('cpus>3'))

        self.assertTrue(n.check('ram==42'))
        self.assertTrue(n.check('ram!=41'))
        self.assertTrue(n.check('ram<=43'))
        self.assertTrue(n.check('ram<=42'))
        self.assertTrue(n.check('ram>=41'))
        self.assertTrue(n.check('ram>=42'))
        self.assertTrue(n.check('ram<43'))
        self.assertTrue(n.check('ram>41'))
        self.assertTrue(n.check('cpus==2'))

        self.assertTrue(n.check('mac==00:01:02:0a:0b:0c'))
        self.assertTrue(n.check('mac!=00:01:02:0a:0b:0e'))
        self.assertTrue(n.check('storage==55'))
        self.assertTrue(n.check('storage<=1G'))
        self.assertTrue(n.check('storage<1M'))


class FuelNodesCollectionTestCase(test.TestCase):

    def test_pop(self):
        node = {
            "cluster": None,
            "mac": "00:01:02:0a:0b:0c",
            "meta": {
                "memory": {"total": 42},
                "cpu": {"total": 2},
                "disks": [{"size": 22}, {"size": 33}]  # total 55
            },
        }

        nodes = [copy.deepcopy(node) for i in range(4)]
        nodes[0]['meta']['cpu']['total'] = 1
        nodes[1]['meta']['cpu']['total'] = 2
        nodes[2]['meta']['memory']['total'] = 16
        nodes[3]['cluster'] = 42  # node with cluster is occupied

        nodes = fuelclient.FuelNodesCollection(nodes)

        node_1cpu = nodes.pop(['cpus==1'])
        self.assertEqual(node_1cpu._get_cpus(), 1)
        self.assertEqual(len(nodes.nodes), 3)

        node_2cpu = nodes.pop(['cpus==2'])
        self.assertEqual(node_2cpu._get_cpus(), 2)
        self.assertEqual(len(nodes.nodes), 2)

        node_16ram_2cpu = nodes.pop(['ram>=16', 'cpus==2'])
        self.assertEqual(node_16ram_2cpu._get_ram(), 16)
        self.assertEqual(node_16ram_2cpu._get_cpus(), 2)
        self.assertEqual(len(nodes.nodes), 1)
        node_none = nodes.pop(['storage>4T'])
        self.assertIsNone(node_none)


class FuelClusterTestCase(test.TestCase):

    def setUp(self):
        super(FuelClusterTestCase, self).setUp()
        self.client = mock.Mock()
        self.config = {'name': 'Cluster'}
        self.cluster = fuelclient.FuelCluster(self.client, **self.config)

    def test_init(self):
        self.client.post.assert_called_once_with('clusters', self.config)

    def test_get_nodes(self):
        self.cluster.cluster = {'id': 42}
        self.cluster.get_nodes()
        self.client.get.assert_called_once_with('nodes?cluster_id=42')

    def test_set_nodes_empty(self):
        self.assertIsNone(self.cluster.set_nodes([], []))

    def test_set_nodes(self):
        nodes = [{'id': 42}, {'id': 43}]
        self.cluster.cluster = {'id': 1}
        self.cluster.set_nodes(nodes, ['role1', 'role2'])

        node42_args = {'cluster_id': 1,
                       'pending_roles': ['role1', 'role2'],
                       'pending_addition': True,
                       'id': 42}
        node43_args = {'cluster_id': 1,
                       'pending_roles': ['role1', 'role2'],
                       'pending_addition': True,
                       'id': 43}
        expected = [
            mock.call.post('clusters', {'name': 'Cluster'}),
            mock.call.put('nodes', [node42_args, node43_args])
        ]
        self.assertEqual(expected, self.client.mock_calls)

    def test_configure_network(self):
        current_network = {'networks': [{'name': 'public',
                                         'key': 'old_val',
                                         'key2': 'val2'}]}

        netconfig = {'public': {'key': 'new_val'}}
        self.cluster.get_network = mock.Mock(return_value=current_network)
        self.cluster.set_network = mock.Mock()

        self.cluster.configure_network(netconfig)

        self.cluster.set_network.assert_called_once_with(
            {'networks': [{'name': 'public',
                           'key': 'new_val',
                           'key2': 'val2'}]})

    @mock.patch('rally.deploy.fuel.fuelclient.time.sleep')
    def test_deploy(self, m_sleep):
        call1 = {'progress': 50}
        call2 = {'progress': 100}
        self.client.get_task.side_effect = [call1, call2]

        tasks = [{'name': 'deploy', 'id': 41}]
        self.client.get_tasks.return_value = tasks

        self.cluster.cluster = {'id': 42}
        self.cluster.deploy()

        expected = [
            mock.call.post('clusters', {'name': 'Cluster'}),
            mock.call.put('clusters/42/changes', {}),
            mock.call.get_tasks(42),
            mock.call.get_task(41),
            mock.call.get_task(41)
        ]
        self.assertEqual(expected, self.client.mock_calls)

    def test_get_network(self):
        self.cluster.cluster = {'id': 42, 'net_provider': 'nova_network'}
        self.cluster.get_network()
        self.client.get.assert_called_once_with(
            'clusters/42/network_configuration/nova_network')

    def test_set_network(self):
        self.cluster.cluster = {'id': 42, 'net_provider': 'nova_network'}
        self.cluster.verify_network = mock.Mock()
        self.cluster.set_network({'key': 'val'})

        self.client.put.assert_called_once_with(
            'clusters/42/network_configuration/nova_network', {'key': 'val'})
        self.cluster.verify_network.assert_called_once_with({'key': 'val'})

    @mock.patch('rally.deploy.fuel.fuelclient.time.sleep')
    def test_verify_network(self, m_sleep):
        call1 = {'progress': 50}
        call2 = {'progress': 100, 'message': ''}

        self.client.put.return_value = {'id': 42}
        self.client.get_task.side_effect = [call1, call2]
        self.cluster.cluster = {'id': 43, 'net_provider': 'nova_network'}

        self.cluster.verify_network({'key': 'val'})

        self.client.put.assert_called_once_with(
            'clusters/43/network_configuration/nova_network/verify',
            {'key': 'val'})
        self.assertEqual([mock.call(42), mock.call(42)],
                         self.client.get_task.mock_calls)

    @mock.patch('rally.deploy.fuel.fuelclient.time.sleep')
    def test_verify_network_fail(self, m_sleep):
        self.client.put.return_value = {'id': 42}
        self.client.get_task.return_value = {'progress': 100,
                                             'message': 'error'}
        self.cluster.cluster = {'id': 43, 'net_provider': 'nova_network'}
        self.assertRaises(fuelclient.FuelNetworkVerificationFailed,
                          self.cluster.verify_network, {'key': 'val'})

    def test_get_attributes(self):
        self.cluster.cluster = {'id': 52}
        self.cluster.get_attributes()
        self.client.get.assert_called_once_with('clusters/52/attributes')

    def test_get_endpoint_ip_multinode(self):
        self.cluster.cluster = {'mode': 'multinode'}
        node1 = {'roles': ['compute', 'cinder']}
        node2 = {'roles': ['controller'],
                 'network_data': [{'name': 'private'},
                                  {'name': 'public', 'ip': '42.42.42.42/24'}]}
        fake_nodes = [node1, node2]
        self.cluster.get_nodes = mock.Mock(return_value=fake_nodes)
        ip = self.cluster.get_endpoint_ip()
        self.assertEqual('42.42.42.42', ip)

    def test_get_endpoint_ip_ha(self):
        ip = '1.2.3.4'
        self.cluster.cluster = {'id': 42, 'mode': 'ha_compact'}
        self.cluster.get_network = mock.Mock(return_value={'public_vip': ip})
        self.assertEqual(ip, self.cluster.get_endpoint_ip())


class FuelClientTestCase(test.TestCase):

    def setUp(self):
        super(FuelClientTestCase, self).setUp()
        self.client = fuelclient.FuelClient('http://10.20.0.2:8000/api/v1/')

    @mock.patch('rally.deploy.fuel.fuelclient.requests')
    def test__request_non_json(self, fake_req):
        reply = mock.Mock()
        reply.status_code = 200
        reply.headers = {'content-type': 'application/x-httpd-php'}
        reply.text = '{"reply": "ok"}'
        fake_req.method = mock.Mock(return_value=reply)

        retval = self.client._request('method', 'url', data={'key': 'value'})

        self.assertEqual(retval, reply)

    @mock.patch('rally.deploy.fuel.fuelclient.requests')
    def test__request_non_2xx(self, fake_req):
        reply = mock.Mock()
        reply.status_code = 300
        reply.headers = {'content-type': 'application/json'}
        reply.text = '{"reply": "ok"}'
        fake_req.method = mock.Mock(return_value=reply)
        self.assertRaises(fuelclient.FuelClientException,
                          self.client._request, 'method', 'url',
                          data={'key': 'value'})

    @mock.patch('rally.deploy.fuel.fuelclient.requests')
    def test__request(self, fake_req):
        reply = mock.Mock()
        reply.status_code = 202
        reply.headers = {'content-type': 'application/json'}
        reply.text = '{"reply": "ok"}'
        fake_req.method = mock.Mock(return_value=reply)

        retval = self.client._request('method', 'url', data={'key': 'value'})
        fake_req.method.assert_called_once_with(
            'http://10.20.0.2:8000/api/v1/url',
            headers={'content-type': 'application/json'},
            data='{"key": "value"}')
        self.assertEqual(retval, {'reply': 'ok'})

    @mock.patch.object(fuelclient.FuelClient, '_request')
    def test_get(self, m_request):
        self.client.get('url')
        m_request.assert_called_once_with('get', 'url')

    @mock.patch.object(fuelclient.FuelClient, '_request')
    def test_delete(self, m_request):
        self.client.delete('url')
        m_request.assert_called_once_with('delete', 'url')

    @mock.patch.object(fuelclient.FuelClient, '_request')
    def test_post(self, m_request):
        self.client.post('url', {'key': 'val'})
        m_request.assert_called_once_with('post', 'url', {'key': 'val'})

    @mock.patch.object(fuelclient.FuelClient, '_request')
    def test_put(self, m_request):
        self.client.put('url', {'key': 'val'})
        m_request.assert_called_once_with('put', 'url', {'key': 'val'})

    @mock.patch.object(fuelclient.FuelClient, 'get')
    def test_get_releases(self, m_get):
        self.client.get_releases()
        m_get.assert_called_once_with('releases')

    @mock.patch.object(fuelclient.FuelClient, 'get')
    def test_get_task(self, m_get):
        self.client.get_task(42)
        m_get.assert_called_once_with('tasks/42')

    @mock.patch.object(fuelclient.FuelClient, 'get')
    def test_get_tasks(self, m_get):
        self.client.get_tasks(42)
        m_get.assert_called_once_with('tasks?cluster_id=42')

    @mock.patch.object(fuelclient.FuelClient, 'get')
    def test_get_node(self, m_get):
        self.client.get_node(42)
        m_get.assert_called_once_with('nodes/42')

    @mock.patch.object(fuelclient, 'FuelNodesCollection')
    @mock.patch.object(fuelclient.FuelClient, 'get')
    def test_get_nodes(self, m_get, m_collection):
        m_get.return_value = 'fake_nodes'
        m_collection.return_value = 'fake_collection'
        retval = self.client.get_nodes()
        self.assertEqual('fake_collection', retval)
        m_collection.assert_called_once_with('fake_nodes')
        m_get.assert_called_once_with('nodes')

    @mock.patch.object(fuelclient.FuelClient, 'delete')
    def test_delete_cluster(self, m_delete):
        self.client.delete_cluster(42)
        m_delete.assert_called_once_with('clusters/42')
