#!/usr/bin/python
#
#    test-open-iscsi.py quality assurance test script for open-iscsi
#    Copyright (C) 2011 Canonical Ltd.
#    Author: Jamie Strandboge <jamie@canonical.com>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License version 3,
#    as published by the Free Software Foundation.
#
#    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, see <http://www.gnu.org/licenses/>.
#
# packages required for test to run:
# QRT-Packages: open-iscsi
# packages where more than one package can satisfy a runtime requirement:
# QRT-Alternates: 
# files and directories required for the test to run:
# QRT-Depends: 
# privilege required for the test to run (remove line if running as user is okay):
# QRT-Privilege: root

'''
    In general, this test should be run in a virtual machine (VM) or possibly
    a chroot and not on a production machine. While efforts are made to make
    these tests non-destructive, there is no guarantee this script will not
    alter the machine. You have been warned.

    How to run in a clean VM:
    $ sudo apt-get -y install python-unit <QRT-Packages> && sudo ./test-PKG.py -v'

    How to run in a clean schroot named 'lucid':
    $ schroot -c lucid -u root -- sh -c 'apt-get -y install python-unit <QRT-Packages> && ./test-PKG.py -v'


    NOTES:
    - currently only tested on Ubuntu 8.04
'''


from netifaces import gateways, AF_INET
import unittest, subprocess, sys, os
import testlib
from tempfile import mkdtemp
import time

# There are setup based on README.multipurpose-vm. Feel free to override.
remote_server = ''
username = 'ubuntu'
password = 'passwd'
username_in = 'ubuntu'
password_in = 'ubuntupasswd'
initiatorname = 'iqn.2009-10.com.example.hardy-multi:iscsi-01'

try:
    from private.qrt.OpenIscsi import PrivateOpenIscsiTest
except ImportError:
    class PrivateOpenIscsiTest(object):
        '''Empty class'''
    print >>sys.stdout, "Skipping private tests"

class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest):
    '''Test my thing.'''

    def setUp(self):
        '''Set up prior to each test_* function'''
        self.pidfile = "/var/run/iscsid.pid"
        self.exe = "/sbin/iscsid"
        self.daemon = testlib.TestDaemon(["service", "open-iscsi"])
        self.initiatorname_iscsi = '/etc/iscsi/initiatorname.iscsi'
        self.iscsid_conf = '/etc/iscsi/iscsid.conf'

    def tearDown(self):
        '''Clean up after each test_* function'''
        global remote_server
        global initiatorname

        # If remote server is setup, convert back to manual, logout, remove
        # testlib configs and restart (in that order)
        if remote_server != '':
            testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--op=update', '--name', 'node.startup', '--value=manual'])
            testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--op=update', '--name',  'node.conn[0].startup', '--value=manual'])
            testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--logout'])

        testlib.config_restore(self.initiatorname_iscsi)
        testlib.config_restore(self.iscsid_conf)
        self.daemon.restart()

    def test_daemon(self):
        '''Test iscsid'''
        self.assertTrue(self.daemon.stop())
        time.sleep(2)
        self.assertFalse(self.daemon.status()[0])

        self.assertTrue(self.daemon.start())
        time.sleep(2)
        self.assertTrue(self.daemon.status()[0])

        self.assertTrue(self.daemon.restart())
        time.sleep(2)
        self.assertTrue(self.daemon.status()[0])

    def test_discovery_with_server(self):
        '''Test iscsi_discovery to remote server'''
        global remote_server
        global username
        global password
        global username_in
        global password_in
        global initiatorname

        if remote_server == '':
            return self._skipped("--remote-server not specified")

        contents = '''
InitiatorName=%s
InitiatorAlias=ubuntu
''' % (initiatorname)
        testlib.config_replace(self.initiatorname_iscsi, contents, True)

        contents = '''
node.session.auth.authmethod = CHAP
node.session.auth.username = %s
node.session.auth.password = %s
node.session.auth.username_in = %s
node.session.auth.password_in = %s

discovery.sendtargets.auth.authmethod = CHAP
discovery.sendtargets.auth.username = %s
discovery.sendtargets.auth.password = %s
discovery.sendtargets.auth.username_in = %s
discovery.sendtargets.auth.password_in = %s
''' % (username, password, username_in, password_in, username, password, username_in, password_in)
        testlib.config_replace(self.iscsid_conf, contents, True)

        self.assertTrue(self.daemon.restart())
        time.sleep(2)
        self.assertTrue(self.daemon.status()[0])

        rc, report = testlib.cmd(["/sbin/iscsi_discovery", remote_server])
        expected = 0
        result = 'Got exit code %d, expected %d\n' % (rc, expected)
        self.assertEquals(expected, rc, result + report)
        for i in ['starting discovery to %s' % remote_server,
                  'Testing iser-login to target %s portal %s' % (initiatorname, remote_server),
                  'starting to test tcp-login to target %s portal %s' % (initiatorname, remote_server),
                  'discovered 1 targets at %s, connected to 1' % remote_server]:
            result = "Could not find '%s' in report:\n" % i
            self.assertTrue(i in report, result + report)

    def test_net_interface_handler_execute_bit(self):
        '''Test /lib/open-iscsi/net-interface-handler is executable.'''
        nih_path = '/lib/open-iscsi/net-interface-handler'
        self.assertTrue(os.access(nih_path, os.X_OK))

reason = (
    "Skipped MAAS ephemeral test in ppc64el because it lacks nested "
    "virtualization support.")
