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.
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()
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:
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.