#!/usr/bin/python
# Copyright 2015 Comcast Cable Communications Management, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


''' 
    This reads a configuration file and checks for functioning
    network links in /sys/class/net/*, then emits a ks.cfg network line. 
    '''

import os
import re

# Global bad: 
global DEBUG
DEBUG = True

global TO_LOG
# This "logs" to stdout which is captured during kickstart
TO_LOG = True

# This is the standard interface we install to. It is set to a speed value of 
# 5 (vice 100,1000 or 10000) later on and any other interface will override it 
# if you've got something faster installed. 
standard_interface=['p4p1']

## These are configuration settings:
# Name of Configuration file:
cfg_file = "network.cfg"

# Where linux is putting the interface stuff: 
iface_dir = '/sys/class/net/'

ignore_interfaces = ['lo','bond']

# Where we kickstart mounts the ISO, and our config directory:
base_cfg_dir = '/mnt/stage2/ks_scripts/'

# Remember the ? makes the match non-greedy. This is important. 
cfg_line = re.compile("\s*(?P<key>.*?)=(?P<value>.*)\s*$")

# Pick the interface speed for bonding, or "Auto". 
# Auto assumes you want the fastest connections with more than 1 interface,
# Or if there's not 2 interfaces at the same speed you want the fastest.
# auto is expected to be a string, otherwise use integers: 
# Speed is in megs. 1000 is 1 gig, 10000 is 10G. 

iface_speed = 'auto'

restring="%(iface_dir)s(?P<iface>.*)/speed" %vars()
iface_search = re.compile(restring)


def read_config(config_file):
    ''' Reads our network config file and hands back a dict of key:value 
    pairs '''

    net_cfg = {} 
    with open(config_file,'r') as cfg: 
        network_lines = cfg.readlines()
        for line in network_lines:
            if cfg_line.match(line):
                key = cfg_line.match(line).group('key')
                value = cfg_line.match(line).group('value')
                net_cfg[key] = value
    return net_cfg


def find_usable_net_devs(location):
    ''' Search through iface_dir looking for /speed files. 
    Build a dict keyed on speed (in otherwords the speed is the key with a list of
    interfaces as teh value). '''
    # We "pre-seed" the dictionary with the standard interface names at a 
    # speed of 5 so that if there's nothing else we set that up. This 
    # makes it easier  to reconfigure later.
    ifaces = {5:standard_interface}
    bad_ifaces={}
    devs = os.listdir(location)
    for dev in devs:
        dev_path = os.path.join(location,dev,'speed')
        add=True
        if os.path.isfile(dev_path):
            with open(dev_path,'r') as iface:
                try:
                    speed = iface.readlines()
                    # speed should only have one line: 
                    speed = int(speed[0])
                # if there is no link some drivers/cards/whatever will 
                # throw an IOError when you try to read the speed file. 
                except IOError:
                    speed = 0
        # Other cards will return a -1, which is fine, but *some* of them 
        # return a 65535. Those we set to 0 as well.
        if speed == 65535:
            speed = 0
        for i_face in ignore_interfaces:
            if i_face in dev:
                add = False
        if speed  <= 0:
            add = False
        if TO_LOG: print add, dev
        if add:
            if speed in ifaces:
                this_speed = ifaces[speed]
                this_speed.append(dev)
                ifaces[speed]=this_speed
            else:
                ifaces[speed]=[dev]
        else:
            bad_ifaces[dev] = speed
    print "We find these interfaces have link and might be useful:", ifaces
    if TO_LOG: print "And these aren't useful:", bad_ifaces
    return ifaces


