#!/usr/bin/python

"""SUSENebula cloud node configurator."""

import glob
import os
import re
import socket
import sys
import syslog
import time

#------------------------------------------------------------------------
def configureNode(confSettings):
    """Configure this node."""
    # Setup keyboard layout
    layout = confSettings['keyLayout']
    msg = 'Setting keyboard layout to: %s\n' %layout
    syslog.syslog(syslog.LOG_INFO, msg)
    os.system('/sbin/yast2 keyboard set layout=%s' %layout)
    # Setup the timezone
    zone = confSettings['timezone']
    hwc = confSettings['hwclock'].lower()
    msg = 'Setting timezone and hwclock to: %s, %s' %(zone, hwc)
    syslog.syslog(syslog.LOG_INFO, msg)
    os.system('/sbin/yast2 timezone set timezone=%s hwclock=%s' %(zone, hwc))
        # Setup hostname
    domain = confSettings['domainname']
    name = confSettings['hostname']
    host = '%s.%s' %(name, domain)
    msg = 'Setting hostname to: %s\n' %host
    hf = open('/etc/HOSTNAME', 'w')
    hf.write(host)
    hf.close()
    # Add the hostname to the dhcp configuration info
    lines = open('/etc/sysconfig/network/dhcp', 'r').readlines()
    dhf = open('/etc/sysconfig/network/dhcp', 'w')
    for ln in lines:
        if ln.find('DHCLIENT_HOSTNAME_OPTION') != -1:
            dhf.write('DHCLIENT_HOSTNAME_OPTION="%s"' %name)
            continue
        dhf.write(ln)
    dhf.close()
    # Add the dhcp feature to avoid dhcp acceptance from servers other than
    # the head node
    feature = confSettings['dhcpFeature']
    msg = 'Setting dhclient required feature to: %s\n' %feature
    syslog.syslog(syslog.LOG_INFO, msg)
    dhcf = open('/etc/dhclient.conf','a')
    dhcf.write('option %s code 239 = ip-address;\n' %feature)
    dhcf.write('request %s;\n' %feature)
    dhcf.write('require %s;\n' %feature)
    dhcf.close()
    # Setup the name resolution
    syslog.syslog(syslog.LOG_INFO, 'Setting up resolve.conf\n')
    res = open('/etc/resolv.conf', 'w')
    res.write(confSettings['resolve'])
    res.close()
    # Setup the routes
    syslog.syslog(syslog.LOG_INFO, 'Setting up routes\n')
    routes = open('/etc/sysconfig/network/routes', 'w')
    routes.write(confSettings['routes'])
    routes.close()
    # Setup the root password
    syslog.syslog(syslog.LOG_INFO, 'Setting root password\n')
    lines = open('/etc/shadow', 'r').readlines()
    shadow = open('/etc/shadow','w')
    for ln in lines:
        if ln[:4] == 'root':
            shadow.write('root:')
            shadow.write(confSettings['rootpass'])
            shadow.write(':')
            shadow.write(ln.split(':')[2])
            shadow.write('::::::\n')
            continue
        shadow.write(ln)
    shadow.close()
    # Setup the cloud administartor acount
    # Use eval; convert strings to tuple
    gname, gid = eval(confSettings['groupID'])
    uname, uid, pwd = eval(confSettings['userID'])
    msg = 'Setting up %s with uid, gid: %s, %s\n' %(uname,uid,gid)
    syslog.syslog(syslog.LOG_INFO, msg)
    res = os.system('/usr/sbin/groupadd -g %s %s' %(gid, gname))
    if res:
        msg = 'Could not add %s group. ' %gname
        msg += 'Incomplete configuration\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    groups = 'kvm,root,%s' %gname
    res = os.system(
        '/usr/sbin/useradd -c "%s" -d %s -g %s -G %s -u %s -p %s -m %s'
        %('Cloud admin', '/var/lib/one', gid, groups, uid, pwd, uname))
    if res:
        msg = 'Could not add %s user. ' %uname
        msg += 'Incomplete configuration\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    # Setup nfs mount in fstab
    syslog.syslog(syslog.LOG_INFO, 'Setting up NFS mount in fstab\n')
    fst = open('/etc/fstab', 'a')
    fst.write(confSettings['serverIP'])
    fst.write(':/var/lib/one')
    fst.write('\t/var/lib/one\tnfs\tdefaults,comment=systemd.automount\t1 1\n')
    fst.close()
            
    return 1

