Preconfiguration Script

Because ZTP starts when an unconfigured device is powered on, the device may fail to meet DHCP requirements, preventing it from connecting to the network. A pre-configuration script can be executed before ZTP starts, allowing the device to communicate with the DHCP server. Currently, a pre-configuration script is mainly used to achieve the following targets:

The file name extension of a pre-configuration script must be .py. The file name is a string of 1 to 65 case-sensitive characters which can be a combination of digits, letters, and underscores (_). It cannot contain spaces. The file name must not start with a digit or contain other special characters. A pre-configuration script can be named as preconfig.py for example. Use the Python 3.7 syntax to compile or modify the script file. For details about script file explanation, see Preconfiguration Script File Explanation.

Preconfiguration Script File Example

The following preconfiguration script file is only an example and needs to be modified as required.

The SHA256 checksum in the following file is only an example.

#sha256sum="68549835edaa5c5780d7b432485ce0d4fdaf6027a8af24f322a91b9f201a5101"
#!/usr/bin/env python
# coding=utf-8
#
# Copyright (C) Huawei Technologies Co., Ltd. 2008-2013. All rights reserved.
# ----------------------------------------------------------------------------------------------------------------------
# Project Code    : VRPV8
# File name       : preconfig.py
# ----------------------------------------------------------------------------------------------------------------------
# History:
# Date               Modification
# 20180415           created file.
# ----------------------------------------------------------------------------------------------------------------------

import sys
import http.client
import logging
import logging.handlers
import string
import traceback
import re
import xml.etree.ElementTree as etree
import ops

from time import sleep

# error code
OK          = 0
ERR         = 1
NOT_START_PNP = 2

# User Input: TYPE: list()
ETHTRUNK_MEMBER_LIST = [
    'GigabitEthernet0/1/1',
    'GigabitEthernet0/1/0'
]

# User Input:  TYPE: integer
VLAN = 127


ETHTRUNK_WORK_MODE = 'Static'
MAX_TIMES_CHECK_STARTUPCFG = 36
CHECK_CHECK_STARTUP_CFG_INTERVAL = 5

class OPIExecError(Exception):
    """"""
    pass

class NoNeedPNP(Exception):
    """"""
    pass

class OPSConnection(object):
    """Make an OPS connection instance."""

    def __init__(self, host, port=80):
        self.host = host
        self.port = port
        self.headers = {
            "Content-type": "application/xml",
            "Accept":       "application/xml"
            }

        self.conn = http.client.HTTPConnection(self.host, self.port)

    def close(self):
        """Close the connection"""
        self.conn.close()

    def create(self, uri, req_data):
        """Create a resource on the server"""
        ret = self._rest_call("POST", uri, req_data)
        return ret

    def delete(self, uri, req_data):
        """Delete a resource on the server"""
        ret = self._rest_call("DELETE", uri, req_data)
        return ret

    def get(self, uri, req_data=None):
        """Retrieve a resource from the server"""
        ret = self._rest_call("GET", uri, req_data)
        return ret

    def set(self, uri, req_data):
        """Update a resource on the server"""
        ret = self._rest_call("PUT", uri, req_data)
        return ret

    def _rest_call(self, method, uri, req_data):
        """REST call"""
        if req_data is None:
            body = ""
        else:
            body = req_data

        self.conn.request(method, uri, body, self.headers)
        response = self.conn.getresponse()
        rest_message = convert_byte_to_str(response.read())
        ret = (response.status, response.reason, rest_message)
        if response.status != http.client.OK:
            logging.info(body)
        return ret

def convert_byte_to_str(data):
    result = data
    if type(data) != type(""):
        result = str(data, "iso-8859-1")
    return result

