Beispiel #1
0
def ztp_features(verboseFlag=False):
    features = getFeatures()

    for feat in features:
        if verboseFlag:
            print('%s: %s: %s' %
                  (feat, getCfg('info-' + feat, ztp_cfg=ztp_cfg),
                   getCfg(feat, ztp_cfg=ztp_cfg)))
        else:
            if getCfg(feat) is True:
                print(feat)
Beispiel #2
0
    def __createProvScriptJson(self):
        '''!
         Create ZTP JSON data to execute provisioning script specified by DHCP Option 239 URL.
        '''

        json_data = '{"ztp": {"provisioning-script":{"plugin":{"url":"file://' + getCfg(
            'provisioning-script') + '","ignore-section-data":true}}\
                   ,"restart-ztp-no-config":false}}'

        f = open(getCfg('ztp-json'), 'w')
        f.write(json_data)
        f.close
Beispiel #3
0
 def __forceRestartDiscovery(self, msg):
     # Remove existing leases to source new provisioning data
     self.__cleanup_dhcp_leases()
     _msg = '%s. Waiting for %d seconds before restarting ZTP.' % (
         msg, getCfg('restart-ztp-interval'))
     logger.warning(_msg)
     updateActivity(_msg)
     time.sleep(getCfg('restart-ztp-interval'))
     self.ztp_mode = 'DISCOVERY'
     # Force install of ZTP configuration profile
     self.__ztp_profile_loaded = False
     # Restart link-scan
     self.__intf_state = dict()
Beispiel #4
0
def signal_handler(signum, frame):
    '''!
    This signal handler is called on SIGTERM or SIGINT
    '''
    logger.warning('Received terminate signal. Shutting down.')
    updateActivity('Received terminate signal. Shutting down.')
    # Wait for some time
    count = getCfg('sighandler-wait-interval')
    while count > 0:
        done = True
        for pid in runcmd_pids:
            if check_pid(pid):
                done = False
                try:
                    (wpid, status) = os.waitpid(pid, os.WNOHANG)
                    if wpid == pid:
                        print('Process pid %d returned with status %d.' %
                              (pid, status))
                except OSError as v:
                    print('pid %d : %s' % (pid, str(v)))
        if done:
            break
        time.sleep(1)
        count -= 1

    # Kill any process which might still be running
    for pid in runcmd_pids:
        if check_pid(pid):
            print('Process %d still alive, send kill signal.' % (pid))
            os.kill(pid, signal.SIGKILL)

    sys.exit(0)
Beispiel #5
0
    def setlogFile(self, log_file=None):
        '''!
        Set the log file to store all logs generated during ZTP

        @param log_file (str) log file
        @exception Raise TypeError if incorrect parameter type
        '''

        rsyslog_conf_file = getCfg("rsyslog-ztp-log-file-conf",
                                   '/etc/rsyslog.d/10-ztp.conf')
        if log_file is None or (isString(log_file) and log_file == ''):
            if os.path.isfile(rsyslog_conf_file):
                os.remove(rsyslog_conf_file)
            return

        if not isString(log_file):
            raise TypeError("Log file must be a string")

        self.__log_file = log_file
        change = True
        if os.path.isfile(rsyslog_conf_file):
            fh = open(rsyslog_conf_file, 'r')
            if fh.readline().strip(
            ) == ':programname, contains, "sonic-ztp"  ' + log_file:
                change = False
            fh.close()

        if change:
            fh = open(rsyslog_conf_file, 'w')
            fh.write(':programname, contains, "sonic-ztp"  ' + log_file)
            fh.close()
            runCommand('systemctl restart rsyslog', capture_stdout=False)
 def getcfg(self):
     ztp_cfg = ZTPCfg()
     ad = getCfg('admin-mode', ztp_cfg=ztp_cfg)
     if ad == False:
         return "disabled"
     else:
         return "enabled"