def useable_interfaces(net_devs, nc, iface_speed):
    ''' This takes a go at figuring out which interfaces to use.'''
    iface_list = False
    notes = False
    if TO_LOG: print "in usable interfaces"

    if not ("bond" in nc['BOND_DEVICE'].lower()):
        if TO_LOG: print "useable interfaces if not", nc['BOND_DEVICE'] 
        #  Not doing a bond, so  we check to make sure the requested device, 
        #  nc['BOND_DEVICE'], is in the list of devices with carrier: 
        if nc['BOND_DEVICE'] == '""': 
            # In this case we have no network interface in the configuration but we
            # network settings.
            # First we check how many net_devs we have: 
            if TO_LOG: print "nc['BOND_DEVICE']=''", len(net_devs), net_devs
            if len(net_devs) == 1: # This is a dict of speed: devices
                speeds = net_devs.keys()
                speeds.sort(reverse=True)
                speed = speeds[0]
                possibles = net_devs[speed]
                if TO_LOG: print possibles
                # At this point we have options, but no information, so: 
                notes = "No device in the configuration file and multiple devices found. Picking the first"
                iface_list = [possibles[0]]
        else:
            if TO_LOG: print "inner else"
            for speed in net_devs:
                if nc['BOND_DEVICE'] in net_devs[speed]:
                    iface_list = [nc['BOND_DEVICE']]
                else:
                    iface_list = [nc['BOND_DEVICE']]
                    notes = "{0} did not have carrier at install time, and may not work".format(nc['BOND_DEVICE'])
    elif iface_speed is not 'auto':
        if len(net_devs[iface_speed]) > 0:
            iface_list = net_devs[iface_speed]
        else:
            notes = "no devices set to {0}".format(iface_speed)
    else: # This SHOULD be iface_speed == auto, and nc['BOND_DEVCE'] containing bond. 
        #  if not it is anyway. 
        # Thus we are doing a bond of some sort. 
        # This gives us the fastest interfaces first: 
        speeds = net_devs.keys()
        speeds.sort(reverse=True)
        fastest = speeds[0]
        # Walk through "speeds" and take the first one that has more than one
        # interface. This will only set iface_list if there are 2 or more interfaces: 
        # previous_speed = 0
        for i in speeds:
            if len(net_devs[i]) > 1: 
                iface_list = net_devs[i]
                break
        if TO_LOG: print "iface list:", iface_list
        # if iface_list is still false, and we are requesting a bond, we will 
        # want the fastest interface with link: 
        if (iface_list == False) and ("bond" in nc['BOND_DEVICE'].lower()):
                if TO_LOG: print len(net_devs), net_devs, i
                if len(net_devs) == 0:
                    iface_list = net_devs
                    notes = "no devices found for the bond. Will not have network after reboot"
                else:
                    iface_list = net_devs[fastest] # This is assuming that we'll want to bond the fastest interaface.
                    if TO_LOG: print "dev:", net_devs[fastest]
    if TO_LOG: print iface_list, notes
    return iface_list, notes


# Find our network configuration file:
if os.path.isfile(os.path.join(base_cfg_dir,cfg_file)):
    cfg_path = os.path.join(base_cfg_dir,cfg_file)
elif os.path.isfile(cfg_file):
    cfg_path = cfg_file
else: 
    cfg_path = ''

if cfg_path: 
    nc = read_config(cfg_path)
else:
    # if we don't have a working config file we use this
    # The IPs and hostnames are bad.
    nc = { IPADDR:"10.0.0.2",
        NETMASK:"255.255.255.252",
        GATEWAY:"10.0.0.1",
        BOND_DEVICE:"bond0",
        MTU:"9000",
        NAMESERVER:"192.168.0.1",
        HOSTNAME:"bad.example.com",
        NETWORKING_IPV6:"yes",
        IPV6ADDR:" 2001:0db8:0a0b:12f0:0000:0000:0000:0002/64",
        IPV6_DEFAULTGW:" 2001:0db8:0a0b:12f0:0000:0000:0000:0001",
        BONDING_OPTS:"miimon=100 mode=4 lacp_rate=fast xmit_hash_policy=layer3+4",
        DHCP:"no" }
# This should be set to no in the config file, but that could change: 
if not nc.has_key('DHCP'):
    nc['DHCP']='no'

# This should be set to no in the config file, but that could change: 
if not nc.has_key('DHCP'):
    nc['DHCP']='no'

net_devs = find_usable_net_devs(iface_dir)
bondable_iface, iface_problems = useable_interfaces(net_devs, nc, iface_speed)

# turn bondable_iface into a string for the network line:
if bondable_iface and len(bondable_iface) > 1:
    dev_list = bondable_iface
    dev_str = dev_list.pop()
    for d in dev_list:
        dev_str="%(dev_str)s,%(d)s" %vars()
else: