No relevant resource is found in the selected language.

This site uses cookies. By continuing to browse the site you are agreeing to our use of cookies. Read our privacy policy>Search

Reminder

To have a better experience, please upgrade your IE browser.

upgrade

CX11x, CX31x, CX710 (Earlier Than V6.03), and CX91x Series Switch Modules V100R001C10 Configuration Guide 12

The documents describe the configuration of various services supported by the CX11x&CX31x&CX91x series switch modules The description covers configuration examples and function configurations.
Rate and give feedback:
Huawei uses machine translation combined with human proofreading to translate this document to different languages in order to help you better understand the content of this document. Note: Even the most advanced machine translation cannot match the quality of professional translators. Huawei shall not bear any responsibility for translation accuracy and it is recommended that you refer to the English document (a link for which has been provided).
Configuration Examples

Configuration Examples

This section provides ZTP configuration examples, including the networking requirements, configuration roadmap, and configuration procedure.

Example for Configuring Unconfigured Devices to Implement Automatic Deployment Through DHCP

Networking Requirements

As shown in Figure 1-3, SwitchA and SwitchB are two unconfigured switches on the network, and both are connected to SwitchC. SwitchC functions as the egress gateway of SwitchA and SwitchB. The routes between SwitchC and the DHCP server, and between SwitchC and the file server are reachable.

The customer requires that SwitchA and SwitchB automatically load system software and configuration files after they are powered on to reduce labor costs and device deployment time.

Table 1-15 lists information about SwitchA and SwitchB, and the files that the switches need to load.
Table 1-15 Device information and files to be loaded

New Device

Device Model

Serial Number

File to Be Loaded

SwitchA

CX110

210235527210D4000028

  • System software: CX110-Switch-V103.cc

  • Configuration file: conf_210235527210D4000028.cfg

SwitchB

CX110

210235527210D4000046

  • System software: CX110-Switch-V103.cc

  • Configuration file: conf_210235527210D4000046.cfg

Figure 1-3 Configuring ZTP
Configuration Roadmap
The configuration roadmap is as follows:
  1. Configure an FTP server as the file server to save the intermediate file, system software, and configuration files.

  2. Edit the intermediate file ztp_script.py to enable the switches to obtain their system software packages and configuration files according to the intermediate file.

  3. Configure the DHCP server and relay agent to enable unconfigured switches to obtain DHCP information.

  4. Power on SwitchA and SwitchB to start the ZTP process.

Procedure

  1. Configure the file server. (The following example uses a PC as the file server. If a device of a different type functions as the file server, configure the device according to the corresponding operation guide.)

    1. Configure FTP server functions on the PC. Run an FTP server program (for example, WFTPD32) on a PC. As shown in Figure 1-4, choose Security > Users/rights. In the displayed dialog box, click New User to set the user name and password. Here, the user name is ftpuser and the password is Pwd123. Enter the FTP working directory in the Home Directory text box. Here, the working directory is D:\ztp. Click Done to close the dialog box.

      Figure 1-4 Configuring the file server
    2. Configure the IP address and gateway for the file server. Ensure that the file server and gateway of SwitchA and SwitchB have reachable routes to each other.

    After configuring the file server, save the system software and configuration files to be loaded to switches in the working directory D:\ztp.

  2. Edit the intermediate file.

    Edit the intermediate file according to Python Script. The file is named ztp_script.py. See Example of ztp_script.py for the file contents.

    After editing the intermediate file, save the file to the working directory D:\ztp on the file server.

  3. Configure the DHCP server.

    # Configure the IP address pool to be allocated by the DHCP server to clients and configure the Option value of the DHCP server. For details, see the related DHCP server documentation.

    Table 1-16 Options of the DHCP server

    Option No.

    Description

    Value

    1

    Subnet mask of an IP address.

    255.255.225.0

    3

    Egress gateway of the DHCP client

    10.1.1.1

    67

    File server address and intermediate file name.

    ftp://ftpuser:Pwd123@10.1.3.2/ztp_script.py

    # Configure the IP address and gateway for the DHCP server. Ensure that the DHCP server and gateway of SwitchA and SwitchB have reachable routes to each other.

  4. Configure the DHCP relay agent.

    # On SwitchC, configure the DHCP relay function and set the IP address of the VLANIF interface connected to SwitchA and SwitchB to 10.1.1.1. The VLANIF interface functions as the default gateway of SwitchA and SwitchB.

    <HUAWEI> system-view
    [~HUAWEI] sysname SwitchC
    [*HUAWEI] commit
    [~SwitchC] vlan batch 10
    [*SwitchC] interface 10ge 1/17/1
    [*SwitchC-10GE1/17/1] port link-type trunk
    [*SwitchC-10GE1/17/1] port trunk allow-pass vlan 10
    [*SwitchC-10GE1/17/1] port trunk pvid vlan 10
    [*SwitchC-10GE1/17/1] quit
    [*SwitchC] interface 10ge 1/17/2
    [*SwitchC-10GE1/17/2] port link-type trunk
    [*SwitchC-10GE1/17/2] port trunk allow-pass vlan 10
    [*SwitchC-10GE1/17/2] port trunk pvid vlan 10
    [*SwitchC-10GE1/17/2] quit
    [*SwitchC] interface vlanif 10
    [*SwitchC-Vlanif10] ip address 10.1.1.1 24
    [*SwitchC-Vlanif10] quit
    [*SwitchC] dhcp enable
    [*SwitchC] interface vlanif 10
    [*SwitchC-Vlanif10] dhcp select relay
    [*SwitchC-Vlanif10] dhcp relay binding server ip 10.1.2.2
    [*SwitchC-Vlanif10] commit
    

  5. Power on SwitchA and SwitchB to start the ZTP process.
  6. Verify the configuration.

    # The switches complete the ZTP process 15 minutes after they are powered on. Log in to the switches and run the display startup command to check whether the current system software and configuration files are the required ones. Use SwitchA as an example.
    <SwitchA> display startup
    MainBoard:
      Configured startup system software:        flash:/CX110-Switch-V103.cc
      Startup system software:                   flash:/CX110-Switch-V103.cc
      Next startup system software:              flash:/CX110-Switch-V103.cc
      Startup saved-configuration file:          flash:/conf_210235527210D4000028.cfg
      Next startup saved-configuration file:     flash:/conf_210235527210D4000028.cfg
      Startup paf file:                          default
      Next startup paf file:                     default
      Startup patch package:                     NULL
      Next startup patch package:                NULL
    

Example of ztp_script.py
NOTE:

#md5sum= is the MD5 code of the intermediate file ztp_script.py. You can modify the contents of ztp_script.py according to actual networking requirements. After the modification, use an MD5 calculation tool, such as md5sum, to generate the MD5 code of the modified file.

Note that the intermediate file cannot contain #md5sum= when the MD5 code is generated. Add #md5sum= to the beginning of the script after the MD5 code is generated.

#md5sum="780a5a62bc24a87a13d3106f5a7bf439"
#!/usr/bin/env python
#
# Copyright (C) Huawei Technologies Co., Ltd. 2008-2013. All rights reserved.
# ----------------------------------------------------------------------------------------------------------------------
# History:
# Date                Author                      Modification
# 20130629            Author                      created file.
# ----------------------------------------------------------------------------------------------------------------------

"""
Zero Touch Provisioning (ZTP) enables devices to automatically load version files including system software,
patch files, configuration files when the device starts up, the devices to be configured must be new devices
or have no configuration files.

This is a sample of Zero Touch Provisioning user script. You can customize it to meet the requirements of
your network environment.
"""

import httplib
import urllib
import string
import re
import xml.etree.ElementTree as etree
import os
import stat
import logging
import traceback
import hashlib

from urlparse import urlparse
from urlparse import urlunparse
from time import sleep

# error code
OK          = 0
ERR         = 1

# File server in which stores the necessary system software, configuration and patch files:
#   1) Specify the file server which supports the following format.
#      tftp://hostname
#      ftp://[username[:password]@]hostname[:port]
#      sftp://[username[:password]@]hostname[:port]
#      http://hostname[:port]
#   2) Do not add a trailing slash at the end of file server path.
FILE_SERVER         = 'ftp://ftpuser:Pwd123@10.1.3.2'

# Remote file paths:
#   1) The path may include directory name and file name.
#   2) If file name is not specified, indicate the procedure can be skipped.
# File paths of system software on file server, filename extension is '.cc'.
REMOTE_PATH_IMAGE   = {
    'CX110'    :   '/CX110-Switch-V103.cc',
    'CX310'    :   '/CX310-Switch-V103.cc'
  }
# File path of configuration file on file server, filename extension is '.cfg', '.zip' or '.dat'.
REMOTE_PATH_CONFIG  = '/conf_%s.cfg'
# File path of patch file on file server, filename extension is '.pat'
REMOTE_PATH_PATCH   = ''
# File path of stack member ID file on file server, filename extension is '.txt'
REMOTE_PATH_MEMID   = ''
# File path of md5 file, contains md5 value of image / patch / memid file, file extension if '.txt'
REMOTE_PATH_MD5     = ''


# Max times to retry get startup when no query result
GET_STARTUP_INTERVAL        = 15    # seconds
MAX_TIMES_GET_STARTUP       = 120   # Max times to retry

# Max times to retry when download file faild
MAX_TIMES_RETRY_DOWNLOAD    = 3


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 = httplib.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 == None:
            body = ""
        else:
            body = req_data

        logging.debug('HTTP request: %s %s HTTP/1.1', method, uri)
        self.conn.request(method, uri, body, self.headers)
        response = self.conn.getresponse()
        ret = (response.status, response.reason, response.read())
        if response.status != httplib.OK:
            logging.debug('%s', body)
        logging.debug('HTTP response: HTTP/1.1 %s %s\n%s', ret[0], ret[1], ret[2])
        return ret

class OPIExecError(Exception):
    """OPI executes error."""
    pass

class ZTPErr(Exception):
    """ZTP error."""
    pass


