# 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 mock

from rally.deploy import engine
from rally import objects
from tests.unit import test

MOD = 'rally.deploy.engines.lxc.'


class LxcEngineTestCase(test.TestCase):

    def setUp(self):
        super(LxcEngineTestCase, self).setUp()
        self.config = {
            'type': 'LxcEngine',
            'container_name': 'rally',
            'containers_per_host': 2,
            'tunnel_to': ['1.1.1.1', '2.2.2.2'],
            'distribution': 'ubuntu',
            'start_lxc_network': '10.128.128.0/28',
            'engine': {
                'name': 'FakeEngine',
                'config': {
                    'key': 'value',
                },
            },
            'provider': {
                'type': 'DummyProvider',
                'credentials': [{'user': 'root', 'host': 'host1.net'},
                                {'user': 'root', 'host': 'host2.net'}]
            }
        }
        self.deployment = {
            'uuid': 'test-deployment-uuid',
            'config': self.config,
        }
        self.engine = engine.EngineFactory.get_engine('LxcEngine',
                                                      self.deployment)

    @mock.patch(MOD + 'objects')
    @mock.patch(MOD + 'engine')
    def test__deploy_first(self, m_engine, m_objects):
        fake_credentials = {'user': 'admin', 'host': 'host.net'}
        fake_deployment = mock.Mock()
        fake_engine = mock.Mock()
        m_objects.Deployment = mock.Mock(return_value=fake_deployment)
        m_engine.EngineFactory.get_engine = mock.Mock(return_value=fake_engine)

        fake_host = mock.Mock()
        fake_so = mock.Mock()
        fake_so.get_credentials.return_value = fake_credentials
        fake_host.get_server_object = mock.Mock(return_value=fake_so)
        self.engine._deploy_first(fake_host, 'name', 'dist', 'release')
        host_calls = [
            mock.call.prepare(),
            mock.call.create_container('name', 'dist', 'release'),
            mock.call.start_containers(),
            mock.call.get_server_object('name'),
            mock.call.stop_containers()]
        self.assertEqual(host_calls, fake_host.mock_calls)
        fake_engine.deploy.assert_called_once_with()
        m_engine.EngineFactory.get_engine.assert_called_once_with(
            'FakeEngine', fake_deployment)
        engine_config = self.config['engine'].copy()
        engine_config['provider'] = {'credentials': [fake_credentials],
                                     'type': 'DummyProvider'}
        m_objects.Deployment.assert_called_once_with(
            config=engine_config, parent_uuid='test-deployment-uuid')

    @mock.patch(MOD + 'provider.ProviderFactory.get_provider')
    def test__get_provider(self, m_get_provider):
        m_get_provider.return_value = 'fake_provider'
        provider = self.engine._get_provider()
        self.assertEqual('fake_provider', provider)
        m_get_provider.assert_called_once_with(self.config['provider'],
                                               self.deployment)

    @mock.patch(MOD + 'open', create=True)
    @mock.patch(MOD + 'get_script_path', return_value='fake_sp')
    @mock.patch(MOD + 'lxc.LxcHost')
    @mock.patch(MOD + 'LxcEngine._deploy_first')
    @mock.patch(MOD + 'LxcEngine._get_provider')
    def test_deploy(self, m_get_provider, m_deploy_first, m_LxcHost,
                    m_gsp, m_open):
        m_open.return_value = 'fs'
        fake_containers = ((mock.Mock(), mock.Mock()),
                           (mock.Mock(), mock.Mock()))
        fake_hosts = m_LxcHost.side_effect = [mock.Mock(), mock.Mock()]
        fake_hosts[0].get_server_objects.return_value = fake_containers[0]
        fake_hosts[1].get_server_objects.return_value = fake_containers[1]
        fake_hosts[0]._port_cache = {1: 2, 3: 4}
        fake_hosts[1]._port_cache = {5: 6, 7: 8}
        fake_provider = m_get_provider.return_value = mock.Mock()
        fake_servers = [mock.Mock(), mock.Mock()]
        fake_servers[0].get_credentials.return_value = 'fc1'
        fake_servers[1].get_credentials.return_value = 'fc2'
        fake_provider.create_servers.return_value = fake_servers
        with mock.patch.object(self.engine, 'deployment') as m_deployment:
            endpoint = self.engine.deploy()

        self.assertIsInstance(endpoint["admin"], objects.Endpoint)
        LxcHost_calls = [
            mock.call(fake_servers[0], {'network': '10.128.128.0/28',
                                        'tunnel_to': ['1.1.1.1', '2.2.2.2']}),
            mock.call(fake_servers[1], {'network': '10.128.128.16/28',
                                        'tunnel_to': ['1.1.1.1', '2.2.2.2']})]
        self.assertEqual(LxcHost_calls, m_LxcHost.mock_calls)
        deploy_first_calls = [
            mock.call(fake_hosts[0], 'rally-10-128-128-0-000', 'ubuntu', None),
            mock.call(fake_hosts[1], 'rally-10-128-128-16-000', 'ubuntu',
                      None)]
        self.assertEqual(deploy_first_calls, m_deploy_first.mock_calls)

        host1_calls = [
            mock.call.create_clone('rally-10-128-128-0-001',
                                   'rally-10-128-128-0-000'),
            mock.call.start_containers(),
            mock.call.get_server_objects()]

        host2_calls = [
            mock.call.create_clone('rally-10-128-128-16-001',
                                   'rally-10-128-128-16-000'),
            mock.call.start_containers(),
            mock.call.get_server_objects()]

        self.assertEqual(host1_calls, fake_hosts[0].mock_calls)
        self.assertEqual(host2_calls, fake_hosts[1].mock_calls)

        self.assertEqual([mock.call('fake_sp', 'rb')] * 4, m_open.mock_calls)

        for host in fake_containers:
            for container in host:
                self.assertEqual([mock.call.ssh.run('/bin/sh -e', stdin='fs')],
                                 container.mock_calls)
        add_res_calls = [
            mock.call(provider_name='LxcEngine',
                      info={'host': 'fc1',
                            'config': {'network': '10.128.128.0/28',
                                       'tunnel_to': ['1.1.1.1', '2.2.2.2']},
                            'forwarded_ports': [(1, 2), (3, 4)],
                            'containers': fake_hosts[0].containers}),
            mock.call(provider_name='LxcEngine',
                      info={'host': 'fc2',
                            'config': {'network': '10.128.128.16/28',
                                       'tunnel_to': ['1.1.1.1', '2.2.2.2']},
                            'forwarded_ports': [(5, 6), (7, 8)],
                            'containers': fake_hosts[1].containers})]
        self.assertEqual(add_res_calls, m_deployment.add_resource.mock_calls)

    @mock.patch(MOD + 'LxcEngine._get_provider')
    @mock.patch(MOD + 'lxc.LxcHost')
    @mock.patch(MOD + 'provider.Server.from_credentials')
    def test_cleanup(self, m_from_c, m_lxc_host, m_get_provider):
        m_get_provider.return_value = fake_provider = mock.Mock()
        m_lxc_host.side_effect = fake_hosts = [mock.Mock(), mock.Mock()]
        m_from_c.side_effect = ['s1', 's2']
        fake_resources = []
        for i in range(2):
            res = mock.Mock()
            res.info = {'host': 'host%d' % i,
                        'config': 'fake_config%d' % i,
                        'forwarded_ports': [(1, 2), (3, 4)],
                        'containers': 'fake_containers'}
            fake_resources.append(res)
        with mock.patch.object(self.engine, 'deployment') as m_deployment:
            m_deployment.get_resources.return_value = fake_resources
            self.engine.cleanup()

        for host in fake_hosts:
            self.assertEqual('fake_containers', host.containers)
            self.assertEqual([mock.call.destroy_containers(),
                              mock.call.destroy_ports([(1, 2), (3, 4)]),
                              mock.call.delete_tunnels()], host.mock_calls)

        for res in fake_resources:
            m_deployment.assert_has_calls(mock.call.delete_resource(res.id))

        fake_provider.destroy_servers.assert_called_once_with()