Beispiel #7
0
    def __init_json(self):

        content = """{
    "ztp": {
        "0002-test-plugin": {
           "message" : "0002-test-plugin",
           "message-file" : "/etc/ztp.results",
           "fail" : true,
           "ignore-result" : true,
           "halt-on-failure" : true
        },
        "0003-test-plugin": {
           "plugin" : {
             "name" : "test-plugin"
           },
           "message" : "0003-test-plugin",
           "message-file" : "/etc/ztp.results",
           "fail" : false
        },
        "0001-test-plugin": {
           "plugin" : "test-plugin",
           "message" : "0001-test-plugin",
           "message-file" : "/etc/ztp.results",
           "fail" : false,
           "ignore-result" : true
        }
    }
}"""
        f = open(getCfg('ztp-json'), "w")
        f.write(content)
        f.close()
Beispiel #8
0
    def __init__(self, json_src_file=None, json_dst_file=None):
        '''!
         Constructor for ConfigurationSection class.

         @param json_src_file (str, optional) Configuration section input.json file to be prcoessed. If not specified,
                                              /etc/sonic/ztp_data.json file is used.
         @param json_dst_file (str, optional) Destination file to which processed JSON data is saved to. If not specified,
                                              json_src_file is used as destination file.

         @exception Raise ValueError if any error or exception encountered while processing the json_src_file

        '''
        if json_src_file is not None:
            self.json_src_file = json_src_file
        else:
            self.json_src_file = getCfg('ztp-json')
        if json_dst_file is None:
            self.json_dst_file = self.json_src_file
        else:
            self.json_dst_file = json_dst_file
        try:
            self.objJson, self.jsonDict = JsonReader(self.json_src_file,
                                                     self.json_dst_file,
                                                     indent=4)
        except:
            raise ValueError('ZTP JSON load failed')
Beispiel #9
0
    def __cleanup_dhcp_leases(self):

        # Use ZTP interface used to obtain provisioning information
        runCommand('rm -f /var/lib/dhcp/dhclient*.eth0.leases',
                   capture_stdout=False)
        if getCfg('feat-inband'):
            runCommand('rm -f /var/lib/dhcp/dhclient*.Ethernet*.leases',
                       capture_stdout=False)
Beispiel #10
0
    def __writeConfigSections(self, key, val):
        '''!
          Split ZTP JSON into individual configuration sections.

          @param key (str) Configuration section name
          @param value (object) Configuration section data
        '''
        section_dir = getCfg('ztp-tmp-persistent') + '/' + key
        section_file = section_dir + '/' + getCfg('section-input-file')
        try:
            if os.path.isdir(section_dir) is False:
                os.makedirs(section_dir)
                self.objJson.writeJson(section_file, {key: val},
                                       getCfg('json-indent'))
        except:
            raise ValueError(
                'Unable to write Configuration Section %s JSON data to file %s'
                % (key, section_file))
Beispiel #11
0
    def __cleanup(self):
        '''!
          Remove stale ZTP session data.
        '''
        dir_list = ['ztp-tmp', 'ztp-tmp-persistent']

        try:
            # Remove temporary files created by previous ZTP run
            for d in dir_list:
                if os.path.isdir(getCfg(d)):
                    shutil.rmtree(getCfg(d), ignore_errors=True)
            # Create them again
            for d in dir_list:
                os.makedirs(getCfg(d))
        except OSError as e:
            logger.error(
                'Exception [%s] encountered while cleaning up temp directories.'
                % str(e))
Beispiel #12
0
    def test_ztp_graphservice_do_not_exist(self, tmpdir):
        '''!
        Test when the plugin is a graphservice and does not exist.
        '''
        content = """{
    "ztp": {
        "0001-test-provisioning-script": {
            "halt-on-failure": false,
            "ignore-result": false,
            "plugin": "graphservice",
            "reboot-on-failure": false,
            "reboot-on-success": false,
            "status": "BOOT",
            "timestamp": "2019-04-18 19:49:49"
        },
        "config-fallback": false,
        "halt-on-failure": false,
        "ignore-result": false,
        "reboot-on-failure": false,
        "reboot-on-success": false,
        "restart-ztp-no-config": true,
        "restart-ztp-on-failure": false,
        "status": "BOOT",
        "timestamp": "2019-04-18 19:49:49",
        "ztp-json-version": "1.0"
    }
}"""
        self.__write_file(
            "/tmp/test_firmware_" + socket.gethostname() + ".json", content)
        d = tmpdir.mkdir("valid")
        fh = d.join("test.json")
        fh.write("""
        {
          "ztp" : {
            "dynamic-url" : {
              "source" : {
               "prefix" : "file:///tmp/test_firmware_",
               "identifier" : "hostname",
               "suffix" : ".json"
              }
            }
          }
        }
        """)
        d2 = tmpdir.mkdir("dest")
        fh2 = d2.join("dest_file")
        ztpjson = ZTPJson(str(fh), json_dst_file=str(fh2))
        assert (self.__read_file(str(fh2)) == content)

        content = """
        #!/bin/sh

        echo Hello
        exit 2
"""
        plugin_name = ztpjson.plugin('0001-test-provisioning-script')
        assert (plugin_name == getCfg('plugins-dir') + '/graphservice')