def get_startup_cfg_info(ops_conn):
    uri = "/cfg/startupInfos/startupInfo"
    req_data = '''<?xml version="1.0" encoding="UTF-8"?>
<startupInfo>
<position/>
<configedSysSoft/>
<curSysSoft/>
<nextSysSoft/>
<curStartupFile/>
<nextStartupFile/>
<curPatchFile/>
<nextPatchFile/>
</startupInfo>'''

    config = None
    config1 = None
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != http.client.OK or rsp_data is '':
        logging.warning('Failed to get the startup information')
        return ERR, config, config1

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp': 'http://www.huawei.com/netconf/vrp'}
    mpath = 'data' + uri.replace('/', '/vrp:')  # match path
    nslen = len(namespaces['vrp'])
    elem = root_elem.find(mpath, namespaces)
    if elem is None:
        logging.error('Failed to get the startup information')
        return ERR, config, config1

    for child in elem:
        tag = child.tag[nslen + 2:]
        if tag == 'curStartupFile' and child.text != 'NULL':
            config = child.text
        if tag == 'nextStartupFile' and child.text != 'NULL':
            config1 = child.text
        else:
            continue

    return OK, config, config1

def is_need_start_pnp(ops_conn):
    ret, config, _ = get_startup_cfg_info(ops_conn)
    if ret is OK and config is not None and config != "cfcard:/vrpcfg.zip":
        logging.info("No need to run ztp pre-configuration when device starts with configuration file")
        return False
    return True

def check_nextstartup_file(ops_conn):
    cnt = 0
    check_time = MAX_TIMES_CHECK_STARTUPCFG
    while cnt < check_time:
        ret, _, config1 = get_startup_cfg_info(ops_conn)
        if ret is OK and config1 is not None and config1 == "cfcard:/vrpcfg.zip":
            logging.info("check next startup file successful")
            return OK

        sleep(CHECK_CHECK_STARTUP_CFG_INTERVAL)  # sleep to wait for system ready when no query result
        if (cnt%6 == 0):
            logging.info("check next startup file...")
        cnt += 1
    return OK

def print_precfg_info(precfg_info):
    """ Print Pre Config Info """

    str_temp = string.Template(
        'Pre-config infomation:\n'
        '  Eth-Trunk Name:       $ethtrunk_name\n'
        '  Eth-Trunk Work Mode:  $ethtrunk_work_mode\n'
        '  Eth-Trunk MemberIfs:  $ethtrunk_member_ifs\n'
        '  Vlan:                 $vlan_pool\n'
    )

    precfg = str_temp.substitute(ethtrunk_name=precfg_info.get('ethtrunk_ifname'),
                                 ethtrunk_work_mode=precfg_info.get('ethtrunk_work_mode'),
                                 ethtrunk_member_ifs=', '.join(precfg_info.get('ethtrunk_member_ifs')),
                                 vlan_pool=precfg_info.get('vlan'))

    logging.info(precfg)

def get_device_productname(ops_conn):
    """Get system info, returns a dict"""
    logging.info("Get the system information...")
    uri = "/system/systemInfo"
    req_data =  \
