#!/usr/bin/python3
import json
import re
import subprocess
from subprocess import Popen, PIPE
import time
import datetime
import logging
import os
from os import path
import sys
from collections import defaultdict
from collections import OrderedDict
try:
    from viavi.mts.device_th import send_od_to_otu_logging, log_ztp_update_info
except ImportError:
    from jdsu.mts.device_th import send_od_to_otu_logging, log_ztp_update_info
try:
    from viavi.mts.ScpiAccess import ScpiAccess, EsrError
except ImportError:
    from jdsu.mts.ScpiAccess import ScpiAccess, EsrError

########################################
# Check key(s) in a dictionary
def checkKey(dict, key):
    if key in dict.keys():
        return dict[key]
    else:
        return None
def checkKeys(dict, liste):
    subdict=dict
    for n in range(0, len(liste)):
        if not subdict:
            return None
        elem = checkKey(subdict,liste[n])
        if elem == None:
            return None
        subdict=subdict[liste[n]]
    return elem


###################################################
def verify_ztp_conf_file_availability(upg_file_path):
    file_path = path.exists(upg_file_path)
    if file_path != True:
        #print ("Input file ", upg_file_path, "does not exist")
        rc = False
    else:
        #print ("Input file is available at ", sys.argv[1])
        rc = True
    return rc


###################################################
def set_status_and_msg_code(e_code):
    if e_code > 0:
        return (FAILED_STATUS, CONFIG_FAILED_MSG)
    else:
        return (SUCCESS_STATUS, CONFIG_ACCEPTED_MSG)


##################################################
def  log_error_to_console_userlog(start_time, err_details, e_code, current_otu_version, loc_ztp_conf_update_time):
    #
    #  Create error detail message err_detail
    ztp_conf_upd_status = FAILED_STATUS
    #
    #  Calculate upgrade duration, set the update status and log the data
    update_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
    #
    #  Log message at console and user log
    log_mnfdata(err_details, ztp_conf_upd_status, current_otu_version, update_dur, loc_ztp_conf_update_time)
    #
    #  Set the error code and exit the script
    ztp_out_console = send_to_console_jsonformat(ztp_conf_upd_status, e_code, update_dur, err_details, None)
    sys.stdout.write(ztp_out_console)
    sys.stdout.flush()
    return

#######################################################################
def log_mnfdata(con_error, status, current_otu_version, duration, upd_time):
    d_state = OrderedDict((("ztp_conf_upd_status" , status), \
                           ("current_otu_version", current_otu_version), \
                           ("duration", str(duration))))
    ztp_conf_upd_time = "OTU ZTP Upgarde Info ----  " + upd_time
    send_od_to_otu_logging(con_error, \
                        ztp_conf_upd_time, \
                        d_state)
    return True


###################################################
def send_to_console_jsonformat(ztp_conf_upd_status, lerr_code, update_dur, err_details, ztp_pl_conf):
    if ztp_pl_conf == None:
        otu_conf= OrderedDict((("status" , ztp_conf_upd_status), \
                               ("return_code", str(lerr_code)), \
                               ("duration", str(update_dur)), \
                               ("message", err_details)))
        otu_conf_state = json.dumps(otu_conf, sort_keys=False, indent=4)
        otu_conf_state = otu_conf_state.replace("}","}\n")
    else:
        otu_conf= OrderedDict((("status" , ztp_conf_upd_status), \
                               ("return_code", str(lerr_code)), \
                               ("duration", str(update_dur)), \
                               ("message", err_details), \
                               ("details", ztp_pl_conf)))
        otu_conf_state = json.dumps(otu_conf, sort_keys=False, indent=4)
    return (otu_conf_state)