def get_addr_by_hostname(ops_conn, host, addr_type = '1'):
    """Translate a host name to IPv4 address format. The IPv4 address is returned as a string."""
    logging.info("Get IP address by host name...")
    uri = "/dns/dnsNameResolution"
    root_elem = etree.Element('dnsNameResolution')
    etree.SubElement(root_elem, 'host').text = host
    etree.SubElement(root_elem, 'addrType').text = addr_type
    req_data = etree.tostring(root_elem, "UTF-8")
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK:
        raise OPIExecError('Failed to get address by host name')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    elem = root_elem.find(uri + "ipv4Addr", namespaces)
    if elem is None:
        raise OPIExecError('Failed to get IP address by host name')

    return elem.text

def _http_download_file(ops_conn, url, local_path):
    """Download file using HTTP."""
    logging.info('HTTP download "%s" to "%s".', url, local_path)

    url_tuple = urlparse(url)
    if not re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        netloc = get_addr_by_hostname(ops_conn, url_tuple.hostname)
        if url_tuple.port:
            netloc += ':' + str(url_tuple.port)
        url = urlunparse((url_tuple.scheme, netloc, url_tuple.path, url_tuple.params, url_tuple.query,
                          url_tuple.fragment))

    ret = OK
    opener = urllib.URLopener()
    try:
        dst_file_path = "%s/%s" % (os.getcwd(), os.path.basename(url))
        dst_file_path = os.path.abspath(dst_file_path)
        logging.info('HTTP download destination file=%s.', dst_file_path)
        opener.retrieve(url, dst_file_path)
        os.chmod(dst_file_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
    except (KeyboardInterrupt, Exception), reason:
        if os.path.exists(dst_file_path):
            os.remove(dst_file_path)    # Remove incomplete file
        logging.error(reason)
        print('Error: Failed to download file "%s" using HTTP' % os.path.basename(url))
        ret = ERR

    return ret

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : ftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _ftp_download_file(ops_conn, url, local_path):
    """Download file using FTP."""
    logging.info('FTP download "%s" to "%s".', url, local_path)
    uri = "/ftpc/ftpcTransferFiles/ftpcTransferFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<ftpcTransferFile>
    <serverIpv4Address>$serverIp</serverIpv4Address>
    <commandType>get</commandType>
    <userName>$username</userName>
    <password>$password</password>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
</ftpcTransferFile>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, username = url_tuple.username, password = url_tuple.password,
                                   remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using FTP' % os.path.basename(local_path))
        return ERR

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _del_rsa_peer_key
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _del_rsa_peer_key(ops_conn, key_name):
    """Delete RSA peer key configuration"""
    logging.debug("Delete RSA peer key %s", key_name)
    uri = "/rsa/rsaPeerKeys/rsaPeerKey"
    root_elem = etree.Element('rsaPeerKey')
    etree.SubElement(root_elem, 'keyName').text = key_name
    req_data = etree.tostring(root_elem, "UTF-8")
    try:
        ret, _, _ = ops_conn.delete(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete RSA peer key')

    except Exception, reason:
        logging.debug(reason)

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _del_sshc_rsa_key
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _del_sshc_rsa_key(ops_conn, server_name, key_type = 'RSA'):
    """Delete SSH client RSA key configuration"""
    logging.debug("Delete SSH client RSA key for %s", server_name)
    uri = "/sshc/sshCliKeyCfgs/sshCliKeyCfg"
    root_elem = etree.Element('sshCliKeyCfg')
    etree.SubElement(root_elem, 'serverName').text = server_name
    etree.SubElement(root_elem, 'pubKeyType').text = key_type
    req_data = etree.tostring(root_elem, "UTF-8")
    try:
        ret, _, _ = ops_conn.delete(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete SSH client RSA key')

    except Exception, reason:
        logging.debug(reason)

    _del_rsa_peer_key(ops_conn, server_name)

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _set_sshc_first_time
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _set_sshc_first_time(ops_conn, switch):
    """Set SSH client attribute of authenticating user for the first time access"""
    if switch not in ['Enable', 'Disable']:
        return ERR

    logging.debug('Set SSH client first-time enable switch = %s', switch)
    uri = "/sshc/sshClient"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<sshClient>
    <firstTimeEnable>$enable</firstTimeEnable>
</sshClient>
''')
    req_data = str_temp.substitute(enable = switch)
    ret, _, _ = ops_conn.set(uri, req_data)
    if ret != httplib.OK:
        if switch == 'Enable':
            raise OPIExecError('Failed to enable SSH client first-time')
        else:
            raise OPIExecError('Failed to disable SSH client first-time')

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : sftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _sftp_download_file(ops_conn, url, local_path):
    """Download file using SFTP."""
    _set_sshc_first_time(ops_conn, 'Enable')

    logging.debug('SFTP download "%s" to "%s".', url, local_path)
    uri = "/sshc/sshcConnects/sshcConnect"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<sshcConnect>
    <HostAddrIPv4>$serverIp</HostAddrIPv4>
    <commandType>get</commandType>
    <userName>$username</userName>
    <password>$password</password>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
    <identityKey>ssh-rsa</identityKey>
    <transferType>SFTP</transferType>
</sshcConnect>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, username = url_tuple.username, password = url_tuple.password,
                                   remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using SFTP' % os.path.basename(local_path))
        ret = ERR
    else:
        ret = OK

    _del_sshc_rsa_key(ops_conn, server_ip)
    _set_sshc_first_time(ops_conn, 'Disable')
    return ret

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : tftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _tftp_download_file(ops_conn, url, local_path):
    """Download file using TFTP."""
    logging.debug('TFTP download "%s" to "%s".', url, local_path)
    uri = "/tftpc/tftpcTransferFiles/tftpcTransferFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<tftpcTransferFile>
    <serverIpv4Address>$serverIp</serverIpv4Address>
    <commandType>get_cmd</commandType>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
</tftpcTransferFile>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using TFTP' % os.path.basename(local_path))
        return ERR

    return OK

def _usb_download_file(ops_conn, url, local_path):
    """Download file using usb"""
    logging.info('USB download "%s" to "%s".', url, local_path)

    url_tuple = urlparse(url, allow_fragments=False)
    src_path = url_tuple.path[1:]
    try:
        copy_file(ops_conn, src_path, local_path)
    except:
        print('Failed to download file "%s" using USB' % os.path.basename(local_path))
        return ERR
    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def download_file(ops_conn, url, local_path, retry_times = 0):
    """Download file, support TFTP, FTP, SFTP and HTTP.

    tftp://hostname/path
    ftp://[username[:password]@]hostname[:port]/path
    sftp://[username[:password]@]hostname[:port]/path
    http://hostname[:port]/path

    Args:
      ops_conn: OPS connection instance
      url: URL of remote file
      local_path: local path to put the file

    Returns:
        A integer of return code
    """
    print("Info: Download %s to %s" % (url, local_path))
    url_tuple = urlparse(url)
    func_dict = {'tftp': _tftp_download_file,
                 'ftp':  _ftp_download_file,
                 'sftp': _sftp_download_file,
                 'http': _http_download_file,
                 'file': _usb_download_file}
    scheme = url_tuple.scheme
    if scheme not in func_dict.keys():
        raise ZTPErr('Unknown file transfer scheme %s' % scheme)

    ret = OK
    cnt = 0
    while (cnt < 1 + retry_times):
        if cnt:
            print('Retry downloading...')
            logging.info('Retry downloading...')
        ret = func_dict[scheme](ops_conn, url, local_path)
        if ret is OK:
            break
        cnt += 1

    if ret is not OK:
        raise ZTPErr('Failed to download file "%s"' % os.path.basename(url))

    return OK


class StartupInfo(object):
    """Startup configuration information

    image: startup system software
    config: startup saved-configuration file
    patch: startup patch package
    """
    def __init__(self, image = None, config = None, patch = None):
        self.image = image
        self.config = config
        self.patch = patch

class Startup(object):
    """Startup configuration information

    current: current startup configuration
    next: current next startup configuration
    """
    def __init__(self, ops_conn):
        self.ops_conn = ops_conn
        self.current, self.next = self._get_startup_info()

    def _get_startup_info(self):
        """Get the startup information."""
        logging.info("Get the startup information...")
        uri = "/cfg/startupInfos/startupInfo"
        req_'<?xml version="1.0" encoding="UTF-8"?>
<startupInfo>
    <position/>
    <configedSysSoft/>
    <curSysSoft/>
    <nextSysSoft/>
    <curStartupFile/>
    <nextStartupFile/>
    <curPatchFile/>
    <nextPatchFile/>
</startupInfo>'''

        cnt = 0
        while (cnt < MAX_TIMES_GET_STARTUP):
            ret, _, rsp_data = self.ops_conn.get(uri, req_data)
            if ret != httplib.OK or rsp_data is '':
                cnt += 1
                logging.info('Failed to get the startup information')
                continue

            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 not None:
                break
            logging.debug('No query result while getting startup info')
            sleep(GET_STARTUP_INTERVAL)     # sleep to wait for system ready when no query result
            cnt += 1

        if elem is None:
            raise OPIExecError('Failed to get the startup information')

        current = StartupInfo()     # current startup info
        curnext = StartupInfo()     # next startup info
        for child in elem:
            tag = child.tag[nslen + 2:]       # skip the namespace, '{namespace}text'
            if tag == 'curSysSoft':
                current.image = child.text
            elif tag == 'nextSysSoft':
                curnext.image = child.text
            elif tag == 'curStartupFile' and child.text != 'NULL':
                current.config = child.text
            elif tag == 'nextStartupFile' and child.text != 'NULL':
                curnext.config = child.text
            elif tag == 'curPatchFile' and child.text != 'NULL':
                current.patch = child.text
            elif tag == 'nextPatchFile' and child.text != 'NULL':
                curnext.patch = child.text
            else:
                continue

        return current, curnext

    def _set_startup_image_file(self, file_path):
        """Set the next startup system software"""
        logging.info("Set the next startup system software to %s...", file_path)
        uri = "/sum/startupbymode"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<startupbymode>
    <softwareName>$fileName</softwareName>
    <mode>STARTUP_MODE_ALL</mode>
</startupbymode>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup system software")

    def _set_startup_config_file(self, file_path):
        """Set the next startup saved-configuration file"""
        logging.info("Set the next startup saved-configuration file to %s...", file_path)
        uri = "/cfg/setStartup"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<setStartup>
    <fileName>$fileName</fileName>
</setStartup>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup configuration file")

    def _del_startup_config_file(self):
        """Delete startup config file"""
        logging.info("Delete the next startup config file...")
        uri = "/cfg/deleteStartup"
        req_data = '''<?xml version="1.0" encoding="UTF-8"?>
<deleteStartup>
</deleteStartup>
'''
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to delete startup configuration file")

    def _set_startup_patch_file(self, file_path):
        """Set the next startup patch file"""
        logging.info("Set the next startup patch file to %s...", file_path)
        uri = "/patch/startup"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<startup>
    <packageName>$fileName</packageName>
</startup>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup patch file")

    def _get_cur_stack_member_id(self):
        """rest api: Get current stack member id"""

        logging.info("Get current stack member ID...")
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        req_data =  \
            '''<?xml version="1.0" encoding="UTF-8"?>
            <stackMemberInfo>
                    <memberID></memberID>
                </stackMemberInfo>
            '''
        ret, _, rsp_data = self.ops_conn.get(uri, req_data)
        if ret != httplib.OK or rsp_data is '':
            raise OPIExecError('Failed to get current stack member id, rsp not ok')

        root_elem = etree.fromstring(rsp_data)
        namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
        uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
        elem = root_elem.find(uri + "memberID", namespaces)
        if elem is None:
            raise OPIExecError('Failed to get the current stack member id for no "memberID" element')

        return elem.text

    def _set_stack_member_id(self, file_path, esn):
        """Set the next stack member ID"""

        def get_stackid_from_file(fname, esn):
            """parse esn_id.txt file and get stack id according to esn num
            format of esn_stackid file is like below:

            sn              Irf group              Irf number
            Sdddg              100                         1
            Sddde              100                         2
            """
            # fname must exist, guaranteed by caller
            fname = os.path.basename(fname)
            with open(fname, 'rb') as item:
                for line in item:
                    token = line.strip('[\r\n]')
                    token = token.split()
                    if token[0] == esn:
                        return token[2]
            return None

        logging.info('Set the next stack member ID, filename %s', file_path)
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        str_temp = string.Template(
            '''<?xml version="1.0" encoding="UTF-8"?>
                <stackMemberInfo>
                    <memberID>$curmemberid</memberID>
                    <nextMemberID>$memberid</nextMemberID>
                </stackMemberInfo>
            ''')

        cur_memid = self._get_cur_stack_member_id()
        next_memid = get_stackid_from_file(file_path, esn)
        if not next_memid:
            logging.debug('Failed to get stack id from %s, esn %s', file_path, esn)
            return

        req_data = str_temp.substitute(curmemberid = cur_memid, memberid = next_memid)
        ret, _, _ = self.ops_conn.set(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to set stack id {}'.format(next_memid))

        return OK

    def _reset_stack_member_id(self):
        """rest api: reset stack member id"""

        logging.info('Reset the next stack member ID')
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        req_data = \
            '''<?xml version="1.0" encoding="UTF-8"?>
                <stackMemberInfo>
                    <memberID>1</memberID>
                    <nextMemberID>1</nextMemberID>
                </stackMemberInfo>
            '''

        ret, _, _ = self.ops_conn.set(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to reset stack id ')

        return OK

    def _reset_startup_patch_file(self):
        """Rest patch file for system to startup"""
        logging.info("Reset the next startup patch file...")
        uri = "/patch/resetpatch"
        req_data = '''<?xml version="1.0" encoding="UTF-8"?>
<resetpatch>
</resetpatch>
'''
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to reset patch')

    def reset_startup_info(self, slave):
        """Reset startup info and delete the downloaded files"""
        logging.info("Reset the next startup information...")
        _, configured = self._get_startup_info()

        # 1. Reset next startup config file and delete it
        try:
            if configured.config != self.next.config:
                if self.next.config is None:
                    self._del_startup_config_file()
                else:
                    self._set_startup_config_file(self.next.config)
                    if configured.config is not None:
                        del_file_all(self.ops_conn, configured.config, slave)

        except Exception, reason:
            logging.error(reason)

        # 2. Reset next startup patch file
        try:
            if configured.patch != self.next.patch:
                if self.next.patch is None:
                    self._reset_startup_patch_file()
                else:
                    self._set_startup_patch_file(self.next.patch)

                if configured.patch is not None:
                    del_file_all(self.ops_conn, configured.patch, slave)
        except Exception, reason:
            logging.error(reason)

        # 3. Reset next startup system software and delete it
        try:
            if configured.image != self.next.image:
                self._set_startup_image_file(self.next.image)
                del_file_all(self.ops_conn, configured.image, slave)
        except Exception, reason:
            logging.error(reason)

        # 4. reset stack member id
        try:
            self._reset_stack_member_id()
        except Exception, reason:
            logging.error(reason)

    def set_startup_info(self, image_file, config_file, patch_file, memid_file, slave, esn_str):
        """Set the next startup information."""
        logging.info("Set the next startup information...")
        # 1. Set next startup system software
        if image_file is not None:
            try:
                self._set_startup_image_file(image_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, image_file, slave)
                self.reset_startup_info(slave)
                raise

        # 2. Set next startup config file
        if config_file is not None:
            try:
                self._set_startup_config_file(config_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, config_file, slave)
                self.reset_startup_info(slave)
                raise

        # 3. Set next startup patch file
        if patch_file is not None:
            try:
                self._set_startup_patch_file(patch_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, patch_file, slave)
                self.reset_startup_info(slave)
                raise

        # 4. Set next member id
        if memid_file is not None:
            try:
                self._set_stack_member_id(memid_file, esn_str)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, memid_file, None)
                self.reset_startup_info(slave)
                raise

def get_cwd(ops_conn):
    """Get the full filename of the current working directory"""
    logging.info("Get the current working directory...")
    uri = "/vfm/pwds/pwd"
    req_data =  \
'''<?xml version="1.0" encoding="UTF-8"?>
<pwd>
    <dictionaryName/>
</pwd>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the current working directory')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    elem = root_elem.find(uri + "dictionaryName", namespaces)
    if elem is None:
        raise OPIExecError('Failed to get the current working directory for no "directoryName" element')

    return elem.text

def file_exist(ops_conn, file_path):
    """Returns True if file_path refers to an existing file, otherwise returns False"""
    uri = "/vfm/dirs/dir"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<dir>
    <fileName>$fileName</fileName>
</dir>
''')
    req_data = str_temp.substitute(fileName = file_path)
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to list information about the file "%s"' % file_path)

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

    return True

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : del_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def del_file(ops_conn, file_path):
    """Delete a file permanently"""
    if file_path is None or file_path is '':
        return

    logging.info("Delete file %s permanently", file_path)
    uri = "/vfm/deleteFileUnRes"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<deleteFileUnRes>
    <fileName>$filePath</fileName>
</deleteFileUnRes>
''')
    req_data = str_temp.substitute(filePath = file_path)
    try:
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete the file "%s" permanently' % file_path)

    except Exception, reason:
        logging.debug(reason)

def del_file_all(ops_conn, file_path, slave):
    """Delete a file permanently on all main boards"""
    if file_path:
        del_file(ops_conn, file_path)
        if slave:
            del_file(ops_conn, 'slave#' + file_path)

def copy_file(ops_conn, src_path, dest_path):
    """Copy a file"""
    print('Info: Copy file %s to %s...' % (src_path, dest_path))
    logging.info('Copy file %s to %s...', src_path, dest_path)
    uri = "/vfm/copyFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<copyFile>
    <srcFileName>$src</srcFileName>
    <desFileName>$dest</desFileName>
</copyFile>
''')
    req_data = str_temp.substitute(src = src_path, dest = dest_path)

    # it is a action operation, so use create for HTTP POST
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        raise OPIExecError('Failed to copy "%s" to "%s"' % (src_path, dest_path))

def has_slave_mpu(ops_conn):
    """Whether device has slave MPU, returns a bool value"""
    logging.info("Test whether device has slave MPU...")
    uri = "/devm/phyEntitys"
    req_data =  \
'''<?xml version="1.0" encoding="UTF-8"?>
<phyEntitys>
    <phyEntity>
        <entClass>mpuModule</entClass>
        <entStandbyState/>
        <position/>
    </phyEntity>
</phyEntitys>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the device slave information')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    for entity in root_elem.findall(uri + 'phyEntity', namespaces):
        elem = entity.find("vrp:entStandbyState", namespaces)
        if elem is not None and elem.text == 'slave':
            return True

    return False

def get_system_info(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/>
    <esn/>
    <mac/>
</systemInfo>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the system information')

    sys_info = {}.fromkeys(('productName', 'esn', 'mac'))
    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:')
    nslen = len(namespaces['vrp'])
    elem = root_elem.find(uri, namespaces)
    if elem is not None:
        for child in elem:
            tag = child.tag[nslen + 2:]       # skip the namespace, '{namespace}esn'
            if tag in sys_info.keys():
                sys_info[tag] = child.text

    return sys_info

def test_file_paths(image, config, patch, stack_memid, md5_file):
    """Test whether argument paths are valid."""
    logging.info("Test whether argument paths are valid...")
    # check image file path
    file_name = os.path.basename(image)
    if file_name is not '' and not file_name.lower().endswith('.cc'):
        print('Error: Invalid filename extension of system software')
        return False

    # check config file path
    file_name = os.path.basename(config)
    file_name = file_name.lower()
    _, ext = os.path.splitext(file_name)
    if file_name is not '' and ext not in ['.cfg', '.zip', '.dat']:
        print('Error: Invalid filename extension of configuration file')
        return False

    # check patch file path
    file_name = os.path.basename(patch)
    if file_name is not '' and not file_name.lower().endswith('.pat'):
        print('Error: Invalid filename extension of patch file')
        return False

    # check stack member id file path
    file_name = os.path.basename(stack_memid)
    if file_name is not '' and not file_name.lower().endswith('.txt'):
        print('Error: Invalid filename extension of stack member ID file')
        return False

    # check md5 file path
    file_name = os.path.basename(md5_file)
    if file_name is not '' and not file_name.lower().endswith('.txt'):
        print('Error: Invalid filename extension of md5 file')
        return False

    return True

def md5sum(fname, need_skip_first_line = False):  
    """ 
    Calculate md5 num for this file. 
    """

    def read_chunks(fhdl):  
        chunk = fhdl.read(8096)  
        while chunk:
            yield chunk
            chunk = fhdl.read(8096)  
        else:
            fhdl.seek(0)

    md5_obj = hashlib.md5()
    if isinstance(fname, basestring) and os.path.exists(fname):  
        with open(fname, "rb") as fhdl:
            #skip the first line
            fhdl.seek(0)
            if need_skip_first_line:
                fhdl.readline()
            for chunk in read_chunks(fhdl):
                md5_obj.update(chunk)
    elif fname.__class__.__name__ in ["StringIO", "StringO"] or isinstance(fname, file):  
        for chunk in read_chunks(fname):
            md5_obj.update(chunk)
    else:
        pass
    return md5_obj.hexdigest()

def md5_get_from_file(fname):
    """Get md5 num form file, stored in first line"""

    with open(fname, "rb") as fhdl:
        fhdl.seek(0)
        line_first = fhdl.readline()

    # if not match pattern, the format of this file is not supported
    if not re.match('^#md5sum="[\\w]{32}"[\r\n]+$', line_first):
        return ''

    return line_first[9:41]

def md5_check_with_first_line(fname):
    """Validate md5 for this file"""

    fname = os.path.basename(fname)
    md5_calc = md5sum(fname, True)
    print('MD5 checksum of the file "%s" is %s' % (fname, md5_calc))

    md5_file = md5_get_from_file(fname)
    print('MD5 checksum received from the file "%s" is %s' % (fname, md5_file))

    if md5_file != md5_calc:
        return False

    return True

def md5_check_with_dic(md5_dic, fname):
    """"""
    if not md5_dic.has_key(fname):
        logging.debug('md5_dic does not has key %s', fname)
        return True
    if md5_dic[fname] == md5sum(fname, False):
        return True
    return False

def parse_md5_file(fname):
    """parse md5 file"""

    def read_line(fhdl):  
        line = fhdl.readline()  
        while line:
            yield line
            line = fhdl.readline()  
        else:
            fhdl.seek(0)

    md5_dic = {}
    with open(fname, "rb") as fhdl:
        for line in read_line(fhdl):
            line_spilt = line.split()
            if 2 != len(line_spilt):
                continue
            dic_tmp = {line_spilt[0]: line_spilt[1]}
            md5_dic.update(dic_tmp)
    return md5_dic

def verify_and_parse_md5_file(fname):
    """
    vefiry data integrity of md5 file and parse this file

    format of this file is like:
    ------------------------------------------------------------------
    #md5sum="517cf194e2e1960429c6aedc0e4dba37"

    file-name              md5
    conf_5618642831132.cfg c0ace0f0542950beaacb39cd1c3b5716
    ------------------------------------------------------------------
    """
    if not md5_check_with_first_line(fname):
        return ERR, None
    return OK, parse_md5_file(fname)

def main_proc(ops_conn):
    """Main processing"""
    sys_info = get_system_info(ops_conn)    # Get system info, such as esn and system mac
    cwd = get_cwd(ops_conn)                 # Get the current working directory
    startup = Startup(ops_conn)
    slave = has_slave_mpu(ops_conn)         # Check whether slave MPU board exists or not
    chg_flag = False

    # check remote file paths
    if not test_file_paths(REMOTE_PATH_IMAGE.get(sys_info['productName'], ''), REMOTE_PATH_CONFIG,
                           REMOTE_PATH_PATCH, REMOTE_PATH_MEMID, REMOTE_PATH_MD5):
        return ERR

    # download md5 file first, used to verify data integrity of files which will be downloaded next
    local_path_md5 = None
    file_path = REMOTE_PATH_MD5
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_md5 = cwd + file_name
        ret = download_file(ops_conn, url, local_path_md5, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download MD5 file "%s"' % file_name)
            return ERR
        print('Info: Download MD5 file successfully')
        ret, md5_dic = verify_and_parse_md5_file(file_name)
        # delete the file immediately
        del_file_all(ops_conn, local_path_md5, None)
        if ret is ERR:
            print('Error: MD5 check failed, file "%s"' % file_name)
            return ERR
    else:
        md5_dic = {}

    # download configuration file
    local_path_config = None
    file_path = REMOTE_PATH_CONFIG % sys_info['esn']
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_config = cwd + file_name
        ret = download_file(ops_conn, url, local_path_config, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download configuration file "%s"' % file_name)
            return ERR
        print('Info: Download configuration file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_config, 'slave#' + local_path_config)
        chg_flag = True

    # download patch file
    local_path_patch = None
    file_path = REMOTE_PATH_PATCH
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if startup.current.patch:
        cur_pat = os.path.basename(startup.current.patch).lower()
    else:
        cur_pat = ''
    if file_name is not '' and file_name.lower() != cur_pat:
        url  = FILE_SERVER + file_path
        local_path_patch = cwd + file_name
        ret = download_file(ops_conn, url, local_path_patch, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download patch file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            return ERR
        print('Info: Download patch file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_patch, 'slave#' + local_path_patch)
        chg_flag = True

    # download stack member ID file
    local_path_memid = None
    file_path = REMOTE_PATH_MEMID
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_memid = cwd + file_name
        ret = download_file(ops_conn, url, local_path_memid, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download system software "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            return ERR
        print('Info: Download stack member ID file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            return ERR
        chg_flag = True
        #no need copy to slave board

    # download system software
    local_path_image = None
    file_path = REMOTE_PATH_IMAGE.get(sys_info['productName'], '')
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if startup.current.image:
        cur_image = os.path.basename(startup.current.image).lower()
    else:
        cur_image = ''
    if file_name is not '' and file_name.lower() != cur_image:
        url  = FILE_SERVER + file_path
        local_path_image = cwd + file_name
        ret = download_file(ops_conn, url, local_path_image, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download system software "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            del_file_all(ops_conn, local_path_memid, slave)
            return ERR
        print('Info: Download system software file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            del_file_all(ops_conn, local_path_memid, slave)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_image, 'slave#' + local_path_image)
        chg_flag = True

    if chg_flag is False:
        return ERR

    # set startup info
    startup.set_startup_info(local_path_image, local_path_config, local_path_patch,
                             local_path_memid, slave, sys_info['esn'])

    # delete stack member ID file after used
    del_file_all(ops_conn, local_path_memid, None)

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : main
# Date Created    : 2013-7-2
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def main(usb_path = ''):
    """The main function of user script. It is called by ZTP frame, so do not remove or change this function.

    Args:
    Raises:
    Returns: user script processing result
    """
    host = "localhost"
    if usb_path and len(usb_path):
        logging.debug('ztp_script usb_path: %s', usb_path)
        global FILE_SERVER
        FILE_SERVER = 'file:///' + usb_path
    try:
        # Make an OPS connection instance.
        ops_conn = OPSConnection(host)
        ret = main_proc(ops_conn)

    except OPIExecError, reason:
        logging.debug('OPI execute error: %s', reason)
        print("Error: %s" % reason)
        ret = ERR

    except ZTPErr, reason:
        logging.debug('ZTP error: %s', reason)
        print("Error: %s" % reason)
        ret = ERR

    except Exception, reason:
        logging.debug(reason)
        traceinfo = traceback.format_exc()
        logging.debug(traceinfo)
        ret = ERR

    finally:
        # Close the OPS connection
        ops_conn.close()

    return ret

if __name__ == "__main__":
    main()

Configuration Files
  • Configuration file of SwitchC

    #
    sysname SwitchC
    #
    vlan batch 10
    #
    dhcp enable
    #
    interface Vlanif10
     ip address 10.1.1.1 255.255.255.0
     dhcp select relay
     dhcp relay binding server ip 10.1.2.2
    #
    interface 10GE1/17/1
     port link-type trunk
     port trunk pvid vlan 10
     port trunk allow-pass vlan 10
    #
    interface 10GE1/17/2
     port link-type trunk
     port trunk pvid vlan 10
     port trunk allow-pass vlan 10
    #
    return
    

Example for Configuring Unconfigured Devices to Implement Automatic Deployment and Set up a Stack Through DHCP

Networking Requirements

As shown in Figure 1-5, SwitchA and SwitchB are two unconfigured switches on the network, and both are connected to SwitchC. SwitchC functions as the egress gateway of SwitchA and SwitchB. The routes between SwitchC and DHCP server, and between SwitchC and the file server are reachable.

To reduce labor costs and device deployment time, the customer requires that SwitchA and SwitchB can automatically load system software and configuration file after they are powered on and set up a stack after automatic deployment is complete.

Table 1-17 lists information about SwitchA and SwitchB, and the files that the switches need to load.
Table 1-17 Device information and files to be loaded

New Device

Device Model

Serial Number

Stack Member ID

File to Be Loaded

SwitchA

CX110

210235527210D4000028

1

  • System software: CX110-Switch-V103.cc

  • Configuration file: conf_210235527210D4000028.cc

SwitchB

CX110

210235527210D4000046

2

  • System software: CX110-Switch-V103.cc

  • Configuration file: conf_210235527210D4000046.cfg

NOTE:
To ensure that SwitchA and SwitchB can set up a stack, the following conditions must be met:
  • SwitchA and SwitchB have been connected using stack cables before they are powered on.

  • The configuration file must contain all stack configurations related to the stack member ID to ensure that a stack can be set up successfully after the configuration file is loaded. For example, the configuration file conf_210235527210D4000046.cfg must contain the stack configurations related to stack member ID 2.

Figure 1-5 Configuring ZTP
Configuration Roadmap
The configuration roadmap is as follows:
  1. Configure an FTP server as the file server to save the intermediate file, system software, and configuration files.

  2. Edit the intermediate file ztp_script.py and the stack member ID file stack_memberid.txt so that the switches can obtain their system software packages and configuration files according to the intermediate file, and obtain stack member IDs according to the stack member ID file.

  3. Configure the DHCP server and relay agent to enable unconfigured switches to obtain DHCP information.

  4. Power on SwitchA and SwitchB to start the ZTP process.

Procedure

  1. Configure the file server. (The following example uses a PC as the file server. If a device of a different type functions as the file server, configure the device according to the corresponding operation guide.)

    1. Configure FTP server functions on the PC. Run an FTP server program (for example, WFTPD32) on a PC. As shown in Figure 1-6, choose Security > Users/rights. In the displayed dialog box, click New User to set the user name and password. Here, the user name is ftpuser and the password is Pwd123. Enter the FTP working directory in the Home Directory text box. Here, the working directory is D:\ztp. Click Done to close the dialog box.

      Figure 1-6 Configuring the file server
    2. Configure the IP address and gateway for the file server. Ensure that the file server and gateway of SwitchA and SwitchB have reachable routes to each other.

    After configuring the file server, save the system software and configuration files to be loaded to switches in the working directory D:\ztp.

  2. Edit the intermediate file.

    Edit the intermediate file according to Python Script. The file is named ztp_script.py. See Example of ztp_script.py for the file contents.

    After editing the intermediate file, save the file to the working directory D:\ztp on the file server.

  3. Edit the stack member ID file.

    Write stack member IDs of SwitchA and SwitchB into stack_memberid.txt in the following format:

    ESN                   Stack group         Stack member
    210235527210D4000028  10                  1
    210235527210D4000046  10                  2

    ESN is the equipment serial number, and Stack member indicates the stack member ID of the switches.

    After editing the stack member ID file, save the file to the working directory D:\ztp on the file server.

  4. Configure the DHCP server.

    # Configure the IP address pool to be allocated by the DHCP server to clients and configure the Option value of the DHCP server. For details, see the related DHCP server documentation.

    Table 1-18 Options of the DHCP server

    Option No.

    Description

    Value

    1

    Subnet mask of an IP address.

    255.255.225.0

    3

    Egress gateway of the DHCP client

    10.1.1.1

    67

    File server address and intermediate file name.

    ftp://ftpuser:Pwd123@10.1.3.2/ztp_script.py

    # Configure the IP address and gateway for the DHCP server. Ensure that the DHCP server and gateway of SwitchA and SwitchB have reachable routes to each other.

  5. Configure the DHCP relay agent.

    # On SwitchC, configure the DHCP relay function and set the IP address of the VLANIF interface connected to SwitchA and SwitchB to 10.1.1.1. The VLANIF interface functions as the default gateway of SwitchA and SwitchB.

    <HUAWEI> system-view
    [~HUAWEI] sysname SwitchC
    [*HUAWEI] commit
    [~SwitchC] vlan batch 10
    [*SwitchC] interface 10ge 1/17/1
    [*SwitchC-10GE1/17/1] port link-type trunk
    [*SwitchC-10GE1/17/1] port trunk allow-pass vlan 10
    [*SwitchC-10GE1/17/1] port trunk pvid vlan 10
    [*SwitchC-10GE1/17/1] quit
    [*SwitchC] interface 10ge 1/17/2
    [*SwitchC-10GE1/17/2] port link-type trunk
    [*SwitchC-10GE1/17/2] port trunk allow-pass vlan 10
    [*SwitchC-10GE1/17/2] port trunk pvid vlan 10
    [*SwitchC-10GE1/17/2] quit
    [*SwitchC] interface vlanif 10
    [*SwitchC-Vlanif10] ip address 10.1.1.1 24
    [*SwitchC-Vlanif10] quit
    [*SwitchC] dhcp enable
    [*SwitchC] interface vlanif 10
    [*SwitchC-Vlanif10] dhcp select relay
    [*SwitchC-Vlanif10] dhcp relay binding server ip 10.1.2.2
    [*SwitchC-Vlanif10] commit
    

  6. Power on SwitchA and SwitchB to start the ZTP process.
  7. Verify the configuration.

    # The switches complete the ZTP process 15 minutes after they are powered on. Log in to the stack and run the display startup command to check whether the current system software and configuration files are the required ones.
    <SwitchA> display startup
    MainBoard:
      Configured startup system software:        flash:/CX110-Switch-V103.cc
    
      Startup system software:                   flash:/CX110-Switch-V103.cc
    
      Next startup system software:              flash:/CX110-Switch-V103.cc
    
      Startup saved-configuration file:          flash:/conf_210235527210D4000028.cfg
      Next startup saved-configuration file:     flash:/conf_210235527210D4000028.cfg
      Startup paf file:                          default
      Next startup paf file:                     default
      Startup patch package:                     NULL
      Next startup patch package:                NULL
    SlaveBoard:
      Configured startup system software:        flash:/CX110-Switch-V103.cc
    
      Startup system software:                   flash:/CX110-Switch-V103.cc
    
      Next startup system software:              flash:/CX110-Switch-V103.cc
    
      Startup saved-configuration file:          flash:/conf_210235527210D4000028.cfg
      Next startup saved-configuration file:     flash:/conf_210235527210D4000028.cfg
      Startup paf file:                          default
      Next startup paf file:                     default
      Startup patch package:                     NULL
      Next startup patch package:                NULL
    

ztp_script.py File
NOTE:

#md5sum= is the MD5 code of the intermediate file ztp_script.py. You can modify the contents of ztp_script.py according to actual networking requirements. After the modification, use an MD5 calculation tool, such as md5sum, to generate the MD5 code of the modified file.

Note that the intermediate file cannot contain #md5sum= when the MD5 code is generated. Add #md5sum= to the beginning of the script after the MD5 code is generated.

#md5sum="07bd3588792c770b3c480ba127aec376"
#!/usr/bin/env python
#
# Copyright (C) Huawei Technologies Co., Ltd. 2008-2013. All rights reserved.
# ----------------------------------------------------------------------------------------------------------------------
# History:
# Date                Author                      Modification
# 20130629            Author                      created file.
# ----------------------------------------------------------------------------------------------------------------------

"""
Zero Touch Provisioning (ZTP) enables devices to automatically load version files including system software,
patch files, configuration files when the device starts up, the devices to be configured must be new devices
or have no configuration files.

This is a sample of Zero Touch Provisioning user script. You can customize it to meet the requirements of
your network environment.
"""

import httplib
import urllib
import string
import re
import xml.etree.ElementTree as etree
import os
import stat
import logging
import traceback
import hashlib

from urlparse import urlparse
from urlparse import urlunparse
from time import sleep

# error code
OK          = 0
ERR         = 1

# File server in which stores the necessary system software, configuration and patch files:
#   1) Specify the file server which supports the following format.
#      tftp://hostname
#      ftp://[username[:password]@]hostname[:port]
#      sftp://[username[:password]@]hostname[:port]
#      http://hostname[:port]
#   2) Do not add a trailing slash at the end of file server path.
FILE_SERVER         = 'ftp://ftpuser:Pwd123@10.1.3.2'

# Remote file paths:
#   1) The path may include directory name and file name.
#   2) If file name is not specified, indicate the procedure can be skipped.
# File paths of system software on file server, filename extension is '.cc'.
REMOTE_PATH_IMAGE   = {
    'CX110'    :   '/CX110-Switch-V103.cc
',
    'CX310'    :   '/CX310-Switch-V103.cc
'
 
}
# File path of configuration file on file server, filename extension is '.cfg', '.zip' or '.dat'.
REMOTE_PATH_CONFIG  = '/conf_%s.cfg'
# File path of patch file on file server, filename extension is '.pat'
REMOTE_PATH_PATCH   = ''
# File path of stack member ID file on file server, filename extension is '.txt'
REMOTE_PATH_MEMID   = '/stack_memberid.txt'
# File path of md5 file, contains md5 value of image / patch / memid file, file extension if '.txt'
REMOTE_PATH_MD5     = ''


# Max times to retry get startup when no query result
GET_STARTUP_INTERVAL        = 15    # seconds
MAX_TIMES_GET_STARTUP       = 120   # Max times to retry

# Max times to retry when download file faild
MAX_TIMES_RETRY_DOWNLOAD    = 3


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 = httplib.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 == None:
            body = ""
        else:
            body = req_data

        logging.debug('HTTP request: %s %s HTTP/1.1', method, uri)
        self.conn.request(method, uri, body, self.headers)
        response = self.conn.getresponse()
        ret = (response.status, response.reason, response.read())
        if response.status != httplib.OK:
            logging.debug('%s', body)
        logging.debug('HTTP response: HTTP/1.1 %s %s\n%s', ret[0], ret[1], ret[2])
        return ret

class OPIExecError(Exception):
    """OPI executes error."""
    pass

class ZTPErr(Exception):
    """ZTP error."""
    pass


def get_addr_by_hostname(ops_conn, host, addr_type = '1'):
    """Translate a host name to IPv4 address format. The IPv4 address is returned as a string."""
    logging.info("Get IP address by host name...")
    uri = "/dns/dnsNameResolution"
    root_elem = etree.Element('dnsNameResolution')
    etree.SubElement(root_elem, 'host').text = host
    etree.SubElement(root_elem, 'addrType').text = addr_type
    req_data = etree.tostring(root_elem, "UTF-8")
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK:
        raise OPIExecError('Failed to get address by host name')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    elem = root_elem.find(uri + "ipv4Addr", namespaces)
    if elem is None:
        raise OPIExecError('Failed to get IP address by host name')

    return elem.text

def _http_download_file(ops_conn, url, local_path):
    """Download file using HTTP."""
    logging.info('HTTP download "%s" to "%s".', url, local_path)

    url_tuple = urlparse(url)
    if not re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        netloc = get_addr_by_hostname(ops_conn, url_tuple.hostname)
        if url_tuple.port:
            netloc += ':' + str(url_tuple.port)
        url = urlunparse((url_tuple.scheme, netloc, url_tuple.path, url_tuple.params, url_tuple.query,
                          url_tuple.fragment))

    ret = OK
    opener = urllib.URLopener()
    try:
        dst_file_path = "%s/%s" % (os.getcwd(), os.path.basename(url))
        dst_file_path = os.path.abspath(dst_file_path)
        logging.info('HTTP download destination file=%s.', dst_file_path)
        opener.retrieve(url, dst_file_path)
        os.chmod(dst_file_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
    except (KeyboardInterrupt, Exception), reason:
        if os.path.exists(dst_file_path):
            os.remove(dst_file_path)    # Remove incomplete file
        logging.error(reason)
        print('Error: Failed to download file "%s" using HTTP' % os.path.basename(url))
        ret = ERR

    return ret

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : ftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _ftp_download_file(ops_conn, url, local_path):
    """Download file using FTP."""
    logging.info('FTP download "%s" to "%s".', url, local_path)
    uri = "/ftpc/ftpcTransferFiles/ftpcTransferFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<ftpcTransferFile>
    <serverIpv4Address>$serverIp</serverIpv4Address>
    <commandType>get</commandType>
    <userName>$username</userName>
    <password>$password</password>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
</ftpcTransferFile>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, username = url_tuple.username, password = url_tuple.password,
                                   remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using FTP' % os.path.basename(local_path))
        return ERR

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _del_rsa_peer_key
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _del_rsa_peer_key(ops_conn, key_name):
    """Delete RSA peer key configuration"""
    logging.debug("Delete RSA peer key %s", key_name)
    uri = "/rsa/rsaPeerKeys/rsaPeerKey"
    root_elem = etree.Element('rsaPeerKey')
    etree.SubElement(root_elem, 'keyName').text = key_name
    req_data = etree.tostring(root_elem, "UTF-8")
    try:
        ret, _, _ = ops_conn.delete(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete RSA peer key')

    except Exception, reason:
        logging.debug(reason)

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _del_sshc_rsa_key
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _del_sshc_rsa_key(ops_conn, server_name, key_type = 'RSA'):
    """Delete SSH client RSA key configuration"""
    logging.debug("Delete SSH client RSA key for %s", server_name)
    uri = "/sshc/sshCliKeyCfgs/sshCliKeyCfg"
    root_elem = etree.Element('sshCliKeyCfg')
    etree.SubElement(root_elem, 'serverName').text = server_name
    etree.SubElement(root_elem, 'pubKeyType').text = key_type
    req_data = etree.tostring(root_elem, "UTF-8")
    try:
        ret, _, _ = ops_conn.delete(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete SSH client RSA key')

    except Exception, reason:
        logging.debug(reason)

    _del_rsa_peer_key(ops_conn, server_name)

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : _set_sshc_first_time
# Date Created    : 2013-8-1
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _set_sshc_first_time(ops_conn, switch):
    """Set SSH client attribute of authenticating user for the first time access"""
    if switch not in ['Enable', 'Disable']:
        return ERR

    logging.debug('Set SSH client first-time enable switch = %s', switch)
    uri = "/sshc/sshClient"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<sshClient>
    <firstTimeEnable>$enable</firstTimeEnable>
</sshClient>
''')
    req_data = str_temp.substitute(enable = switch)
    ret, _, _ = ops_conn.set(uri, req_data)
    if ret != httplib.OK:
        if switch == 'Enable':
            raise OPIExecError('Failed to enable SSH client first-time')
        else:
            raise OPIExecError('Failed to disable SSH client first-time')

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : sftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _sftp_download_file(ops_conn, url, local_path):
    """Download file using SFTP."""
    _set_sshc_first_time(ops_conn, 'Enable')

    logging.debug('SFTP download "%s" to "%s".', url, local_path)
    uri = "/sshc/sshcConnects/sshcConnect"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<sshcConnect>
    <HostAddrIPv4>$serverIp</HostAddrIPv4>
    <commandType>get</commandType>
    <userName>$username</userName>
    <password>$password</password>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
    <identityKey>ssh-rsa</identityKey>
    <transferType>SFTP</transferType>
</sshcConnect>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, username = url_tuple.username, password = url_tuple.password,
                                   remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using SFTP' % os.path.basename(local_path))
        ret = ERR
    else:
        ret = OK

    _del_sshc_rsa_key(ops_conn, server_ip)
    _set_sshc_first_time(ops_conn, 'Disable')
    return ret

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : tftp_download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def _tftp_download_file(ops_conn, url, local_path):
    """Download file using TFTP."""
    logging.debug('TFTP download "%s" to "%s".', url, local_path)
    uri = "/tftpc/tftpcTransferFiles/tftpcTransferFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<tftpcTransferFile>
    <serverIpv4Address>$serverIp</serverIpv4Address>
    <commandType>get_cmd</commandType>
    <localFileName>$localPath</localFileName>
    <remoteFileName>$remotePath</remoteFileName>
</tftpcTransferFile>
''')
    url_tuple = urlparse(url)
    if re.match(r"\d+\.\d+\.\d+\.\d+", url_tuple.hostname):
        server_ip = url_tuple.hostname
    else:
        server_ip = get_addr_by_hostname(ops_conn, url_tuple.hostname)
    req_data = str_temp.substitute(serverIp = server_ip, remotePath = url_tuple.path[1:], localPath = local_path)
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        print('Failed to download file "%s" using TFTP' % os.path.basename(local_path))
        return ERR

    return OK

def _usb_download_file(ops_conn, url, local_path):
    """Download file using usb"""
    logging.info('USB download "%s" to "%s".', url, local_path)

    url_tuple = urlparse(url, allow_fragments=False)
    src_path = url_tuple.path[1:]
    try:
        copy_file(ops_conn, src_path, local_path)
    except:
        print('Failed to download file "%s" using USB' % os.path.basename(local_path))
        return ERR
    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : download_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def download_file(ops_conn, url, local_path, retry_times = 0):
    """Download file, support TFTP, FTP, SFTP and HTTP.

    tftp://hostname/path
    ftp://[username[:password]@]hostname[:port]/path
    sftp://[username[:password]@]hostname[:port]/path
    http://hostname[:port]/path

    Args:
      ops_conn: OPS connection instance
      url: URL of remote file
      local_path: local path to put the file

    Returns:
        A integer of return code
    """
    print("Info: Download %s to %s" % (url, local_path))
    url_tuple = urlparse(url)
    func_dict = {'tftp': _tftp_download_file,
                 'ftp':  _ftp_download_file,
                 'sftp': _sftp_download_file,
                 'http': _http_download_file,
                 'file': _usb_download_file}
    scheme = url_tuple.scheme
    if scheme not in func_dict.keys():
        raise ZTPErr('Unknown file transfer scheme %s' % scheme)

    ret = OK
    cnt = 0
    while (cnt < 1 + retry_times):
        if cnt:
            print('Retry downloading...')
            logging.info('Retry downloading...')
        ret = func_dict[scheme](ops_conn, url, local_path)
        if ret is OK:
            break
        cnt += 1

    if ret is not OK:
        raise ZTPErr('Failed to download file "%s"' % os.path.basename(url))

    return OK


class StartupInfo(object):
    """Startup configuration information

    image: startup system software
    config: startup saved-configuration file
    patch: startup patch package
    """
    def __init__(self, image = None, config = None, patch = None):
        self.image = image
        self.config = config
        self.patch = patch

class Startup(object):
    """Startup configuration information

    current: current startup configuration
    next: current next startup configuration
    """
    def __init__(self, ops_conn):
        self.ops_conn = ops_conn
        self.current, self.next = self._get_startup_info()

    def _get_startup_info(self):
        """Get the startup information."""
        logging.info("Get the startup information...")
        uri = "/cfg/startupInfos/startupInfo"
        req_'<?xml version="1.0" encoding="UTF-8"?>
<startupInfo>
    <position/>
    <configedSysSoft/>
    <curSysSoft/>
    <nextSysSoft/>
    <curStartupFile/>
    <nextStartupFile/>
    <curPatchFile/>
    <nextPatchFile/>
</startupInfo>'''

        cnt = 0
        while (cnt < MAX_TIMES_GET_STARTUP):
            ret, _, rsp_data = self.ops_conn.get(uri, req_data)
            if ret != httplib.OK or rsp_data is '':
                cnt += 1
                logging.info('Failed to get the startup information')
                continue

            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 not None:
                break
            logging.debug('No query result while getting startup info')
            sleep(GET_STARTUP_INTERVAL)     # sleep to wait for system ready when no query result
            cnt += 1

        if elem is None:
            raise OPIExecError('Failed to get the startup information')

        current = StartupInfo()     # current startup info
        curnext = StartupInfo()     # next startup info
        for child in elem:
            tag = child.tag[nslen + 2:]       # skip the namespace, '{namespace}text'
            if tag == 'curSysSoft':
                current.image = child.text
            elif tag == 'nextSysSoft':
                curnext.image = child.text
            elif tag == 'curStartupFile' and child.text != 'NULL':
                current.config = child.text
            elif tag == 'nextStartupFile' and child.text != 'NULL':
                curnext.config = child.text
            elif tag == 'curPatchFile' and child.text != 'NULL':
                current.patch = child.text
            elif tag == 'nextPatchFile' and child.text != 'NULL':
                curnext.patch = child.text
            else:
                continue

        return current, curnext

    def _set_startup_image_file(self, file_path):
        """Set the next startup system software"""
        logging.info("Set the next startup system software to %s...", file_path)
        uri = "/sum/startupbymode"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<startupbymode>
    <softwareName>$fileName</softwareName>
    <mode>STARTUP_MODE_ALL</mode>
</startupbymode>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup system software")

    def _set_startup_config_file(self, file_path):
        """Set the next startup saved-configuration file"""
        logging.info("Set the next startup saved-configuration file to %s...", file_path)
        uri = "/cfg/setStartup"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<setStartup>
    <fileName>$fileName</fileName>
</setStartup>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup configuration file")

    def _del_startup_config_file(self):
        """Delete startup config file"""
        logging.info("Delete the next startup config file...")
        uri = "/cfg/deleteStartup"
        req_data = '''<?xml version="1.0" encoding="UTF-8"?>
<deleteStartup>
</deleteStartup>
'''
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to delete startup configuration file")

    def _set_startup_patch_file(self, file_path):
        """Set the next startup patch file"""
        logging.info("Set the next startup patch file to %s...", file_path)
        uri = "/patch/startup"
        str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<startup>
    <packageName>$fileName</packageName>
</startup>
''')
        req_data = str_temp.substitute(fileName = file_path)
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError("Failed to set startup patch file")

    def _get_cur_stack_member_id(self):
        """rest api: Get current stack member id"""

        logging.info("Get current stack member ID...")
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        req_data =  \
            '''<?xml version="1.0" encoding="UTF-8"?>
            <stackMemberInfo>
                    <memberID></memberID>
                </stackMemberInfo>
            '''
        ret, _, rsp_data = self.ops_conn.get(uri, req_data)
        if ret != httplib.OK or rsp_data is '':
            raise OPIExecError('Failed to get current stack member id, rsp not ok')

        root_elem = etree.fromstring(rsp_data)
        namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
        uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
        elem = root_elem.find(uri + "memberID", namespaces)
        if elem is None:
            raise OPIExecError('Failed to get the current stack member id for no "memberID" element')

        return elem.text

    def _set_stack_member_id(self, file_path, esn):
        """Set the next stack member ID"""

        def get_stackid_from_file(fname, esn):
            """parse esn_id.txt file and get stack id according to esn num
            format of esn_stackid file is like below:

            sn              Irf group              Irf number
            Sdddg              100                         1
            Sddde              100                         2
            """
            # fname must exist, guaranteed by caller
            fname = os.path.basename(fname)
            with open(fname, 'rb') as item:
                for line in item:
                    token = line.strip('[\r\n]')
                    token = token.split()
                    if token[0] == esn:
                        return token[2]
            return None

        logging.info('Set the next stack member ID, filename %s', file_path)
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        str_temp = string.Template(
            '''<?xml version="1.0" encoding="UTF-8"?>
                <stackMemberInfo>
                    <memberID>$curmemberid</memberID>
                    <nextMemberID>$memberid</nextMemberID>
                </stackMemberInfo>
            ''')

        cur_memid = self._get_cur_stack_member_id()
        next_memid = get_stackid_from_file(file_path, esn)
        if not next_memid:
            logging.debug('Failed to get stack id from %s, esn %s', file_path, esn)
            return

        req_data = str_temp.substitute(curmemberid = cur_memid, memberid = next_memid)
        ret, _, _ = self.ops_conn.set(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to set stack id {}'.format(next_memid))

        return OK

    def _reset_stack_member_id(self):
        """rest api: reset stack member id"""

        logging.info('Reset the next stack member ID')
        uri = "/stack/stackMemberInfos/stackMemberInfo"
        req_data = \
            '''<?xml version="1.0" encoding="UTF-8"?>
                <stackMemberInfo>
                    <memberID>1</memberID>
                    <nextMemberID>1</nextMemberID>
                </stackMemberInfo>
            '''

        ret, _, _ = self.ops_conn.set(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to reset stack id ')

        return OK

    def _reset_startup_patch_file(self):
        """Rest patch file for system to startup"""
        logging.info("Reset the next startup patch file...")
        uri = "/patch/resetpatch"
        req_data = '''<?xml version="1.0" encoding="UTF-8"?>
<resetpatch>
</resetpatch>
'''
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = self.ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to reset patch')

    def reset_startup_info(self, slave):
        """Reset startup info and delete the downloaded files"""
        logging.info("Reset the next startup information...")
        _, configured = self._get_startup_info()

        # 1. Reset next startup config file and delete it
        try:
            if configured.config != self.next.config:
                if self.next.config is None:
                    self._del_startup_config_file()
                else:
                    self._set_startup_config_file(self.next.config)
                    if configured.config is not None:
                        del_file_all(self.ops_conn, configured.config, slave)

        except Exception, reason:
            logging.error(reason)

        # 2. Reset next startup patch file
        try:
            if configured.patch != self.next.patch:
                if self.next.patch is None:
                    self._reset_startup_patch_file()
                else:
                    self._set_startup_patch_file(self.next.patch)

                if configured.patch is not None:
                    del_file_all(self.ops_conn, configured.patch, slave)
        except Exception, reason:
            logging.error(reason)

        # 3. Reset next startup system software and delete it
        try:
            if configured.image != self.next.image:
                self._set_startup_image_file(self.next.image)
                del_file_all(self.ops_conn, configured.image, slave)
        except Exception, reason:
            logging.error(reason)

        # 4. reset stack member id
        try:
            self._reset_stack_member_id()
        except Exception, reason:
            logging.error(reason)

    def set_startup_info(self, image_file, config_file, patch_file, memid_file, slave, esn_str):
        """Set the next startup information."""
        logging.info("Set the next startup information...")
        # 1. Set next startup system software
        if image_file is not None:
            try:
                self._set_startup_image_file(image_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, image_file, slave)
                self.reset_startup_info(slave)
                raise

        # 2. Set next startup config file
        if config_file is not None:
            try:
                self._set_startup_config_file(config_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, config_file, slave)
                self.reset_startup_info(slave)
                raise

        # 3. Set next startup patch file
        if patch_file is not None:
            try:
                self._set_startup_patch_file(patch_file)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, patch_file, slave)
                self.reset_startup_info(slave)
                raise

        # 4. Set next member id
        if memid_file is not None:
            try:
                self._set_stack_member_id(memid_file, esn_str)
            except Exception, reason:
                logging.debug(reason)
                del_file_all(self.ops_conn, memid_file, None)
                self.reset_startup_info(slave)
                raise

def get_cwd(ops_conn):
    """Get the full filename of the current working directory"""
    logging.info("Get the current working directory...")
    uri = "/vfm/pwds/pwd"
    req_data =  \
'''<?xml version="1.0" encoding="UTF-8"?>
<pwd>
    <dictionaryName/>
</pwd>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the current working directory')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    elem = root_elem.find(uri + "dictionaryName", namespaces)
    if elem is None:
        raise OPIExecError('Failed to get the current working directory for no "directoryName" element')

    return elem.text

def file_exist(ops_conn, file_path):
    """Returns True if file_path refers to an existing file, otherwise returns False"""
    uri = "/vfm/dirs/dir"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<dir>
    <fileName>$fileName</fileName>
</dir>
''')
    req_data = str_temp.substitute(fileName = file_path)
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to list information about the file "%s"' % file_path)

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

    return True

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : del_file
# Date Created    : 2013-7-6
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def del_file(ops_conn, file_path):
    """Delete a file permanently"""
    if file_path is None or file_path is '':
        return

    logging.info("Delete file %s permanently", file_path)
    uri = "/vfm/deleteFileUnRes"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<deleteFileUnRes>
    <fileName>$filePath</fileName>
</deleteFileUnRes>
''')
    req_data = str_temp.substitute(filePath = file_path)
    try:
        # it is a action operation, so use create for HTTP POST
        ret, _, _ = ops_conn.create(uri, req_data)
        if ret != httplib.OK:
            raise OPIExecError('Failed to delete the file "%s" permanently' % file_path)

    except Exception, reason:
        logging.debug(reason)