Beispiel #13
0
 def test_invalid_log_level(self):
     '''!
     Test invalid log level
     '''
     saved_value = getCfg('log-level')
     setCfg('log-level', None)
     log = Logger()
     assert (log != None)
     setCfg('log-level', saved_value)
Beispiel #14
0
 def __read_ztp_interface(self):
     intf_file = getCfg('ztp-run-dir') + '/ztp.lock/interface'
     if os.path.isfile(intf_file):
         f = open(intf_file, 'r')
         try:
             self.__ztp_interface = f.readline().strip().split(':')[1]
         except:
             self.__ztp_interface = None
             pass
         f.close()
Beispiel #15
0
    def __link_scan(self):
        '''!
        Scan all in-band interface's operational status to detect a link up event
        '''

        # Do not attempt link scan when in test mode
        if self.test_mode:
            return False

        if self.__link_scan_enabled is None:
            # Check if ZTP configuration is active
            (rc, op, errStr
             ) = runCommand("redis-cli -n 4 HGET \"ZTP|mode\" \"profile\"")
            if rc == 0 and len(op) != 0 and op[0] == 'active':
                self.__link_scan_enabled = 'True'
            else:
                self.__link_scan_enabled = 'False'

        if self.__link_scan_enabled == 'False':
            return False

        link_scan_result = False
        (rc, port_table_data,
         err) = runCommand('redis-dump -d 0 -k PORT_TABLE:Ethernet*')
        if rc != 0:
            port_table = None
        else:
            try:
                port_table = json.loads(''.join(port_table_data))
            except:
                port_table = None
        intf_data = os.listdir('/sys/class/net')
        if getCfg('feat-inband'):
            r_intf = re.compile("Ethernet.*|eth.*")
        else:
            r_intf = re.compile("eth.*")
        intf_list = list(filter(r_intf.match, intf_data))
        for intf in intf_list:
            try:
                if intf[0:3] == 'eth':
                    fh = open('/sys/class/net/{}/operstate'.format(intf), 'r')
                    operstate = fh.readline().strip().lower()
                    fh.close()
                else:
                    operstate = port_table.get('PORT_TABLE:' + intf).get(
                        'value').get('oper_status').lower()
            except:
                operstate = 'down'
            if ((self.__intf_state.get(intf) is None) or \
                (self.__intf_state.get(intf) != operstate)) and \
                operstate == 'up':
                link_scan_result = True
                logger.info('Link up detected for interface %s' % intf)
            self.__intf_state[intf] = operstate
        return link_scan_result
Beispiel #16
0
    def getIdentifier(self):
        '''!
         Obtain the resolved identifier string based on the identifier data.

         @return
              In case of success: \n
                Identifier string \n
              In case of error: \n
                None
        '''
        try:
            identifier_data = self.identifier_data
            if isinstance(identifier_data, dict) is False:
                if identifier_data == "hostname":
                    return socket.gethostname()
                elif identifier_data == "hostname-fqdn":
                    return socket.getfqdn()
                elif identifier_data == "serial-number":
                    return sysEeprom.get_serial_number()
                elif identifier_data == "product-name":
                    return sysEeprom.get_product_name()
                elif identifier_data == "sonic-version":
                    return get_sonic_version()
                else:
                    return identifier_data
            elif identifier_data.get('url') is not None:
                url_data = identifier_data.get('url')
                (fd, filename) = tempfile.mkstemp(prefix='identifier_',
                                                  dir=getCfg('ztp-tmp'))
                os.close(fd)
                urlObj = URL(url_data, filename)
                updateActivity('Downloading identifier script from \'%s\'' %
                               (urlObj.getSource()))
                rc, identifier_script = urlObj.download()
                if rc == 0 and os.path.isfile(identifier_script):
                    updateActivity(
                        'Executing identifier script downloaded from \'%s\'' %
                        (urlObj.getSource()))
                    (rc, cmd_stdout,
                     cmd_stderr) = runCommand([identifier_script])
                    if rc != 0:
                        logger.error(
                            'Error encountered while executing identifier %s Exit code: (%d).'
                            % (identifier_script, rc))
                        return None
                    if len(cmd_stdout) == 0:
                        return ""
                    else:
                        return cmd_stdout[0]
        except (TypeError, ValueError) as e:
            logger.error(
                'Exception: [%s] encountered while processing identifier data in dynamic URL.'
                % str(e))
            return None
        return None
