#!/usr/bin/python

"""SUSENebula configuration service."""

# Author: Robert Schweikert
# License: GPL v2
# Copyright: SUSE

import SocketServer
import os
import re
import signal
import sys
import syslog

ipPattern = r'\d{1,3}?\.\d{1,3}?\.\d{1,3}?\.\d{1,3}?'
ipPat = re.compile(ipPattern)

base = r'\d{0,2}?[A-F]{0,2}?:'
macPattern = 5*base + r'\d{0,2}?[A-F]{0,2}?'
macPat = re.compile(macPattern)

#---------------------------------------------------------------------------
class CloudConfigService(SocketServer.StreamRequestHandler):
    """Cloud head node information provider."""
    def __init__(self, request, client_address, server):
        self.srvInfo = {}
        # Collect the information we want to make propagate to the cloud node
        # Keyboard layout
        os.system('/sbin/yast2 keyboard summary >& /tmp/.kdb.info')
        info = open('/tmp/.kdb.info', 'r').read()
        os.remove('/tmp/.kdb.info')
        self.srvInfo['keyLayout'] = info.split(':')[-1].strip()
        # Timezone and HW clock setting (assume HW clock on nodes is set the
        # same by default
        os.system('/sbin/yast2 timezone summary >& /tmp/.tz.info')
        info = open('/tmp/.tz.info', 'r').readlines()
        os.remove('/tmp/.tz.info')
        timezone = None
        hwClockSet = None
        for ln in info:
            if ln.find('Current Time Zone') != -1:
                timezone = ln.split(':')[-1].strip()
            if ln.find('Hardware Clock') != -1:
                hwClockSet = ln.split(':')[-1].strip()
        self.srvInfo['timezone'] = timezone
        self.srvInfo['hwclock'] = hwClockSet 
        # Root passwd
        lines = open('/etc/shadow', 'r').readlines()
        passWd = None
        for ln in lines:
            if ln[0:4] == 'root':
                passWd = ln.split(':')[1]
        self.srvInfo['rootpass'] = passWd
        # IP address
        lines = open('/etc/sysconfig/network/ifcfg-br0', 'r').readlines()
        ipAddr = None
        for ln in lines:
            if ln[0:7] == 'IPADDR=':
                # [1:] at end removes leading quote
                ipAddr = ln.split('=')[-1].split('/')[0].strip()[1:]
        self.srvInfo['serverIP'] = ipAddr
        # DHCP feature identifier
        lines = open('/etc/dhcpd.conf','r').readlines()
        dhcpFeature = None
        domainname = None
        for ln in lines:
            if ln.find('domain-name') != -1:
                # [1:-2] remove leading and trailing quotes and ; 
                domainname = ln.split()[-1].strip()[1:-2]
            # 239 is the assigned ID for the feature, hard coded in the YaST
            # configuration module
            if ln.find('code 239') != -1:
                dhcpFeature = ln.split()[1].strip()
        self.srvInfo['dhcpFeature'] = dhcpFeature
        self.srvInfo['domainname'] = domainname
        # Name resolution
        self.srvInfo['resolve'] = open('/etc/resolv.conf', 'r').read()
        # The configured routes
        routes = open('/etc/sysconfig/network/routes', 'r').read()
        self.srvInfo['routes'] = routes
        # The one use ID
        lines = open('/etc/passwd', 'r').readlines()
        for ln in lines:
            if ln.find('oneadmin') != -1:
                self.srvInfo['userID'] = ('oneadmin', ln.split(':')[2],
                                          ln.split(':')[1])
                break
        # The cloud group ID
        lines = open('/etc/group', 'r').readlines()
        for ln in lines:
            if ln.find('cloud') != -1:
                self.srvInfo['groupID'] = ('cloud', ln.split(':')[2])
                break
        SocketServer.StreamRequestHandler.__init__(self, request,
                                                   client_address, server)
        
    #----------------------------------------------------------------------
    def generateHostName(self):
        """Generate a new host name"""
        # Create a host name for the host requesting the information
        while (os.path.exists('/var/lock/nebula-hostname.lock')):
            # We have to wait another cloud node is being configured
            pass
        os.system('touch /var/lock/nebula-hostname.lock')
        cntr = None
        if not os.path.exists('/var/lib/one/.nodecnt'):
            cntr = 1
            cntFl = open('/var/lib/one/.nodecnt', 'w')
            cntFl.write('%d' %cntr)
            cntFl.close()
        if not cntr:
            cntr = open('/var/lib/one/.nodecnt', 'r').read()
            cntr = eval(cntr)
            cntr += 1
            cntFl = open('/var/lib/one/.nodecnt', 'w')
            cntFl.write('%d' %cntr)
            cntFl.close()
        hostname = self.srvInfo['dhcpFeature'] + '-%d' %cntr
        os.remove('/var/lock/nebula-hostname.lock')
        return hostname

    #----------------------------------------------------------------------
    def handle(self):
        """Handle the information request"""
        requestType = self.request.recv(512).strip()
        if requestType == 'PROVIDE_CONFIG':
            syslog.openlog('[CLOUD_INFO_SRV]',syslog.LOG_PID,syslog.LOG_DAEMON)
            syslog.syslog(syslog.LOG_INFO, 'Provide cloud config info\n')
            self.srvInfo['hostname'] = self.generateHostName()
            for key in self.srvInfo.keys():
                # Send a key value pair for each entry
                self.request.send('%s=%s;' %(key,self.srvInfo[key]))
            self.request.send('COMPLETE=finished')
        syslog.closelog()

