Pre-Configuration Script

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

A pre-configuration script is a Python file that uses the .py file name extension. The file name is a string of 1 to 65 case-sensitive characters and can contain only digits, letters, and underscores (_). The file name cannot contain spaces or other special characters, and must not start with a digit. For example, a pre-configuration script can be named preconfig.py. Use the Python 3.7 syntax to compile or modify a script. For details about pre-configuration scripts, see Description of a Pre-Configuration Script.

Example of a Pre-Configuration Script

The following pre-configuration script is an example and can be modified as required.

The SHA256 verification code 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):
    sleep(15)
    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()

Description of a Pre-Configuration Script

The content in bold can be modified as needed.

  • Specify the SHA256 verification code of the script file.

    #sha256sum="68549835edaa5c5780d7b432485ce0d4fdaf6027a8af24f322a91b9f201a5101"

    You can use the SHA256 verification code to check the integrity of the script file downloaded by the device.

    To generate a SHA256 verification code for a script, use either of the following methods:

    1. Use a SHA256 calculation tool (such as HashMyFiles).
    2. Run the certutil -hashfile filename SHA256 command provided by Windows.

      The SHA256 verification code is calculated based on the content below the "#sha256sum=" line. When a SHA256 verification code is generated, the first line in this example needs to be deleted and the second line is then used as the first line. After the calculation is complete, the newly generated SHA256 verification code "#sha256sum=" is written to the beginning of the file.

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

  • Specify the Eth-Trunk member interfaces used by the device.

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

    GigabitEthernet0/1/1 indicates the name of a device interface.

  • Specify the VLAN ID used by the DHCP client.

    VLAN = 127

    You do not need to edit this field.

  • Specify an Eth-Trunk working mode on the device.

    ETHTRUNK_WORK_MODE = 'Static'

    You do not need to edit this field.

  • Specify the maximum number of retries allowed if 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 the 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 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.

  • Display pre-configuration 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 SZTP process.

    def main_proc()
    def main()

    You do not need to edit this field.

    The main() function is mandatory. If the main() function is unavailable, the script cannot be executed.

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