Beispiel #17
0
 def test_load_config_file(self):
     '''!
     Assuming that the key [ztp-json] in ztp_cfg.json
     exist and is a valid json file, verify that the constructor is loading this file.
     '''
     self.__init_json()
     config_section = ConfigSection()
     assert (config_section != None)
     json_src_file = getCfg('ztp-json')
     ztpcfg = ConfigSection(json_src_file, json_src_file)
     assert (ztpcfg != None)
Beispiel #18
0
 def test_load_config_file_nexist(self):
     '''!
     Assuming that the key [ztp-json] in ztp_cfg.json
     exist and is a valid json file, verify that the constructor is loading this file.
     '''
     self.__init_json()
     json_src_file = getCfg('ztp-json')
     shutil.move(json_src_file, json_src_file + '.saved')
     with pytest.raises(ValueError):
         ztpjson = ZTPJson()
     shutil.move(json_src_file + '.saved', json_src_file)
Beispiel #19
0
    def pluginArgs(self, section_name):
        '''!
         Resolve the plugin arguments used to be passed as command line arguments to
         the plugin used to process configuration section

         @param section_name (str) Configuration section name whose plugin arguments needs to be resolved.

         @return
              Concatenated string of all the argements defined \n
              If no arguments are defined or error encountered: \n
                None
        '''

        if isString(section_name) is False:
            raise TypeError('Invalid argument used as section name')
        elif self.ztpDict.get(section_name) is None:
            logger.error('Configuration Section %s not found.' % section_name)
            return None

        # Obtain plugin data
        plugin_data = self.ztpDict.get(section_name).get('plugin')

        # Get the location of this configuration section's input data parsed from the input ZTP JSON file
        plugin_input = getCfg(
            'ztp-tmp-persistent') + '/' + section_name + '/' + getCfg(
                'section-input-file')

        plugin_args = ''
        ignore_section_data_arg = getField(plugin_data, 'ignore-section-data',
                                           bool, False)
        _plugin_json_args = getField(plugin_data, 'args', str, None)

        if ignore_section_data_arg is False:
            plugin_args = plugin_input

        if _plugin_json_args is not None:
            plugin_args = plugin_args + ' ' + _plugin_json_args

        logger.debug('Plugin arguments for %s evaluated to be %s.' %
                     (section_name, plugin_args))
        return plugin_args
def getActivityString():
    if ztp_active() != 0:
        return 'ZTP Service is not running'

    activity_str = None
    f = getCfg('ztp-activity')
    if os.path.isfile(f):
        fh = open(f, 'r')
        activity_str = fh.readline().strip()
        fh.close()

    if activity_str is not None and activity_str != '':
        return getTimeString(activity_str)
