# Copyright 2015 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.

from six.moves import range
import time

import botocore.exceptions
from oslo_log import log
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators

from ec2api_tempest_plugin import base
from ec2api_tempest_plugin import config
from ec2api_tempest_plugin.scenario import base as scenario_base

CONF = config.CONF
LOG = log.getLogger(__name__)


class TagsPagingTest(scenario_base.BaseScenarioTest):

    # NOTE(andrey-mp): limit for tags for one resource in amazon
    TAGS_COUNT = 10

    @classmethod
    @base.safe_setup
    def setUpClass(cls):
        super(TagsPagingTest, cls).setUpClass()
        if 'amazon' in CONF.aws.ec2_url:
            raise cls.skipException('Paging is broken in Amazon.')

    def _create_volume_and_tags(self):
        data = self.client.create_volume(
            Size=1, AvailabilityZone=CONF.aws.aws_zone)
        volume_id = data['VolumeId']
        self.addResourceCleanUp(self.client.delete_volume, VolumeId=volume_id)
        self.get_volume_waiter().wait_available(volume_id)

        keys = list()
        for dummy in range(0, self.TAGS_COUNT):
            key = data_utils.rand_name('key')
            value = 'aaa' if dummy < 6 else 'bbb'
            data = self.client.create_tags(Resources=[volume_id],
                Tags=[{'Key': key, 'Value': value}])
            keys.append(key)

        return volume_id, keys

    @decorators.idempotent_id('8df6e612-07cd-466d-99de-9f37954a6c9a')
    def test_simple_tags_paging_with_many_results(self):
        volume_id = self._create_volume_and_tags()[0]

        data = self.client.describe_tags(MaxResults=500,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]}])
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])
        self.assertEqual(self.TAGS_COUNT, len(data['Tags']))

    @decorators.idempotent_id('683883d5-9a94-43f2-a1eb-d193db0e44e9')
    def test_simple_tags_paging_with_min_results(self):
        volume_id = self._create_volume_and_tags()[0]

        data = self.client.describe_tags(
            MaxResults=5,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])

    @decorators.idempotent_id('1db8cc5a-d0b3-4e5f-b411-d84cfa4f21e0')
    def test_tags_paging_second_page_only_with_token(self):
        volume_id = self._create_volume_and_tags()[0]

        data = self.client.describe_tags(
            MaxResults=5,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])
        data = self.client.describe_tags(
            NextToken=data['NextToken'],
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])

    @decorators.idempotent_id('a4d7b315-9616-4f9e-85b7-0f892e09a9a2')
    def test_tags_paging_with_const_filter(self):
        volume_id = self._create_volume_and_tags()[0]

        data = self.client.describe_tags(
            MaxResults=5,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])
        data = self.client.describe_tags(
            MaxResults=5, NextToken=data['NextToken'],
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])

    @decorators.idempotent_id('ad4b793a-8231-4d30-8c26-43736b7b71e4')
    def test_tags_paging_with_differenet_filters(self):
        volume_id = self._create_volume_and_tags()[0]

        data = self.client.describe_tags(
            MaxResults=5,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]},
                     {'Name': 'tag-value', 'Values': ['aaa']}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])
        data = self.client.describe_tags(
            MaxResults=5, NextToken=data['NextToken'],
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]}])
        self.assertNotEmpty(data['Tags'])
        self.assertLessEqual(1, len(data['Tags']))

    @decorators.idempotent_id('ec6d68bb-37f3-4c5c-b4c5-000d73fbc1bf')
    def test_tags_paging_with_tags_deletion(self):
        volume_id, keys = self._create_volume_and_tags()

        data = self.client.describe_tags(MaxResults=5,
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Tags'])
        for key in keys:
            self.client.delete_tags(Resources=[volume_id], Tags=[{'Key': key}])
        data = self.client.describe_tags(
            MaxResults=5, NextToken=data['NextToken'],
            Filters=[{'Name': 'resource-id', 'Values': [volume_id]}])
        self.assertNotIn('NextToken', data)
        self.assertEmpty(data['Tags'])

    @decorators.idempotent_id('37eb0597-998f-4744-8462-d56e5599dcd8')
    def test_invalid_max_results(self):
        self.assertRaises('InvalidParameterValue',
            self.client.describe_tags, MaxResults=4)

        # NOTE(andrey-mp): value more than 1000 in not invalid
        # but amazon returns 1000 elements
        self.client.describe_tags(MaxResults=1100)


class VolumesPagingTest(scenario_base.BaseScenarioTest):

    VOLUMES_COUNT = 6

    @classmethod
    @base.safe_setup
    def setUpClass(cls):
        super(VolumesPagingTest, cls).setUpClass()
        if 'amazon' in CONF.aws.ec2_url:
            raise cls.skipException('Paging is broken in Amazon.')

        zone = CONF.aws.aws_zone
        cls.ids = list()
        for dummy in range(0, cls.VOLUMES_COUNT):
            data = cls.client.create_volume(Size=1, AvailabilityZone=zone)
            volume_id = data['VolumeId']
            cls.addResourceCleanUpStatic(cls.client.delete_volume,
                                         VolumeId=volume_id)
            cls.ids.append(volume_id)
        for volume_id in cls.ids:
            cls.get_volume_waiter().wait_available(volume_id)

    @decorators.idempotent_id('d44ea940-d9ae-42a4-b3ce-add296a1678c')
    def test_simple_volumes_paging_with_many_results(self):
        data = self.client.describe_volumes(MaxResults=500)
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Volumes'])
        self.assertLessEqual(self.VOLUMES_COUNT, len(data['Volumes']))

    @decorators.idempotent_id('9780c871-ee90-411c-b6ec-1e2f1785926b')
    def test_simple_volumes_paging_with_min_results(self):
        data = self.client.describe_volumes(MaxResults=5)
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Volumes'])

    @decorators.idempotent_id('692684c4-62bc-457a-899a-07cc5382c9ab')
    def test_volumes_paging_second_page(self):
        data = self.client.describe_volumes(MaxResults=5)
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Volumes'])
        data = self.client.describe_volumes(
            MaxResults=5, NextToken=data['NextToken'])
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Volumes'])

    @decorators.idempotent_id('83183fac-bb9b-4c36-8d23-84ed55c57015')
    def test_invalid_paging(self):
        self.assertRaises('InvalidParameterValue',
            self.client.describe_volumes, MaxResults=4)

        self.assertRaises('InvalidParameterCombination',
            self.client.describe_volumes,
            MaxResults=5, VolumeIds=[self.ids[0]])

    @decorators.idempotent_id('2a777d78-9f0b-4ab0-a841-73dbaafae0dd')
    def test_volumes_paging_with_filters(self):
        data = self.client.describe_volumes(MaxResults=5,
            Filters=[{'Name': 'volume-id', 'Values': [self.ids[0]]}])
        self.assertNotEmpty(data['Volumes'])
        if 'NextToken' in data:
            # Amazon way
            data = self.client.describe_volumes(
                MaxResults=5, NextToken=data['NextToken'],
                Filters=[{'Name': 'volume-id', 'Values': [self.ids[0]]}])
            self.assertNotIn('NextToken', data)
            self.assertEmpty(data['Volumes'])

        data = self.client.describe_volumes(MaxResults=5,
            Filters=[{'Name': 'volume-id', 'Values': ['vol-*']}])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Volumes'])
        data = self.client.describe_volumes(
            MaxResults=5, NextToken=data['NextToken'],
            Filters=[{'Name': 'volume-id', 'Values': ['vol-*']}])
        self.assertNotEmpty(data['Volumes'])


class SnapshotPagingTest(scenario_base.BaseScenarioTest):

    SNAPSHOTS_COUNT = 6

    @classmethod
    @base.safe_setup
    def setUpClass(cls):
        super(SnapshotPagingTest, cls).setUpClass()
        if 'amazon' in CONF.aws.ec2_url:
            raise cls.skipException('Paging is broken in Amazon.')

        zone = CONF.aws.aws_zone

        data = cls.client.create_volume(Size=1, AvailabilityZone=zone)
        volume_id = data['VolumeId']
        cls.addResourceCleanUpStatic(cls.client.delete_volume,
                                     VolumeId=volume_id)
        cls.get_volume_waiter().wait_available(volume_id)

        def _create_snapshot():
            try:
                return cls.client.create_snapshot(VolumeId=volume_id)
            except botocore.exceptions.ClientError as e:
                code = (e.response.get('ResponseMetadata', {})
                                  .get('HTTPStatusCode'))
                if not code or code != 500:
                    raise

        waiter = base.EC2Waiter(_create_snapshot)
        cls.ids = list()
        while len(cls.ids) < cls.SNAPSHOTS_COUNT:
            time.sleep(10)
            data = waiter.wait_for_result()
            snapshot_id = data['SnapshotId']
            cls.addResourceCleanUpStatic(cls.client.delete_snapshot,
                                         SnapshotId=snapshot_id)
            cls.get_snapshot_waiter().wait_available(snapshot_id,
                                                     final_set=('completed'))
            cls.ids.append(snapshot_id)

    @decorators.idempotent_id('f44729b1-42d7-4f18-b5e0-f8dc2a03e624')
    def test_simple_snapshots_paging_with_many_results(self):
        data = self.client.describe_snapshots(MaxResults=500,
                                              OwnerIds=['self'])
        self.assertNotEmpty(data['Snapshots'])
        count = 0
        for s in data['Snapshots']:
            if s['SnapshotId'] in self.ids:
                count += 1
        self.assertEqual(self.SNAPSHOTS_COUNT, count)

    @decorators.idempotent_id('3146c81d-84c0-4817-9318-328f92bece7f')
    def test_simple_snapshots_paging_with_min_results(self):
        data = self.client.describe_snapshots(MaxResults=5, OwnerIds=['self'])
        self.assertIn('NextToken', data)
        self.assertNotEmpty(data['Snapshots'])

    @decorators.idempotent_id('fef90b60-0a46-4802-a822-98ccb58ff18c')
    def test_snapshots_paging(self):
        count = 0
        max_results = 5
        kwargs = {'MaxResults': max_results, 'OwnerIds': ['self']}
        while True:
            data = self.client.describe_snapshots(*[], **kwargs)
            self.assertGreaterEqual(max_results, len(data['Snapshots']))
            for s in data['Snapshots']:
                if s['SnapshotId'] in self.ids:
                    count += 1
            if 'NextToken' not in data:
                break
            kwargs['NextToken'] = data['NextToken']

        self.assertEqual(self.SNAPSHOTS_COUNT, count)

    @decorators.idempotent_id('8379d875-2979-4573-858f-2fd331ae992c')
    def test_invalid_paging(self):
        self.assertRaises('InvalidParameterValue',
            self.client.describe_snapshots, MaxResults=4)

        self.assertRaises('InvalidParameterCombination',
            self.client.describe_snapshots,
            MaxResults=5, SnapshotIds=[self.ids[0]])


class InstancePagingTest(scenario_base.BaseScenarioTest):

    RESERVATIONS_COUNT = 2
    INSTANCES_IN_RESERVATIONS_COUNT = 3

    @classmethod
    @base.safe_setup
    def setUpClass(cls):
        super(InstancePagingTest, cls).setUpClass()
        if 'amazon' in CONF.aws.ec2_url:
            raise cls.skipException('Paging is broken in Amazon.')
        if not CONF.aws.image_id:
            raise cls.skipException('aws image_id does not provided')

        cls.ids = list()
        cls.reservation_ids = list()
        kwargs = {
            'ImageId': CONF.aws.image_id,
            'InstanceType': CONF.aws.instance_type,
            'Placement': {'AvailabilityZone': CONF.aws.aws_zone},
            'MinCount': cls.INSTANCES_IN_RESERVATIONS_COUNT,
            'MaxCount': cls.INSTANCES_IN_RESERVATIONS_COUNT
        }
        for dummy in range(0, cls.RESERVATIONS_COUNT):
            data = cls.client.run_instances(*[], **kwargs)
            for instance in data['Instances']:
                cls.ids.append(instance['InstanceId'])
            cls.reservation_ids.append(data['ReservationId'])

        cls.addResourceCleanUpStatic(cls.client.terminate_instances,
                                     InstanceIds=cls.ids)
        for instance_id in cls.ids:
            cls.get_instance_waiter().wait_available(instance_id,
                                                     final_set=('running'))

    @decorators.idempotent_id('703da498-c73f-4fd1-a2be-2feddb5292d0')
    def test_simple_instances_paging_with_many_results(self):
        data = self.client.describe_instances(MaxResults=500)
        self.assertNotIn('NextToken', data)
        self.assertNotEmpty(data['Reservations'])
        rcount = 0
        for r in data['Reservations']:
            if r['ReservationId'] in self.reservation_ids:
                rcount += 1
        self.assertEqual(self.RESERVATIONS_COUNT, rcount)
        count = self.RESERVATIONS_COUNT * self.INSTANCES_IN_RESERVATIONS_COUNT
        instances = set()
        self._collect_own_instances(data, instances)
        self.assertEqual(count, len(instances))

    @decorators.idempotent_id('f494a2a8-6d75-4ef4-ae15-ac4fd1269107')
    def test_simple_instances_paging_with_min_results(self):
        max_results = 5
        data = self.client.describe_instances(MaxResults=max_results)
        self.assertIn('NextToken', data)
        self.assertEqual(max_results, self._count_instances(data))

    @decorators.idempotent_id('429802be-d599-4732-a310-3ffe8274df54')
    def test_instances_paging(self):
        max_results = 5
        kwargs = {'MaxResults': max_results}
        instances = set()
        while True:
            data = self.client.describe_instances(*[], **kwargs)
            self.assertGreaterEqual(max_results, self._count_instances(data))
            self._collect_own_instances(data, instances)
            if 'NextToken' not in data:
                break
            kwargs['NextToken'] = data['NextToken']

        count = self.RESERVATIONS_COUNT * self.INSTANCES_IN_RESERVATIONS_COUNT
        self.assertEqual(count, len(instances))

    @decorators.idempotent_id('061d564d-6d3a-44a8-bec9-9dba04f6f362')
    def test_invalid_paging(self):
        self.assertRaises('InvalidParameterValue',
            self.client.describe_instances, MaxResults=4)

        self.assertRaises('InvalidParameterCombination',
            self.client.describe_instances,
            MaxResults=5, InstanceIds=[self.ids[0]])

    def _collect_own_instances(self, data, instances):
        for reservation in data['Reservations']:
            for instance in reservation['Instances']:
                if instance['InstanceId'] in self.ids:
                    instances.add(instance['InstanceId'])

    def _count_instances(self, data):
        count = 0
        for reservation in data['Reservations']:
            count += len(reservation['Instances'])
        return count
