# 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.common import objects
from rally.deployment import engine
from tests.unit import test

MOD = "rally.deployment.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.Engine.get_engine("LxcEngine",
                                               self.deployment)

    @mock.patch(MOD + "objects")
    @mock.patch(MOD + "engine")
    def test__deploy_first(self, mock_engine, mock_objects):
        fake_credentials = {"user": "admin", "host": "host.net"}
        fake_deployment = mock.Mock()
        fake_engine = mock.Mock()
        mock_objects.Deployment = mock.Mock(return_value=fake_deployment)
        mock_engine.Engine.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()
        mock_engine.Engine.get_engine.assert_called_once_with(
            "FakeEngine", fake_deployment)
        engine_config = self.config["engine"].copy()
        engine_config["provider"] = {"credentials": [fake_credentials],
                                     "type": "DummyProvider"}
        mock_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, mock_provider_factory_get_provider):
        mock_provider_factory_get_provider.return_value = "fake_provider"
        provider = self.engine._get_provider()
        self.assertEqual("fake_provider", provider)
        mock_provider_factory_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, mock__get_provider, mock__deploy_first,
                    mock_lxc_host, mock_get_script_path, mock_open):
        mock_open.return_value = "fs"
        fake_containers = ((mock.Mock(), mock.Mock()),
                           (mock.Mock(), mock.Mock()))
        fake_hosts = mock_lxc_host.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 = mock__get_provider.return_value
        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

        add_res_calls = [
            {"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}},
            {"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}}]

        def add_resource(**actual_kwargs):
            expected_kwargs = add_res_calls.pop(0)

            self.assertEqual(expected_kwargs["provider_name"],
                             actual_kwargs["provider_name"])
            self.assertEqual(expected_kwargs["info"]["host"],
                             actual_kwargs["info"]["host"])
            self.assertEqual(expected_kwargs["info"]["config"],
                             actual_kwargs["info"]["config"])
            self.assertEqual(expected_kwargs["info"]["containers"],
                             actual_kwargs["info"]["containers"])
            self.assertSequenceEqual(
                expected_kwargs["info"]["forwarded_ports"],
                actual_kwargs["info"]["forwarded_ports"])

        fake_deployment = mock.MagicMock()
        fake_deployment.add_resource = add_resource

        with mock.patch.object(self.engine, "deployment", fake_deployment):
            endpoint = self.engine.deploy()

        self.assertIsInstance(endpoint["admin"], objects.Endpoint)
        lxc_host_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(lxc_host_calls, mock_lxc_host.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, mock__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,
                         mock_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)

    @mock.patch(MOD + "LxcEngine._get_provider")
    @mock.patch(MOD + "lxc.LxcHost")
    @mock.patch(MOD + "provider.Server.from_credentials")
    def test_cleanup(self, mock_server_from_credentials, mock_lxc_host,
                     mock__get_provider):
        mock__get_provider.return_value = fake_provider = mock.Mock()
        mock_lxc_host.side_effect = fake_hosts = [mock.Mock(), mock.Mock()]
        mock_server_from_credentials.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 mock_deployment:
            mock_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)

        delete_calls = [mock.call.delete_resource(r.id)
                        for r in fake_resources]
        self.assertEqual(delete_calls,
                         mock_deployment.delete_resource.call_args_list)

        fake_provider.destroy_servers.assert_called_once_with()