@unittest.skipIf(testlib.manager.dpkg_arch == "ppc64el", reason)
class MAASEphemeralTest(testlib.TestlibCase, PrivateOpenIscsiTest):
    '''Test MAAS ephemeral image can boot.

    Downloads MAAS ephemeral image, patches the image to use dep8's built
    open-iscsi package, sets the image to be served by tgt and then boot that
    image under (nested) kvm. It runs cloud-init in the booted VM to collect
    some data and verify the image worked as expected.
    '''

    def setUp(self):
        self.here = os.path.dirname(os.path.abspath(__file__))
        self.release = testlib.ubuntu_release()
        self.subarch = 'hwe-{}'.format(self.release[0])
        # Download MAAS ephemeral image.
        self.get_maas_ephemeral()
        # Patch the image to use the dep8 built open-iscsi.
        self.patch_image()
        self.output_disk_path = self.create_output_disk(self.here)

    def create_output_disk(self, base_path):
        output_disk_path = os.path.join(base_path, 'output_disk.img')
        subprocess.call([
            'qemu-img', 'create', '-f',  'raw', output_disk_path, '10M'])
        return output_disk_path

    def get_maas_ephemeral(self):
        get_eph_cmd = os.path.join(self.here, 'get-maas-eph')
        subprocess.call([
            get_eph_cmd, os.path.join(self.here, '{}.d'.format(self.release)),
            self.release, self.subarch])

    def patch_image(self):
        '''Patch root-image with dep8 built open-iscsi package.'''
        root_image_path = os.path.join(
            self.here, '{}.d'.format(self.release), 'root-image')
        open_iscsi_deb_path = os.path.join(
        # XXX: matsubara fix this path hackery.
        #   os.environ['ADTTMP'], 'binaries', 'open-iscsi.deb')
            self.here, '..', '..', '..', '..', 'binaries', 'open-iscsi.deb')
        open_iscsi_deb = open(open_iscsi_deb_path)
        install_cmd = (
            'cat >/tmp/my.deb && dpkg -i /tmp/my.deb; ret=$?;'
            'rm -f /tmp/my.deb; exit $ret')
        subprocess.check_output([
            'mount-image-callback', '--system-mounts',
            root_image_path, '--', 'chroot', '_MOUNTPOINT_', '/bin/sh', '-c',
            install_cmd], stdin=open_iscsi_deb)

    def extract_files(self, path):
        tmpdir = mkdtemp()
        subprocess.call(['tar', '-C', tmpdir, '-xf', path])
        return [os.path.join(tmpdir, f) for f in os.listdir(tmpdir)]

    def get_default_route_ip(self):
        gws = gateways()
        return gws['default'][AF_INET][0]

    def test_tgt_boot(self):
        tgt_boot_cmd = os.path.join(self.here, 'tgt-boot-test')
        env = os.environ
        env['HOST_IP'] = self.get_default_route_ip()
        # Add self.here to PATH so xkvm will be available to tgt-boot-test
        env['PATH'] = env['PATH'] + ":%s" % self.here
        env['OUTPUT_DISK'] = self.output_disk_path
        rel_dir = '{}.d'.format(self.release)
        subprocess.call([
            tgt_boot_cmd,
            os.path.join(self.here, rel_dir, 'root-image'),
            os.path.join(self.here, rel_dir, self.subarch, 'boot-kernel'),
            os.path.join(self.here, rel_dir, self.subarch, 'boot-initrd')],
            env=env)
        files = self.extract_files(self.output_disk_path)
        for f in files:
            if f.endswith('resolv.conf'):
                resolvconf_contents = open(f).read()
        self.assertIn(
            '10.0.2.3', resolvconf_contents,
            msg="10.0.2.3 not in resolvconf contents: \n{}".format(
                resolvconf_contents))


if __name__ == '__main__':
    import optparse
    parser = optparse.OptionParser()
    parser.add_option("-v", "--verbose", dest="verbose", help="Verbose", action="store_true")
    parser.add_option("-s", "--remote-server", dest="remote_server", help="Specify host with available iSCSI volumes", metavar="HOST")

    parser.add_option("-n", "--initiatorname", dest="initiatorname", help="Specify initiatorname for use with --remote-server", metavar="NAME")

    parser.add_option("--password", dest="password", help="Specify password for use with --remote-server", metavar="PASS")
    parser.add_option("--password-in", dest="password_in", help="Specify password_in for use with --remote-server", metavar="PASS")

    parser.add_option("--username", dest="username", help="Specify username for use with --remote-server", metavar="USER")
    parser.add_option("--username-in", dest="username_in", help="Specify username_in for use with --remote-server", metavar="USER")

    (options, args) = parser.parse_args()

    if options.remote_server:
        remote_server = options.remote_server

        if options.username:
            username = options.username
        if options.password:
            password = options.password
        if options.username_in:
            username_in = options.username_in
        if options.password_in:
            password_in = options.password_in
        if options.initiatorname:
            initiatorname = options.initiatorname
        print "Connecting to remote server with:"
        print " host = %s " % remote_server
        print ' initiatorname = %s' % initiatorname
        print ' username = %s' % username
        print ' password = %s' % password
        print ' username_in = %s' % username_in
        print ' password_in = %s' % password_in

    suite = unittest.TestSuite()
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest))
    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
        MAASEphemeralTest))
    rc = unittest.TextTestRunner(verbosity=2).run(suite)
    if not rc.wasSuccessful():
        sys.exit(1)
