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

import os

from kubernetes import client as kubernetes_client
from kubernetes.client.rest import ApiException
from rally import exceptions
from rally_openstack.task.scenarios.magnum import utils
from tests.unit import test

MAGNUM_UTILS = "rally_openstack.task.scenarios.magnum.utils"

CONF = utils.CONF


class MagnumScenarioTestCase(test.ScenarioTestCase):
    def setUp(self):
        super(MagnumScenarioTestCase, self).setUp()
        self.cluster_template = mock.Mock()
        self.cluster = mock.Mock()
        self.pod = mock.Mock()
        self.scenario = utils.MagnumScenario(self.context)

    def test_list_cluster_templates(self):
        fake_list = [self.cluster_template]

        self.clients("magnum").cluster_templates.list.return_value = fake_list
        return_ct_list = self.scenario._list_cluster_templates()
        self.assertEqual(fake_list, return_ct_list)

        self.clients("magnum").cluster_templates.list.assert_called_once_with()
        self._test_atomic_action_timer(self.scenario.atomic_actions(),
                                       "magnum.list_cluster_templates")

    def test_create_cluster_template(self):
        self.scenario.generate_random_name = mock.Mock(
            return_value="generated_name")
        fake_ct = self.cluster_template
        self.clients("magnum").cluster_templates.create.return_value = fake_ct

        return_cluster_template = self.scenario._create_cluster_template(
            image="test_image",
            keypair="test_key",
            external_network="public",
            dns_nameserver="8.8.8.8",
            flavor="m1.large",
            docker_volume_size=50,
            network_driver="docker",
            coe="swarm")

        self.assertEqual(fake_ct, return_cluster_template)
        _, kwargs = self.clients("magnum").cluster_templates.create.call_args
        self.assertEqual("generated_name", kwargs["name"])

        self._test_atomic_action_timer(self.scenario.atomic_actions(),
                                       "magnum.create_cluster_template")

    def test_get_cluster_template(self):
        client = self.clients("magnum")
        client.cluster_templates.get.return_value = self.cluster_template
        return_cluster_template = self.scenario._get_cluster_template("uuid")
        client.cluster_templates.get.assert_called_once_with("uuid")
        self.assertEqual(self.cluster_template, return_cluster_template)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.get_cluster_template")

    def test_list_clusters(self):
        return_clusters_list = self.scenario._list_clusters(limit="foo1")
        client = self.clients("magnum")
        client.clusters.list.assert_called_once_with(limit="foo1")
        self.assertEqual(client.clusters.list.return_value,
                         return_clusters_list)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.list_clusters")

    def test_create_cluster(self):
        self.scenario.generate_random_name = mock.Mock(
            return_value="generated_name")
        self.clients("magnum").clusters.create.return_value = self.cluster
        return_cluster = self.scenario._create_cluster(
            cluster_template="generated_uuid", node_count=2)
        self.mock_wait_for_status.mock.assert_called_once_with(
            self.cluster,
            ready_statuses=["CREATE_COMPLETE"],
            failure_statuses=["CREATE_FAILED", "ERROR"],
            update_resource=self.mock_get_from_manager.mock.return_value,
            check_interval=CONF.openstack.
            magnum_cluster_create_poll_interval,
            timeout=CONF.openstack.magnum_cluster_create_timeout,
            id_attr="uuid")
        _, kwargs = self.clients("magnum").clusters.create.call_args
        self.assertEqual("generated_name", kwargs["name"])
        self.assertEqual("generated_uuid", kwargs["cluster_template_id"])
        self.mock_get_from_manager.mock.assert_called_once_with()
        self.assertEqual(
            self.mock_wait_for_status.mock.return_value, return_cluster)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.create_cluster")

    def test_get_cluster(self):
        self.clients("magnum").clusters.get.return_value = self.cluster
        return_cluster = self.scenario._get_cluster("uuid")
        self.clients("magnum").clusters.get.assert_called_once_with("uuid")
        self.assertEqual(self.cluster, return_cluster)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.get_cluster")

    def test_get_ca_certificate(self):
        self.scenario._get_ca_certificate(self.cluster.uuid)
        self.clients("magnum").certificates.get.assert_called_once_with(
            self.cluster.uuid)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.get_ca_certificate")

    def test_create_ca_certificate(self):
        csr_req = {"cluster_uuid": "uuid", "csr": "csr file"}
        self.scenario._create_ca_certificate(csr_req)
        self.clients("magnum").certificates.create.assert_called_once_with(
            **csr_req)
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.create_ca_certificate")

    @mock.patch("kubernetes.client.api_client.ApiClient")
    @mock.patch("kubernetes.client.api.core_v1_api.CoreV1Api")
    def test_get_k8s_api_client_using_tls(self, mock_core_v1_api,
                                          mock_api_client):

        if hasattr(kubernetes_client, "ConfigurationObject"):
            # it is k8s-client < 4.0.0
            m = mock.patch("kubernetes.client.ConfigurationObject")
        else:
            m = mock.patch("kubernetes.client.Configuration")

        mock_configuration_object = m.start()
        self.addCleanup(m.stop)

        self.context.update({
            "ca_certs_directory": "/home/stack",
            "tenant": {
                "id": "rally_tenant_id",
                "cluster": "rally_cluster_uuid"
            }
        })
        self.scenario = utils.MagnumScenario(self.context)
        cluster_uuid = self.context["tenant"]["cluster"]
        client = self.clients("magnum")
        client.clusters.get.return_value = self.cluster
        cluster = self.scenario._get_cluster(cluster_uuid)
        self.cluster_template.tls_disabled = False
        client.cluster_templates.get.return_value = self.cluster_template
        dir = self.context["ca_certs_directory"]
        key_file = os.path.join(dir, cluster_uuid.__add__(".key"))
        cert_file = os.path.join(dir, cluster_uuid.__add__(".crt"))
        ca_certs = os.path.join(dir, cluster_uuid.__add__("_ca.crt"))
        config = mock_configuration_object.return_value
        config.host = cluster.api_address
        config.ssl_ca_cert = ca_certs
        config.cert_file = cert_file
        config.key_file = key_file
        _api_client = mock_api_client.return_value
        self.scenario._get_k8s_api_client()
        mock_configuration_object.assert_called_once_with()
        if hasattr(kubernetes_client, "ConfigurationObject"):
            # k8s-python < 4.0.0
            mock_api_client.assert_called_once_with(config=config)
        else:
            mock_api_client.assert_called_once_with(config)

        mock_core_v1_api.assert_called_once_with(_api_client)

    @mock.patch("kubernetes.client.api_client.ApiClient")
    @mock.patch("kubernetes.client.api.core_v1_api.CoreV1Api")
    def test_get_k8s_api_client(self, mock_core_v1_api, mock_api_client):

        if hasattr(kubernetes_client, "ConfigurationObject"):
            # it is k8s-client < 4.0.0
            m = mock.patch("kubernetes.client.ConfigurationObject")
        else:
            m = mock.patch("kubernetes.client.Configuration")

        mock_configuration_object = m.start()
        self.addCleanup(m.stop)

        self.context.update({
            "tenant": {
                "id": "rally_tenant_id",
                "cluster": "rally_cluster_uuid"
            }
        })
        self.scenario = utils.MagnumScenario(self.context)
        cluster_uuid = self.context["tenant"]["cluster"]
        client = self.clients("magnum")
        client.clusters.get.return_value = self.cluster
        cluster = self.scenario._get_cluster(cluster_uuid)
        self.cluster_template.tls_disabled = True
        client.cluster_templates.get.return_value = self.cluster_template
        config = mock_configuration_object.return_value
        config.host = cluster.api_address
        config.ssl_ca_cert = None
        config.cert_file = None
        config.key_file = None
        _api_client = mock_api_client.return_value
        self.scenario._get_k8s_api_client()
        mock_configuration_object.assert_called_once_with()
        if hasattr(kubernetes_client, "ConfigurationObject"):
            # k8s-python < 4.0.0
            mock_api_client.assert_called_once_with(config=config)
        else:
            mock_api_client.assert_called_once_with(config)
        mock_core_v1_api.assert_called_once_with(_api_client)

    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_list_v1pods(self, mock__get_k8s_api_client):
        k8s_api = mock__get_k8s_api_client.return_value
        self.scenario._list_v1pods()
        k8s_api.list_node.assert_called_once_with(
            namespace="default")
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.k8s_list_v1pods")

    @mock.patch("random.choice")
    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_create_v1pod(self, mock__get_k8s_api_client,
                          mock_random_choice):
        k8s_api = mock__get_k8s_api_client.return_value
        manifest = (
            {"apiVersion": "v1", "kind": "Pod",
             "metadata": {"name": "nginx"}})
        podname = manifest["metadata"]["name"] + "-"
        for i in range(5):
            podname = podname + mock_random_choice.return_value
        k8s_api.create_namespaced_pod = mock.MagicMock(
            side_effect=[ApiException(status=403), self.pod])
        not_ready_pod = kubernetes_client.models.V1Pod()
        not_ready_status = kubernetes_client.models.V1PodStatus()
        not_ready_status.phase = "not_ready"
        not_ready_pod.status = not_ready_status
        almost_ready_pod = kubernetes_client.models.V1Pod()
        almost_ready_status = kubernetes_client.models.V1PodStatus()
        almost_ready_status.phase = "almost_ready"
        almost_ready_pod.status = almost_ready_status
        ready_pod = kubernetes_client.models.V1Pod()
        ready_condition = kubernetes_client.models.V1PodCondition(
            status="True", type="Ready")
        ready_status = kubernetes_client.models.V1PodStatus()
        ready_status.phase = "Running"
        ready_status.conditions = [ready_condition]
        ready_pod_metadata = kubernetes_client.models.V1ObjectMeta()
        ready_pod_metadata.uid = "123456789"
        ready_pod_spec = kubernetes_client.models.V1PodSpec(
            node_name="host_abc",
            containers=[]
        )
        ready_pod.status = ready_status
        ready_pod.metadata = ready_pod_metadata
        ready_pod.spec = ready_pod_spec
        k8s_api.read_namespaced_pod = mock.MagicMock(
            side_effect=[not_ready_pod, almost_ready_pod, ready_pod])
        self.scenario._create_v1pod(manifest)
        k8s_api.create_namespaced_pod.assert_called_with(
            body=manifest, namespace="default")
        k8s_api.read_namespaced_pod.assert_called_with(
            name=podname, namespace="default")
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.k8s_create_v1pod")

    @mock.patch("time.time")
    @mock.patch("random.choice")
    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_create_v1pod_timeout(self, mock__get_k8s_api_client,
                                  mock_random_choice, mock_time):
        k8s_api = mock__get_k8s_api_client.return_value
        manifest = (
            {"apiVersion": "v1", "kind": "Pod",
             "metadata": {"name": "nginx"}})
        k8s_api.create_namespaced_pod.return_value = self.pod
        mock_time.side_effect = [1, 2, 3, 4, 5, 1800, 1801]
        not_ready_pod = kubernetes_client.models.V1Pod()
        not_ready_status = kubernetes_client.models.V1PodStatus()
        not_ready_status.phase = "not_ready"
        not_ready_pod_metadata = kubernetes_client.models.V1ObjectMeta()
        not_ready_pod_metadata.uid = "123456789"
        not_ready_pod.status = not_ready_status
        not_ready_pod.metadata = not_ready_pod_metadata
        k8s_api.read_namespaced_pod = mock.MagicMock(
            side_effect=[not_ready_pod
                         for i in range(4)])

        self.assertRaises(
            exceptions.TimeoutException,
            self.scenario._create_v1pod, manifest)

    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_list_v1rcs(self, mock__get_k8s_api_client):
        k8s_api = mock__get_k8s_api_client.return_value
        self.scenario._list_v1rcs()
        (k8s_api.list_namespaced_replication_controller
            .assert_called_once_with(namespace="default"))
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.k8s_list_v1rcs")

    @mock.patch("random.choice")
    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_create_v1rc(self, mock__get_k8s_api_client,
                         mock_random_choice):
        k8s_api = mock__get_k8s_api_client.return_value
        manifest = (
            {"apiVersion": "v1",
             "kind": "ReplicationController",
             "metadata": {"name": "nginx-controller"},
             "spec": {"replicas": 2,
                      "selector": {"name": "nginx"},
                      "template": {"metadata":
                                   {"labels":
                                    {"name": "nginx"}}}}})
        suffix = "-"
        for i in range(5):
            suffix = suffix + mock_random_choice.return_value
        rcname = manifest["metadata"]["name"] + suffix
        rc = kubernetes_client.models.V1ReplicationController()
        rc.spec = kubernetes_client.models.V1ReplicationControllerSpec()
        rc.spec.replicas = manifest["spec"]["replicas"]
        k8s_api.create_namespaced_replication_controller.return_value = rc
        not_ready_rc = kubernetes_client.models.V1ReplicationController()
        not_ready_rc_status = (
            kubernetes_client.models.V1ReplicationControllerStatus(replicas=0))
        not_ready_rc.status = not_ready_rc_status
        ready_rc = kubernetes_client.models.V1ReplicationController()
        ready_rc_status = (
            kubernetes_client.models.V1ReplicationControllerStatus(
                replicas=manifest["spec"]["replicas"])
        )
        ready_rc_metadata = kubernetes_client.models.V1ObjectMeta()
        ready_rc_metadata.uid = "123456789"
        ready_rc_metadata.name = rcname
        ready_rc.status = ready_rc_status
        ready_rc.metadata = ready_rc_metadata
        k8s_api.read_namespaced_replication_controller = mock.MagicMock(
            side_effect=[not_ready_rc, ready_rc])
        self.scenario._create_v1rc(manifest)
        (k8s_api.create_namespaced_replication_controller
            .assert_called_once_with(body=manifest, namespace="default"))
        (k8s_api.read_namespaced_replication_controller
            .assert_called_with(name=rcname, namespace="default"))
        self._test_atomic_action_timer(
            self.scenario.atomic_actions(), "magnum.k8s_create_v1rc")

    @mock.patch("time.time")
    @mock.patch("random.choice")
    @mock.patch(MAGNUM_UTILS + ".MagnumScenario._get_k8s_api_client")
    def test_create_v1rc_timeout(self, mock__get_k8s_api_client,
                                 mock_random_choice, mock_time):
        k8s_api = mock__get_k8s_api_client.return_value
        manifest = (
            {"apiVersion": "v1",
             "kind": "ReplicationController",
             "metadata": {"name": "nginx-controller"},
             "spec": {"replicas": 2,
                      "selector": {"app": "nginx"},
                      "template": {"metadata":
                                   {"labels":
                                    {"name": "nginx"}}}}})
        rc = kubernetes_client.models.V1ReplicationController()
        rc.spec = kubernetes_client.models.V1ReplicationControllerSpec()
        rc.spec.replicas = manifest["spec"]["replicas"]
        mock_time.side_effect = [1, 2, 3, 4, 5, 1800, 1801]
        k8s_api.create_namespaced_replication_controller.return_value = rc
        not_ready_rc = kubernetes_client.models.V1ReplicationController()
        not_ready_rc_status = (
            kubernetes_client.models.V1ReplicationControllerStatus(replicas=0))
        not_ready_rc_metadata = kubernetes_client.models.V1ObjectMeta()
        not_ready_rc_metadata.uid = "123456789"
        not_ready_rc.status = not_ready_rc_status
        not_ready_rc.metadata = not_ready_rc_metadata
        k8s_api.read_namespaced_replication_controller = mock.MagicMock(
            side_effect=[not_ready_rc
                         for i in range(4)])

        self.assertRaises(
            exceptions.TimeoutException,
            self.scenario._create_v1rc, manifest)