def del_file_all(ops_conn, file_path, slave):
    """Delete a file permanently on all main boards"""
    if file_path:
        del_file(ops_conn, file_path)
        if slave:
            del_file(ops_conn, 'slave#' + file_path)

def copy_file(ops_conn, src_path, dest_path):
    """Copy a file"""
    print('Info: Copy file %s to %s...' % (src_path, dest_path))
    logging.info('Copy file %s to %s...', src_path, dest_path)
    uri = "/vfm/copyFile"
    str_temp = string.Template(
'''<?xml version="1.0" encoding="UTF-8"?>
<copyFile>
    <srcFileName>$src</srcFileName>
    <desFileName>$dest</desFileName>
</copyFile>
''')
    req_data = str_temp.substitute(src = src_path, dest = dest_path)

    # it is a action operation, so use create for HTTP POST
    ret, _, _ = ops_conn.create(uri, req_data)
    if ret != httplib.OK:
        raise OPIExecError('Failed to copy "%s" to "%s"' % (src_path, dest_path))

def has_slave_mpu(ops_conn):
    """Whether device has slave MPU, returns a bool value"""
    logging.info("Test whether device has slave MPU...")
    uri = "/devm/phyEntitys"
    req_data =  \
'''<?xml version="1.0" encoding="UTF-8"?>
<phyEntitys>
    <phyEntity>
        <entClass>mpuModule</entClass>
        <entStandbyState/>
        <position/>
    </phyEntity>
</phyEntitys>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the device slave information')

    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:') + '/vrp:'
    for entity in root_elem.findall(uri + 'phyEntity', namespaces):
        elem = entity.find("vrp:entStandbyState", namespaces)
        if elem is not None and elem.text == 'slave':
            return True

    return False

def get_system_info(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/>
    <esn/>
    <mac/>
</systemInfo>
'''
    ret, _, rsp_data = ops_conn.get(uri, req_data)
    if ret != httplib.OK or rsp_data is '':
        raise OPIExecError('Failed to get the system information')

    sys_info = {}.fromkeys(('productName', 'esn', 'mac'))
    root_elem = etree.fromstring(rsp_data)
    namespaces = {'vrp' : 'http://www.huawei.com/netconf/vrp'}
    uri = 'data' + uri.replace('/', '/vrp:')
    nslen = len(namespaces['vrp'])
    elem = root_elem.find(uri, namespaces)
    if elem is not None:
        for child in elem:
            tag = child.tag[nslen + 2:]       # skip the namespace, '{namespace}esn'
            if tag in sys_info.keys():
                sys_info[tag] = child.text

    return sys_info

