# 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 six
import six.moves.urllib.parse as urlparse
import webob

from nova.api.openstack import common
from nova.api.openstack.compute import flavors as flavors_v21
from nova.api.openstack.compute.legacy_v2 import flavors as flavors_v2
import nova.compute.flavors
from nova import context
from nova import db
from nova import exception
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import matchers

NS = "{http://docs.openstack.org/compute/api/v1.1}"
ATOMNS = "{http://www.w3.org/2005/Atom}"


FAKE_FLAVORS = {
    'flavor 1': {
        "flavorid": '1',
        "name": 'flavor 1',
        "memory_mb": '256',
        "root_gb": '10',
        "ephemeral_gb": '20',
        "swap": '10',
        "disabled": False,
        "vcpus": '',
    },
    'flavor 2': {
        "flavorid": '2',
        "name": 'flavor 2',
        "memory_mb": '512',
        "root_gb": '20',
        "ephemeral_gb": '10',
        "swap": '5',
        "disabled": False,
        "vcpus": '',
    },
}


def fake_flavor_get_by_flavor_id(flavorid, ctxt=None):
    return FAKE_FLAVORS['flavor %s' % flavorid]


def fake_get_all_flavors_sorted_list(context=None, inactive=False,
                                     filters=None, sort_key='flavorid',
                                     sort_dir='asc', limit=None, marker=None):
    if marker in ['99999']:
        raise exception.MarkerNotFound(marker)

    def reject_min(db_attr, filter_attr):
        return (filter_attr in filters and
                int(flavor[db_attr]) < int(filters[filter_attr]))

    filters = filters or {}
    res = []
    for (flavor_name, flavor) in FAKE_FLAVORS.items():
        if reject_min('memory_mb', 'min_memory_mb'):
            continue
        elif reject_min('root_gb', 'min_root_gb'):
            continue

        res.append(flavor)

    res = sorted(res, key=lambda item: item[sort_key])
    output = []
    marker_found = True if marker is None else False
    for flavor in res:
        if not marker_found and marker == flavor['flavorid']:
            marker_found = True
        elif marker_found:
            if limit is None or len(output) < int(limit):
                output.append(flavor)

    return output


def fake_get_limit_and_marker(request, max_limit=1):
    params = common.get_pagination_params(request)
    limit = params.get('limit', max_limit)
    limit = min(max_limit, limit)
    marker = params.get('marker')

    return limit, marker


def empty_get_all_flavors_sorted_list(context=None, inactive=False,
                                      filters=None, sort_key='flavorid',
                                      sort_dir='asc', limit=None, marker=None):
    return []


def return_flavor_not_found(flavor_id, ctxt=None):
    raise exception.FlavorNotFound(flavor_id=flavor_id)


