#!/usr/bin/python

import sys, os, subprocess, shutil
import re
import constants
import utils, netinfo

import neterrors as ne

NET_CONF_PREF = utils.NET_CONF_DIR + 'ifcfg-'
PROC_NET_BONDING = '/proc/net/bonding'
CONFFILE_HEADER = '# automatically generated by vdsm'
DELETED_HEADER = '# original file did not exist'
EX_OVIRT_FUNCTIONS = '/usr/libexec/ovirt-functions'

def usage():
    print """Usage:

%s {bridge|''} {vlan-id|''} {bonding|''} {nic[,nic]|''}

Delete a network defined by a bridge, tagged by vlan-id,
connected through bonding device to nics (or parts thereof)

bridge - network name
vlan-id - integer 0-4095 or empty string if no vlan
bonding - bonding device name or empty string if a single nic
nic[,nic] - possibly-multiple nics
""" % (sys.argv[0])
    sys.exit(ne.ERR_BAD_PARAMS)

def conffileBackup(filename):
    import pwd
    try:
        os.mkdir(utils.NET_CONF_BACK_DIR)
	os.chown(utils.NET_CONF_BACK_DIR, pwd.getpwnam('vdsm').pw_uid, 0)
    except:
        pass
    if os.path.exists(EX_OVIRT_FUNCTIONS):
        subprocess.call([constants.EXT_SH, EX_OVIRT_FUNCTIONS,
                         'unmount_config', filename])
    (dummy, basename) = os.path.split(filename)
    backup = os.path.join(utils.NET_CONF_BACK_DIR, basename)
    if os.path.exists(backup):
        # original copy already backed up
        return
    if os.path.exists(filename):
        shutil.copy2(filename, backup)
    else:
        file(backup, 'w').write(DELETED_HEADER + '\n')
    os.chown(backup, pwd.getpwnam('vdsm').pw_uid, 0)

def assertBonding(bonding, nics):
    if not bonding:
        return

    if not re.match('^bond[0-9]+$', bonding):
        print 'delNetwork: %s is not a valid bonding device name' % bonding
        sys.exit(ne.ERR_BAD_BONDING)

    ensnics = _netinfo["bondings"][bonding]["slaves"]
    if set(nics) != set(ensnics):
        print 'delNetwork: %s are not all nics enslaved to %s %s' % (
                nics, bonding, ensnics)
        sys.exit(ne.ERR_BAD_NIC)

def assertVlan(vlan):
    if not vlan:
        return
    try:
        if not 0 <= int(vlan) <= 4095:
            raise ValueError
    except:
        print 'delNetwork: \'%s\' not a valid vlan id' % (vlan)
        sys.exit(ne.ERR_BAD_VLAN)

def assertBridgeClean(bridge, vlan, bonding, nics):
    if bridge not in _netinfo['networks']:
        print 'delNetwork: bridge \'%s\' unknown' % (bridge)
        sys.exit(ne.ERR_BAD_BRIDGE)
    brifs = os.listdir('/sys/class/net/%s/brif/' % bridge)
    for nic in nics:
        try:
            brifs.remove(nic)
        except:
            pass
    if vlan:
        brif = (bonding or nics[0]) + '.' + vlan
    else:
        brif = bonding
    try:
        brifs.remove(brif)
    except:
        pass

    if brifs:
        print 'bridge %s has interfaces %s connected' % (bridge, brifs)
        sys.exit(ne.ERR_USED_BRIDGE)

def rmOvirtFile(filename):
    if os.path.exists(EX_OVIRT_FUNCTIONS):
        subprocess.call([constants.EXT_SH, EX_OVIRT_FUNCTIONS,
                         'ovirt_safe_delete_config', filename])
    else:
        utils.rmFile(filename)

def ifaceUsers(iface):
    users = set()
    for b, bdict in _netinfo['networks'].iteritems():
        if iface in bdict['ports']:
            users.add(b)
    for b, bdict in _netinfo['bondings'].iteritems():
        if iface in bdict['slaves']:
            users.add(b)
    for v, vdict in _netinfo['vlans'].iteritems():
        if iface == vdict['iface']:
            users.add(v)
    return users

def nicOtherUsers(bridge, vlan, bonding, nic):
    if bonding:
        owner = bonding
    elif vlan:
        owner = nic + '.' + vlan
    else:
        owner = bridge
    users = ifaceUsers(nic)
    if bonding:
        users.update(bondingOtherUsers(bridge, vlan, bonding))
    users.discard(owner)
    return users

def bondingOtherUsers(bridge, vlan, bonding):
    if vlan:
        owner = bonding + '.' + vlan
    else:
        owner = bridge
    users = ifaceUsers(bonding)
    users.discard(owner)
    return users

def ifdown(iface):
    p = subprocess.Popen([constants.EXT_IFDOWN, iface], stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE, close_fds=True)
    out, err = p.communicate()
    sys.stdout.write(out)
    sys.stderr.write('\n'.join([line for line in err.splitlines()
                                if not line.endswith(' does not exist!')]))
    return p.returncode


if len(sys.argv) <= 4:
    usage()

bridge, vlan, bonding, nics = sys.argv[1:5]
if nics == '':
    nics = []
else:
    nics = nics.split(',')

options = {}
for arg in sys.argv[5:]:
    k, v = arg.split('=', 1)
    options[k] = v

if len(nics) > 1 and not bonding:
    usage()

_netinfo = netinfo.get()
if not utils.tobool(options.get('force')):
    assertBonding(bonding, nics)
    assertVlan(vlan)
    assertBridgeClean(bridge, vlan, bonding, nics)

if bridge:
    ifdown(bridge)
    subprocess.call([constants.EXT_BRCTL, 'delbr', bridge])
if vlan:
    vlandev = (bonding or nics[0]) + '.' + vlan
    ifdown(vlandev)
    subprocess.call([constants.EXT_VCONFIG, 'rem', vlandev], stderr=subprocess.PIPE)
if bonding:
    if not bondingOtherUsers(bridge, vlan, bonding):
        ifdown(bonding)
for nic in nics:
    if not nicOtherUsers(bridge, vlan, bonding, nic):
        ifdown(nic)
for nic in nics:
    if nicOtherUsers(bridge, vlan, bonding, nic):
        continue
    cf = NET_CONF_PREF + nic
    conffileBackup(cf)
    try:
        hwlines = [ line for line in file(cf).readlines()
                    if line.startswith('HWADDR=') ]
        l = ['DEVICE=%s\n' % nic, 'ONBOOT=yes\n', 'BOOTPROTO=none\n'] + hwlines
        file(cf, 'w').writelines(l)
    except IOError:
        pass
if bonding:
    if not bondingOtherUsers(bridge, vlan, bonding):
        conffileBackup(NET_CONF_PREF + bonding)
        rmOvirtFile(NET_CONF_PREF + bonding)
if vlan:
    conffileBackup(NET_CONF_PREF + (bonding or nics[0]) + '.' + vlan)
    rmOvirtFile(NET_CONF_PREF + (bonding or nics[0]) + '.' + vlan)
if bridge:
    conffileBackup(NET_CONF_PREF + bridge)
    rmOvirtFile(NET_CONF_PREF + bridge)
    # the deleted bridge should never be up at this stage.
    if bridge in netinfo.bridges():
        print 'delNetwork: bridge %s still exists' % bridge
        sys.exit(ne.ERR_USED_BRIDGE)