#----------------------------------------------------------------------------
def getConfigFromServer():
    """Get the configuration from the service running on the head node."""
    syslog.syslog(syslog.LOG_INFO, 'Obtain node configuration from head\n')
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('169.254.1.1',  8445))
    except:
        msg = 'Could not connect to head node at 169.254.1.1 port  8445\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    # Request the configuration info from the head node
    s.send('PROVIDE_CONFIG')
    confSettings = {}
    confData = ''
    # Read the configuration data from the socket
    while 1:
        data = s.recv(4096)
        confData += data
        if confData.find('COMPLETE') != -1:
            break
    s.close()
    # Populate the configuration data dict
    for confItem in confData.split(';'):
        key, value = confItem.split('=', 1)
        if key != 'COMPLETE':
            confSettings[key] = value
    return confSettings

#----------------------------------------------------------------------------
def getFirstConnectedInterface():
    """Find the interface that has a cable connected."""
    # Bring up every interface so we can reliably figure out the one
    # that is connected
    iFaces = glob.glob('/sys/class/net/eth*')
    for iFace in iFaces:
        name = iFace.split('/')[-1]
        os.system('/sbin/ifconfig %s up' %name)
        # Allow time for the interface to activate
        time.sleep(5)
                
    iFaceInfo = os.popen('/usr/sbin/hwinfo --network').readlines()
    iFaceName = None
    processBlock = None
    for ln in iFaceInfo:
        if ln.strip() == '':
            processBlock = None
            continue
        if processBlock:
            if ln.find('SysFS ID') != -1:
                iFaceName = ln.split(':')[-1].split('/')[-1].strip()
                continue
            if ln.find('Link detected') != -1:
                carrier = ln.split(':')[-1].strip()
                if carrier == 'yes':
                    if iFaceName:
                        return iFaceName
                else:
                    continue
        if re.match(r'^\d+:.*Ethernet$',ln):
            processBlock = 1
            continue

    return -1 # No network interface is connected 

#----------------------------------------------------------------------------
def registerNode():
    """Register this node with the head node."""
    syslog.syslog(syslog.LOG_INFO, 'Register the node with the head node.\n')
    # Get the IP and MAC Address of this node
    ipInfo = os.popen('/sbin/ifconfig').readlines()
    processBlk = None
    ipAddr = None
    macAddr = None
    for ln in ipInfo:
        if ipAddr and macAddr:
            break
        if processBlk:
            if ln.find('inet addr') != -1:
                ipAddr = ln.split(':')[1].split()[0].strip()
                continue
        # We always setup br0, see setupCloudNetwork()
        if ln.find('br0') != -1:
            processBlk = 1
            macAddr = ln.split()[-1].strip()
    if not ipAddr:
        msg = 'Could not determine IP of this node. '
        msg += 'Unable to gergister node.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    if not macAddr:
        msg = 'Could not determine MAC of this node. '
        msg += 'Unable to gergister node.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    # Get the server IP from fstab, see configureNode()
    fst = open('/etc/fstab','r').readlines()
    servIP = None
    for ln in fst:
        if ln.find('/var/lib/one') != -1:
            servIP = ln.split(':')[0].strip()
    if not servIP:
        msg = 'Could not determine head node IP from /etc/fstab. '
        msg += 'Unable to gergister node.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    # Hostname
    myName = open('/etc/HOSTNAME', 'r').read().split('.')[0].strip()
    # Connect to server and register
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((servIP,  8445))
    except:
        msg = 'Could not connect to head node at %s port  8445\n' %servIP
        syslog.syslog(syslog.LOG_INFO, msg)
        return -1
    regInfo = '%s;%s;%s' %(macAddr, ipAddr, myName)
    s.send(regInfo)
    s.close()

    return 1
    
#----------------------------------------------------------------------------
def removeConfNetworkSetting(iFace):
    """Shutdown the configuration network and remove the configuration."""
    syslog.syslog(syslog.LOG_INFO, 'Shutdown configuration network\n')
    os.system('/sbin/ifdown %s' %iFace)
    msg = 'Remove configuration network interface config\n'
    syslog.syslog(syslog.LOG_INFO, msg)
    os.remove('/etc/sysconfig/network/ifcfg-%s' %iFace)

#----------------------------------------------------------------------------
def setupCloudNetwork(iFace):
    """Setup the configuration for the cloud network and fire it up."""
    msg = 'Setup cloud network configuration as br0\n'
    syslog.syslog(syslog.LOG_INFO, msg)
    cfgF = open('/etc/sysconfig/network/ifcfg-br0', 'w')
    cfgF.write("BOOTPROTO='dhcp'\n")
    cfgF.write("BRIDGE='yes'\n")
    cfgF.write("BRIDGE_FORWARDDELAY='0'\n")
    cfgF.write("BRIDGE_PORTS='%s'\n" %iFace)
    cfgF.write("BRIDGE_STP='off'\n")
    cfgF.write("BROADCAST=''\n")
    cfgF.write("ETHTOOL_OPTIONS=''\n")
    cfgF.write("MTU=''\n")
    cfgF.write("NAME=''\n")
    cfgF.write("NETWORK=''\n")
    cfgF.write("REMOTE_IPADDR=''\n")
    cfgF.write("STARTMODE='auto'\n")
    cfgF.write("USERCONTROL='no'")
    cfgF.close()
    
