# Copyright 2016 OpenStack Foundation
#    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 abc
import itertools

from oslo_serialization import jsonutils

from neutron_lib import constants as const
from neutron_lib.db import constants as db_const
from neutron_lib.objects import common_types
from neutron_lib.tests import _base as test_base
from neutron_lib.tests import tools
from neutron_lib.utils import net


class TestField:

    def test_coerce_good_values(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual(out_val, self.field.coerce('obj', 'attr', in_val))

    def test_coerce_bad_values(self):
        for in_val in self.coerce_bad_values:
            self.assertRaises((TypeError, ValueError),
                              self.field.coerce, 'obj', 'attr', in_val)

    def test_to_primitive(self):
        for in_val, prim_val in self.to_primitive_values:
            self.assertEqual(prim_val, self.field.to_primitive('obj', 'attr',
                                                               in_val))

    def test_to_primitive_json_serializable(self):
        for in_val, _ in self.to_primitive_values:
            prim = self.field.to_primitive('obj', 'attr', in_val)
            jsencoded = jsonutils.dumps(prim)
            self.assertEqual(prim, jsonutils.loads(jsencoded))

    def test_from_primitive(self):
        class ObjectLikeThing:
            _context = 'context'

        for prim_val, out_val in self.from_primitive_values:
            from_prim = self.field.from_primitive(ObjectLikeThing, 'attr',
                                                  prim_val)
            self.assertEqual(out_val, from_prim)
            # ensure it's coercable for sanity
            self.field.coerce('obj', 'attr', from_prim)

    @abc.abstractmethod
    def test_stringify(self):
        '''This test should validate stringify() format for new field types.'''


class IPV6ModeEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.IPV6ModeEnumField()
        self.coerce_good_values = [(mode, mode)
                                   for mode in const.IPV6_MODES]
        self.coerce_bad_values = ['6', 4, 'type', 'slaacc']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class DscpMarkFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.DscpMarkField()
        self.coerce_good_values = [(val, val)
                                   for val in const.VALID_DSCP_MARKS]
        self.coerce_bad_values = ['6', 'str', [], {}, object()]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("%s" % in_val, self.field.stringify(in_val))


class IPNetworkPrefixLenFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.IPNetworkPrefixLenField()
        self.coerce_good_values = [(x, x) for x in (0, 32, 128, 42)]
        self.coerce_bad_values = ['len', '1', 129, -1]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("%s" % in_val, self.field.stringify(in_val))


class MACAddressFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.MACAddressField()
        mac1 = tools.get_random_EUI()
        mac2 = tools.get_random_EUI()
        self.coerce_good_values = [(mac1, mac1), (mac2, mac2)]
        self.coerce_bad_values = [
            'XXXX', 'ypp', 'g3:vvv',
            # the field type is strict and does not allow to pass strings, even
            # if they represent a valid MAC address
            net.get_random_mac('fe:16:3e:00:00:00'.split(':')),
        ]
        self.to_primitive_values = ((a1, str(a2))
                                    for a1, a2 in self.coerce_good_values)
        self.from_primitive_values = ((a2, a1)
                                      for a1, a2 in self.to_primitive_values)

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual('%s' % in_val, self.field.stringify(in_val))


class IPNetworkFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.IPNetworkField()
        addrs = [
            tools.get_random_ip_network(version=ip_version)
            for ip_version in const.IP_ALLOWED_VERSIONS
        ]
        self.coerce_good_values = [(addr, addr) for addr in addrs]
        self.coerce_bad_values = [
            'ypp', 'g3:vvv',
            # the field type is strict and does not allow to pass strings, even
            # if they represent a valid IP network
            '10.0.0.0/24',
        ]
        self.to_primitive_values = ((a1, str(a2))
                                    for a1, a2 in self.coerce_good_values)
        self.from_primitive_values = ((a2, a1)
                                      for a1, a2 in self.to_primitive_values)

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual('%s' % in_val, self.field.stringify(in_val))


class IPVersionEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.IPVersionEnumField()
        self.coerce_good_values = [(val, val)
                                   for val in const.IP_ALLOWED_VERSIONS]
        self.coerce_bad_values = [5, 0, -1, 'str']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("%s" % in_val, self.field.stringify(in_val))


class FlowDirectionEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.FlowDirectionEnumField()
        self.coerce_good_values = [(val, val)
                                   for val in const.VALID_DIRECTIONS]
        self.coerce_bad_values = ['test', '8', 10, []]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class FlowDirectionAndAnyEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.FlowDirectionAndAnyEnumField()
        self.coerce_good_values = [
            (val, val) for val in const.VALID_DIRECTIONS_AND_ANY]
        self.coerce_bad_values = ['test', '8', 10, []]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class PortRangesFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.PortRangesField()
        self.coerce_good_values = [(val, val) for val in (
            '80:80', '80:90', '80', 80)]
        self.coerce_bad_values = ('x', 0, 99999, '99999',
                                  '9999:', ':9999',
                                  '99999:100000', '80:70')
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values


class DomainNameFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.DomainNameField()
        self.coerce_good_values = [
            (val, val)
            for val in ('www.google.com', 'hostname', '1abc.com')
        ]
        self.coerce_bad_values = ['x' * (db_const.FQDN_FIELD_SIZE + 1), 10, []]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class EtherTypeEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.EtherTypeEnumField()
        self.coerce_good_values = [(val, val)
                                   for val in const.VALID_ETHERTYPES]
        self.coerce_bad_values = ['IpV4', 8, 'str', 'ipv6']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class IpProtocolEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.IpProtocolEnumField()
        self.coerce_good_values = [
            (val, val)
            for val in itertools.chain(
                const.IP_PROTOCOL_MAP.keys(),
                [str(v) for v in range(256)]
            )
        ]
        self.coerce_bad_values = ['test', 'Udp', 256]
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class UUIDFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.UUIDField()
        self.coerce_good_values = [
            ('f1d9cb3f-c263-45d3-907c-d12a9ef1629e',
                'f1d9cb3f-c263-45d3-907c-d12a9ef1629e'),
            ('7188f6637cbd4097a3b1d1bb7897c7c0',
                '7188f6637cbd4097a3b1d1bb7897c7c0')]
        self.coerce_bad_values = [
            'f1d9cb3f-c263-45d3-907c-d12a9ef16zzz',
            '7188f6637cbd4097a3b1d1bb7897']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual('%s' % in_val, self.field.stringify(in_val))


class DictOfMiscValuesFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.DictOfMiscValues
        test_dict_1 = {'a': True,
                       'b': 1.23,
                       'c': ['1', 1.23, True],
                       'd': {'aa': 'zz'},
                       'e': '10.0.0.1'}
        test_dict_str = jsonutils.dumps(test_dict_1)
        self.coerce_good_values = [
            (test_dict_1, test_dict_1),
            (test_dict_str, test_dict_1)
        ]
        self.coerce_bad_values = [str(test_dict_1), '{"a":}']
        self.to_primitive_values = [
            (test_dict_1, test_dict_str)
        ]
        self.from_primitive_values = [
            (test_dict_str, test_dict_1)
        ]

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual(jsonutils.dumps(in_val),
                             self.field.stringify(in_val))


class NetworkSegmentRangeNetworkTypeEnumFieldTest(test_base.BaseTestCase,
                                                  TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.NetworkSegmentRangeNetworkTypeEnumField()
        self.coerce_good_values = [(val, val)
                                   for val in [const.TYPE_VLAN,
                                               const.TYPE_VXLAN,
                                               const.TYPE_GRE,
                                               const.TYPE_GENEVE]]
        self.coerce_bad_values = [const.TYPE_FLAT, 'foo-network-type']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))


class NumaAffinityPoliciesEnumFieldTest(test_base.BaseTestCase, TestField):
    def setUp(self):
        super().setUp()
        self.field = common_types.NumaAffinityPoliciesEnumField()
        good_values = [const.PORT_NUMA_POLICY_REQUIRED,
                       const.PORT_NUMA_POLICY_PREFERRED,
                       const.PORT_NUMA_POLICY_LEGACY,
                       const.PORT_NUMA_POLICY_SOCKET]
        self.coerce_good_values = [(val, val) for val in good_values]
        self.coerce_bad_values = ['foo-invalid-policy']
        self.to_primitive_values = self.coerce_good_values
        self.from_primitive_values = self.coerce_good_values

    def test_stringify(self):
        for in_val, out_val in self.coerce_good_values:
            self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))
