#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free  Software Foundation; either version 2 of the License, or
# (at your option)  any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.

import virtinst
from virtinst import VirtualDisk
from virtinst import Interface

from tests import utils

import unittest
import logging
import traceback
import os

# Template for adding arguments to test
# {'label' : {\
#       'VAR' : {\
#           'invalid' : [param],
#           'valid'   : [param]},
#       '__init__'  : {\
#           'invalid' : [{'initparam':val}],
#           'valid'   : [{'initparam':val}]}
#
#   Anything in 'valid' should throw no error
#   Anything in 'invalid' should throw ValueError or TypeError

# We install several storage pools on the connection to ensure
# we aren't bumping up against errors in that department.
testconn = utils.open_testdriver()
testcaps = virtinst.CapabilitiesParser.parse(testconn.getCapabilities())

virtimage = virtinst.ImageParser.parse_file("tests/image-xml/image.xml")

volinst = virtinst.Storage.StorageVolume(conn=testconn,
                                         pool_name="default-pool",
                                         name="val-vol",
                                         capacity=1)

tmppool = testconn.storagePoolLookupByName("inactive-pool")
tmppool.destroy()

iface_proto1 = Interface.InterfaceProtocol.protocol_class_for_family(
                Interface.InterfaceProtocol.INTERFACE_PROTOCOL_FAMILY_IPV4)()
iface_proto2 = Interface.InterfaceProtocol.protocol_class_for_family(
                Interface.InterfaceProtocol.INTERFACE_PROTOCOL_FAMILY_IPV6)()
iface_ip1 = Interface.InterfaceProtocolIPAddress("129.63.1.2")
iface_ip2 = Interface.InterfaceProtocolIPAddress("fe80::215:58ff:fe6e:5",
                                                 prefix="64")