def test_file_paths(image, config, patch, stack_memid, md5_file):
    """Test whether argument paths are valid."""
    logging.info("Test whether argument paths are valid...")
    # check image file path
    file_name = os.path.basename(image)
    if file_name is not '' and not file_name.lower().endswith('.cc'):
        print('Error: Invalid filename extension of system software')
        return False

    # check config file path
    file_name = os.path.basename(config)
    file_name = file_name.lower()
    _, ext = os.path.splitext(file_name)
    if file_name is not '' and ext not in ['.cfg', '.zip', '.dat']:
        print('Error: Invalid filename extension of configuration file')
        return False

    # check patch file path
    file_name = os.path.basename(patch)
    if file_name is not '' and not file_name.lower().endswith('.pat'):
        print('Error: Invalid filename extension of patch file')
        return False

    # check stack member id file path
    file_name = os.path.basename(stack_memid)
    if file_name is not '' and not file_name.lower().endswith('.txt'):
        print('Error: Invalid filename extension of stack member ID file')
        return False

    # check md5 file path
    file_name = os.path.basename(md5_file)
    if file_name is not '' and not file_name.lower().endswith('.txt'):
        print('Error: Invalid filename extension of md5 file')
        return False

    return True

def md5sum(fname, need_skip_first_line = False):
    """
    Calculate md5 num for this file.
    """

    def read_chunks(fhdl):
        chunk = fhdl.read(8096)
        while chunk:
            yield chunk
            chunk = fhdl.read(8096)
        else:
            fhdl.seek(0)

    md5_obj = hashlib.md5()
    if isinstance(fname, basestring) and os.path.exists(fname):
        with open(fname, "rb") as fhdl:
            #skip the first line
            fhdl.seek(0)
            if need_skip_first_line:
                fhdl.readline()
            for chunk in read_chunks(fhdl):
                md5_obj.update(chunk)
    elif fname.__class__.__name__ in ["StringIO", "StringO"] or isinstance(fname, file):
        for chunk in read_chunks(fname):
            md5_obj.update(chunk)
    else:
        pass
    return md5_obj.hexdigest()

