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