args = {

'guest' : {
    'name'  : {
        'invalid' : ['123456789', 'im_invalid!', '', 0,
                     'verylongnameverylongnameverylongnamevery'
                     'longnameveryvery', "test",
                   ],
        'valid'   : ['Valid_name.01']},
    'memory' : {
        'invalid' : [-1, 0, ''],
        'valid'   : [200, 2000]},
    'maxmemory' : {
        'invalid' : [-1, 0, ''],
        'valid'   : [200, 2000], },
    'uuid'      : {
        'invalid' : ['', 0, '1234567812345678123456781234567x'],
        'valid'   : ['12345678123456781234567812345678',
                     '12345678-1234-1234-ABCD-ABCDEF123456']},
    'vcpus'     : {
        'invalid' : [-1, 0, 1000, ''],
        'valid'   : [1, 32]},
    'type'      : {
        'invalid' : [],
        'valid'   : ['sometype']},
    'cdrom'     : {
        'invalid' : ['', 0, '/somepath'],
        'valid'   : ['/dev/loop0']
  },
    'arch'      : {
        'invalid' : [],
        'valid'   : ["i386", 'i686', 'x86_64'],
  },
    'os_type'   : {
        'invalid' : ['notpresent', 0, ''],
        'valid'   : ['other', 'windows', 'unix', 'linux']},
    'os_variant': {
        'invalid' : ['', 0, 'invalid'],
        'valid'   : ['rhel5', 'sles10']},
},


'disk' : {
  'init_conns' : [testconn, None],
  '__init__' : {

   'invalid' : [
    {'path' : 0},
    {'path' : '/root'},
    {'path' : 'valid', 'size' : None},
    {'path' : "valid", 'size' : 'invalid'},
    {'path' : 'valid', 'size' : -1},
    {'path' : None},
    {'path' : "noexist1", 'size' : 900000, 'sparse' : False},
    {'path' : "noexist2", 'type' : VirtualDisk.DEVICE_CDROM},
    {'volName' : ("default-pool", "default-vol")},
    {'conn' : testconn, 'volName' : ("pool-noexist", "default-vol")},
    {'conn' : testconn, 'volName' : ("default-pool", "vol-noexist")},
    {'conn' : testconn, 'volName' : (1234, "vol-noexist")},
    {'path' : 'valid', 'size' : 1, 'driverCache' : 'invalid'},
    {'conn' : testconn, "path" : "/full-pool/newvol.img", "size" : 1,
      'sparse' : False},
    # Inactive pool w/ volume
    {'conn' : testconn, "path" : "/inactive-pool/inactive-vol"},
 ],

   'valid' : [
    {'path' : '/dev/loop0'},
    {'path' : 'nonexist', 'size' : 1},
    {'path' : '/dev/null'},
    {'path' : None, 'device' : VirtualDisk.DEVICE_CDROM},
    {'path' : None, 'device' : VirtualDisk.DEVICE_FLOPPY},
    {'conn' : testconn, 'volName' : ("default-pool", "default-vol")},
    {'conn' : testconn, 'path' : "/default-pool/default-vol"},
    {'conn' : testconn, 'path' : "/default-pool/vol-noexist", 'size' : 1},
    {'conn' : testconn, 'volInstall': volinst},
    {'path' : 'nonexist', 'size' : 1, 'driverCache' : 'writethrough'},
    # Full pool, but we are nonsparse
    {'conn' : testconn, "path" : "/full-pool/newvol.img", "size" : 1},
 ]
},

  'shareable' : {
    'invalid': [None, 1234],
    'valid': [True, False]
},
},

'installer' : {
    'init_conns' : [testconn, None],
    'extraargs' : {
        'invalid' : [],
        'valid'   : ['someargs']},
    'arch' : {
        'invalid' : [],
        'valid'   : ['i686', 'i386', 'x86_64'],
  }
},

'distroinstaller' : {
    'init_conns' : [testconn, None],
    'location'  : {
        'invalid' : ['nogood', 'http:/nogood', [], None,
                     ("pool-noexist", "default-vol"),
                     ("default-pool", "vol-noexist"),
                  ],
        'valid'   : ['/dev/null', 'http://web', 'ftp://ftp', 'nfs:nfsserv',
                     '/tmp', ("default-pool", "default-vol"),
                  ]}
},

'livecdinstaller' : {
    'init_conns' : [testconn, None],
    'location'  : {
        'invalid' : ['path-noexist',
                     ("pool-noexist", "default-vol"),
                     ("default-pool", "vol-noexist"),
                  ],
        'valid'   : ['/dev/null', ("default-pool", "default-vol"),
                  ]}
},

'imageinstaller' : {
    '__init__' : {
        'invalid' :
            [{'image' : virtimage, 'capabilities': testcaps, 'boot_index': 5},
             {'image' : virtimage, 'capabilities': "foo"}],
        'valid'   :
            [{'image' : virtimage, 'capabilities': testcaps, 'boot_index': 1},
            {'image' : virtimage},
            {'image' : virtimage, 'capabilities': testcaps, 'conn': None}],
  }
},

'network'   : {
    'init_conns' : [testconn, None],
    '__init__'  : {
        'invalid' : [{'macaddr' : 0}, {'macaddr' : ''}, {'macaddr' : '$%XD'},
                      {'type' : 'network'}],
        'valid'   : []}
},

'clonedesign' : {
    'original_guest' : {
        'invalid' : ['idontexist'],
        'valid'   : ['test']},
    'clone_name': {'invalid' : [0, 'test'],
                    'valid'   : ['some.valid-name_9']},
    'clone_uuid': {'invalid' : [0],
                    'valid'   : ['12345678123456781234567812345678']},
    'clone_mac' : {'invalid' : ['badformat'],
                    'valid'   : ['AA:BB:CC:DD:EE:FF']},
    'clone_bs'  : {'invalid' : [], 'valid'   : ['valid']},
},

'inputdev' : {
    'init_conns' : [testconn],
    'type' : {
        'valid'   : ["mouse", "tablet"],
        'invalid' : ["foobar", 1234]},
    'bus' : {
        'valid'   : ["ps2", "xen", "usb"],
        'invalid' : ["foobar", 1234]},
},

'chardev' : {
    'init_conns' : [testconn],
    'source_path': {
        'invalid'   : [],
        'valid'     : ["/some/path"]},
    'source_mode': {
        'invalid'   : [None],
        'valid'     : virtinst.VirtualCharDevice.char_modes},
    'source_host': {
        'invalid'   : [],
        'valid'     : ["some.source.host"]},
    'source_port': {
        'invalid'   : ["foobar"],
        'valid'     : [1234]},
    'connect_host': {
        'invalid'   : [],
        'valid'     : ["some.connect.com"]},
    'connect_port': {
        'invalid'   : ["foobar"],
        'valid'     : [1234]},
    'protocol': {
        'invalid'   : [None],
        'valid'     : virtinst.VirtualCharDevice.char_protocols},
},

'interface' : {
    'init_conns'    : [testconn],
    'name' : {
        'invalid'   : ["eth0", None, 1234],
        'valid'     : ["foobar"], },
    'mtu' : {
        'invalid'   : [],
        'valid'     : [None, 0, 1, 1234, "1234"], },
    'start_mode' : {
        'invalid'   : ["foobar", None],
        'valid'     : [Interface.Interface.INTERFACE_START_MODE_ONBOOT], },
    'macaddr' : {
        'invalid'   : [0, 100, "22:22:33", "foobar"],
        'valid'     : [None, "22:22:33:44:55:66"], },

    'protocols' : {
        'invalid'   : [],
        'valid'     : [[], [iface_proto1, iface_proto2]], },

    # Bond params
    'bond_mode' : {
        'invalid'   : [],
        'valid'     : ["active-backup", "broadcast"], },
    'monitor_mode' : {
        'invalid'   : [],
        'valid'     :
            [Interface.InterfaceBond.INTERFACE_BOND_MONITOR_MODE_ARP,
             Interface.InterfaceBond.INTERFACE_BOND_MONITOR_MODE_MII], },

    'mii_frequency' : {
        'invalid'   : [],
        'valid'     : ["123", 123], },
    'mii_updelay' : {
        'invalid'   : [],
        'valid'     : ["123", 123], },
    'mii_downdelay' : {
        'invalid'   : [],
        'valid'     : ["123", 123], },
    'mii_carrier_mode' : {
        'invalid'   : [],
        'valid'     : ["ioctl", "netif"], },

    'arp_interval' : {
        'invalid'   : [],
        'valid'     : [123, "123"], },
    'arp_target' : {
        'invalid'   : [],
        'valid'     : ["129.168.1.2"], },
    'arp_validate_mode' : {
        'invalid'   : [],
        'valid'     : ["active", "backup"], },

    # VLAN params
    'tag' : {
        'invalid'   : [],
        'valid'     : [123, "123"], },
    'parent_interface' : {
        'invalid'   : [None],
        'valid'     : ["eth1", testconn.interfaceLookupByName("eth1")], },

    # Bridge params
    'stp' : {
        'invalid'   : [0, 1, "foo"],
        'valid'     : [True, False], },
    'delay' : {
        'invalid'   : [],
        'valid'     : [0, 1, "4"], },
},

'interface_proto' : {
    'gateway' : {
        'invalid'   : [],
        'valid'     : ["129.168.1.2"], },

    'autoconf' : {
        'invalid'   : [],
        'valid'     : [True, False], },
    'dhcp' : {
        'invalid'   : [],
        'valid'     : [True, False], },
    'peerdns' : {
        'invalid'   : [],
        'valid'     : [True, False], },
    'ips' : {
        'invalid'   : [],
        'valid'     : [[], [iface_ip1, iface_ip2]], },

}

}  # End of validation dict


