# Copyright (c) 2012, 2013 Oracle and/or its affiliates. All rights reserved.
#
# 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; version 2 of the License.
#
# 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 St, Fifth Floor, Boston, MA  02110-1301 USA

#!/bin/env python
import platform

(pymajor, pyminor, pypatch) = map(int, platform.python_version_tuple())
assert pymajor == 2 

import sys

import os
import os.path

tst_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
mcc_dir = os.path.dirname(tst_dir)

# Paramiko from Bazaar 
sys.path += [tst_dir, mcc_dir, '/opt/csw/lib/python/site-packages' ]

import logging
if pyminor < 7:
    print('Running with Python version %s', 
          str(platform.python_version_tuple()))
    import unittest2 as unittest
else:
    import unittest

utmod = unittest

import traceback    
import json
import urlparse
import time
import socket
import stat
import tempfile
import platform
import time
import contextlib

import request_handler
import config_parser
import util

from util import mock_msg, is_set
from clusterhost import ABClusterHost, LocalClusterHost, produce_ABClusterHost
from remote_clusterhost import RemoteClusterHost

from paramiko import SSHClient, WarningPolicy

_ndb_config = os.path.join('..', '..', '..', 'bin', 'ndb_config')

def tst_tempify(*fs):
	td = util.get_val(os.environ, 'MYSQL_TMP_DIR', tempfile.gettempdir())
	#td = tempfile.mkdtemp()
	return os.path.join(td, *fs)

def defdatadir():
    return os.path.join(os.path.expanduser('~'), 'MySQL_Cluster', 'data')

# Default tst configuration. Can be overridden in config.json
_cfg = { 'debuglevel': 'DEBUG' }

def cfg():
    return _cfg

request_handler.configdir = '..'

def_datadir = os.path.join(os.path.expanduser('~'), 'MySQL_Cluster', 'data')


def mock_ABClusterHost(hle):
    return produce_ABClusterHost(hostname=hle['hostInfoRep']['host']['name'], user=util.get_val(hle, 'username'), pwd=util.get_val(hle, 'password'))

def local_ipv4_ssh_addr():
	for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(socket.gethostname(), 22):
		if family == socket.AF_INET:
			return sockaddr[0]
		
def host_is_unreachable(hostname, port=22):
    rd = util._retdict('ECONNREFUSED', True)
    rd[socket.EAI_NONAME] = True

    return util.try_connect(hostname, port, False, rd)

def is_local_sshd_available():
    with contextlib.closing(SSHClient()) as c:
        c.set_missing_host_key_policy(WarningPolicy())
        #c.load_system_host_keys()
        try:
            c.connect(local_ipv4_ssh_addr(), password=util.get_val(os.environ, 'SSH_PWD'))
        except:
            logging.exception('No usable sshd on this machine: ')
            return False
        else:
            return True

def mock_msg_as_json(cmd, body):
    return json.dumps(mock_msg(cmd,body))

def json_normalize(x):
    return json.loads(json.dumps(x))

class Test00Utils(utmod.TestCase):
    def test_version_tuple(self):
        print [ int(filter(str.isdigit, vn)) for vn in  ('2', '7', '2+')]

    def test_Param(self):
        print util.Param({'name':'--c', 'val':'bar', 'sep':'@'})
        
        x = util.Param('--a')
        print json.dumps(x)
        print util.Param('--b','foo')
        print util.Param('--d','baz','#')

    def test_host_is_unreachable(self):
        self.assertTrue(host_is_unreachable('some_non_existent_host_name'))

    @utmod.skipIf(not is_local_sshd_available(), 'FIXME: Sshd is not running by default on Windows - test some other port?')
    def test_host_is_unreachable(self):
        self.assertFalse(host_is_unreachable(local_ipv4_ssh_addr()))

    def test_to_json(self):
        obj = [ "sleep" "300" ]
        json_string = json.dumps(obj)
        jobj = json.loads(json_string)
        self.assertEqual(obj, jobj)