Beispiel #21
0
    def __buildDefaults(self, section):
        '''!
         Helper API to include missing objects in a configuration section and validate their values.
         Below are the objects that are validated and added if not present: \n
           - ignore-result\n
           - reboot-on-success\n
           - reboot-on-failure\n
           - halt-on-failure\n
           - timestamp\n

         Below are the objects whose value is validated:\n
           - status
           - suspend-exit-code

         If the specified value is invalid  of if the object is not specified, its value is read from ztp_cfg.json

         @param section (dict) Configuration Section input data read from JSON file.

        '''
        default_objs = [
            'ignore-result', 'reboot-on-success', 'reboot-on-failure',
            'halt-on-failure'
        ]
        # Loop through objects and update them with default values
        for key in default_objs:
            _val = getField(section, key, bool, getCfg(key))
            section[key] = _val

        # set status
        if section.get('status') is None:
            section['status'] = 'BOOT'
            section['timestamp'] = getTimestamp()
        elif isString(section.get('status')) is False or \
            section.get('status') not in ['BOOT', 'IN-PROGRESS', 'SUSPEND', 'DISABLED', 'FAILED', 'SUCCESS']:
            logger.warning(
                'Invalid value (%s) used for configuration section status. Setting it to DISABLED.'
                % section.get('status'))
            section['status'] = 'DISABLED'
            section['timestamp'] = getTimestamp()

        # Validate suspend-exit code
        if section.get('suspend-exit-code') is not None:
            suspend_exit_code = getField(section, 'suspend-exit-code', int,
                                         None)
            if suspend_exit_code is None or suspend_exit_code < 0:
                del section['suspend-exit-code']

        # Add timestamp if missing
        if section.get('timestamp') is None:
            section['timestamp'] = getTimestamp()
Beispiel #22
0
    def __buildDefaults(self):
        '''!
         Helper API to include missing objects in a configuration section and validate their values.
        '''
        # Call more abstract API to insert default values
        self._ConfigSection__buildDefaults(self.ztpDict)

        default_objs = [
            'restart-ztp-on-failure', 'restart-ztp-no-config',
            'config-fallback'
        ]
        # Loop through objects and update them with default values
        for key in default_objs:
            _val = getField(self.ztpDict, key, bool, getCfg(key))
            self.ztpDict[key] = _val
Beispiel #23
0
    def __detect_intf_state(self):
        '''!
        Identifies all the interfaces on which ZTP discovery needs to be performed.
        Link state of each identified interface is checked and stored in a dictionary
        for reference.

           @return  True   - If an interface moved from link down to link up state
                    False  - If no interface transitions have been observed
        '''
        link_up_detected = False
        intf_data = os.listdir('/sys/class/net')
        if getCfg('feat-inband'):
            r_intf = re.compile("Ethernet.*|eth.*")
        else:
            r_intf = re.compile("eth.*")
        intf_list = list(filter(r_intf.match, intf_data))
        for intf in natsorted(intf_list):
            try:
                if intf[0:3] == 'eth':
                    fh = open('/sys/class/net/{}/operstate'.format(intf), 'r')
                    operstate = fh.readline().strip().lower()
                    fh.close()
                else:
                    if self.applDB.exists(self.applDB.APPL_DB,
                                          'PORT_TABLE:' + intf):
                        port_entry = self.applDB.get_all(
                            self.applDB.APPL_DB, 'PORT_TABLE:' + intf)
                        operstate = port_entry.get('oper_status').lower()
                    else:
                        operstate = 'down'
            except:
                operstate = 'down'
            if ((self.__intf_state.get(intf) is None) or \
                (self.__intf_state.get(intf).get('operstate') != operstate)) and \
                operstate == 'up':
                link_up_detected = True
                logger.info('Link up detected for interface %s' % intf)
            if self.__intf_state.get(intf) is None:
                self.__intf_state[intf] = dict()
            self.__intf_state[intf]['operstate'] = operstate

        # Weed out any stale interfaces that may exist when an expanded port is joined back
        intf_snapshot = list(self.__intf_state.keys())
        for intf in intf_snapshot:
            if intf not in intf_list:
                del self.__intf_state[intf]

        return link_up_detected
Beispiel #24
0
    def __removeZTPProfile(self):
        '''!
         If ZTP configuration profile is operational, remove ZTP configuration profile and load
         startup configuration file. If there is no startup configuration file,
         load factory default configuration.
        '''

        # Do not attempt to remove ZTP configuration if working in unit test mode
        if self.test_mode:
            return

        # Remove ZTP configuration profile if loaded
        updateActivity('Verifying configuration')

        # Use a fallback default configuration if configured to
        _config_fallback = ''
        if (self.objztpJson is not None and (self.objztpJson['status'] == 'FAILED' or self.objztpJson['status'] == 'SUCCESS') \
            and self.objztpJson['config-fallback']) or \
           (self.objztpJson is None and getCfg('config-fallback') is True):
            _config_fallback = ' config-fallback'

        # Execute profile removal command with appropriate options
        rc = runCommand(getCfg('ztp-lib-dir') + '/ztp-profile.sh remove' +
                        _config_fallback,
                        capture_stdout=False)

        # Remove ZTP configuration startup-config
        if os.path.isfile(getCfg('config-db-json')) is True:
            try:
                config_db = None
                with open(getCfg('config-db-json')) as json_file:
                    config_db = json.load(json_file)
                    json_file.close()
                if config_db is not None and config_db.get('ZTP'):
                    logger.info("Deleting ZTP configuration saved in '%s'." %
                                (getCfg('config-db-json')))
                    del config_db['ZTP']
                    with open(getCfg('config-db-json'), 'w') as json_file:
                        json.dump(config_db, json_file, indent=4)
                        json_file.close()
            except Exception as e:
                logger.error(
                    "Exception [%s] encountered while verifying '%s'." %
                    (str(e), getCfg('config-db-json')))

        self.__ztp_profile_loaded = False