class TestValidation(unittest.TestCase):

    def _getInitConns(self, label):
        if "init_conns" in args[label]:
            return args[label]["init_conns"]
        return [testconn]

    def _runObjInit(self, testclass, valuedict, defaultsdict=None):
        if defaultsdict:
            for key in defaultsdict.keys():
                if key not in valuedict:
                    valuedict[key] = defaultsdict.get(key)
        return testclass(*(), **valuedict)

    def _testInvalid(self, name, obj, testclass, paramname, paramvalue):
        try:
            if paramname == '__init__':
                self._runObjInit(testclass, paramvalue)
            else:
                setattr(obj, paramname, paramvalue)

            msg = ("Expected TypeError or ValueError: None Raised.\n"
                   "For '%s' object, paramname '%s', val '%s':" %
                   (name, paramname, paramvalue))
            raise AssertionError(msg)

        except AssertionError:
            raise
        except (TypeError, ValueError):
            # This is an expected error
            pass
        except Exception, e:
            msg = ("Unexpected exception raised: %s\n" % e +
                   "Original traceback was: \n%s\n" % traceback.format_exc() +
                   "For '%s' object, paramname '%s', val '%s':" %
                   (name, paramname, paramvalue))
            raise AssertionError(msg)

    def _testValid(self, name, obj, testclass, paramname, paramvalue):

        try:
            if paramname is '__init__':
                conns = self._getInitConns(name)
                if "conn" in paramvalue:
                    conns = [paramvalue["conn"]]
                for conn in conns:
                    paramvalue["conn"] = conn
                    self._runObjInit(testclass, paramvalue)
            else:
                setattr(obj, paramname, paramvalue)
        except Exception, e:
            msg = ("Validation case failed, expected success.\n" +
                   "Exception received was: %s\n" % e +
                   "Original traceback was: \n%s\n" % traceback.format_exc() +
                   "For '%s' object, paramname '%s', val '%s':" %
                   (name, paramname, paramvalue))
            raise AssertionError(msg)

    def _testArgs(self, obj, testclass, name, exception_check=None,
                  manual_dict=None):
        """@obj Object to test parameters against
           @testclass Full class to test initialization against
           @name String name indexing args"""
        logging.debug("Testing '%s'", name)
        testdict = args[name]
        if manual_dict is not None:
            testdict = manual_dict

        for paramname in testdict.keys():
            if paramname == "init_conns":
                continue

            for val in testdict[paramname]['invalid']:
                self._testInvalid(name, obj, testclass, paramname, val)

            for val in testdict[paramname]['valid']:
                if exception_check:
                    if exception_check(obj, paramname, val):
                        continue
                self._testValid(name, obj, testclass, paramname, val)


    # Actual Tests
    def testGuestValidation(self):
        g = virtinst.Guest(conn=testconn, type="xen")
        self._testArgs(g, virtinst.Guest, 'guest')

    def testDiskValidation(self):
        disk = VirtualDisk("/dev/loop0")
        self._testArgs(disk, VirtualDisk, 'disk')

    def testNetworkValidation(self):
        network = virtinst.VirtualNetworkInterface(conn=testconn)
        self._testArgs(network, virtinst.VirtualNetworkInterface, 'network')

        # Test dynamic MAC/Bridge success
        try:
            network = virtinst.VirtualNetworkInterface()
            network.setup(testconn)
        except Exception, e:
            raise AssertionError(
            "Network setup with no params failed, expected success." +
            " Exception was: %s: %s" %
            (str(e), "".join(traceback.format_exc())))

    def testDistroInstaller(self):
        def exception_check(obj, paramname, paramvalue):
            if paramname == "location":
                # Skip NFS test as non-root
                if paramvalue[0:3] == "nfs" and os.geteuid() != 0:
                    return True

                # Don't pass a tuple location if installer has no conn
                if not obj.conn and type(paramvalue) == tuple:
                    return True

            return False

        label = 'distroinstaller'
        for conn in self._getInitConns(label):
            dinstall = virtinst.DistroInstaller(conn=conn)
            self._testArgs(dinstall, virtinst.DistroInstaller, 'installer',
                           exception_check)
            self._testArgs(dinstall, virtinst.DistroInstaller, label,
                           exception_check)

    def testLiveCDInstaller(self):
        def exception_check(obj, paramname, paramvalue):
            if paramname == 'location':
                # Don't pass a tuple location if installer has no conn
                if not obj.conn and type(paramvalue) == tuple:
                    return True

            return False

        label = 'livecdinstaller'
        for conn in self._getInitConns(label):
            dinstall = virtinst.LiveCDInstaller(conn=conn)
            self._testArgs(dinstall, virtinst.LiveCDInstaller, 'installer',
                           exception_check)
            self._testArgs(dinstall, virtinst.LiveCDInstaller, label,
                           exception_check)

    def testImageInstaller(self):
        label = 'imageinstaller'
        inst_obj = virtinst.ImageInstaller(image=virtimage,
                                           capabilities=testcaps)
        #self._testArgs(inst_obj, virtinst.ImageInstaller, 'installer')
        self._testArgs(inst_obj, virtinst.ImageInstaller, label)

    def testCloneManager(self):
        label = 'clonedesign'
        for conn in self._getInitConns(label):
            cman = virtinst.CloneManager.CloneDesign(conn)
            self._testArgs(cman, virtinst.CloneManager.CloneDesign, label)

    def testInputDev(self):
        label = 'inputdev'
        for conn in self._getInitConns(label):
            cman = virtinst.VirtualInputDevice(conn)
            self._testArgs(cman, virtinst.VirtualInputDevice, label)

    def testCharDev(self):
        label = 'chardev'
        paramdict = args[label]
        devs = []

        for conn in self._getInitConns(label):
            for dev in virtinst.VirtualCharDevice.dev_types:
                for char in virtinst.VirtualCharDevice.char_types:
                    devs.append(virtinst.VirtualCharDevice.get_dev_instance(conn, dev, char))

        for dev in devs:
            custom_dict = {}
            for key in paramdict.keys():
                if hasattr(dev, key):
                    custom_dict[key] = paramdict[key]
            self._testArgs(dev, virtinst.VirtualCharDevice, label,
                           manual_dict=custom_dict)

    def testInterface(self):
        label = 'interface'
        paramdict = args[label]

        for itype in Interface.Interface.INTERFACE_TYPES:
            custom_dict = {}
            iclass = Interface.Interface.interface_class_for_type(itype)

            for key in paramdict.keys():
                if hasattr(iclass, key):
                    custom_dict[key] = paramdict[key]

            for conn in self._getInitConns(label):
                iobj = iclass("foo-validation-test", conn)

                self._testArgs(iobj, iclass, label, manual_dict=custom_dict)

    def testInterfaceProtocol(self):
        label = 'interface_proto'
        paramdict = args[label]

        for itype in Interface.InterfaceProtocol.INTERFACE_PROTOCOL_FAMILIES:
            custom_dict = {}
            iclass = Interface.InterfaceProtocol.protocol_class_for_family(itype)

            for key in paramdict.keys():
                if hasattr(iclass, key):
                    custom_dict[key] = paramdict[key]

            iobj = iclass()
            self._testArgs(iobj, iclass, label, manual_dict=custom_dict)

if __name__ == "__main__":
    unittest.main()