#############################################################
def handle_otu_device_profile(jconf_data):
    dev_config_status = 0
    conf_upd_start = time.time()

    dev_cdi = checkKeys(jconf_data, ['device', 'name'])
    if dev_cdi != None:
        try:
            otu_p.SendCommand("OTU:NAME \"%s\"" %dev_cdi)
            otu_device = Device(dev_cdi)
        except Exception as error_e:
            otu_device = Device("Please-Give-Me-A-Name")
            ret = 1
    else:
        otu_device = Device("Please-Give-Me-A-Name")

    dev_cdi = checkKeys(jconf_data, ['device', 'serial_number'])
    if dev_cdi != None:
        otu_device.serial_number = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'type'])
    if dev_cdi != None:
        otu_device.type = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'module_type'])
    if dev_cdi != None:
        otu_device.module_type = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'port_count'])
    if dev_cdi != None:
        otu_device.port_count = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'date'])
    if dev_cdi != None:
        otu_device.date = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'software_version'])
    if dev_cdi != None:
        otu_device.software_version = dev_cdi

    dev_cdi = checkKeys(jconf_data, ['device', 'licences'])
    if dev_cdi != None:
        otu_device.licences = dev_cdi

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(dev_config_status)
    return (dev_config_status, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_snmp_polling_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    snmp_value = checkKeys(jconf_data, ['snmp_polling_agent','active' ])
    if snmp_value != None:
        try:
            if str(snmp_value).lower() == "true":
                otu_p.SendCommand("OTU:API:CONF:SNMP:AGENT TRUE")
            else:
                otu_p.SendCommand("OTU:API:CONF:SNMP:AGENT FALSE")
        except Exception as error_e:
            ret += 1
            pass

    snmp_value = checkKeys(jconf_data, ['snmp_polling_agent','community' ])
    if snmp_value != None:
       cmd = "sed -i -r 's/^com2sec readwrite default.*/com2sec readwrite default     %s/' /etc/snmp/snmpd.conf"%snmp_value
       ret += subprocess.call(cmd, shell=True)

    snmp_value = checkKeys(jconf_data, ['snmp_polling_agent','port' ])
    if snmp_value != None:
       cmd = "sed -i -r 's/^agentAddress .*/agentAddress udp:%s,tcp:%s,udp6:%s,tcp6:%s/' /etc/snmp/snmpd.conf"\
               %(snmp_value,snmp_value,snmp_value,snmp_value)
       ret += subprocess.call(cmd, shell=True)

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


###############################################################
def set_otu_snmp_traphosts(jconf_data):
    ret = 0
    #
    #  Fetch the list of ztp config items
    z_traps = OrderedDict()
    z_traps = checkKeys(jconf_data, ['snmp_traps','traphosts' ])

    if z_traps != None:
        #
        #  Iterate through the list to extract
        #  the list of snmp trap managers
        for item in z_traps:
            snmp_id = item['id']
            snmp_ip = item['ip']
            snmp_comm = item['community']
            snmp_port = item['port']

            #
            #  Compose the snmp_traps_manager command
            scpi_trap_text = str(snmp_id) + ",\"" + snmp_ip + "\",\"" + snmp_comm + "\"," +  str(snmp_port)
            scpi_trap_req = "OTU:API:CONF:SNMP:CONF "
            scpi_trap_host_cmd = scpi_trap_req + scpi_trap_text
            #print ("SCPI Trap Command = ", scpi_trap_host_cmd)

            try:
                #
                #  Send the OTU:API:CONF:SNMP:CONF
                conf_code = otu_p.SendCommand(scpi_trap_host_cmd)
                if conf_code != None:
                    ret += 1
            except Exception as error_e:
                ret = 1

    return (ret)


################################################################
def snmp_traps_enable_disable(jconf_data):
    ret = 0

    try:
        #
        #  Request to Enable/Disable SNMP trap handling
        #  SCPI Command "OTU:API:CONF:SNMP:ENABLE
        ztp_snmp_traps = checkKeys(jconf_data, ['snmp_traps','active'])
        if isinstance(ztp_snmp_traps, bool):
            conf_code = otu_p.SendCommand("OTU:API:CONF:SNMP:ENABLE %s"%str(ztp_snmp_traps))
            if conf_code != None:
                ret += 1
    except Exception as error_e:
        ret = 1

    return (ret)


#################################################################
def snmp_traps_iamalive(jconf_data):
    DEFAULT_INTERVAL = "7"
    DISABLED = "disable"
    ret = 0

    try:
        #
        #  One SCPI command will handle the configuration of the
        #  "i_am_alive" and the "i_am_alive_period_minutes"
        #  config items
        #
        #  Required SCPI command is OTU:API:CONF:SNMP:IMAlive X
        #    if X = "?" -  the SCPI command is a query which returns the
        #                  current interval (NOT USED in this method)
        #    if X is a numeric entry, the command will will set the
        #                   alive interval to the specified number
        #    if X = 0   -  the SCPI command disables the
        #                  "I'm Alive" trap
        #
        ztp_imalive_trap = checkKeys(jconf_data, ['snmp_traps','i_am_alive'])
        ztp_imalive_interval = checkKeys(jconf_data, ['snmp_traps','i_am_alive_period_minutes'])
        if ztp_imalive_trap == DISABLED:
            conf_code = otu_p.SendCommand("OTU:API:CONF:SNMP:IMAlive %s"%str(0))
        elif ztp_imalive_interval != None:
             conf_code = otu_p.SendCommand("OTU:API:CONF:SNMP:IMAlive %s"%str(ztp_imalive_interval))
        else:
             conf_code = otu_p.SendCommand("OTU:API:CONF:SNMP:IMAlive %s"%DEFAULT_INTERVAL)

        if conf_code != None:
                ret += 1
    except Exception as error_e:
        ret = 1

    return (ret)


#################################################################
def handle_snmp_traps_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    #
    #  Extract the SNMP Trap Managers list and compose
    #  the SCPI command to set the list of SNMP traps managers
    ret += set_otu_snmp_traphosts(jconf_data)

    #
    #  Handle the "snmp_traps"-"active" config item
    ret += snmp_traps_enable_disable(jconf_data)

    #
    #  Handle the "snmp_traps"-"i_am_alive"
    #  and "i_am_alive_period_minutes" config items
    #  Note 1:  The alive period should be configured
    #           after the snmp_traphosts are configured
    ret += snmp_traps_iamalive(jconf_data)

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)

    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_ipv4_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    try:
        ip_value = checkKeys(jconf_data, ['ipv4_management','dhcp' ])
        if ip_value == True:
            otu_p.SendCommand("OTU:SYST:COM:MODE DHCP")

        else:
            if ip_value == False:
                otu_p.SendCommand("OTU:SYST:COM:MODE FIX")
                ip_value = checkKeys(jconf_data, ['ipv4_management','ip' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:IP ETH0,%s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management','subnet_mask' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:SUBNET ETH0,%s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management','gateway' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:GATE %s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management','dns' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:DNS %s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management','domain' ])
                if len(ip_value) > 0:
                   otu_p.SendCommand("OTU:SYST:COM:DOMAIN \"%s\""%ip_value)
    except Exception as error_e:
        ret = 1

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_ipv6_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    try:
        ipv6_active = checkKeys(jconf_data, ['ipv6_management','active' ])
        if ipv6_active == None or ipv6_active == True:
            ip_value = checkKeys(jconf_data, ['ipv6_management','dhcp' ])
            if ip_value == True:
                otu_p.SendCommand("OTU:SYST:COM:IPV6:MODE DHCP")
            else:
                if ip_value == False:
                    otu_p.SendCommand("OTU:SYST:COM:IPV6:MODE FIX")
                    ip_value = checkKeys(jconf_data, ['ipv6_management','ip' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:IP ETH0,\"%s\""%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management','gateway' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:GATE \"%s\""%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management','dns' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:DNS \"%s\""%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management','site' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:SITE \"%s\""%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management','prefix' ])
                    if len(ip_value) > 0:
                        try:
                            otu_p.SendCommand("OTU:SYST:COM:IPV6:NPRE ETH0, %s"%ip_value)
                        except Exception:
                            pass
        else:
            otu_p.SendCommand("OTU:SYST:COM:IPV6:MODE OFF")

    except Exception as error_e:
        ret = 1

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_ipv4_backup_intf_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    try:
        ip_value = checkKeys(jconf_data, ['ipv4_management_backup','dhcp' ])
        if ip_value == True:
            otu_p.SendCommand("OTU:SYST:COM:NMODE ETH1,DHCP")

        else:
            if ip_value == False:
                otu_p.SendCommand("OTU:SYST:COM:NMODE ETH1,FIX")
                ip_value = checkKeys(jconf_data, ['ipv4_management_backup','ip' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:IP ETH1,%s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management_backup','subnet_mask' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:SUBNET ETH1,%s"%ip_value)
                ip_value = checkKeys(jconf_data, ['ipv4_management_backup','gateway' ])
                if len(ip_value) > 0:
                   ip_value=ip_value.replace('.',',')
                   otu_p.SendCommand("OTU:SYST:COM:NGATE ETH1,%s"%ip_value)
    except Exception as error_e:
        ret = 1

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_ipv6_backup_intf_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    try:
        ipv6_active = checkKeys(jconf_data, ['ipv6_management_backup','active' ])
        if ipv6_active == None or ipv6_active == True:
            ip_value = checkKeys(jconf_data, ['ipv6_management_backup','dhcp' ])
            if ip_value == True:
                otu_p.SendCommand("OTU:SYST:COM:IPV6:NMODE ETH1, DHCP")
            else:
                if ip_value == False:
                    otu_p.SendCommand("OTU:SYST:COM:IPV6:NMODE ETH1, FIX")
                    ip_value = checkKeys(jconf_data, ['ipv6_management_backup','ip' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:IP ETH1,%s"%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management_backup','gateway' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:NGATE ETH1, %s"%ip_value)
                    ip_value = checkKeys(jconf_data, ['ipv6_management_backup','prefix' ])
                    if len(ip_value) > 0:
                       otu_p.SendCommand("OTU:SYST:COM:IPV6:NPRE ETH1,%s"%ip_value)
        else:
            otu_p.SendCommand("OTU:SYST:COM:IPV6:NMODE ETH1, OFF")

    except Exception as error_e:
        ret = 1

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)

#############################################################
def handle_ntp_server_profile(jconf_data):
    ret = 0
    STATIC_MODE = "STATIC"
    DYNAMIC_MODE = "DYN"
    NOT_CONFIGURED = "server_not_specified"
    conf_upd_start = time.time()
    #
    #  Enable/disable NTP server configuration
    #  SCPI Command -- otu:api:conf:ntp:enable true/false
    nss = checkKeys(jconf_data, ['ntp_server','active'])
    if nss != None:
        ntp_active = nss == True
        try:
            ntp_endis_req = "otu:api:conf:ntp:enable "+str(ntp_active)
            otu_p.SendCommand(ntp_endis_req)
            #print ("\nNTP Enable/Disable request = ", ntp_endis_req)
        except:
            #print ("\nExc - NTP Enable/Disable request = ", ntp_endis_req)
            ret += 1

    #
    #  NTP Server Configuration
    #  SCPI Command -- otu:api:conf:ntp:conf true/false, "IP/Host"
    ntp_state = checkKeys(jconf_data, ['ntp_server','dynamic'])
    ntp_srv = checkKeys(jconf_data, ['ntp_server','ip'])
    ntp_mode = STATIC_MODE
    if (ntp_state != None) and (str(ntp_state).lower() == "true"):
        ntp_mode = DYNAMIC_MODE
    #
    if (ntp_srv == None):
        ntp_srv_ip = ""
    elif len(ntp_srv) == 0:
        ntp_srv_ip = ""
    else:
        ntp_srv_ip = ntp_srv
    #
    if (ntp_state != None) and (ntp_srv != None):
        try:
            ntp_conf_req = "otu:api:conf:ntp:conf "+str(ntp_mode)+", \""+str(ntp_srv_ip)+"\""
            ntp_cmd = otu_p.SendCommand(ntp_conf_req)
            #print ("\nNTP Server SCPI request = ", ntp_conf_req)
        except:
            #print ("\nExc - NTP Server SCPI request = ", ntp_conf_req)
            ret += 1

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)

    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_https_security_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    #
    #  General Note:  Each of the following configurations is subjected
    #  to a verification/validation check which producess a sanitized
    #  copy of either a private key, certifcate or chain-certificate
    #  as appropriate to the config item.  The sanitized file is then
    #  installed in the appropriate /etc/pki/tls target directory.
    #

    #
    #  ssl_private_key
    ssl_key = checkKeys(jconf_data, ['https_security','ssl_private_key'])
    #print ("SSL_KEY ====== \n", ssl_key)
    if len(ssl_key) > 0:
        with open('/tmp/ztp_ssl_private.key', 'w') as temp_key_file:
            temp_key_file.write(ssl_key)
            temp_key_file.write("\n")
            temp_key_file.close()

        ssl_cmd = "chmod 766 /tmp/ztp_ssl_private.key"
        subprocess.call(ssl_cmd, shell=True)

        ssl_cmd = "/usr/lib/jdsu/scripts/otu_check_certificates.sh /tmp/ztp_ssl_private.key sslkey >/dev/null 2>&1 "
        locret = subprocess.call(ssl_cmd, shell=True)

        if locret == 0:
            ssl_cmd = "/usr/lib/jdsu/scripts/smartotu_copy_files.sh /tmp/sanitized_ssl_key_file ssl_key >/dev/null 2>&1"
            ret =  subprocess.call(ssl_cmd, shell=True)
        else:
            ret += locret
        os.remove("/tmp/ztp_ssl_private.key")

    #
    #  ssl_certificate
    ssl_cert = checkKeys(jconf_data, ['https_security','ssl_cert'])
    #print ("SSL_CERT ====== \n", ssl_cert)
    if len(ssl_cert) > 0:
        with open('/tmp/ztp_ssl_cert.crt', 'w') as temp_cert_file:
            temp_cert_file.write(ssl_cert)
            temp_cert_file.write("\n")
            temp_cert_file.close()

        ssl_cmd = "chmod 766 /tmp/ztp_ssl_cert.crt"
        subprocess.call(ssl_cmd, shell=True)

        ssl_cmd = "/usr/lib/jdsu/scripts/otu_check_certificates.sh /tmp/ztp_ssl_cert.crt cert >/dev/null 2>&1"
        locret = subprocess.call(ssl_cmd, shell=True)

        if locret == 0:
            ssl_cmd = "/usr/lib/jdsu/scripts/smartotu_copy_files.sh  /tmp/sanitized_certificate_file.crt  ssl_cert >/dev/null 2>&1"
            ret +=  subprocess.call(ssl_cmd, shell=True)
        else:
            ret += locret
        os.remove("/tmp/ztp_ssl_cert.crt")

    #
    #  ssl_chain_certificate
    ssl_chain = checkKeys(jconf_data, ['https_security','ssl_cert_chain'])
    #print ("SSL_CERT_CHAIN ====== \n", ssl_chain)
    if len(ssl_chain) > 0:
        with open('/tmp/ztp_ssl_cert_chain.crt', 'w') as temp_chain_file:
            temp_chain_file.write(ssl_chain)
            temp_chain_file.write("\n")
            temp_chain_file.close()

        ssl_cmd = "chmod 766 /tmp/ztp_ssl_cert_chain.crt"
        subprocess.call(ssl_cmd, shell=True)

        ssl_cmd = "/usr/lib/jdsu/scripts/otu_check_certificates.sh  /tmp/ztp_ssl_cert_chain.crt chain >/dev/null 2>&1"
        locret = subprocess.call(ssl_cmd, shell=True)

        if locret == 0:
            ssl_cmd = "/usr/lib/jdsu/scripts/smartotu_copy_files.sh /tmp/sanitized_chain_certificate_file.crt ssl_chain-cert >/dev/null 2>&1"
            ret +=  subprocess.call(ssl_cmd, shell=True)
        else:
            ret += locret
        os.remove("/tmp/ztp_ssl_cert_chain.crt")

    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)

    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_credentials_profile(jconf_data):
    rc_smt = 0
    conf_upd_start = time.time()
    generic_pswd = "xxxxxxxxxx"
    #
    #  Handle "credentials / smart_otu" config item
    crd_smt = checkKeys(jconf_data, ['credentials', 'smart_otu'])
    if crd_smt != None:
        crd_login = checkKeys(crd_smt, ['login'])
        crd_pass_login = checkKeys(crd_smt, ['password'])
        #print ("Credentials SMART-OTU == %s, %s"%(crd_login, crd_pass_login))
        if (crd_pass_login != generic_pswd):
            if (len(crd_login) > 0 ) and (len(crd_pass_login) > 0 ):
                #
                #  Configure "login" username in the smart_otu tuple
                smt_log_cmd = "/usr/lib/jdsu/scripts/change_smartotu_login.sh %s >/dev/null 2>&1 "%crd_login
                rc_smt = subprocess.call(smt_log_cmd, shell=True)
                #
                if rc_smt == 0:
                    #  Configure "password" for the login specified in the smart_otu tuple
                    smt_pswd_cmd = "/usr/lib/jdsu/scripts/change_smartotu_password.sh %s >/dev/null 2>&1"%crd_pass_login
                    subprocess.call(smt_pswd_cmd, shell=True)
            #
            #  Handle "credentials / cli" config item
        else:
            rc_smt += 1
    #
    crd_cli = checkKeys(jconf_data, ['credentials', 'cli'])
    if crd_cli != None:
        cli_pwd = checkKeys(crd_cli, ['password'])
        #print ("\n\n\nCredentials CLI Password ====== %s, rc_smt %d\n"%(cli_pwd, rc_smt))
        if cli_pwd != generic_pswd:
            clp_cmd="echo root:%s | chpasswd"%cli_pwd
            subprocess.call(clp_cmd, shell=True)
        else:
            rc_smt += 1
    #
    conf_dur =  (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(rc_smt)
    return (rc_smt, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
def handle_otdr_ports_profile(jconf_data):
    ret = 0
    conf_upd_start = time.time()

    ztp_config_ports = OrderedDict()
    ztp_config_ports =  checkKeys(jconf_data, ['otdr_ports'])
    if ztp_config_ports != None:
        #
        #  Use ztp_config_ports to sequence through the OTU
        #  using the OTU:API:PORT:CONF SCPI command to set the
        #  name for each port in the ZTP json config list
        for item in ztp_config_ports:
            port_number = item['port']
            port_name = item['name']
            #print ("\nPort Number ==  ", str(port_number))
            #print ("\nPort Name   ==  ", port_name)
            otdr_cmd = "OTU:API:PORT:CONF "
            otdr_cmd_txt = str(port_number) + ", \"" + port_name + "\", TRUE"
            otdr_scpi_req = otdr_cmd + otdr_cmd_txt
            try:
                otu_p.SendCommand(otdr_scpi_req)
            except Exception as error_e:
                ret += 1
    conf_dur = (datetime.timedelta(seconds=(time.time() - conf_upd_start)))
    loc_upd_st, loc_upd_msg1 = set_status_and_msg_code(ret)
    return (ret, loc_upd_st, loc_upd_msg1, conf_dur)


#############################################################
class Device:

    def __init__(self, name):
        self.name = name
        self.serial_number = ""
        self.type = ""
        self.module_type = ""
        self.port_count = ""
        self.date = ""
        self.licences = ""
        self.software_version = ""


###############################################################
def set_ztp_otu_date(target_time, d_conf_update_time):
    #
    otu_p = ScpiAccess("localhost", 1400, 5, "*PASS \"RTU\"\"PASSWORD\"\n")
    #
    req_time = 0
    #
    #  Setting the OTU date in Epoch format.
    #  Reject input date if not numeric
    try:
        req_time = int(target_time[1])
    except Exception as error_e:
        ztp_conf_upd_status = FAILED_STATUS
        conf_msg = CONFIG_FAILED_MSG
        e_msg_details = "Input data must be a positive number"
        ds_code = 1
        #
        #  Format status info for console display
        update_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
        ztp_date_out = send_to_console_jsonformat(ztp_conf_upd_status, ds_code, update_dur, conf_msg, e_msg_details)
        #
        #  Send Unitary date configuration status info to console
        sys.stdout.write(ztp_date_out)
        sys.stdout.write("\n")
        sys.stdout.flush()
        #
        #  Log meaningfull data set at /var/log/user.log
        log_ztp_update_info(ztp_date_out, d_conf_update_time)
        return(ds_code)

    if req_time > 0:
        try:
            otu_p.SendCommand("OTU:API:CONF:DATE %d"%req_time)
            ztp_conf_upd_status = SUCCESS_STATUS
            conf_msg = CONFIG_ACCEPTED_MSG
            e_msg_details = "OTU Date updated"
            ds_code = 0
        except Exception as error_e:
            ztp_conf_upd_status = FAILED_STATUS
            conf_msg = CONFIG_FAILED_MSG
            e_msg_details = "Input date was rejected"
            ds_code = 1
    else:
        ztp_conf_upd_status = FAILED_STATUS
        conf_msg = CONFIG_FAILED_MSG
        e_msg_details = "Input date must be a positive value"
        ds_code = 1
    #
    #  Format status info for console display
    update_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
    ztp_date_out = send_to_console_jsonformat(ztp_conf_upd_status, ds_code, update_dur, conf_msg, e_msg_details)
    #
    #  Send Unitary date configuration status info to console
    sys.stdout.write(ztp_date_out)
    sys.stdout.write("\n")
    sys.stdout.flush()
    #
    #  Log meaningfull data set at /var/log/user.log
    log_ztp_update_info(ztp_date_out, d_conf_update_time)

    return(ds_code)


###############################################################
def ztp_resest_otu_factory_settings(d_conf_update_time):
    #
    otu_p = ScpiAccess("localhost", 1400, 15, "*PASS \"RTU\"\"PASSWORD\"\n")

    try:
        otu_p.SendCommand("OTU:API:CONF:RESET")
        ztp_conf_upd_status = SUCCESS_STATUS
        conf_msg = CONFIG_ACCEPTED_MSG
        e_msg_details = "In Progress - Restting OTU to factory settings."
        ds_code = 0
    except Exception as error_e:
        ztp_conf_upd_status = FAILED_STATUS
        conf_msg = CONFIG_FAILED_MSG
        e_msg_details = "OTU Rest To Factory Setting Was Rejected"
        ds_code = 1
    #
    #  Format status info for console display
    update_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
    ztp_fact_reset = send_to_console_jsonformat(ztp_conf_upd_status, ds_code, update_dur, conf_msg, e_msg_details)
    #
    #  Send Unitary date configuration status info to console
    sys.stdout.write(ztp_fact_reset)
    sys.stdout.write("\n")
    sys.stdout.flush()
    #
    #  Log meaningfull data set at /var/log/user.log
    log_ztp_update_info(ztp_fact_reset, d_conf_update_time)
    return(ds_code)


###############################################################
def ztp_otu_reboot(d_conf_update_time):
    #
    otu_p = ScpiAccess("localhost", 1400, 5, "*PASS \"RTU\"\"PASSWORD\"\n")

    try:
        otu_p.SendCommand("OTU:SYST:REB")
        ztp_conf_upd_status = SUCCESS_STATUS
        conf_msg = CONFIG_ACCEPTED_MSG
        e_msg_details = "OTU Reboot In Progress"
        ds_code = 0
    except Exception as error_e:
        ztp_conf_upd_status = FAILED_STATUS
        conf_msg = CONFIG_FAILED_MSG
        e_msg_details = "OTU Reboot was rejected"
        ds_code = 1
    #
    #  Format status info for console display
    update_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
    ztp_reb_out = send_to_console_jsonformat(ztp_conf_upd_status, ds_code, update_dur, conf_msg, e_msg_details)
    #
    #  Send Unitary date configuration status info to console
    sys.stdout.write(ztp_reb_out)
    sys.stdout.write("\n")
    sys.stdout.flush()
    #
    #  Log meaningfull data set at /var/log/user.log
    log_ztp_update_info(ztp_reb_out, d_conf_update_time)
    return(ds_code)


###############################################################
def build_elem_msg( up_state, config_dur, ec_code, upd_message):

    msg_payload = OrderedDict((("status", up_state), \
                          ("duration", str(config_dur)), \
                          ("return_code", int(ec_code)),  \
                          ("message", upd_message)))
    return (msg_payload)


###############################################################
#<><><><><>><><>-------<><><><><><><>-------<><><><><><><>

if __name__ == "__main__":
    #
    #  Constants referenced by the script
    CONFIG_FAILED_MSG = "Device did not accept configuration"
    CONFIG_ACCEPTED_MSG = "Successfully configured"
    ZTP_CONFIG_FAILED = "At least one of the configurations fields has failed"
    ZTP_CONFIG_PASSED = "All configuration applied successfully"
    JSON_ERROR_MSG1 = "Input is not a properly formatted JSON file"
    MAX_PYTHON_SCRIPTINPUTS = 2
    MAX_NUM_OF_INPUT_ARGUMENTS = MAX_PYTHON_SCRIPTINPUTS - 1
    FAILED_STATUS = "fail"
    SUCCESS_STATUS = "pass"

    OTU_DATE_IND = "date_otu"
    OTU_REBOOT_IND = "reboot_otu"
    OTU_RESET_FACTORY_IND ="reset_factory_otu"

    err_details = ""
    e_code = 0
    ztp_conf_upd_status = "initiated OTU upgrade"
    current_otu_version = "not available"
    json_read_error = 0

    script_name = sys.argv[0]

    #
    #  Get the current running version of the OTU
    otu_info_text = (((subprocess.check_output(["/sbin/getinfoversion", "Otu"])).decode("utf-8")).split(";"))
    current_otu_version = otu_info_text[3].replace("V",'')

    #
    #  Start update timers
    ztp_conf_update_time = time.strftime("%c")
    start_time = time.time()

    #
    #  Handling a request to set the OTU date in Epoch format.
    if OTU_DATE_IND in sys.argv[0]:
        e_code = set_ztp_otu_date(sys.argv, ztp_conf_update_time)
        exit (e_code)

    #
    #  Handling an OTU reboot request.
    if OTU_REBOOT_IND in sys.argv[0]:
        e_code = ztp_otu_reboot(ztp_conf_update_time)
        exit (e_code)

    #
    #  Handling a request to reset the OTU back to factory settingsdate
    if OTU_RESET_FACTORY_IND in sys.argv[0]:
        e_code = ztp_resest_otu_factory_settings(ztp_conf_update_time)
        exit (e_code)

    #
    #  Validate the the number of input arguments
    groot_arguments = len(sys.argv)
    if groot_arguments != MAX_PYTHON_SCRIPTINPUTS:
        e_code = 1
        err_details = "ZTP Conf Update Error --- Number of input arguments must be " + str(MAX_NUM_OF_INPUT_ARGUMENTS)
        log_error_to_console_userlog(start_time, err_details, e_code, current_otu_version, ztp_conf_update_time)
        exit(e_code)

    #
    #  Validate the path and presence of the ZTP upgrade tarball file
    groot_path = verify_ztp_conf_file_availability(sys.argv[1])
    if groot_path != True:
        e_code = 1
        err_details = "ZTP Conf Update Error --- Input file " + sys.argv[1] + " does not exist"
        log_error_to_console_userlog(start_time, err_details, e_code, current_otu_version, ztp_conf_update_time)
        exit (e_code)

    #
    #  Refactor the following block
    #  into a " try -> except -> finally "  block
    ztp_item_config_result = OrderedDict()

    try:
        with open( sys.argv[1], 'r') as dev_app_conf:
           otu_dev_data = dev_app_conf.read()
        #
        otu_p = ScpiAccess("localhost", 1400, 60, "*PASS \"RTU\"\"PASSWORD\"\n")

        try:
            #
            #  Load the JSON formatted config file into an ordered
            #  dictionary file.  We can an exception if the inout
            #  file is not a properly formatted JSON file.
            otu_dev_conf_data = json.loads(otu_dev_data, object_pairs_hook=OrderedDict)
        except ValueError as e:
            json_read_error = 1

        #
        #  This "for" loop iterates through the ordered dictionary
        #  of the JSON config data.  The loop handles each data type
        #  in the order of occurance retrieved from the input data file.
        for key, value in otu_dev_conf_data.items():

            if key == "device":
                key_title = "device"
                ert_code, up_state, upd_message, config_dur = handle_otu_device_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == "snmp_polling_agent":
                key_title = "snmp_polling_agent"
                ert_code, up_state, upd_message, config_dur = handle_snmp_polling_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == "snmp_traps":
                key_title = "snmp_traps"
                ert_code, up_state, upd_message, config_dur = handle_snmp_traps_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == 'credentials':
                ert_code, up_state, upd_message, config_dur = handle_credentials_profile(otu_dev_conf_data)
                key_title="credentials"
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == 'https_security':
                key_title = "https_security"
                ert_code, up_state, upd_message, config_dur = handle_https_security_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == 'otdr_ports':
                key_title = "otdr_ports"
                ert_code, up_state, upd_message, config_dur = handle_otdr_ports_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue

            if key == 'ntp_server':
                key_title = "ntp_server"
                ert_code, up_state, upd_message, config_dur = handle_ntp_server_profile(otu_dev_conf_data)
                e_code += ert_code
                ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)
                #print (key_title)
                continue
        #
        #  Process the IPv4 and IPv6 Configuration items at the end of the sequence
        #  as standalone config items to avoid OTU reboot timing complications
        if checkKeys(otu_dev_conf_data, ['ipv4_management']) != None:
            key_title = "ipv4_management"
            ert_code, up_state, upd_message, config_dur = handle_ipv4_profile(otu_dev_conf_data)
            e_code += ert_code
            ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)

        if checkKeys(otu_dev_conf_data, ['ipv6_management']) != None:
            key_title = "ipv6_management"
            ert_code, up_state, upd_message, config_dur = handle_ipv6_profile(otu_dev_conf_data)
            e_code += ert_code
            ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)

        if checkKeys(otu_dev_conf_data, ['ipv4_management_backup']) != None:
            key_title = "ipv4_management_backup"
            ert_code, up_state, upd_message, config_dur = handle_ipv4_backup_intf_profile(otu_dev_conf_data)
            e_code += ert_code
            ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)

        if checkKeys(otu_dev_conf_data, ['ipv6_management_backup']) != None:
            key_title = "ipv6_management_backup"
            ert_code, up_state, upd_message, config_dur = handle_ipv6_backup_intf_profile(otu_dev_conf_data)
            e_code += ert_code
            ztp_item_config_result[key_title] = build_elem_msg( up_state, config_dur, int(ert_code), upd_message)

    except Exception as error_e:
        e_code = 1
        details = error_e

    finally:
        ztp_conf_upd_dur = (datetime.timedelta(seconds=(time.time() - start_time)))
        #
        #  Print to console log
        #  Print to user.log
        #
        if json_read_error == 1:
            ztp_conf_upd_status = FAILED_STATUS
            msg_detail = JSON_ERROR_MSG1
        elif e_code == 0:
            ztp_conf_upd_status =  SUCCESS_STATUS
            msg_detail = ZTP_CONFIG_PASSED
        else:
            ztp_conf_upd_status = FAILED_STATUS
            msg_detail = ZTP_CONFIG_FAILED
        #
        #  Send meaningfull data set to condole
        if json_read_error == 1:
            ztp_out_console = send_to_console_jsonformat(ztp_conf_upd_status, e_code, ztp_conf_upd_dur, msg_detail, None)
        else:
            ztp_out_console = send_to_console_jsonformat(ztp_conf_upd_status, e_code, ztp_conf_upd_dur, msg_detail, ztp_item_config_result)
        #
        #
        sys.stdout.write(ztp_out_console)
        sys.stdout.write("\n")
        sys.stdout.flush()

        #
        #  Log meaningfull data set at /var/log/user.log
        log_ztp_update_info(ztp_out_console, ztp_conf_update_time)

    exit (e_code)
