#!/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()