class Test0ConfigIni(utmod.TestCase):
    def setUp(self):
        self.ini = os.path.join(tst_dir, 'example_config.ini')
        self.cp = config_parser.parse_config_ini(self.ini)
    
    def tearDown(self):
        pass
            
    def test_get_option_values(self):
        self.assertEqual(config_parser.get_option_value_set(self.cp, 'HostName'), set(['siv27','siv28']))
 
    def test_get_node_dicts(self):
        print config_parser.get_node_dicts(self.cp, 0)
        
    def test_get_configvalues(self):
        print config_parser.get_configvalues(self.cp)
        print config_parser.get_processes(self.cp)

    def test_parse(self):
        logging.debug('ex_ini as json:\n'+json.dumps(config_parser.parse_cluster_config_ini(self.ini)))

    def test_parsex(self):
        logging.debug('ex_ini as json:\n'+str(config_parser.parse_cluster_config_ini_x(self.ini)))

    def test_write(self):
        logging.debug('ex_ini write back:\n'+config_parser.write_cluster_config_ini(config_parser.parse_cluster_config_ini(self.ini)))

    @utmod.skip('Need a way to specify the cluster installdir (default cluster we are part of?')
    def test_ndb_config(self):
        print 'ndb config as json: ', json.dumps(util.xml_to_python(self.cluster.ndb_config_xml())) 
        
@utmod.skipIf(not is_local_sshd_available(), 'FIXME: Sshd is not running by default on Windows - test some other port?')
class Test3Ports(utmod.TestCase):
    def testPortNotAvailable(self):
        self.assertFalse(util.is_port_available(local_ipv4_ssh_addr(), 22))

    def testPortAvailable(self):
        self.assertTrue(util.is_port_available(local_ipv4_ssh_addr(), 23))

    def testFindPort(self):
        self.assertEqual(util.first_available_port(local_ipv4_ssh_addr(), 22, 23), 23)

    def testNotFindPort(self):
        self.assertRaises(util.NoPortAvailableException, 
                          util.first_available_port, local_ipv4_ssh_addr(), 22, 22)
       
class _TestABClusterHost:
    def setUp(self):
        self.rtd = time.strftime("%Y-%m-%dT%H-%M-%S",time.localtime())+str(self.__class__.__name__)
        logging.debug('rtd: '+self.rtd)
        self.ch.mkdir_p(self.rtd)

    def tearDown(self):
        if sys.exc_info() == (None, None, None):
            logging.debug('tearDown: success')
            self.ch.rm_r(self.rtd)
        else:
            logging.debug('tearDown: failure')
            logging.exception('try this...')
        self.ch.drop()
        
    @utmod.skip('FIXME: Need the path to a program that will always be available')
    def test_fg_exec(self):
        self.assertTrue('This program will retrieve config options for a ndb cluster' in self.ch.exec_blocking([_ndb_config, '--help']))
            
    def test_fileops(self):
        d = self.ch.path_module.join(self.rtd, 'foo', 'bar', '')
        logging.debug('d='+d)
        self.ch.mkdir_p(d)
        bazname = self.ch.path_module.join(d, 'baz')
        with self.ch.open(bazname, 'w+') as f:
            f.write('Some text here...\n')

        with self.ch.open(bazname) as f:
            self.assertEqual(f.read(), 'Some text here...\n')

    def test_stat_dir_2(self):
        if not hasattr(self.ch, 'sftp'):
            return
        self.assertTrue(stat.S_ISDIR(self.ch.sftp.stat(self.rtd).st_mode))

    @utmod.skip('Cannot create files with attributes')    
    def test_list_dir_no_x(self):
        nox_name = self.ch.path_module.join(self.rtd, 'nox')
        notmine_name = self.ch.path_module.join(self.rtd, 'not_mine')
        nox_mode = self.self.ch.sftp.stat(nox_name).st_mode
        notmine_mode = self.self.ch.sftp.stat(notmine_name).st_mode

        self.assertTrue(stat.S_ISDIR(nox_mode))
        self.assertTrue(is_set(stat.S_IMODE(nox_mode), stat.S_IXGRP))
        
        self.assertTrue(stat.S_ISDIR(notmine_mode))
        self.assertTrue(is_set(stat.S_IMODE(notmine_mode), stat.S_IXOTH))

        logging.debug('listdir(nox)='+str(self.self.ch.list_dir(nox_name)))
        logging.debug('listdir(notmine)='+str(self.self.ch.list_dir(notmine_name)))
        
    def test_mkdir_p(self):
        somedir_name = self.ch.path_module.join(self.rtd, 'some_dir')
        logging.debug('somedir_name'+somedir_name)
        self.ch.mkdir_p(somedir_name)

    def test_hostInfo(self):
        self.assertGreater(self.ch.hostInfo.ram, 1000)
        self.assertGreater(self.ch.hostInfo.cores, 0)
        self.assertIsNotNone(self.ch.hostInfo.homedir)