Beispiel #25
0
    def __loadZTPProfile(self, event):
        '''!
         Load ZTP configuration profile if there is no saved configuration file.
         This establishes connectivity to all interfaces and starts DHCP discovery.
        '''
        # Do not attempt to install ZTP configuration if working in unit test mode
        if self.test_mode:
            return False

        if self.__ztp_profile_loaded is False:
            updateActivity('Checking running configuration')
            logger.info(
                'Checking running configuration to load ZTP configuration profile.'
            )
            cmd = getCfg('ztp-lib-dir') + '/ztp-profile.sh install ' + event
            # When performing ZTP discovery, force load ZTP profile. When
            # ZTP is resuming previous session, use configuration already loaded during
            # config-setup
            rc = runCommand(cmd, capture_stdout=False)
            self.__ztp_profile_loaded = True
            return True
        return False
Beispiel #26
0
    def __createGraphserviceJson(self):
        '''!
         Create ZTP JSON data to load graph file specified by DHCP Option 225 URL. Also
         includes ACL JSON file if specified by DHCP Option 226.
        '''

        # Verify that graph file can be downloaded
        if self.__downloadURL(getCfg('graph-url'),
                              '/tmp/test_minigraph.xml') is False:
            return False
        else:
            # Clean up
            os.remove('/tmp/test_minigraph.xml')

        # Verify that acl json file can be downloaded
        if os.path.isfile(getCfg('acl-url')):
            if self.__downloadURL(getCfg('acl-url'),
                                  '/tmp/test_acl.json') is False:
                return False
            else:
                # Clean up
                os.remove('/tmp/test_acl.json')

        # Read the url file and identify the URL to be downloaded
        f = open(getCfg('graph-url'), 'r')
        graph_url_str = f.readline().strip()
        f.close()

        acl_url_str = None
        if os.path.isfile(getCfg('acl-url')):
            f = open(getCfg('acl-url'), 'r')
            acl_url_str = f.readline().strip()
            f.close()
        json_data = '{"ztp":{"graphservice": { "minigraph-url" : { "url":"' + graph_url_str + '"}'
        if acl_url_str is not None and len(acl_url_str) != 0:
            json_data = json_data + ', "acl-url" : { "url":"' + acl_url_str + '"}'
        json_data = json_data + '}, "restart-ztp-no-config":false} }'
        f = open(getCfg('ztp-json'), 'w')
        f.write(json_data)
        f.close
        return True
Beispiel #27
0
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''

import sys
import os
import multiprocessing
import json
import pytest

from .testlib import createPySymlink
from ztp.ZTPLib import runCommand, getCfg
from ztp.DecodeSysEeprom import sysEeprom

sys.path.append(getCfg('plugins-dir'))
createPySymlink(getCfg('plugins-dir')+'/configdb-json')

createPySymlink('/usr/lib/ztp/plugins/configdb-json')
from configdb_json import ConfigDBJson

class TestClass(object):

    '''!
    This class allow to define unit tests for class Snmp
    '''

    def __read_file(self, fname):
        try:
            f = open(fname, 'r')
            return f.read()