class FlavorsTestV21(test.TestCase):
    _prefix = "/v2/fake"
    Controller = flavors_v21.FlavorsController
    fake_request = fakes.HTTPRequestV21
    _rspv = "v2/fake"
    _fake = "/fake"

    def setUp(self):
        super(FlavorsTestV21, self).setUp()
        self.flags(osapi_compute_extension=[])
        fakes.stub_out_networking(self)
        fakes.stub_out_rate_limiting(self.stubs)
        self.stubs.Set(nova.compute.flavors, "get_all_flavors_sorted_list",
                       fake_get_all_flavors_sorted_list)
        self.stubs.Set(nova.compute.flavors,
                       "get_flavor_by_flavor_id",
                       fake_flavor_get_by_flavor_id)
        self.controller = self.Controller()

    def _set_expected_body(self, expected, ephemeral, swap, disabled):
        # NOTE(oomichi): On v2.1 API, some extensions of v2.0 are merged
        # as core features and we can get the following parameters as the
        # default.
        expected['OS-FLV-EXT-DATA:ephemeral'] = ephemeral
        expected['OS-FLV-DISABLED:disabled'] = disabled
        expected['swap'] = swap

    def test_get_flavor_by_invalid_id(self):
        self.stubs.Set(nova.compute.flavors,
                       "get_flavor_by_flavor_id",
                       return_flavor_not_found)
        req = self.fake_request.blank(self._prefix + '/flavors/asdf')
        self.assertRaises(webob.exc.HTTPNotFound,
                          self.controller.show, req, 'asdf')

    def test_get_flavor_by_id(self):
        req = self.fake_request.blank(self._prefix + '/flavors/1')
        flavor = self.controller.show(req, '1')
        expected = {
            "flavor": {
                "id": "1",
                "name": "flavor 1",
                "ram": "256",
                "disk": "10",
                "vcpus": "",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://localhost/" + self._rspv +
                             "/flavors/1",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://localhost" + self._fake +
                             "/flavors/1",
                    },
                ],
            },
        }
        self._set_expected_body(expected['flavor'], ephemeral='20',
                                swap='10', disabled=False)
        self.assertEqual(flavor, expected)

    def test_get_flavor_with_custom_link_prefix(self):
        self.flags(osapi_compute_link_prefix='http://zoo.com:42',
                   osapi_glance_link_prefix='http://circus.com:34')
        req = self.fake_request.blank(self._prefix + '/flavors/1')
        flavor = self.controller.show(req, '1')
        expected = {
            "flavor": {
                "id": "1",
                "name": "flavor 1",
                "ram": "256",
                "disk": "10",
                "vcpus": "",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://zoo.com:42/" + self._rspv +
                             "/flavors/1",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://zoo.com:42" + self._fake +
                             "/flavors/1",
                    },
                ],
            },
        }
        self._set_expected_body(expected['flavor'], ephemeral='20',
                                swap='10', disabled=False)
        self.assertEqual(expected, flavor)

    def test_get_flavor_list(self):
        req = self.fake_request.blank(self._prefix + '/flavors')
        flavor = self.controller.index(req)
        expected = {
            "flavors": [
                {
                    "id": "1",
                    "name": "flavor 1",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/1",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/1",
                        },
                    ],
                },
                {
                    "id": "2",
                    "name": "flavor 2",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
        }
        self.assertEqual(flavor, expected)

    def test_get_flavor_list_with_marker(self):
        self.maxDiff = None
        url = self._prefix + '/flavors?limit=1&marker=1'
        req = self.fake_request.blank(url)
        flavor = self.controller.index(req)
        expected = {
            "flavors": [
                {
                    "id": "2",
                    "name": "flavor 2",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
            'flavors_links': [
                {'href': 'http://localhost/' + self._rspv +
                    '/flavors?limit=1&marker=2',
                 'rel': 'next'}
            ]
        }
        self.assertThat(flavor, matchers.DictMatches(expected))

    def test_get_flavor_list_with_invalid_marker(self):
        req = self.fake_request.blank(self._prefix + '/flavors?marker=99999')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, req)

    def test_get_flavor_detail_with_limit(self):
        url = self._prefix + '/flavors/detail?limit=1'
        req = self.fake_request.blank(url)
        response = self.controller.detail(req)
        response_list = response["flavors"]
        response_links = response["flavors_links"]

        expected_flavors = [
            {
                "id": "1",
                "name": "flavor 1",
                "ram": "256",
                "disk": "10",
                "vcpus": "",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://localhost/" + self._rspv +
                             "/flavors/1",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://localhost" + self._fake +
                             "/flavors/1",
                    },
                ],
            },
        ]
        self._set_expected_body(expected_flavors[0], ephemeral='20',
                                swap='10', disabled=False)

        self.assertEqual(response_list, expected_flavors)
        self.assertEqual(response_links[0]['rel'], 'next')

        href_parts = urlparse.urlparse(response_links[0]['href'])
        self.assertEqual('/' + self._rspv + '/flavors/detail', href_parts.path)
        params = urlparse.parse_qs(href_parts.query)
        self.assertThat({'limit': ['1'], 'marker': ['1']},
                        matchers.DictMatches(params))

    def test_get_flavor_with_limit(self):
        req = self.fake_request.blank(self._prefix + '/flavors?limit=2')
        response = self.controller.index(req)
        response_list = response["flavors"]
        response_links = response["flavors_links"]

        expected_flavors = [
            {
                "id": "1",
                "name": "flavor 1",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://localhost/" + self._rspv +
                             "/flavors/1",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://localhost" + self._fake +
                             "/flavors/1",
                    },
                ],
            },
            {
                "id": "2",
                "name": "flavor 2",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://localhost/" + self._rspv +
                             "/flavors/2",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://localhost" + self._fake +
                             "/flavors/2",
                    },
                ],
            }
        ]
        self.assertEqual(response_list, expected_flavors)
        self.assertEqual(response_links[0]['rel'], 'next')

        href_parts = urlparse.urlparse(response_links[0]['href'])
        self.assertEqual('/' + self._rspv + '/flavors', href_parts.path)
        params = urlparse.parse_qs(href_parts.query)
        self.assertThat({'limit': ['2'], 'marker': ['2']},
                        matchers.DictMatches(params))

    def test_get_flavor_with_default_limit(self):
        self.stubs.Set(common, "get_limit_and_marker",
                       fake_get_limit_and_marker)
        self.flags(osapi_max_limit=1)
        req = fakes.HTTPRequest.blank('/v2/fake/flavors?limit=2')
        response = self.controller.index(req)
        response_list = response["flavors"]
        response_links = response["flavors_links"]

        expected_flavors = [
            {
                "id": "1",
                "name": "flavor 1",
                "links": [
                    {
                        "rel": "self",
                        "href": "http://localhost/v2/fake/flavors/1",
                    },
                    {
                        "rel": "bookmark",
                        "href": "http://localhost/fake/flavors/1",
                    }
                ]
           }
        ]

        self.assertEqual(response_list, expected_flavors)
        self.assertEqual(response_links[0]['rel'], 'next')
        href_parts = urlparse.urlparse(response_links[0]['href'])
        self.assertEqual('/v2/fake/flavors', href_parts.path)
        params = urlparse.parse_qs(href_parts.query)
        self.assertThat({'limit': ['2'], 'marker': ['1']},
                        matchers.DictMatches(params))

    def test_get_flavor_list_detail(self):
        req = self.fake_request.blank(self._prefix + '/flavors/detail')
        flavor = self.controller.detail(req)
        expected = {
            "flavors": [
                {
                    "id": "1",
                    "name": "flavor 1",
                    "ram": "256",
                    "disk": "10",
                    "vcpus": "",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/1",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/1",
                        },
                    ],
                },
                {
                    "id": "2",
                    "name": "flavor 2",
                    "ram": "512",
                    "disk": "20",
                    "vcpus": "",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
        }
        self._set_expected_body(expected['flavors'][0], ephemeral='20',
                                swap='10', disabled=False)
        self._set_expected_body(expected['flavors'][1], ephemeral='10',
                                swap='5', disabled=False)
        self.assertEqual(expected, flavor)

    def test_get_empty_flavor_list(self):
        self.stubs.Set(nova.compute.flavors, "get_all_flavors_sorted_list",
                       empty_get_all_flavors_sorted_list)

        req = self.fake_request.blank(self._prefix + '/flavors')
        flavors = self.controller.index(req)
        expected = {'flavors': []}
        self.assertEqual(flavors, expected)

    def test_get_flavor_list_filter_min_ram(self):
        # Flavor lists may be filtered by minRam.
        req = self.fake_request.blank(self._prefix + '/flavors?minRam=512')
        flavor = self.controller.index(req)
        expected = {
            "flavors": [
                {
                    "id": "2",
                    "name": "flavor 2",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
        }
        self.assertEqual(flavor, expected)

    def test_get_flavor_list_filter_invalid_min_ram(self):
        # Ensure you cannot list flavors with invalid minRam param.
        req = self.fake_request.blank(self._prefix + '/flavors?minRam=NaN')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, req)

    def test_get_flavor_list_filter_min_disk(self):
        # Flavor lists may be filtered by minDisk.
        req = self.fake_request.blank(self._prefix + '/flavors?minDisk=20')
        flavor = self.controller.index(req)
        expected = {
            "flavors": [
                {
                    "id": "2",
                    "name": "flavor 2",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
        }
        self.assertEqual(flavor, expected)

    def test_get_flavor_list_filter_invalid_min_disk(self):
        # Ensure you cannot list flavors with invalid minDisk param.
        req = self.fake_request.blank(self._prefix + '/flavors?minDisk=NaN')
        self.assertRaises(webob.exc.HTTPBadRequest,
                          self.controller.index, req)

    def test_get_flavor_list_detail_min_ram_and_min_disk(self):
        """Tests that filtering work on flavor details and that minRam and
        minDisk filters can be combined
        """
        req = self.fake_request.blank(self._prefix + '/flavors/detail'
                                      '?minRam=256&minDisk=20')
        flavor = self.controller.detail(req)
        expected = {
            "flavors": [
                {
                    "id": "2",
                    "name": "flavor 2",
                    "ram": "512",
                    "disk": "20",
                    "vcpus": "",
                    "links": [
                        {
                            "rel": "self",
                            "href": "http://localhost/" + self._rspv +
                                 "/flavors/2",
                        },
                        {
                            "rel": "bookmark",
                            "href": "http://localhost" + self._fake +
                                 "/flavors/2",
                        },
                    ],
                },
            ],
        }
        self._set_expected_body(expected['flavors'][0], ephemeral='10',
                                swap='5', disabled=False)
        self.assertEqual(expected, flavor)


class FlavorsTestV20(FlavorsTestV21):
    Controller = flavors_v2.Controller
    fake_request = fakes.HTTPRequest

    def _set_expected_body(self, expected, ephemeral, swap, disabled):
        pass


class DisabledFlavorsWithRealDBTestV21(test.TestCase):
    """Tests that disabled flavors should not be shown nor listed."""
    Controller = flavors_v21.FlavorsController
    _prefix = "/v2"
    fake_request = fakes.HTTPRequestV21

    def setUp(self):
        super(DisabledFlavorsWithRealDBTestV21, self).setUp()

        # Add a new disabled type to the list of flavors
        self.req = self.fake_request.blank(self._prefix + '/flavors')
        self.context = self.req.environ['nova.context']
        self.admin_context = context.get_admin_context()

        self.disabled_type = self._create_disabled_instance_type()
        self.inst_types = db.flavor_get_all(
                self.admin_context)
        self.controller = self.Controller()

    def tearDown(self):
        db.flavor_destroy(
                self.admin_context, self.disabled_type['name'])

        super(DisabledFlavorsWithRealDBTestV21, self).tearDown()

    def _create_disabled_instance_type(self):
        inst_types = db.flavor_get_all(self.admin_context)

        inst_type = inst_types[0]

        del inst_type['id']
        inst_type['name'] += '.disabled'
        inst_type['flavorid'] = six.text_type(max(
                [int(flavor['flavorid']) for flavor in inst_types]) + 1)
        inst_type['disabled'] = True

        disabled_type = db.flavor_create(
                self.admin_context, inst_type)

        return disabled_type

    def test_index_should_not_list_disabled_flavors_to_user(self):
        self.context.is_admin = False

        flavor_list = self.controller.index(self.req)['flavors']
        api_flavorids = set(f['id'] for f in flavor_list)

        db_flavorids = set(i['flavorid'] for i in self.inst_types)
        disabled_flavorid = str(self.disabled_type['flavorid'])

        self.assertIn(disabled_flavorid, db_flavorids)
        self.assertEqual(db_flavorids - set([disabled_flavorid]),
                         api_flavorids)

    def test_index_should_list_disabled_flavors_to_admin(self):
        self.context.is_admin = True

        flavor_list = self.controller.index(self.req)['flavors']
        api_flavorids = set(f['id'] for f in flavor_list)

        db_flavorids = set(i['flavorid'] for i in self.inst_types)
        disabled_flavorid = str(self.disabled_type['flavorid'])

        self.assertIn(disabled_flavorid, db_flavorids)
        self.assertEqual(db_flavorids, api_flavorids)

    def test_show_should_include_disabled_flavor_for_user(self):
        """Counterintuitively we should show disabled flavors to all users and
        not just admins. The reason is that, when a user performs a server-show
        request, we want to be able to display the pretty flavor name ('512 MB
        Instance') and not just the flavor-id even if the flavor id has been
        marked disabled.
        """
        self.context.is_admin = False

        flavor = self.controller.show(
                self.req, self.disabled_type['flavorid'])['flavor']

        self.assertEqual(flavor['name'], self.disabled_type['name'])

    def test_show_should_include_disabled_flavor_for_admin(self):
        self.context.is_admin = True

        flavor = self.controller.show(
                self.req, self.disabled_type['flavorid'])['flavor']

        self.assertEqual(flavor['name'], self.disabled_type['name'])


class DisabledFlavorsWithRealDBTestV20(DisabledFlavorsWithRealDBTestV21):
    """Tests that disabled flavors should not be shown nor listed."""
    Controller = flavors_v2.Controller
    _prefix = "/v2/fake"
    fake_request = fakes.HTTPRequest


class ParseIsPublicTestV21(test.TestCase):
    Controller = flavors_v21.FlavorsController

    def setUp(self):
        super(ParseIsPublicTestV21, self).setUp()
        self.controller = self.Controller()

    def assertPublic(self, expected, is_public):
        self.assertIs(expected, self.controller._parse_is_public(is_public),
                      '%s did not return %s' % (is_public, expected))

    def test_None(self):
        self.assertPublic(True, None)

    def test_truthy(self):
        self.assertPublic(True, True)
        self.assertPublic(True, 't')
        self.assertPublic(True, 'true')
        self.assertPublic(True, 'yes')
        self.assertPublic(True, '1')

    def test_falsey(self):
        self.assertPublic(False, False)
        self.assertPublic(False, 'f')
        self.assertPublic(False, 'false')
        self.assertPublic(False, 'no')
        self.assertPublic(False, '0')

    def test_string_none(self):
        self.assertPublic(None, 'none')
        self.assertPublic(None, 'None')

    def test_other(self):
        self.assertRaises(
                webob.exc.HTTPBadRequest, self.assertPublic, None, 'other')


class ParseIsPublicTestV20(ParseIsPublicTestV21):
    Controller = flavors_v2.Controller