def md5_get_from_file(fname):
    """Get md5 num form file, stored in first line"""

    with open(fname, "rb") as fhdl:
        fhdl.seek(0)
        line_first = fhdl.readline()

    # if not match pattern, the format of this file is not supported
    if not re.match('^#md5sum="[\\w]{32}"[\r\n]+$', line_first):
        return ''

    return line_first[9:41]

def md5_check_with_first_line(fname):
    """Validate md5 for this file"""

    fname = os.path.basename(fname)
    md5_calc = md5sum(fname, True)
    print('MD5 checksum of the file "%s" is %s' % (fname, md5_calc))

    md5_file = md5_get_from_file(fname)
    print('MD5 checksum received from the file "%s" is %s' % (fname, md5_file))

    if md5_file != md5_calc:
        return False

    return True

def md5_check_with_dic(md5_dic, fname):
    """"""
    if not md5_dic.has_key(fname):
        logging.debug('md5_dic does not has key %s', fname)
        return True
    if md5_dic[fname] == md5sum(fname, False):
        return True
    return False

def parse_md5_file(fname):
    """parse md5 file"""

    def read_line(fhdl):
        line = fhdl.readline()
        while line:
            yield line
            line = fhdl.readline()
        else:
            fhdl.seek(0)

    md5_dic = {}
    with open(fname, "rb") as fhdl:
        for line in read_line(fhdl):
            line_spilt = line.split()
            if 2 != len(line_spilt):
                continue
            dic_tmp = {line_spilt[0]: line_spilt[1]}
            md5_dic.update(dic_tmp)
    return md5_dic

