#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# DHCP common stuff
# Based on work from Frédéric Pauget and Valentin Samir
#
# Author : Pierre-Elliott Bécue <becue@crans.org>
# Licence : GPLv3
# Date : 14/07/2014
#
# TODO : Take care of each XXX or TODO and think about updating
# gestion/config/dhcp when submitting for production, because
# of .test files.

import socket
import os
import struct

import gestion.config.trigger as trigger_config
import cranslib.clogger as clogger
logger = clogger.CLogger("trigger", "dhcp", trigger_config.log_level, trigger_config.debug)

hostname = socket.gethostname().split(".")[0]
hostname_adm = hostname + ".adm.crans.org"

import lc_ldap.shortcuts
from cranslib.conffile import ConfFile
import gestion.config.dhcp as dhcp_config
import gestion.secrets_new as secrets_new
import gestion.affichage as affichage
import gestion.iptools as iptools
from gestion.trigger.pypureomapi import pack_ip, pack_mac, OMAPI_OP_UPDATE, Omapi, OmapiMessage
from gestion.trigger.host import record_service

if "dhcp" in trigger_config.services[hostname]:
    dhcp_omapi_keyname = secrets_new.get("dhcp_omapi_keyname")
    dhcp_omapi_key = secrets_new.get("dhcp_omapi_keys")[hostname_adm]
    ldap_conn = lc_ldap.shortcuts.lc_ldap_readonly()
else:
    dhcp_omapi_keyname = None
    dhcp_omapi_key = None
    ldap_conn = None

@record_service()
def dhcp(ob_id, operations=None):
    """Regenerates dhcp service taking operations into account.

    """

    # http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html
    # dict are referenced.
    if operations is None:
        operations = {}

    if operations and isinstance(operations, dict):
        for (mac, ip, name) in operations.get("add", []):
            logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name)
            # XXX - Uncommend this when we need to start prod
            # add_dhcp_host(mac, ip, name)
        for (mac, ip) in operations.get("delete", []):
            logger.info("Updating DHCP db by deleting %s, %s", mac, ip)
            # XXX - Uncommend this when we need to start prod
            # delete_dhcp_host(mac, ip)
        for (rmac, rip, mac, ip, name) in operations.get("update", []):
            logger.info("Updating DHCP db by modifying %s, %s to %s, %s, %s", rmac, rip, mac, ip, name)
            # XXX - Uncommend this when we need to start prod
            # delete_dhcp_host(rmac, rip)
            # add_dhcp_host(mac, ip, name)
    elif operations == True:
        hosts = {}
        host_template = """
host %(nom)s {
    hardware ethernet %(mac)s;
    fixed-address %(ip)s;
    option host-name "%(host)s";
}
"""
        affichage.prettyDoin("Chargement des machines", "...")
        machines = ldap_conn.allMachines()
        affichage.prettyDoin("Chargement des machines", "Ok")
        animation = affichage.Animation(texte="Génération de la configuration",
                                        nb_cycles=len(machines),
                                        couleur=True,
                                        kikoo=True)

        for machine in machines:
            for net in dhcp_config.reseaux.keys():
                ip = str(machine['ipHostNumber'][0])
                mac = str(machine['macAddress'][0])
                nom = str(machine['host'][0])
                if '<automatique>' not in [ip, mac] and iptools.AddrInNet(ip, net):
                    d = {'nom' : nom,
                         'host' : nom.split(".", 1)[0],
                         'mac' : mac,
                         'ip' : ip,
                        }
                    try:
                        hosts[net] += host_template % d
                    except:
                        hosts[net] = host_template % d
            animation.new_step()
        # Put a \n after the last iteration.
        animation.end()

        step = "Enregistrement de la configuration dans les fichiers"
        affichage.prettyDoin(step, "...")
        for (net, fichier) in dhcp_config.reseaux.items():
            with ConfFile(fichier) as configFile:
                configFile.header("#")
                if hosts.has_key(net):
                    configFile.write(hosts[net])
        affichage.prettyDoin(step, "Ok")

        step = "Nettoyage des fichiers de leases"
        affichage.prettyDoin(step, "...")
        try:
            lease_clean()
            affichage.prettyDoin(step, "Ok")
        except:
            affichage.prettyDoin(step, "Erreur")
            print "During lease clean, an error occured."
            raise


def add_dhcp_host(mac, ip, name=None):
    """Adds a dhcp host using omapi

    """

    if '<automatique>' in [ip, mac]:
        return
    msg = OmapiMessage.open(b"host")
    msg.message.append((b"create", struct.pack("!I", 1)))
    msg.message.append((b"exclusive", struct.pack("!I", 1)))
    msg.obj.append((b"hardware-address", pack_mac(mac)))
    msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
    msg.obj.append((b"ip-address", pack_ip(ip)))
    if name:
        msg.obj.append((b"name", bytes(name)))
    conn = Omapi(hostname_adm, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
    _ = conn.query_server(msg)
    conn.close()


def delete_dhcp_host(mac, ip):
    """Deletes dhcp host using omapi

    """

    if '<automatique>' in [ip, mac]:
        return
    msg = OmapiMessage.open(b"host")
    msg.obj.append((b"hardware-address", pack_mac(mac)))
    msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
    msg.obj.append((b"ip-address", pack_ip(ip)))
    conn = Omapi(hostname_adm, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
    response = conn.query_server(msg)
    if response.opcode == OMAPI_OP_UPDATE:
        _ = conn.query_server(OmapiMessage.delete(response.handle))
    conn.close()


def lease_clean():
    """Clean the lease file

    """
    # TODO : use ConfigFile structure
    leasefile = open(dhcp_config.dhcplease)
    newleasefile = open(dhcp_config.dhcplease + '.new', 'w')
    line = leasefile.readline()
    write = True
    while line:
        if line.strip().startswith('host'):
            write = False
        if write:
            newleasefile.write(line)
        if not write and line.strip().endswith('}'):
            write = True
        line = leasefile.readline()
    leasefile.close()
    newleasefile.close()
    os.rename(dhcp_config.dhcplease+'.new', dhcp_config.dhcplease)