Beispiel #28
0
    def __init__(self,
                 url=None,
                 dst_file=None,
                 incl_http_headers=None,
                 is_secure=None,
                 timeout=None,
                 retry=None,
                 curl_args=None,
                 encrypted=None):
        '''!
        Constructor for the class, and optionally provide the parameters which can be used later by getUrl()

        @param url (str, optional) url of the file you want to get

        @param dst_file (str, optional) Filename for the data being stored. \n
            If not specified, it will be derived from the url (last part of it, e.g. basename).

        @param incl_http_headers (bool, optional) Include or not the additional HTTP headers
            (product name, serial number, mac address)

        @param is_secure (bool, optional) Every SSL connection curl makes is verified or not to be secure

        @param timeout (int, optional) Maximum number of seconds allowed for curl's connection to take. \n
            This  only  limits the connection phase.

        @param retry (int, optional) Number of times curl will retry in case of a transient error

        @param curl_args (str, optional) Options you want to pass to curl command line program

        @param encrypted (bool) Is the connectin with the server being encrypted?

        @return
            In case of success: \n
                Tupple: (0, data) \n
            In case of error: \n
                Tupple: (error code, html content)
        '''
        ## url of the file you want to get
        self.__url = url
        ## Name of the file where the file retrieved from the server is saved
        self.__dst_file = dst_file
        ## Do we send the HTTP headers (product name, serial number, mac address)?
        if incl_http_headers is None:
            self.__incl_http_headers = getCfg('include-http-headers')
        else:
            self.__incl_http_headers = incl_http_headers
        ## Should curl consider the SSL connection as secure or not?
        if is_secure is not None:
            self.__is_secure = is_secure
        else:
            self.__is_secure = getCfg('https-secure')

        ## Maximum number of seconds allowed for curl's connection to take
        if timeout is None:
            self.__timeout = getCfg('curl-timeout')
        else:
            self.__timeout = timeout
        ## Number of times curl will retry in case of a transient error
        if retry is None:
            self.__retry = getCfg('curl-retries')
        else:
            self.__retry = retry
        ## Optional curl cli program options
        self.__curl_args = curl_args
        ## Is the connection with the server encrypted?
        self.__encrypted = encrypted

        # Read system eeprom
        ## Product name read from the system eeprom
        self.__product_name = sysEeprom.get_product_name()
        ## Serial number read from the system eeprom
        self.__serial_number = sysEeprom.get_serial_number()
        ## MAC address read from the system eeprom
        self.__mac_addr = sysEeprom.get_mac_addr()

        # We need some items from the global ZTP config file
        ## Read which http-user-agent curl will be returning to the server
        self.__user_agent = getCfg('http-user-agent')

        # Read SONiC version
        self.__sonic_version = get_sonic_version()

        # Generate the http headers
        ## We include Product name, S/N and MAC address in the http headers sent to the server
        self.__http_headers = []
        if self.__product_name is not None:
            self.__http_headers.append('PRODUCT-NAME: ' + self.__product_name)
        if self.__serial_number is not None:
            self.__http_headers.append('SERIAL-NUMBER: ' +
                                       self.__serial_number)
        if self.__mac_addr is not None:
            self.__http_headers.append('BASE-MAC-ADDRESS: ' + self.__mac_addr)
        if self.__sonic_version is not None:
            self.__http_headers.append('SONiC-VERSION: ' +
                                       self.__sonic_version)
