#coding:utf-8

import socket
import sunhpc
import sunhpc.core.utils
import sunhpc.db.database

class DatabaseHelper(sunhpc.db.database.Database):
    """
    This class extend the Database class with a set of helper methods
    which deal with the new ORM interface (aka the objects from the 
    sunhpc.db.mappings classes).

    These methods should replace the old methods in :mod:`sunhpc.commands`
    only methods relative to the command line should remain in the that
    module, all DB functionality should be migrated.
    """

    def __init__(self):
        super(DatabaseHelper, self).__init__()

        self._appliances_list   = None
        self._attribute         = None
        self._frontend          = None
        self._cacheAttrs        = {}

    def getFrontendName(self):
        if self._frontend:
            return self._frontend

        self._frontend = self.getHostAttr('localhost', 'Kickstart_PrivateHostname')
        return self._frontend

    def getNodesfromNames(self, names=None, managed_only=0):

        list = []
        if not names:
            
            list = self.getSession().query(Node)
            list = list.all()

            if managed_only:
                managed_list = []
                for hostname in list:
                    if self.getHostAttr(hostname,
                        'managed') == 'true':
                        managed_list.append(hostname)
                return managed_list
            return list

        clause = sqlalchemy.sql.expression.false()
        query = self.getSession().query(Node)

        for name in names:
            if name.find('select ') == 0:    # SQL select
                self.execute(name)
                nodes = [i for i, in self.fetchall()]
                clause = or_(clause, Node.name.in_(nodes))
            elif name.find('%') >= 0:   # SQL % pattern
                clause = or_(clause, Node.name.like(name))

            else:              
                clause = or_(clause, Node.name == self.getHostname(name))
        
        # now we register the query on the table Node and append all our clauses on OR
        query = query.filter(clause)
        return query.all()

    def getHostIp(self, hostname):
        hostname = self.getHostname(hostname)

        rows = self.search('''select n.ip from networks n, subnets s 
                WHERE
                n.subnet = s.id and n.name = "%s" 
                and s.name = "private"
                ''' % hostname)
        if rows:
            return self.fetchone()[0]

    def getHostname(self, hostname=None):
        """涉及到数据库, 数据库中域名, /etc/hosts 和 DNS解析."""

        arghostname = hostname
        if hostname:
            rows = self.search("select * from nodes where name='%s'" % hostname)
            if rows:
                return hostname

            '''
            try:
                int(hostname)
                rows = self.search("select name from nodes where id='%d'" % hostname)
                if rows:
                    return self.fetchone()[0]
            except ValueError:
                return
            '''

        if not hostname:
            hostname = socket.gethostname().split('.')[0]
        try:
            addr = socket.gethostbyname(hostname)
            if not addr == hostname:
                (name, aliases, addrs) = socket.gethostbyaddr(addr)
                if hostname != name and hostname not in aliases:
                    raise NameError
        except:
            addr = None
            if hostname == 'localhost':
                addr = '127.0.0.1'

        if not addr and self.conn:
            self.execute("select name from nodes where name='%s'" % hostname)
            if self.fetchone():
                return hostname

            # let's check if the name is nodeID
            row = self.search("select name from nodes where nodes.id = '%s'" % hostname)
            if row:
                (hostname, ) = self.fetchone()
                return hostname

            # let's check if the name is an alias
            row = self.search("select name from nodes where nodes.alias = '%s'" % hostname)
            if row:
                (hostname, ) = self.fetchone()
                return hostname

            # let's check if this is a mac address
            self.execute("""select nodes.name from networks, nodes where
                nodes.id = networks.node and 
                networks.mac = '%s' """ % hostname)
            try:
                hostname, = self.fetchone()
                return hostname
            except:
                pass

            # let's check if this is a FQDN
            n = hostname.split('.')
            if len(n) > 1:
                name    = n[0]
                domain  = '.'.join(n[1:])
                query = """select n.name from nodes n, networks nt, subnets s where 
                    nt.subnet=s.id and nt.node=n.id and 
                    s.dnszone='{}' and (nt.name='{}' or n.name='{}')
                    """.format(domain, name, name)
                self.execute(query)
            try:
                hostname, = self.fetchone()
                return hostname
            except:
                pass

            #
            # 以下是hostname不在数据库中才会走到这里.
            # 例如, hostname: aaa, resolv: search lan, /etc/hosts: x.x.x.x aaa
            # 最终, hostname: aaa
            # 如果, hostname: aaa.lan , 则会触发 raise cannot resolve host aaa.lan
            # 
            #
            # 例如, hostname: aaa, resolv: search lan, /etc/hosts: x.x.x.x aaa.lan
            # 最终, hostname: aaa.lan
            # 
            # 如果, hostname 不在/etc/hosts中,直接触发 raise
            #

            try:
                fin = open('/etc/resolv.conf', 'r')
            except:
                fin = None
            if fin:
                # ,e.g.. search local hpc.org
                # domains = ['local', 'hpc.org']
                domains = []
                for line in fin.readlines():
                    tokens = line[:-1].split()
                    if len(tokens) > 0 and tokens[0] == 'search':
                        domains = tokens[1:]
                for domain in domains:
                    try:
                        name = '%s.%s' % (hostname, domain)
                        addr = socket.gethostbyname(name)
                        hostname = name
                        break
                    except:
                        pass
                # 如果 hostname 能够在/etc/hosts中匹配.(必须完全匹配包括域名)
                # 那么 addr 就可以正常得到 /etc/hosts对应的IP地址
                # 这个时候 hostname 应该是带域名 例如: aaa.lan
                # 然后使用 aaa.lan 继续使用 getHostname函数进行执行.
                if addr:
                    return self.getHostname(hostname)

                fin.close()

            raise (sunhpc.core.utils.HostnotfoundException(\
                'cannot resolve host "%s"' % hostname))

        if addr == '127.0.0.1': # allow localhost to be valid
            if arghostname == None:
                return 'localhost'
            else:
                return self.getHostname()

        if self.conn:
            rows = self.search('select nodes.name from '
                'networks, nodes where '
                'nodes.id=networks.node and ip="%s"' % addr)
            if not rows:
                rows = self.search('select nodes.name '
                    'from networks, nodes where '
                    'nodes.id=networks.node and '
                    'networks.name="%s"' % hostname)
                if not rows:
                    raise (sunhpc.core.utils.HostnotfoundException(\
                        'host "%s" is not in cluster' % hostname))
            hostname, = self.fetchone()

        return hostname

    def getHostAttrs(self, hostname):

        session  = self.getSession()
        if isinstance(hostname, str):
            hostname = self.getHostname(hostname)
            node = session.query(Node).filter(Node.name == hostname).one()

        elif isinstance(hostname, Node):
            node = hostname
            hostname = node.name
        else:
            assert False, "hostname must be either a string with a hostname or a Node."

        attrs = {}
        attrs['hostname']   = hostname
        attrs['rack']       = str(node.rack)
        attrs['rank']       = str(node.rank)

        query = """select attr,value from attributes, nodes where 
            nodes.id = attributes.node and nodes.name='%s'""" % hostname
        for (attr, value) in self.conn.execute(query):
            attrs[attr] = value
            
        return attrs

    def getHostAttr(self, hostname, attr):
        return self.getHostAttrs(hostname).get(attr)

    def checkHostnameValidity(self, hostname):
        """
        check that the given host name is valid
            it checks that the hostname:
            - it does not contain any .
            - it is not already used
            - it is not in the form of rack<number>
            - it is not an alias
            - it is not a mac address
        """
        if '.' in hostname:
            raise (sunhpc.core.utils.CommandError('Hostname %s can not contains any dot.'
                    % hostname))

        msg = ''
        if hostname.startswith('rack'):
            number = hostname.split('rack')[1]
            try:
                int(number)
                msg = ('Hostname %s can not be in the from ' \
                    + 'of rack<number>.\n') % hostname
                msg += 'select a different hostname.'
            except ValueError:
                pass
        if msg:
            raise (sunhpc.core.utils.CommandError(msg))

        try:
            host = self.getHostname(hostname)
            if host:
                msg = 'Node "%s" already exists.\n' % hostname
                msg += 'Select a different hostname, cabinet '
                msg += 'and/or rank value.'
        except (sunhpc.core.utils.HostnotfoundException, NameError):
            # good, Host does not exists.
            return
        raise sunhpc.core.utils.CommandError(msg)

    def getNodeId(self, host):
        """Lookup hostname in nodes table. Host may be a name
        or an IP address. Returns None if not found."""
        try:
            return int(host)
        except Exception:
            pass

        self.execute('select id from nodes where name="%s"' % host)
        try:
            nodeid, = self.fetchone()
            return nodeid
        except TypeError:
            nodeid = None

        self.execute('select max(n.id) from networks net, \
                        nodes n where net.ip="%s" and net.node=n.id' % host)
        try:
            nodeid, = self.fetchone()
            if nodeid:
                return nodeid
        except TypeError:
            nodeid = None

        self.execute('select max(n.id) from networks net, \
                        nodes n where net.mac="%s" and net.node=n.id' % host)
        try:
            nodeid, = self.fetchone()
            if nodeid:
                return nodeid
        except TypeError:
            nodeid = None

        return nodeid

    def setNewHostAttr(self, hostname, attr, value):

        session = self.getSession()
        hostid  = self.getNodeId(hostname)
        try:
            old_attr = Attribute.loadOne(session, attr=attr, node=hostid)
        except sqlalchemy.orm.exc.NoResultFound:
            new_attr = Attribute(attr=attr, value=value, node=hostid)
            session.add(new_attr)
            session.commit()
            return

        old_value       = old_attr.value
        old_attr.value  = value

        # 如果数据库中已经存在了数据,则将原有数据加上 _old重新存储.
        if not attr.endswith(attr_postfix):
            self.setCategoryAttr(hostname, attr + attr_postfix, old_value)



