#----------------------------------------------------------------------------
def setupConfNetwork(iFace):
    """Setup the interface provided as the configuration network with a
    static IP."""
    syslog.syslog(syslog.LOG_INFO, 'Setup configuration network 169.254.1.2\n')
    cfgF = open('/etc/sysconfig/network/ifcfg-%s' %iFace, 'w')
    cfgF.write("BOOTPROTO='static'\n")
    cfgF.write("BROADCAST=''\n")
    cfgF.write("ETHTOOL_OPTIONS=''\n")
    cfgF.write("IPADDR='169.254.1.2/24'\n")
    cfgF.write("MTU=''\n")
    cfgF.write("NAME='Cloud configuration network'\n")
    cfgF.write("NETWORK=''\n")
    cfgF.write("REMOTE_IPADDR=''\n")
    cfgF.write("STARTMODE='auto'\n")
    cfgF.write("USERCONTROL='no'")
    cfgF.close()

#----------------------------------------------------------------------------
def startCloudNetwork():
    """Start the cloud network."""
    syslog.syslog(syslog.LOG_INFO, 'Starting cloud network\n')
    os.system('/sbin/ifup br0')
    cnt = 0
    # Cannot depend on the ifup exit status as we need to play a trick to
    # disable IPv6 which always leads to a non zero exit status
    # Therefore we probe whether or not we get an IP address assigned
    while cnt < 10:
        time.sleep(15) # give dhcp a chance to get an address
        processBlock = None
        ifaceInfo = os.popen('/sbin/ifconfig').readlines()
        for ln in ifaceInfo:
            if ln.find('br0') != -1:
                processBlock = 1
                continue
            if processBlock and ln.find('inet addr') != -1:
                addr = ln.split(':')[1].split()[0].strip()
                if addr.find('.') != -1:
                    cnt = 99
                    break
        cnt += 1
    if cnt != 100:
        return -1

    return 1

#----------------------------------------------------------------------------
def startNetwork(iFace, type):
    """Start the specified network interface."""
    msg = 'Start %s network interface on %s\n' %(type, iFace)
    syslog.syslog(syslog.LOG_INFO, msg)
    res = os.system('/sbin/ifup %s' %iFace)
    if res:
        return -1

    return 1

#----------------------------------------------------------------------------
if __name__ == "__main__":
    syslog.openlog('[CLOUD_NODE_SETUP]', syslog.LOG_PID, syslog.LOG_DAEMON)
    syslog.syslog(syslog.LOG_INFO, 'Start node registration and setup\n')
    iFace = getFirstConnectedInterface()
    if iFace == -1:
        msg = 'Could not determine connected network interface aborting'
        msg += ' node configuration\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    setupConfNetwork(iFace)
    res = startNetwork(iFace, 'configuration')
    if res == -1:
        msg = 'Could not start the network interface to configure the '
        msg += 'cloud node, manual configuration required.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    confDict = getConfigFromServer()
    if confDict == -1:
        msg = 'Could not connect to configuration server at 169.254.1.1 '
        msg += 'cloud node, manual configuration required.\n'
        msg += 'This IP:  169.254.1.2\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    removeConfNetworkSetting(iFace)
    setupCloudNetwork(iFace)
    res = configureNode(confDict)
    if res == -1:
        msg = 'Configuration failure. Manual configuration required.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    # Setup cloud network
    res = startCloudNetwork()
    if res == -1:
        msg = 'Cloud network setup and start up failed. Manual configuration '
        msg += 'required\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    # Register the node with the head node
    res = registerNode()
    if res == -1:
        msg = 'Could not register the node. Manual registration on head node '
        msg += 'required. (dhcp and one)\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    # Mount the oneadmin home directory
    msg = 'Mount oneadmin home directory.\n'
    syslog.syslog(syslog.LOG_INFO, msg)
    res = os.system('mount /var/lib/one')
    if res != 0:
        msg = 'Mount of oneadmin home directory failed. Manual mount '
        msg += 'required.\n'
        syslog.syslog(syslog.LOG_INFO, msg)
        syslog.closelog()
        sys.exit(1)
    # tmp dir for oned
    if not os.path.exists('/var/tmp/one'):
        if not os.path.exists('/var/tmp'):
            os.mkdir('/var/tmp')
        os.mkdir('/var/tmp/one')
        os.system('chown oneadmin:cloud /var/tmp/one')
            
    msg = 'Cloud node configuration completed with success.\n'
    syslog.syslog(syslog.LOG_INFO, msg)
    syslog.closelog()
