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

from oslo_log import log as logging
from oslo_utils import uuidutils
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
import testtools

from magnum.tests.functional.api import base
from magnum.tests.functional.common import config
from magnum.tests.functional.common import datagen


class ClusterTest(base.BaseTempestTest):

    """Tests for cluster CRUD."""

    LOG = logging.getLogger(__name__)

    def __init__(self, *args, **kwargs):
        super(ClusterTest, self).__init__(*args, **kwargs)
        self.clusters = []
        self.creds = None
        self.keypair = None
        self.cluster_template = None
        self.cluster_template_client = None
        self.keypairs_client = None
        self.cluster_client = None
        self.cert_client = None

    def setUp(self):
        try:
            super(ClusterTest, self).setUp()
            (self.creds, self.keypair) = self.get_credentials_with_keypair(
                type_of_creds='default')
            (self.cluster_template_client,
                self.keypairs_client) = self.get_clients_with_existing_creds(
                    creds=self.creds,
                    type_of_creds='default',
                    request_type='cluster_template')
            (self.cluster_client, _) = self.get_clients_with_existing_creds(
                creds=self.creds,
                type_of_creds='default',
                request_type='cluster')
            (self.cert_client, _) = self.get_clients_with_existing_creds(
                creds=self.creds,
                type_of_creds='default',
                request_type='cert')
            model = datagen.valid_swarm_cluster_template()
            _, self.cluster_template = self._create_cluster_template(model)

            # NOTE (dimtruck) by default tempest sets timeout to 20 mins.
            # We need more time.
            test_timeout = 1800
            self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
        except Exception:
            self.tearDown()
            raise

    def tearDown(self):
        try:
            cluster_list = self.clusters[:]
            for cluster_id in cluster_list:
                self._delete_cluster(cluster_id)
                self.clusters.remove(cluster_id)
            if self.cluster_template:
                self._delete_cluster_template(self.cluster_template.uuid)
        finally:
            super(ClusterTest, self).tearDown()

    def _create_cluster_template(self, cm_model):
        self.LOG.debug('We will create a clustertemplate for %s' % cm_model)
        resp, model = self.cluster_template_client.post_cluster_template(
            cm_model)
        return resp, model

    def _delete_cluster_template(self, cm_id):
        self.LOG.debug('We will delete a clustertemplate for %s' % cm_id)
        resp, model = self.cluster_template_client.delete_cluster_template(
            cm_id)
        return resp, model

    def _create_cluster(self, cluster_model):
        self.LOG.debug('We will create cluster for %s' % cluster_model)
        resp, model = self.cluster_client.post_cluster(cluster_model)
        self.LOG.debug('Response: %s' % resp)
        self.assertEqual(202, resp.status)
        self.assertIsNotNone(model.uuid)
        self.assertTrue(uuidutils.is_uuid_like(model.uuid))
        self.clusters.append(model.uuid)
        self.cluster_uuid = model.uuid
        if config.Config.copy_logs:
            self.addOnException(self.copy_logs_handler(
                lambda: list(
                    [self._get_cluster_by_id(model.uuid)[1].master_addresses,
                     self._get_cluster_by_id(model.uuid)[1].node_addresses]),
                self.cluster_template.coe,
                self.keypair))
        self.cluster_client.wait_for_created_cluster(model.uuid,
                                                     delete_on_error=False)
        return resp, model

    def _delete_cluster(self, cluster_id):
        self.LOG.debug('We will delete a cluster for %s' % cluster_id)
        resp, model = self.cluster_client.delete_cluster(cluster_id)
        self.assertEqual(204, resp.status)
        self.cluster_client.wait_for_cluster_to_delete(cluster_id)
        self.assertRaises(exceptions.NotFound, self.cert_client.get_cert,
                          cluster_id)
        return resp, model

    def _get_cluster_by_id(self, cluster_id):
        resp, model = self.cluster_client.get_cluster(cluster_id)
        return resp, model

    # (dimtruck) Combining all these tests in one because
    # they time out on the gate (2 hours not enough)
    @testtools.testcase.attr('positive')
    def test_create_list_sign_delete_clusters(self):
        gen_model = datagen.valid_cluster_data(
            cluster_template_id=self.cluster_template.uuid, node_count=1)

        # test cluster create
        _, cluster_model = self._create_cluster(gen_model)
        self.assertNotIn('status', cluster_model)

        # test cluster list
        resp, cluster_list_model = self.cluster_client.list_clusters()
        self.assertEqual(200, resp.status)
        self.assertGreater(len(cluster_list_model.clusters), 0)
        self.assertIn(
            cluster_model.uuid, list([x['uuid']
                                      for x in cluster_list_model.clusters]))

        # test invalid cluster update
        patch_model = datagen.cluster_name_patch_data()
        self.assertRaises(
            exceptions.BadRequest,
            self.cluster_client.patch_cluster,
            cluster_model.uuid, patch_model)

        # test ca show
        resp, cert_model = self.cert_client.get_cert(
            cluster_model.uuid)
        self.LOG.debug("cert resp: %s" % resp)
        self.assertEqual(200, resp.status)
        self.assertEqual(cert_model.cluster_uuid, cluster_model.uuid)
        self.assertIsNotNone(cert_model.pem)
        self.assertIn('-----BEGIN CERTIFICATE-----', cert_model.pem)
        self.assertIn('-----END CERTIFICATE-----', cert_model.pem)

        # test ca sign

        csr_sample = """-----BEGIN CERTIFICATE REQUEST-----
MIIByjCCATMCAQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMR8w
HQYDVQQLExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MRcwFQYDVQQDEw53d3cuZ29v
Z2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApZtYJCHJ4VpVXHfV
IlstQTlO4qC03hjX+ZkPyvdYd1Q4+qbAeTwXmCUKYHThVRd5aXSqlPzyIBwieMZr
WFlRQddZ1IzXAlVRDWwAo60KecqeAXnnUK+5fXoTI/UgWshre8tJ+x/TMHaQKR/J
cIWPhqaQhsJuzZbvAdGA80BLxdMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIhl
4PvFq+e7ipARgI5ZM+GZx6mpCz44DTo0JkwfRDf+BtrsaC0q68eTf2XhYOsq4fkH
Q0uA0aVog3f5iJxCa3Hp5gxbJQ6zV6kJ0TEsuaaOhEko9sdpCoPOnRBm2i/XRD2D
6iNh8f8z0ShGsFqjDgFHyF3o+lUyj+UC6H1QW7bn
-----END CERTIFICATE REQUEST-----
"""

        cert_data_model = datagen.cert_data(cluster_model.uuid,
                                            csr_data=csr_sample)
        resp, cert_model = self.cert_client.post_cert(cert_data_model)
        self.LOG.debug("cert resp: %s" % resp)
        self.assertEqual(201, resp.status)
        self.assertEqual(cert_model.cluster_uuid, cluster_model.uuid)
        self.assertIsNotNone(cert_model.pem)
        self.assertIn('-----BEGIN CERTIFICATE-----', cert_model.pem)
        self.assertIn('-----END CERTIFICATE-----', cert_model.pem)

        # test ca sign invalid
        cert_data_model = datagen.cert_data(cluster_model.uuid,
                                            csr_data="invalid_csr")
        self.assertRaises(
            exceptions.BadRequest,
            self.cert_client.post_cert,
            cert_data_model)

        # test cluster delete
        self._delete_cluster(cluster_model.uuid)
        self.clusters.remove(cluster_model.uuid)

    @testtools.testcase.attr('negative')
    def test_create_cluster_for_nonexisting_cluster_template(self):
        cm_id = 'this-does-not-exist'
        gen_model = datagen.valid_cluster_data(cluster_template_id=cm_id)
        self.assertRaises(
            exceptions.BadRequest,
            self.cluster_client.post_cluster, gen_model)

    @testtools.testcase.attr('negative')
    def test_create_cluster_with_node_count_0(self):
        gen_model = datagen.valid_cluster_data(
            cluster_template_id=self.cluster_template.uuid, node_count=0)
        self.assertRaises(
            exceptions.BadRequest,
            self.cluster_client.post_cluster, gen_model)

    @testtools.testcase.attr('negative')
    def test_create_cluster_with_zero_masters(self):
        uuid = self.cluster_template.uuid
        gen_model = datagen.valid_cluster_data(cluster_template_id=uuid,
                                               master_count=0)
        self.assertRaises(
            exceptions.BadRequest,
            self.cluster_client.post_cluster, gen_model)

    @testtools.testcase.attr('negative')
    def test_create_cluster_with_nonexisting_flavor(self):
        gen_model = \
            datagen.cluster_template_data_with_valid_keypair_image_flavor()
        resp, cluster_template = self._create_cluster_template(gen_model)
        self.assertEqual(201, resp.status)
        self.assertIsNotNone(cluster_template.uuid)

        uuid = cluster_template.uuid
        gen_model = datagen.valid_cluster_data(cluster_template_id=uuid)
        gen_model.flavor_id = 'aaa'
        self.assertRaises(exceptions.BadRequest,
                          self.cluster_client.post_cluster, gen_model)

        resp, _ = self._delete_cluster_template(cluster_template.uuid)
        self.assertEqual(204, resp.status)

    @testtools.testcase.attr('negative')
    def test_update_cluster_for_nonexisting_cluster(self):
        patch_model = datagen.cluster_name_patch_data()

        self.assertRaises(
            exceptions.NotFound,
            self.cluster_client.patch_cluster, 'fooo', patch_model)

    @testtools.testcase.attr('negative')
    def test_delete_cluster_for_nonexisting_cluster(self):
        self.assertRaises(
            exceptions.NotFound,
            self.cluster_client.delete_cluster, data_utils.rand_uuid())