def verify_and_parse_md5_file(fname):
    """
    vefiry data integrity of md5 file and parse this file

    format of this file is like:
    ------------------------------------------------------------------
    #md5sum="517cf194e2e1960429c6aedc0e4dba37"

    file-name              md5
    conf_5618642831132.cfg c0ace0f0542950beaacb39cd1c3b5716
    ------------------------------------------------------------------
    """
    if not md5_check_with_first_line(fname):
        return ERR, None
    return OK, parse_md5_file(fname)

def main_proc(ops_conn):
    """Main processing"""
    sys_info = get_system_info(ops_conn)    # Get system info, such as esn and system mac
    cwd = get_cwd(ops_conn)                 # Get the current working directory
    startup = Startup(ops_conn)
    slave = has_slave_mpu(ops_conn)         # Check whether slave MPU board exists or not
    chg_flag = False

    # check remote file paths
    if not test_file_paths(REMOTE_PATH_IMAGE.get(sys_info['productName'], ''), REMOTE_PATH_CONFIG,
                           REMOTE_PATH_PATCH, REMOTE_PATH_MEMID, REMOTE_PATH_MD5):
        return ERR

    # download md5 file first, used to verify data integrity of files which will be downloaded next
    local_path_md5 = None
    file_path = REMOTE_PATH_MD5
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_md5 = cwd + file_name
        ret = download_file(ops_conn, url, local_path_md5, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download MD5 file "%s"' % file_name)
            return ERR
        print('Info: Download MD5 file successfully')
        ret, md5_dic = verify_and_parse_md5_file(file_name)
        # delete the file immediately
        del_file_all(ops_conn, local_path_md5, None)
        if ret is ERR:
            print('Error: MD5 check failed, file "%s"' % file_name)
            return ERR
    else:
        md5_dic = {}

    # download configuration file
    local_path_config = None
    file_path = REMOTE_PATH_CONFIG % sys_info['esn']
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_config = cwd + file_name
        ret = download_file(ops_conn, url, local_path_config, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download configuration file "%s"' % file_name)
            return ERR
        print('Info: Download configuration file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_config, 'slave#' + local_path_config)
        chg_flag = True

    # download patch file
    local_path_patch = None
    file_path = REMOTE_PATH_PATCH
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if startup.current.patch:
        cur_pat = os.path.basename(startup.current.patch).lower()
    else:
        cur_pat = ''
    if file_name is not '' and file_name.lower() != cur_pat:
        url  = FILE_SERVER + file_path
        local_path_patch = cwd + file_name
        ret = download_file(ops_conn, url, local_path_patch, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download patch file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            return ERR
        print('Info: Download patch file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_patch, 'slave#' + local_path_patch)
        chg_flag = True

    # download stack member ID file
    local_path_memid = None
    file_path = REMOTE_PATH_MEMID
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if file_name is not '':
        url = FILE_SERVER + file_path
        local_path_memid = cwd + file_name
        ret = download_file(ops_conn, url, local_path_memid, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download system software "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            return ERR
        print('Info: Download stack member ID file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            return ERR
        chg_flag = True
        #no need copy to slave board

    # download system software
    local_path_image = None
    file_path = REMOTE_PATH_IMAGE.get(sys_info['productName'], '')
    if not file_path.startswith('/'):
        file_path = '/' + file_path
    file_name = os.path.basename(file_path)
    if startup.current.image:
        cur_image = os.path.basename(startup.current.image).lower()
    else:
        cur_image = ''
    if file_name is not '' and file_name.lower() != cur_image:
        url  = FILE_SERVER + file_path
        local_path_image = cwd + file_name
        ret = download_file(ops_conn, url, local_path_image, MAX_TIMES_RETRY_DOWNLOAD)
        if ret is ERR or not file_exist(ops_conn, file_name):
            print('Error: Failed to download system software "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            del_file_all(ops_conn, local_path_memid, slave)
            return ERR
        print('Info: Download system software file successfully')
        if not md5_check_with_dic(md5_dic, file_name):
            print('Error: MD5 check failed, file "%s"' % file_name)
            del_file_all(ops_conn, local_path_config, slave)
            del_file_all(ops_conn, local_path_patch, slave)
            del_file_all(ops_conn, local_path_memid, slave)
            return ERR
        if slave:
            copy_file(ops_conn, local_path_image, 'slave#' + local_path_image)
        chg_flag = True

    if chg_flag is False:
        return ERR

    # set startup info
    startup.set_startup_info(local_path_image, local_path_config, local_path_patch,
                             local_path_memid, slave, sys_info['esn'])

    # delete stack member ID file after used
    del_file_all(ops_conn, local_path_memid, None)

    return OK

# ----------------------------------------------------------------------------------------------------------------------
# Func Name       : main
# Date Created    : 2013-7-2
# Author          : Author
# History         :
# Date                Author                      Modification
# ----------------------------------------------------------------------------------------------------------------------
def main(usb_path = ''):
    """The main function of user script. It is called by ZTP frame, so do not remove or change this function.

    Args:
    Raises:
    Returns: user script processing result
    """
    host = "localhost"
    if usb_path and len(usb_path):
        logging.debug('ztp_script usb_path: %s', usb_path)
        global FILE_SERVER
        FILE_SERVER = 'file:///' + usb_path
    try:
        # Make an OPS connection instance.
        ops_conn = OPSConnection(host)
        ret = main_proc(ops_conn)

    except OPIExecError, reason:
        logging.debug('OPI execute error: %s', reason)
        print("Error: %s" % reason)
        ret = ERR

    except ZTPErr, reason:
        logging.debug('ZTP error: %s', reason)
        print("Error: %s" % reason)
        ret = ERR

    except Exception, reason:
        logging.debug(reason)
        traceinfo = traceback.format_exc()
        logging.debug(traceinfo)
        ret = ERR

    finally:
        # Close the OPS connection
        ops_conn.close()

    return ret

if __name__ == "__main__":
    main()

Configuration Files
  • Configuration file of SwitchC

    #
    sysname SwitchC
    #
    vlan batch 10
    #
    dhcp enable
    #
    interface Vlanif10
     ip address 10.1.1.1 255.255.255.0
     dhcp select relay
     dhcp relay binding server ip 10.1.2.2
    #
    interface 10GE1/17/1
     port link-type trunk
     port trunk pvid vlan 10
     port trunk allow-pass vlan 10
    #
    interface 10GE1/17/2
     port link-type trunk
     port trunk pvid vlan 10
     port trunk allow-pass vlan 10
    #
    return
    
Translation
Download
Updated: 2019-08-09

Document ID: EDOC1000041694

Views: 58809

Downloads: 3621

Average rating:
This Document Applies to these Products
Related Version
Related Documents
Share
Previous Next