# Copyright 2012 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 random
import testtools

from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common import object_storage
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators

CONF = config.CONF


class AccountTest(base.BaseObjectTest):
    """Test account metadata and containers"""

    credentials = [['operator', CONF.object_storage.operator_role],
                   ['operator_alt', CONF.object_storage.operator_role]]
    containers = []

    @classmethod
    def setup_credentials(cls):
        super(AccountTest, cls).setup_credentials()
        cls.os_operator = cls.os_roles_operator_alt

    @classmethod
    def resource_setup(cls):
        super(AccountTest, cls).resource_setup()
        for i in range(ord('a'), ord('f') + 1):
            name = data_utils.rand_name(
                prefix=CONF.resource_name_prefix,
                name='%s-' % bytes((i,)))
            cls.container_client.update_container(name)
            cls.addClassResourceCleanup(object_storage.delete_containers,
                                        [name],
                                        cls.container_client,
                                        cls.object_client)
            cls.containers.append(name)
        cls.containers_count = len(cls.containers)

    @decorators.attr(type='smoke')
    @decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
    def test_list_containers(self):
        """Test listing containers"""
        resp, container_list = self.account_client.list_account_containers()
        self.assertHeaders(resp, 'Account', 'GET')

        self.assertIsNotNone(container_list)

        for container_name in self.containers:
            self.assertIn(str(container_name).encode('utf-8'),
                          container_list)

    @decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
    def test_list_no_containers(self):
        """Test listing containers for an account without container"""

        # To test listing no containers, create new user other than
        # the base user of this instance.
        resp, container_list = \
            self.os_operator.account_client.list_account_containers()

        # When sending a request to an account which has not received a PUT
        # container request, the response does not contain 'accept-ranges'
        # header. This is a special case, therefore the existence of response
        # headers is checked without custom matcher.
        #
        # As the expected response is 204 No Content, Content-Length presence
        # is not checked here intentionally. According to RFC 7230 a server
        # MUST NOT send the header in such responses. Thus, clients should not
        # depend on this header. However, the standard does not require them
        # to validate the server's behavior. We leverage that to not refuse
        # any implementation violating it like Swift [1] or some versions of
        # Ceph RadosGW [2].
        # [1] https://bugs.launchpad.net/swift/+bug/1537811
        # [2] http://tracker.ceph.com/issues/13582
        self.assertIn('x-timestamp', resp)
        self.assertIn('x-account-bytes-used', resp)
        self.assertIn('x-account-container-count', resp)
        self.assertIn('x-account-object-count', resp)
        self.assertIn('content-type', resp)
        self.assertIn('x-trans-id', resp)
        self.assertIn('date', resp)

        # Check only the format of common headers with custom matcher
        self.assertThat(resp, custom_matchers.AreAllWellFormatted())

        self.assertEmpty(container_list)

    @decorators.idempotent_id('1c7efa35-e8a2-4b0b-b5ff-862c7fd83704')
    def test_list_containers_with_format_json(self):
        """Test listing containers setting format parameter to 'json'"""
        params = {'format': 'json'}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertIsNotNone(container_list)
        self.assertTrue([c['name'] for c in container_list])
        self.assertTrue([c['count'] for c in container_list])
        self.assertTrue([c['bytes'] for c in container_list])

    @decorators.idempotent_id('4477b609-1ca6-4d4b-b25d-ad3f01086089')
    def test_list_containers_with_format_xml(self):
        """Test listing containers setting format parameter to 'xml'"""
        params = {'format': 'xml'}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertIsNotNone(container_list)
        self.assertEqual(container_list.tag, 'account')
        self.assertIn('name', container_list.keys())
        self.assertEqual(container_list.find(".//container").tag, 'container')
        self.assertEqual(container_list.find(".//name").tag, 'name')
        self.assertEqual(container_list.find(".//count").tag, 'count')
        self.assertEqual(container_list.find(".//bytes").tag, 'bytes')

    @decorators.idempotent_id('6eb04a6a-4860-4e31-ba91-ea3347d76b58')
    @testtools.skipIf(
        not CONF.object_storage_feature_enabled.discoverability,
        'Discoverability function is disabled')
    def test_list_extensions(self):
        """Test listing capabilities"""
        resp = self.capabilities_client.list_capabilities()

        self.assertThat(resp, custom_matchers.AreAllWellFormatted())

    @decorators.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2')
    def test_list_containers_with_limit(self):
        """Test listing containers with limit parameter

        Listing containers limited to one of them, half of them, and then all
        of them.
        """
        for limit in (1, self.containers_count // 2,
                      self.containers_count):
            params = {'limit': limit}
            resp, container_list = \
                self.account_client.list_account_containers(params=params)
            self.assertHeaders(resp, 'Account', 'GET')

            self.assertEqual(len(container_list), limit)

    @decorators.idempotent_id('638f876d-6a43-482a-bbb3-0840bca101c6')
    def test_list_containers_with_marker(self):
        """Test listing containers with marker parameter

        First expect to get 0 container as we specified the last container
        as marker, second expect to get the bottom half of the containers.
        """
        params = {'marker': self.containers[-1]}
        resp, container_list = \
            self.account_client.list_account_containers(params=params)
        self.assertHeaders(resp, 'Account', 'GET')

        self.assertEmpty(container_list)

        params = {'marker': self.containers[self.containers_count // 2]}
        resp, container_list = \
            self.account_client.list_account_containers(params=params)
        self.assertHeaders(resp, 'Account', 'GET')

        self.assertEqual(len(container_list),
                         self.containers_count // 2 - 1)

    @decorators.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786')
    def test_list_containers_with_end_marker(self):
        """Test listing containers with end_marker parameter

        First expect to get 0 container as we specified first container as
        end_marker, second expect to get the top half of the containers
        """
        params = {'end_marker': self.containers[0]}
        resp, container_list = \
            self.account_client.list_account_containers(params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEmpty(container_list)

        params = {'end_marker': self.containers[self.containers_count // 2]}
        resp, container_list = \
            self.account_client.list_account_containers(params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEqual(len(container_list), self.containers_count // 2)

    @decorators.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e')
    def test_list_containers_with_marker_and_end_marker(self):
        """Test listing containers with marker and end_marker parameter

        If we use the first container as marker, and the last container as
        end_marker, then we should get all containers excluding the first one
        and the last one.
        """
        params = {'marker': self.containers[0],
                  'end_marker': self.containers[self.containers_count - 1]}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEqual(len(container_list), self.containers_count - 2)

    @decorators.idempotent_id('f7064ae8-dbcc-48da-b594-82feef6ea5af')
    def test_list_containers_with_limit_and_marker(self):
        """Test listing containers combining marker and limit parameter

        Result are always limited by the limit whatever the marker.
        """
        for marker in random.choice(self.containers):
            limit = random.randint(0, self.containers_count - 1)
            params = {'marker': marker,
                      'limit': limit}
            resp, container_list = \
                self.account_client.list_account_containers(params=params)
            self.assertHeaders(resp, 'Account', 'GET')

            self.assertLessEqual(len(container_list), limit,
                                 str(container_list))

    @decorators.idempotent_id('888a3f0e-7214-4806-8e50-5e0c9a69bb5e')
    def test_list_containers_with_limit_and_end_marker(self):
        """Test listing containers combining end_marker and limit parameter

        Result are always limited by the limit whatever the end_marker.
        """
        # list containers combining limit and end_marker param
        limit = random.randint(1, self.containers_count)
        params = {'limit': limit,
                  'end_marker': self.containers[self.containers_count // 2]}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEqual(len(container_list),
                         min(limit, self.containers_count // 2))

    @decorators.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd')
    def test_list_containers_with_limit_and_marker_and_end_marker(self):
        """Test listing containers combining marker and end_marker and limit

        Result are always limited by the limit whatever the marker and the
        end_marker.
        """
        limit = random.randint(1, self.containers_count)
        params = {'limit': limit,
                  'marker': self.containers[0],
                  'end_marker': self.containers[self.containers_count - 1]}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEqual(len(container_list),
                         min(limit, self.containers_count - 2))

    @decorators.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa')
    def test_list_containers_with_prefix(self):
        """Test listing containers that have a name starting with a prefix"""
        prefix = 'tempest-a'
        params = {'prefix': prefix}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        for container in container_list:
            self.assertEqual(True, container.decode(
                'utf-8').startswith(prefix))

    @decorators.idempotent_id('b1811cff-d1ed-4c15-a52e-efd8de41cf34')
    def test_list_containers_reverse_order(self):
        """Test listing containers in reverse order"""
        _, orig_container_list = self.account_client.list_account_containers()

        params = {'reverse': True}
        resp, container_list = self.account_client.list_account_containers(
            params=params)
        self.assertHeaders(resp, 'Account', 'GET')
        self.assertEqual(sorted(orig_container_list, reverse=True),
                         container_list)

    @decorators.attr(type='smoke')
    @decorators.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80')
    def test_list_account_metadata(self):
        """Test listing account metadata"""
        # set metadata to account
        metadata = {'test-account-meta1': 'Meta1',
                    'test-account-meta2': 'Meta2'}
        resp, _ = self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata)

        resp, _ = self.account_client.list_account_metadata()
        self.assertHeaders(resp, 'Account', 'HEAD')
        self.assertIn('x-account-meta-test-account-meta1', resp)
        self.assertIn('x-account-meta-test-account-meta2', resp)
        self.account_client.create_update_or_delete_account_metadata(
            delete_metadata=metadata)

    @decorators.idempotent_id('b904c2e3-24c2-4dba-ad7d-04e90a761be5')
    def test_list_no_account_metadata(self):
        """Test listing account metadata for account without metadata"""
        resp, _ = self.account_client.list_account_metadata()
        self.assertHeaders(resp, 'Account', 'HEAD')
        # Filter out X-Account-Meta-Quota-Containers header which is
        # returned by Ceph RadosGW (Squid release and later) as a
        # system-generated header. This is a RadosGW extension not present
        # in Swift.
        # See: https://github.com/ceph/ceph/commit/55f196cb47c9a452fc718ba9a58be7f68bb7a54a  # noqa: E501
        user_metadata_headers = [k for k in resp.keys()
                                 if k.startswith('x-account-meta-') and
                                 k.lower() !=
                                 'x-account-meta-quota-containers']
        self.assertEmpty(user_metadata_headers,
                         'Found user-defined account metadata headers: %s' %
                         user_metadata_headers)

    @decorators.idempotent_id('e2a08b5f-3115-4768-a3ee-d4287acd6c08')
    def test_update_account_metadata_with_create_metadata(self):
        """Test adding metadata to account"""
        metadata = {'test-account-meta1': 'Meta1'}
        resp, _ = self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata)
        self.assertHeaders(resp, 'Account', 'POST')

        resp, _ = self.account_client.list_account_metadata()
        self.assertIn('x-account-meta-test-account-meta1', resp)
        self.assertEqual(resp['x-account-meta-test-account-meta1'],
                         metadata['test-account-meta1'])

        self.account_client.create_update_or_delete_account_metadata(
            delete_metadata=metadata)

    @decorators.idempotent_id('9f60348d-c46f-4465-ae06-d51dbd470953')
    def test_update_account_metadata_with_delete_metadata(self):
        """Test deleting metadata from account"""
        metadata = {'test-account-meta1': 'Meta1'}
        self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata)
        resp, _ = self.account_client.create_update_or_delete_account_metadata(
            delete_metadata=metadata)
        self.assertHeaders(resp, 'Account', 'POST')

        resp, _ = self.account_client.list_account_metadata()
        self.assertNotIn('x-account-meta-test-account-meta1', resp)

    @decorators.idempotent_id('64fd53f3-adbd-4639-af54-436e4982dbfb')
    def test_update_account_metadata_with_create_metadata_key(self):
        """Test adding metadata to account with empty value

        Adding metadata with empty value to account, the metadata is not
        registered.
        """
        metadata = {'test-account-meta1': ''}
        resp, _ = self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata)
        self.assertHeaders(resp, 'Account', 'POST')

        resp, _ = self.account_client.list_account_metadata()
        self.assertNotIn('x-account-meta-test-account-meta1', resp)

    @decorators.idempotent_id('d4d884d3-4696-4b85-bc98-4f57c4dd2bf1')
    def test_update_account_metadata_with_delete_metadata_key(self):
        """Test deleting metadata from account with empty value

        Although the value of metadata is not set, the feature of deleting
        metadata is valid, so the metadata is removed from account.
        """
        metadata_1 = {'test-account-meta1': 'Meta1'}
        self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata_1)
        metadata_2 = {'test-account-meta1': ''}
        resp, _ = self.account_client.create_update_or_delete_account_metadata(
            delete_metadata=metadata_2)
        self.assertHeaders(resp, 'Account', 'POST')

        resp, _ = self.account_client.list_account_metadata()
        self.assertNotIn('x-account-meta-test-account-meta1', resp)

    @decorators.idempotent_id('8e5fc073-59b9-42ee-984a-29ed11b2c749')
    def test_update_account_metadata_with_create_and_delete_metadata(self):
        """Test adding and deleting metadata simultaneously

        Send a request adding and deleting metadata requests simultaneously,
        both adding and deleting of metadata will succeed.
        """
        metadata_1 = {'test-account-meta1': 'Meta1'}
        self.account_client.create_update_or_delete_account_metadata(
            create_update_metadata=metadata_1)
        metadata_2 = {'test-account-meta2': 'Meta2'}
        resp, _ = (
            self.account_client.create_update_or_delete_account_metadata(
                create_update_metadata=metadata_2,
                delete_metadata=metadata_1))
        self.assertHeaders(resp, 'Account', 'POST')

        resp, _ = self.account_client.list_account_metadata()
        self.assertNotIn('x-account-meta-test-account-meta1', resp)
        self.assertIn('x-account-meta-test-account-meta2', resp)
        self.assertEqual(resp['x-account-meta-test-account-meta2'],
                         metadata_2['test-account-meta2'])

        self.account_client.create_update_or_delete_account_metadata(
            delete_metadata=metadata_2)
