# Copyright 2014 OpenStack Foundation
# 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 re

import ddt
import six
from tempest import config
from tempest.lib import exceptions as lib_exc
import testtools
from testtools import testcase as tc

from manila_tempest_tests.common import constants
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils

CONF = config.CONF


@testtools.skipUnless(
    CONF.share.multitenancy_enabled,
    'Share servers can be tested only with multitenant drivers.')
@ddt.ddt
class ShareServersAdminTest(base.BaseSharesAdminTest):

    @classmethod
    def resource_setup(cls):
        super(ShareServersAdminTest, cls).resource_setup()
        # create share type
        cls.share_type = cls._create_share_type()
        cls.share_type_id = cls.share_type['id']
        # create share
        cls.share = cls.create_share(share_type_id=cls.share_type_id)
        cls.share_network = cls.shares_v2_client.get_share_network(
            cls.shares_v2_client.share_network_id)
        if not cls.share_network["name"]:
            sn_id = cls.share_network["id"]
            cls.share_network = cls.shares_v2_client.update_share_network(
                sn_id, name="sn_%s" % sn_id)
        cls.sn_name_and_id = [
            cls.share_network["name"],
            cls.share_network["id"],
        ]

        # Date should be like '2014-13-12T11:10:09.000000'
        cls.date_re = re.compile("^([0-9]{4}-[0-9]{2}-[0-9]{2}[A-Z]{1}"
                                 "[0-9]{2}:[0-9]{2}:[0-9]{2}).*$")

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_without_filters(self):
        servers = self.shares_v2_client.list_share_servers()
        self.assertGreater(len(servers), 0)
        keys = [
            "id",
            "host",
            "status",
            "share_network_name",
            "updated_at",
            "project_id",
        ]
        for server in servers:
            # All expected keys are present
            for key in keys:
                self.assertIn(key, server.keys())
            # 'Updated at' is valid date if set
            if server["updated_at"]:
                self.assertTrue(self.date_re.match(server["updated_at"]))
            # Host is not empty
            self.assertGreater(len(server["host"]), 0)
            # Id is not empty
            self.assertGreater(len(server["id"]), 0)
            # Project id is not empty
            self.assertGreater(len(server["project_id"]), 0)

        # Do not verify statuses because we get all share servers from whole
        # cluster and here can be servers with any state.
        # Server we used is present.
        any(s["share_network_name"] in self.sn_name_and_id for s in servers)

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_with_host_filter(self):
        # Get list of share servers and remember 'host' name
        servers = self.shares_v2_client.list_share_servers()
        # Remember name of server that was used by this test suite
        # to be sure it will be still existing.
        for server in servers:
            if server["share_network_name"] in self.sn_name_and_id:
                if not server["host"]:
                    msg = ("Server '%s' has wrong value for host - "
                           "'%s'.") % (server["id"], server["host"])
                    raise lib_exc.InvalidContentType(message=msg)
                host = server["host"]
                break
        else:
            msg = ("Appropriate server was not found. Its share_network_data"
                   ": '%s'. List of servers: '%s'.") % (self.sn_name_and_id,
                                                        six.text_type(servers))
            raise lib_exc.NotFound(message=msg)
        search_opts = {"host": host}
        servers = self.shares_v2_client.list_share_servers(search_opts)
        self.assertGreater(len(servers), 0)
        for server in servers:
            self.assertEqual(server["host"], host)

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_with_status_filter(self):
        search_opts = {"status": "active"}
        servers = self.shares_v2_client.list_share_servers(search_opts)

        # At least 1 share server should exist always - the one created
        # for this class.
        self.assertGreater(len(servers), 0)
        for server in servers:
            self.assertEqual(server["status"], "active")

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_with_project_id_filter(self):
        search_opts = {"project_id": self.share_network["project_id"]}
        servers = self.shares_v2_client.list_share_servers(search_opts)
        # Should exist, at least, one share server, used by this test suite.
        self.assertGreater(len(servers), 0)
        for server in servers:
            self.assertEqual(server["project_id"],
                             self.share_network["project_id"])

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_with_share_network_name_filter(self):
        search_opts = {"share_network": self.share_network["name"]}
        servers = self.shares_v2_client.list_share_servers(search_opts)
        # Should exist, at least, one share server, used by this test suite.
        self.assertGreater(len(servers), 0)
        for server in servers:
            self.assertEqual(server["share_network_name"],
                             self.share_network["name"])

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_list_share_servers_with_share_network_id_filter(self):
        search_opts = {"share_network": self.share_network["id"]}
        servers = self.shares_v2_client.list_share_servers(search_opts)
        # Should exist, at least, one share server, used by this test suite.
        self.assertGreater(len(servers), 0)
        for server in servers:
            self.assertIn(server["share_network_name"],
                          self.sn_name_and_id)

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_show_share_server(self):
        share = self.shares_v2_client.get_share(self.share["id"])
        server = self.shares_v2_client.show_share_server(
            share["share_server_id"])
        keys = [
            "id",
            "host",
            "project_id",
            "status",
            "share_network_name",
            "created_at",
            "updated_at",
            "backend_details",
        ]
        if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.49"):
            keys.append("is_auto_deletable")
            keys.append("identifier")
        # all expected keys are present
        for key in keys:
            self.assertIn(key, server.keys())

        # 'created_at' is valid date
        self.assertTrue(self.date_re.match(server["created_at"]))

        # 'updated_at' is valid date if set
        if server["updated_at"]:
            self.assertTrue(self.date_re.match(server["updated_at"]))

        # veriy that values for following keys are not empty
        for k in ('host', 'id', 'project_id', 'status', 'share_network_name'):
            self.assertGreater(len(server[k]), 0)

        # 'backend_details' should be a dict
        self.assertIsInstance(server["backend_details"], dict)

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    def test_show_share_server_details(self):
        share = self.shares_v2_client.get_share(self.share['id'])
        details = self.shares_v2_client.show_share_server_details(
            share['share_server_id'])

        # If details are present they and their values should be only strings
        for k, v in details.items():
            self.assertIsInstance(k, six.string_types)
            self.assertIsInstance(v, six.string_types)

    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    @ddt.data(True, False)
    @testtools.skipIf(CONF.share.share_network_id != "",
                      "This test is not suitable for pre-existing "
                      "share_network.")
    def test_delete_share_server(self, delete_share_network):
        # Get network and subnet from existing share_network and reuse it
        # to be able to delete share_server after test ends.
        # TODO(vponomaryov): attach security-services too. If any exist from
        #                    donor share-network.
        new_sn = self.create_share_network(
            neutron_net_id=self.share_network['neutron_net_id'],
            neutron_subnet_id=self.share_network['neutron_subnet_id'])

        # Create server with share
        self.create_share(share_type_id=self.share_type_id,
                          share_network_id=new_sn['id'])

        # List share servers, filtered by share_network_id
        servers = self.shares_v2_client.list_share_servers(
            {"share_network": new_sn["id"]})

        # There can be more than one share server for share network when retry
        # was used and share was created successfully not from first time.
        # So, iterate all share-servers, release all created resources. It will
        # allow share network to be deleted in cleanup.
        for serv in servers:
            # Verify that filtering worked as expected.
            self.assertEqual(new_sn["id"], serv["share_network_id"])

            # List shares by share server id
            shares = self.shares_v2_client.list_shares_with_detail(
                {"share_server_id": serv["id"]})
            for s in shares:
                self.assertEqual(new_sn["id"], s["share_network_id"])

            # Delete shares, so we will have share server without shares
            for s in shares:
                self.shares_v2_client.delete_share(s["id"])

            # Wait for shares deletion
            for s in shares:
                self.shares_v2_client.wait_for_resource_deletion(
                    share_id=s["id"])

            # List shares by share server id, we expect empty list
            empty = self.shares_v2_client.list_shares_with_detail(
                {"share_server_id": serv["id"]})
            self.assertEqual(0, len(empty))

            if delete_share_network:
                # Delete share network, it should trigger share server deletion
                self.shares_v2_client.delete_share_network(new_sn["id"])
            else:
                # Delete share server
                self.shares_v2_client.delete_share_server(serv["id"])

            # Wait for share server deletion
            self.shares_v2_client.wait_for_resource_deletion(
                server_id=serv["id"])

            if delete_share_network:
                self.shares_v2_client.wait_for_resource_deletion(
                    sn_id=new_sn["id"])

    @testtools.skipIf(CONF.share.share_network_id != "",
                      "This test is not suitable for pre-existing "
                      "share_network.")
    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
    @utils.skip_if_microversion_not_supported("2.49")
    def test_share_server_reset_state(self):
        # Get network and subnet from existing share_network and reuse it
        # to be able to delete share_server after test ends.
        new_sn = self.create_share_network(
            neutron_net_id=self.share_network['neutron_net_id'],
            neutron_subnet_id=self.share_network['neutron_subnet_id'])
        share = self.create_share(
            share_type_id=self.share_type_id,
            share_network_id=new_sn['id']
        )
        share = self.shares_v2_client.get_share(share['id'])

        # obtain share server
        share_server = self.shares_v2_client.show_share_server(
            share['share_server_id']
        )

        for state in (constants.SERVER_STATE_ACTIVE,
                      constants.SERVER_STATE_CREATING,
                      constants.SERVER_STATE_DELETING,
                      constants.SERVER_STATE_ERROR,
                      constants.SERVER_STATE_MANAGE_ERROR,
                      constants.SERVER_STATE_MANAGE_STARTING,
                      constants.SERVER_STATE_UNMANAGE_ERROR,
                      constants.SERVER_STATE_UNMANAGE_STARTING):

            # leave it in a new state
            self.shares_v2_client.share_server_reset_state(
                share_server['id'],
                status=state,
            )
            self.shares_v2_client.wait_for_share_server_status(
                share_server['id'],
                status=state
            )

        # bring the share server back in the active state
        self.shares_v2_client.share_server_reset_state(
            share_server['id'],
            status=constants.SERVER_STATE_ACTIVE,
        )
        self.shares_v2_client.wait_for_share_server_status(
            share_server['id'],
            status=constants.SERVER_STATE_ACTIVE
        )

        # delete share
        self.shares_v2_client.delete_share(share["id"])
        self.shares_v2_client.wait_for_resource_deletion(
            share_id=share["id"]
        )

        # delete share network. This will trigger share server deletion
        self.shares_v2_client.delete_share_network(new_sn["id"])
        self.shares_v2_client.wait_for_resource_deletion(
            sn_id=new_sn['id'])