'''<?xml version="1.0" encoding="UTF-8"?>
<systemInfo>
    <productName/>
</systemInfo>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != http.client.OK or rsp_data is '':
        raise OPIExecError('Failed to get the system information')

    productname = ""
    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp': 'http://www.huawei.com/netconf/vrp'}
    uri = uri + '/productName'
    uri = 'data' + uri.replace('/', '/vrp:')
    elem = root_elem.find(uri, namespaces)
    if elem is not None:
        productname = elem.text

    logging.info('Current product name : {0}'.format(productname))
    return productname    

def active_port_license(ops_conn, if_port):
    """ active port-basic license """
    """ ATN910C GE 10GE """
    """ 50GE """
    productname = get_device_productname(ops_conn)
    active_flag = False

     if '910C' in productname:
         # ATN 910C product, all port need active port-basic
        uri = "/devm/portResourceInfos"
        lcsDescription = ['ATN 910C Any 4GE/FE Port RTU',
                          'ATN 910C 4*10GE Port RTU']

        position = re.search('\d+/\\d+/\\d+', if_port)
        position = position.group() if position is not None else None
        if position is not None:   
            for info in lcsDescription:
                root_elem = etree.Element('portResourceInfos')            
                portResourceInfo_elem = etree.SubElement(root_elem, 'portResourceInfo')
                etree.SubElement(portResourceInfo_elem, 'lcsDescription').text = info
                lcsports_elem = etree.SubElement(portResourceInfo_elem, 'lcsPorts')
                lcsport_elem = etree.SubElement(lcsports_elem, 'lcsPort')
                etree.SubElement(lcsport_elem, 'position').text = position
                etree.SubElement(lcsport_elem, 'isAct').text = 'active'

                try:
                    req_data = etree.tostring(root_elem, 'UTF-8')
                    ret, _, _ = ops_conn.set(uri, req_data)
                    if ret == http.client.OK:
                        active_flag = True
                        break
                except OPIExecError:
                    pass
        else:
            logging.error('parse position failed, product: {0}, interface: {1}'.format(productname, if_port))

    elif if_port.startswith('50GE') and if_port.endswith('1'):
        # 2*50GE, only port 1 need active port-basic
        uri = "/lcs/lcsResUsages"

        position = re.search('\d+/\\d+/\\d+', if_port)
        position = position.group() if position is not None else None
        if position is not None:
            root_elem = etree.Element('lcsResUsages')
            lcsResUsages_elem = etree.SubElement(root_elem, 'lcsResUsage')
            etree.SubElement(lcsResUsages_elem, 'resItemName').text = "LANJ50GEE00"
            lcsPorts_elem = etree.SubElement(lcsResUsages_elem, 'lcsPorts')
            lcsport_elem = etree.SubElement(lcsPorts_elem, 'lcsPort')
            etree.SubElement(lcsport_elem, 'position').text = position
            etree.SubElement(lcsport_elem, 'isAct').text = 'active'

            try:
                req_data = etree.tostring(root_elem, 'UTF-8')
                ret, _, _ = ops_conn.set(uri, req_data)
                if ret == http.client.OK:
                    active_flag = True
            except OPIExecError:
                pass
        else:
            logging.error('parse position failed, product: {0}, interface: {1}'.format(productname, if_port))

    else:
        logging.info('The current device no need active port-basic')
        active_flag = True

    if active_flag == False:
        logging.info('{0} port-basic license active failed'.format(if_port))    

def create_ethtrunk(ops_conn, ifname, work_mode, member_ifs):
    """ create interface eth-trunk """
    logging.info('Create interface {0}, Work-Mode: {1}'.format(ifname, work_mode))

    if ifname in ['', None] or work_mode in ['', None] or not member_ifs:
        logging.error('Create Eth-Trunk Parameters is invalid')
        return

    for iface in member_ifs:
        active_port_license(ops_conn, iface)

    uri = '/ifmtrunk/TrunkIfs/TrunkIf'
    str_temp = string.Template("""
<?xml version="1.0" encoding="UTF-8"?>
<TrunkIf operation="create">
    <ifName>$ifName</ifName>
    <workMode>$workmode</workMode>
    <TrunkMemberIfs>
$ifs
    </TrunkMemberIfs>
</TrunkIf>
""")
    ifs_temp = string.Template("""    <TrunkMemberIf operation="create">
        <memberIfName>$memberifname</memberIfName>
    </TrunkMemberIf>""")
    ifs = []
    for iface in member_ifs:
        ifs.append(ifs_temp.substitute(memberifname=iface))

    ifs = '\n'.join(ifs)
    req_data = str_temp.substitute(ifs=ifs, ifName=ifname, workmode=work_mode)

    ret, _, rsp_data = ops_conn.create(uri, req_data)
    if ret != http.client.OK:
        logging.error(rsp_data)
        raise OPIExecError('Failed to create Eth-Trunk interface')

    logging.info('Successed to create Eth-Trunk interface')


def delete_ethtrunk(ops_conn, ifname):
    """ """
    logging.info('Delete interface {0}'.format(ifname))

    uri = '/ifmtrunk/TrunkIfs/TrunkIf'
    str_temp = string.Template("""
<?xml version="1.0" encoding="UTF-8"?>
    <TrunkIf operation="delete">
    <ifName>$ifName</ifName>
    </TrunkIf>
    """)

    req_data = str_temp.substitute(ifName=ifname)

    try:
        ret, _, rsp_data = ops_conn.delete(uri, req_data)
        if ret != http.client.OK:
            logging.error(rsp_data)
            raise OPIExecError('Failed to delete Eth-Trunk interface')
    except Exception as reason:
        logging.error('Error:', reason)
    else:
        logging.info('Successed to delete Eth-Trunk interface')


def config_vlan(ops_conn, vlan):
    """ Config Vlan Pool to Pnp """

    if vlan == 0:
        logging.info('Current vlan is 0, no need config')
        return

    logging.info('Config Vlan Pool To Pnp')
    uri = '/pnp/vlanNotify'

    str_temp = string.Template("""
<?xml version="1.0" encoding="UTF-8"?>
<vlanNotify>
  <startVlan>$startVlan</startVlan>
  <endVlan>$endVlan</endVlan>
</vlanNotify>
    """)

    req_data = str_temp.substitute(startVlan=vlan, endVlan=vlan)
    ret, _, rsp_data = ops_conn.create(uri, req_data)
    if ret != http.client.OK:
        logging.error(rsp_data)
        raise OPIExecError('Failed to config vlan to Pnp')

    logging.info('Successed to config vlan to Pnp')

def config_interface_nego_auto_and_l2mode(_ops):
    handle, err_desp = _ops.cli.open()
    if err_desp not in ['Success','Error: The line has been opened.']:
        raise OPIExecError('Failed to open cli')
    _ops.cli.execute(handle,"sys")
    fd, _, err_desp = _ops.cli.execute(handle,"interface GigabitEthernet0/2/4",None)
    if fd == None or err_desp is not 'Success':
        raise OPIExecError('Failed to execute interface GigabitEthernet0/2/4') 
    _ops.cli.execute(handle,"negotiation auto",None)
    _ops.cli.execute(handle,"portswitch",None)

    fd, _, err_desp = _ops.cli.execute(handle,"interface GigabitEthernet0/2/5",None)
    if fd == None or err_desp is not 'Success':
        raise OPIExecError('Failed to execute interface GigabitEthernet0/2/5') 
    _ops.cli.execute(handle,"negotiation auto",None)

    fd, _, err_desp = _ops.cli.execute(handle,"commit",None)
    if fd == None or err_desp is not 'Success':
        raise OPIExecError('Failed to execute commit')  
    ret = _ops.cli.close(handle)
    logging.info('Successed to config interface nego auto')
    return 0

def undo_autosave_config(_ops):
    handle, err_desp = _ops.cli.open()
    if err_desp not in ['Success','Error: The line has been opened.']:
        raise OPIExecError('Failed to open cli')
    _ops.cli.execute(handle,"sys")
    fd, _, err_desp = _ops.cli.execute(handle,"undo set save-configuration",None)
    if fd == None or err_desp is not 'Success':
        raise OPIExecError('Failed to execute undo set save-configuration') 

    fd, _, err_desp = _ops.cli.execute(handle,"commit",None)
    if fd == None or err_desp is not 'Success':
        raise OPIExecError('Failed to execute commit')
    ret = _ops.cli.close(handle)
    logging.info('Successed to undo auto save configuration')
    return 0

def main_proc(ops_conn, precfg_info):
    """ """

    ifname = precfg_info.get('ethtrunk_ifname')
    work_mode = precfg_info.get('ethtrunk_work_mode')
    member_ifs = precfg_info.get('ethtrunk_member_ifs')
    vlan = precfg_info.get('vlan')
    _ops = ops.ops()

    if is_need_start_pnp(ops_conn) is False:
        return NOT_START_PNP

    sleep(15)
    try:
        undo_autosave_config(_ops)
    except OPIExecError as reason:
        logging.error('Error: %s' % reason)
        return ERR

    try:
        config_interface_nego_auto_and_l2mode(_ops)
    except OPIExecError as reason:
        logging.error('Error: %s' % reason)
        return ERR

    try:
        create_ethtrunk(ops_conn, ifname, work_mode, member_ifs)
    except OPIExecError as reason:
        logging.error('Error: %s' % reason)
        return ERR

    try:
        config_vlan(ops_conn, vlan)
    except OPIExecError as reason:
        logging.error('Error: %s' % reason)
        delete_ethtrunk(ops_conn, ifname)
        return ERR

   try:
        check_nextstartup_file(ops_conn)
    except OPIExecError as reason:
        logging.error('Error: %s', reason)

    return OK


def main():
    """

    :return:
    """

    host = 'localhost'

    try:
        work_mode = ETHTRUNK_WORK_MODE
    except NameError:
        work_mode = 'Static'

    try:
        vlan = VLAN
    except NameError:
        vlan = 0

    try:
        member_list = ETHTRUNK_MEMBER_LIST
    except:
        member_list = []

    precfg_info = {
        'ethtrunk_ifname': 'Eth-Trunk0',
        'ethtrunk_work_mode': work_mode,
        'ethtrunk_member_ifs': member_list,
        'vlan': vlan
    }

    print_precfg_info(precfg_info)

    try:
        ops_conn = OPSConnection(host)
        ret = main_proc(ops_conn, precfg_info)
    except Exception:
        logging.error(traceback.print_exc())
        ret = ERR
    finally:
        ops_conn.close()

    return ret

if __name__ == '__main__':
    """ """
    main()

Preconfiguration Script File Explanation

The information in bold can be modified based on actual requirements.

  • Specify an SHA256 checksum for the script file.

    #sha256sum="68549835edaa5c5780d7b432485ce0d4fdaf6027a8af24f322a91b9f201a5101"

    The SHA256 checksum is used to check the integrity of the script file.

    You can use either of the following methods to generate an SHA256 checksum for a script file:

    1. Use the SHA256 calculation tool, such as HashMyFiles.
    2. Run the certutil -hashfile filename SHA256 command provided by the Windows operating system.

      The SHA256 checksum is calculated based on the content following #sha256sum=. In practice, you need to delete the first line in the file, move the following part one line above, calculate the SHA256 checksum, and write #sha256sum= plus the generated SHA256 checksum at the beginning of the file.

      The SHA256 algorithm can be used to verify the integrity of files. This algorithm has high security.

  • Specify an Eth-Trunk member interface used by the device.

    ETHTRUNK_MEMBER_LIST = [
        'GigabitEthernet0/1/1',
        'GigabitEthernet0/1/0'
    ]

    GigabitEthernet0/1/1 indicates the name of an interface.

  • Specify the VLAN ID used by the DHCP client.

    VLAN = 127

    You do not need to edit this field.

  • Set the Eth-Trunk working mode.

    ETHTRUNK_WORK_MODE = 'Static'

    You do not need to edit this field.

  • Configure the maximum number of retries allowed when the check boot items fail to be set.

    MAX_TIMES_CHECK_STARTUPCFG = 36
  • Specify the interval for checking whether the system software is successfully configured.

    CHECK_CHECK_STARTUP_CFG_INTERVAL = 5
  • Define the OPS connection class.

    class OPSConnection()

    You do not need to edit this field.

  • Encapsulate an OPS connection.

    self.conn = http.client.HTTPConnection

    You do not need to edit this field.

  • Invoke the underlying interface of the platform.

    def close()
    def create()
    def delete()
    def get()
    def set()

    You do not need to edit this field.

  • Define the Representational State Transfer (REST) standard for requests.

    def _rest_call()

    You do not need to edit this field.

  • Define an OPS execution error.

    class OPIExecError()

    You do not need to edit this field.

  • Print preconfiguration information.

    print_precfg_info()

    You do not need to edit this field.

  • Create and configure an Eth-Trunk interface.

    create_ethtrunk()

    You do not need to edit this field.

  • Activate an interface license.

    active_port_license()

    You do not need to edit this field.

  • Delete an Eth-Trunk interface.

    delete_ethtrunk()

    You do not need to edit this field.

  • Configure a VLAN ID for the device.

    config_vlan()

    You do not need to edit this field.

  • Define the overall ZTP process.

    def main_proc()
    def main()

    You do not need to edit this field.

    The main() function must be provided. Otherwise, the script cannot be executed.

Copyright © Huawei Technologies Co., Ltd.
Copyright © Huawei Technologies Co., Ltd.
< Previous topic Next topic >