class Test4LocalClusterHost(utmod.TestCase, _TestABClusterHost):
    def setUp(self):
        self.ch = LocalClusterHost('localhost')
        _TestABClusterHost.setUp(self)

    def tearDown(self):
        _TestABClusterHost.tearDown(self)

@utmod.skipIf(not is_local_sshd_available(), 'No suitable local sshd')
class Test5RemoteClusterHost(utmod.TestCase, _TestABClusterHost):
    def setUp(self):
        self.ch = RemoteClusterHost(local_ipv4_ssh_addr(), password=util.get_val(os.environ, 'SSH_PWD'))
        _TestABClusterHost.setUp(self)
        
    def tearDown(self):
        _TestABClusterHost.tearDown(self)

    def test_copy_file(self):
        if not hasattr(self.ch, 'sftp'):
            return

        content = 'Some text here\nSome more text on another line\n'
        (lh, lname) = tempfile.mkstemp()
        logging.debug('lname: '+lname)
        try:
            os.write(lh, content)
			
            # Note! Might not work with a genuine remote host, as rex.name
            # might not be creatable there
            (rh, rname) = tempfile.mkstemp()
            os.close(rh)
            # Fixme! Assumes SFTP home is C: (More commonly HOMEPATH or USERPROFILE? 
            rname = os.path.basename(rname)
            self.ch.sftp.put(lname, rname)
            try:
                (llh, llname) = tempfile.mkstemp()
                os.close(llh)
                self.ch.sftp.get(rname, llname)
                try:
                    with open(llname) as llh:
                        self.assertEqual(content, llh.read())
                finally:
                    os.remove(llname)
            finally:
                self.ch.sftp.remove(rname)
        finally:
            os.close(lh)
            os.remove(lname)
            
    def test_mkdir(self):
        if not hasattr(self.ch, 'sftp'):
            return
        (h, n) = tempfile.mkstemp()
        os.write(h, "Some text here\nSome more text on another line\n")
        os.close(h)

        some_new_dir_name = self.ch.path_module.join(self.rtd, 'some_new_dir')
        self.ch.sftp.mkdir(some_new_dir_name)
        tmpex_name = self.ch.path_module.join(some_new_dir_name, 'example.txt')
        self.ch.sftp.put(n, tmpex_name)
        os.remove(n)

    def test_stat_dir(self):
        if not hasattr(self.ch, 'sftp'):
            return
        self.assertEqual(stat.S_IFMT(self.ch.sftp.stat(self.rtd).st_mode), stat.S_IFDIR)


class Test6RequestHandler(utmod.TestCase):
    def setUp(self):
        self.ssh = {'user': None, 'pwd': None }
        
    def tearDown(self):
        pass


    def test_hostInfoReq(self):
        json_str = mock_msg_as_json('hostInfoReq', {'ssh': self.ssh,
                                    'hostName': 'localhost'})
        print 'hostInfoReq: '+json_str
        print request_handler.handle_req(json.loads(json_str))

    def test_createFileReq(self):
        json_str = mock_msg_as_json('createFileReq', 
          {'ssh': self.ssh, 'file': {'hostName': 'localhost', 'path': tempfile.gettempdir(), 'name': 'foobar'}, 
           'contentString': 'a=0\nb=1\n', 'contents': {'params': {'sep': None, 'para': [{'name': None, 'sep': None, 'val': None}]}}})
        print json_str
	try:
          print request_handler.handle_req(json.loads(json_str))
	finally:
          try:
            os.remove(os.path.join(tempfile.gettempdir(),'foobar'))
          except:
            pass
        
if __name__ == '__main__':
    if cfg()['debuglevel'] is not None:
        import logging
        fmt = '%(asctime)s: %(levelname)s [%(funcName)s;%(filename)s:%(lineno)d]: %(message)s '
        logging.basicConfig(level=getattr(logging, cfg()['debuglevel']), format=fmt)
   
    try:
        import xmlrunner2
    except:
        traceback.print_exc()
        assert(False) 
        utmod.main(argv=sys.argv)
    else:
        if os.environ.has_key('XMLRUNNER'):
            utmod.main(argv=sys.argv, testRunner=xmlrunner2.XMLTestRunner)
        else:
            utmod.main(argv=sys.argv)