Beispiel #29
0
    def getUrl(self,
               url=None,
               dst_file=None,
               incl_http_headers=None,
               is_secure=True,
               timeout=None,
               retry=None,
               curl_args=None,
               encrypted=None,
               verbose=False):
        '''!
        Fetch a file using a given url. The content retrieved from the server is stored into a file.

        In case of a server erreur, the html content is stored into the file. This can be used to have a finer
        diagnostic of the issue.

        @param url (str, optional) url of the file you want to get. \n
            If the url is not given, the one specified in the constructor will be used.

        @param dst_file (str, optional) Filename for the data being stored. \n
            If not specified here and in the colnstructor, it will be derived from the url \n
            (last part of it, e.g. basename).

        @param incl_http_headers (bool, optional) Include or not the additional HTTP headers \n
            (product name, serial number, mac address)

        @param is_secure (bool, optional) Every SSL connection curl makes is verified or not to be secure. \n
            By default, the SSL connection is assumed secure.

        @param timeout (int, optional) Maximum number of seconds allowed for curl's connection to take. \n
            This  only  limits the connection phase.

        @param retry (int, optional) Number of times curl will retry in case of a transient error

        @param curl_args (str, optional) Options you want to pass to curl command line program. \n
            If no parameters are given here, the one given in the constructor will be used.

        @param encrypted (bool) Is the connection with the server being encrypted?

        @return
            Return a tuple: \n
            - In case of success: (0 destination_filename)\n
            - In case of error:   (error_code None)\n
            See here to see the status codes returned:\n
            https://ec.haxx.se/usingcurl-returns.html \n
            Note that we return error 20 in case of an unknown error.
        '''

        # Use arguments provided in the constructor
        if url is None and self.__url is not None:
            url = self.__url
        if dst_file is None and self.__dst_file is not None:
            dst_file = self.__dst_file
        if incl_http_headers is None and self.__incl_http_headers is not None:
            incl_http_headers = self.__incl_http_headers
        if is_secure is None and self.__is_secure is not None:
            is_secure = self.__is_secure
        if timeout is None and self.__timeout is not None:
            timeout = self.__timeout
        if retry is None and self.__retry is not None:
            retry = self.__retry
        if curl_args is None and self.__curl_args is not None:
            curl_args = self.__curl_args
        if encrypted is None and self.__encrypted is not None:
            encrypted = self.__encrypted

        # We can't run without a URL
        if url is None:
            return (-1, dst_file)

        # If no filename is provided, we use the last part of the url
        if dst_file is None:
            dst_file = os.path.basename(url)

        # If there is no path in the provided filename, we store the file under this default location
        try:
            if dst_file.find('/') == -1:
                dst_file = getCfg('ztp-tmp') + '/' + dst_file
        except (AttributeError) as e:
            logger.error("!Exception : %s" % (str(e)))
            return (20, None)

        # Create curl command
        cmd = '/usr/bin/curl -f -v -s -o ' + dst_file
        if self.__user_agent is not None:
            cmd += ' -A "' + self.__user_agent + '"'  # --user-agent
        if is_secure is False:
            cmd += ' -k'  # --insecure
        if timeout is not None and isinstance(timeout, int) is True:
            cmd += ' --connect-timeout ' + str(timeout)
        if retry is not None and isinstance(retry, int) is True:
            cmd += ' --retry ' + str(retry)
        if incl_http_headers is not None:
            for h in self.__http_headers:
                cmd += ' -H \"' + h + '"'  # --header

        if curl_args is not None:
            cmd += ' ' + curl_args
        cmd += ' ' + url
        if verbose is True:
            logger.debug('%s' % (cmd))

        # Execute curl command
        _retries = retry
        while True:
            _start_time = time.time()
            (rc, cmd_stdout, cmd_stderr) = runCommand(cmd)
            _current_time = time.time()
            if rc != 0 and rc in [
                    5, 6, 7
            ] and _retries != 0 and (_current_time - _start_time) < timeout:
                logger.debug(
                    "!Error (%d) encountered while processing the command : %s"
                    % (rc, cmd))
                time.sleep(timeout - (_current_time - _start_time))
                _retries = _retries - 1
                continue

            if rc != 0:
                logger.error(
                    "!Error (%d) encountered while processing the command : %s"
                    % (rc, cmd))
                for l in cmd_stdout:  # pragma: no cover
                    logger.error(str(l))
                for l in cmd_stderr:
                    logger.error(str(l))
                if os.path.isfile(dst_file):
                    os.remove(dst_file)
                return (20, None)
            else:
                break

        os.chmod(dst_file, stat.S_IRWXU)
        # Use curl result
        return (0, dst_file)
Beispiel #30
0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''

import sys
import signal
import os
import stat
import pytest

from ztp.ZTPLib import runCommand, getField, getCfg, printable
sys.path.append(getCfg('plugins-dir'))


class TestClass(object):
    '''!
    This class allow to define unit tests for class Firmware
    \endcode
    '''
    def test_cmd(self, tmpdir):

        (rc1, cmd_stdout1, cmd_stderr1) = runCommand('/bin/cat /proc/devices')
        (rc2, cmd_stdout2, cmd_stderr2) = runCommand('/bin/cat /proc/devices',
                                                     use_shell=True)
        (rc3, cmd_stdout3,
         cmd_stderr3) = runCommand(['/bin/cat', '/proc/devices'])
        (rc4, cmd_stdout4,