#---------------------------------------------------------------------------
def sigHandler(signum, frame):
    """Signal handler for termination"""
    syslog.openlog('[CLOUD_INFO_SRV]',syslog.LOG_PID,syslog.LOG_DAEMON)
    syslog.syslog(syslog.LOG_INFO, 'Terminating server on signal: %d' %signum)
    syslog.closelog()
    if os.path.exists('/var/lock/nebula-hostname.lock'):
        os.remove('/var/lock/nebula-hostname.lock')
    os.remove('/var/run/suseNebula/infoSrv.pid')
    sys.exit(0)

#---------------------------------------------------------------------------
if __name__ == "__main__":
    syslog.openlog('[CLOUD_INFO_SRV]', syslog.LOG_PID, syslog.LOG_DAEMON)
    syslog.syslog(syslog.LOG_INFO, 'Cloud Info Server start\n')

    # Consistency check for pid file
    if not os.path.exists('/var/run/suseNebula'):
        os.system('mkdir -p /var/run/suseNebula')
    if os.path.exists('/var/run/suseNebula/infoSrv.pid'):
        msg = 'Found pid file, another server is running, not starting a '
        msg += 'new one.'
        syslog.syslog(syslog.LOG_ERR, msg)
        syslog.closelog()
        sys.exit(1)
   
    server = SocketServer.TCPServer(('169.254.1.1',  8445), CloudConfigService)

    if not server:
        syslog.syslog(syslog.LOG_ERR, 'Could not create server, exiting\n')
        syslog.closelog()
        sys.exit(1)

    # Write pid to a file
    pid = os.getpid()
    if not os.path.exists('/var/run/suseNebula'):
        os.mkdir('/var/run/suseNebula')    
    pidFl = open('/var/run/suseNebula/infoSrv.pid', 'w');
    pidFl.write('%d\n' %pid)
    pidFl.close()

    # Register signal handler
    signal.signal(signal.SIGHUP, sigHandler)
    signal.signal(signal.SIGTERM, sigHandler)
    signal.signal(signal.SIGINT, sigHandler)

    # No additional message will be printed, close the log
    syslog.syslog(syslog.LOG_INFO, 'Cloud Info Server running\n')
    syslog.closelog()
    server.serve_forever()
    
