diff --git a/ztp-root/bootstrap/bootstrap b/ztp-root/bootstrap/bootstrap index 45260e8d41e9d27974b3c1ee114e5a56bb977902..7b575a288f0fb8f6fe74b4ec32adfb84acaf844f 100644 --- a/ztp-root/bootstrap/bootstrap +++ b/ztp-root/bootstrap/bootstrap @@ -1,1032 +1,32 @@ #!/usr/bin/env python -# -# Copyright (c) 2014, Arista Networks, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# - Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# - Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# - Neither the name of Arista Networks nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS -# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Bootstrap script -# -# Written by: -# EOS+, Arista Networks +import requests -import datetime -import imp -import json -import jsonrpclib -import logging -import os -import os.path -import re -import sleekxmpp -import shutil -import socket -import subprocess -import sys -import time -import traceback -import urllib2 -import urlparse +HTTP_SERVER_CONFIG_BASE_URL ="http://10.11.1.15:8080/nodes/" -from collections import namedtuple -from logging.handlers import SysLogHandler -from subprocess import PIPE -# Server will replace this value with the correct IP address/hostname -# before responding to the bootstrap request. -SERVER = '$SERVER' +def get_mac(): + f = open('/sys/class/net/ma1/address', 'r') + mac = f.read() + f.close() -LOGGING_FACILITY = 'ztpbootstrap' -SYSLOG = '/dev/log' -CONTENT_TYPE_PYTHON = 'text/x-python' -CONTENT_TYPE_HTML = 'text/html' -CONTENT_TYPE_OTHER = 'text/plain' -CONTENT_TYPE_JSON = 'application/json' - -TEMP = '/tmp' - -COMMAND_API_SERVER = 'localhost' -COMMAND_API_USERNAME = 'ztps' -COMMAND_API_PASSWORD = 'ztps-password' -COMMAND_API_PROTOCOL = 'http' - -HTTP_STATUS_OK = 200 -HTTP_STATUS_CREATED = 201 -HTTP_STATUS_BAD_REQUEST = 400 -HTTP_STATUS_NOT_FOUND = 404 -HTTP_STATUS_CONFLICT = 409 -HTTP_STATUS_INTERNAL_SERVER_ERROR = 500 - -FLASH = '/mnt/flash' - -STARTUP_CONFIG = '%s/startup-config' % FLASH -RC_EOS = '%s/rc.eos' % FLASH -BOOT_EXTENSIONS = '%s/boot-extensions' % FLASH -BOOT_EXTENSIONS_FOLDER = '%s/.extensions' % FLASH - -HTTP_TIMEOUT = 10 - -#pylint: disable=C0103 -syslog_manager = None -xmpp_client = None -#pylint: enable=C0103 - -#---------------------------------XMPP------------------------ -# Uncomment this section in order to enable XMPP debug logging -# logging.basicConfig(level=logging.DEBUG, -# format='%(levelname)-8s %(message)s') - -# You will also have to uncomment the following lines: -for logger in ['sleekxmpp.xmlstream.xmlstream', - 'sleekxmpp.basexmpp']: - xmpp_log = logging.getLogger(logger) - xmpp_log.addHandler(logging.NullHandler()) -#---------------------------------XMPP------------------------ - - -# ------------------Utilities---------------------------- -def _exit(code): - #pylint: disable=W0702 - - # Wait for XMPP messages to drain - time.sleep(3) - - if xmpp_client: - try: - xmpp_client.abort() - except: - pass - - sys.stdout.flush() - sys.stderr.flush() - - #pylint: disable=W0212 - # Need to close background sleekxmpp threads as well - os._exit(code) - -SYSTEM_ID = None -XMPP_MSG_TYPE = None -def log_xmpp(): - return XMPP_MSG_TYPE == 'debug' - -def log(msg, error=False, xmpp=None): - if xmpp is None: - xmpp = log_xmpp() - - timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S') - xmpp_msg = 'ZTPS:%s: %s%s' % (timestamp, - 'ERROR: ' if error else '', - msg) - - if xmpp and xmpp_client and xmpp_client.connected: - xmpp_client.message(xmpp_msg) - - if SYSTEM_ID: - syslog_msg = '%s: %s' % (SYSTEM_ID, msg) - else: - syslog_msg = msg - - if error: - print 'ERROR: %s' % syslog_msg - else: - print syslog_msg - - if syslog_manager: - if error: - syslog_manager.log.error(syslog_msg) - else: - syslog_manager.log.info(syslog_msg) - -#pylint: disable=C0103 -_ntuple_diskusage = namedtuple('usage', 'total used free') -#pylint: enable=C0103 -def flash_usage(): - stats = os.statvfs(FLASH) - free = stats.f_bavail * stats.f_frsize - total = stats.f_blocks * stats.f_frsize - used = (stats.f_blocks - stats.f_bfree) * stats.f_frsize - return _ntuple_diskusage(total, used, free) -# ------------------Utilities---------------------------- - - -# ------------------4.12.x support---------------------------- -def download_file(url, path): - if not urlparse.urlsplit(url).scheme: #pylint: disable=E1103 - url = urlparse.urljoin(SERVER, url) - - log('Retrieving URL: %s' % url) - - url = urllib2.urlopen(url) - output_file = open(path, 'wb') - output_file.write(url.read()) - output_file.close() - -#pylint: disable=C0103 -REQUESTS = 'requests-2.3.0' -REQUESTS_URL = '%s/files/lib/%s.tar.gz' % (SERVER, REQUESTS) -try: - import requests -except ImportError: - requests_url = '/tmp/%s.tar.gz' % REQUESTS - download_file(REQUESTS_URL, requests_url) - cmd = 'sudo tar -xzvf %s -C /tmp;' \ - 'cd /tmp/%s;' \ - 'sudo python setup.py build;' \ - 'sudo python setup.py install' % \ - (requests_url, REQUESTS) - res = os.system(cmd) - if res: - log('%s returned %s' % (cmd, res), error=True) - _exit(1) - import requests -#pylint: enable=C0103 -# ------------------4.12.x support---------------------------- - - -class ZtpError(Exception): - pass - -class ZtpActionError(ZtpError): - pass - -class ZtpUnexpectedServerResponseError(ZtpError): - pass - - -class Attributes(object): - - def __init__(self, local_attr=None, special_attr=None): - self.local_attr = local_attr if local_attr else [] - self.special_attr = special_attr if special_attr else [] - - def get(self, attr, default=None): - if attr in self.local_attr: - return self.local_attr[attr] - elif attr in self.special_attr: - return self.special_attr[attr] - else: - return default - - def copy(self): - attrs = dict() - if self.special_attr: - attrs = self.special_attr.copy() - if self.local_attr: - attrs.update(self.local_attr) - return attrs - - -class Node(object): - #pylint: disable=R0201 - - '''Node object which can be used by actions via: - attributes.get('NODE') - Attributes: - client (jsonrpclib.Server): jsonrpclib connect to Command API engine - ''' - - def __init__(self, server): - self.server_ = server - - Node._enable_api() - - url = '%s://%s:%s@%s/command-api' % (COMMAND_API_PROTOCOL, - COMMAND_API_USERNAME, - COMMAND_API_PASSWORD, - COMMAND_API_SERVER) - self.client = jsonrpclib.Server(url) - - try: - self.api_enable_cmds([]) - except socket.error: - raise ZtpError('unable to enable eAPI') - - # Workaround for BUG89374 - try: - self._disable_copp() - except jsonrpclib.jsonrpc.ProtocolError as err: - log('unable to disable COPP: %s' % err, error=True) - - global SYSTEM_ID #pylint: disable=W0603 - SYSTEM_ID = \ - self.api_enable_cmds(['show version'])[0]['serialNumber'] - - @classmethod - def _cli_enable_cmd(cls, cli_cmd): - bash_cmd = ['FastCli', '-p', '15', '-A', '-c', cli_cmd] - proc = subprocess.Popen(bash_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) - (out, err) = proc.communicate() - code = proc.returncode #pylint: disable=E1101 - return (code, out, err) - - @classmethod - def _cli_config_cmds(cls, cmds): - cls._cli_enable_cmd('\n'.join(['configure'] + cmds)) - - @classmethod - def _enable_api(cls): - cls._cli_config_cmds(['username %s secret %s privilege 15' % - (COMMAND_API_USERNAME, - COMMAND_API_PASSWORD), - 'management api http-commands', - 'no protocol https', - 'protocol %s' % COMMAND_API_PROTOCOL, - 'no shutdown']) - - _, out, _ = cls._cli_enable_cmd('show management api http-commands |' - ' grep running') - retries = 3 - while not out and retries: - log('Waiting for CommandAPI to be enabled...') - time.sleep(1) - retries = retries - 1 - _, out, _ = cls._cli_enable_cmd( - 'show management api http-commands | grep running') - - def _disable_copp(self): - # COPP does not apply to vEOS - if self.system()['model'] != 'vEOS': - self.api_config_cmds(['control-plane', - 'no service-policy input copp-system-policy']) - - def _has_rc_eos(self): - return os.path.isfile(RC_EOS) - - def _append_lines(self, filename, lines): - with open(filename, 'a') as output: - output.write('\n') - output.write('\n'.join(lines)) - - def api_enable_cmds(self, cmds, text_format=False): - '''Run CLI commands via Command API, starting from enable mode. - Commands are ran in order. - Args: - cmds (list): List of CLI commands. - text_format (bool, optional): If true, Command API request will run - in text mode (instead of JSON). - Returns: - list: List of Command API results corresponding to the - input commands. - ''' - req_format = 'text' if text_format else 'json' - result = self.client.runCmds(1, ['enable'] + cmds, req_format) - if text_format: - return [x.values()[0] for x in result][1:] - else: - return result[1:] - - def api_config_cmds(self, cmds): - '''Run CLI commands via Command API, starting from config mode. - Commands are ran in order. - Args: - cmds (list): List of CLI commands. - Returns: - list: List of Command API results corresponding to the - input commands. - ''' - return self.api_enable_cmds(['configure'] + cmds)[1:] - - def system(self): - '''Get system information. - Returns: - dict: System information - Format:: - {'model': <MODEL>, - 'version': <EOS_VERSION>, - 'systemmac': <SYSTEM_MAC>, - 'serialnumber': <SERIAL_NUMBER>} - ''' - - result = {} - info = self.api_enable_cmds(['show version'])[0] - - result['model'] = info['modelName'] - result['version'] = info['version'] - result['systemmac'] = info['systemMacAddress'] - result['serialnumber'] = info['serialNumber'] - - return result - - def neighbors(self): - '''Get neighbors. - Returns: - dict: LLDP neighbor - Format:: - {'neighbors': {<LOCAL_PORT>: - [{'device': <REMOTE_DEVICE>, - 'port': <REMOTE_PORT>}, ...], - ...}} - ''' - - result = {} - info = self.api_enable_cmds(['show lldp neighbors'])[0] - result['neighbors'] = {} - for entry in info['lldpNeighbors']: - neighbor = {} - neighbor['device'] = entry['neighborDevice'] - neighbor['port'] = entry['neighborPort'] - if entry['port'] in result['neighbors']: - result['neighbors'][entry['port']] += [neighbor] - else: - result['neighbors'][entry['port']] = [neighbor] - return result - - def details(self): - '''Get details. - Returns: - dict: System details - Format:: - {'model': <MODEL>, - 'version': <EOS_VERSION>, - 'systemmac': <SYSTEM_MAC>, - 'serialnumber': <SERIAL_NUMBER>, - 'neighbors': <NEIGHBORS> # see neighbors() - } - ''' - - return dict(self.system().items() + - self.neighbors().items()) - - def has_startup_config(self): - '''Check whether startup-config is configured or not. - Returns: - bool: True is startup-config is configured; false otherwise. - ''' - return os.path.isfile(STARTUP_CONFIG) and \ - open(STARTUP_CONFIG).read().strip() - - def append_startup_config_lines(self, lines): - '''Add lines to startup-config. - Args: - lines (list): List of CLI commands - ''' - self._append_lines(STARTUP_CONFIG, lines) - - def append_rc_eos_lines(self, lines): - '''Add lines to rc.eos. - Args: - lines (list): List of bash commands - ''' - if not self._has_rc_eos(): - lines = ['#!/bin/bash'] + lines - self._append_lines(RC_EOS, lines) - - def log_msg(self, msg, error=False): - '''Log message via configured syslog/XMPP. - Args: - msg (string): Message - error (bool, optional): True if msg is an error; false otherwise. - ''' - log(msg, error) - - def rc_eos(self): - '''Get rc.eos path. - Returns: - string: rc.eos path - ''' - return RC_EOS - - def flash(self): - '''Get flash path. - Returns: - string: flash path - ''' - return FLASH - - def startup_config(self): - '''Get startup-config path. - Returns: - string: startup-config path - ''' - return STARTUP_CONFIG - - def retrieve_url(self, url, path): - '''Download resource from server. - If 'path' is somewhere on flash, the client will first request the - metainformation for the resource from the server (in order to Check - whether there is enogh disk space available). - Raises: - ZtpError: resource cannot be retrieved: - - metainformation cannot be retrieved from server OR - - disk space on flash is insufficient OR - - file cannot be written to disk - Returns: - string: startup-config path - ''' - self.server_.get_resource(url, path) - - @classmethod - def server_address(cls): - '''Get ZTP Server URL. - Returns: - string: ZTP Server URL. - ''' - return SERVER - - -class SyslogManager(object): - - def __init__(self): - self.log = logging.getLogger('ztpbootstrap') - self.log.setLevel(logging.DEBUG) - self.formatter = logging.Formatter('ZTPS - %(levelname)s: ' - '%(message)s') - - # syslog to localhost enabled by default - self._add_syslog_handler() - - def _add_handler(self, handler, level=None): - if level is None: - level = 'DEBUG' - - try: - handler.setLevel(logging.getLevelName(level)) - except ValueError: - log('SyslogManager: unknown logging level (%s) - using ' - 'log.DEFAULT instead' % level, error=True) - handler.setLevel(logging.DEBUG) - - handler.setFormatter(self.formatter) - self.log.addHandler(handler) - - def _add_syslog_handler(self): - log('SyslogManager: adding localhost handler') - self._add_handler(SysLogHandler(address=SYSLOG)) - - def _add_file_handler(self, filename, level=None): - log('SyslogManager: adding file handler (%s - level:%s)' % - (filename, level)) - self._add_handler(logging.FileHandler(filename), level) - - def _add_remote_syslog_handler(self, host, port, level=None): - log('SyslogManager: adding remote handler (%s:%s - level:%s)' % - (host, port, level)) - self._add_handler(SysLogHandler(address=(host, port)), level) - - def add_handlers(self, handler_config): - for entry in handler_config: - match = re.match('^file:(.+)', - entry['destination']) - if match: - self._add_file_handler(match.groups()[ 0 ], - entry['level']) - else: - match = re.match('^(.+):(.+)', - entry['destination']) - if match: - self._add_remote_syslog_handler(match.groups()[ 0 ], - int(match.groups()[ 1 ]), - entry['level']) - else: - log('SyslogManager: Unable to create syslog handler for' - ' %s' % str(entry), error=True) - - -class Server(object): - - def __init__(self): - pass - - @classmethod - def _http_request(cls, path=None, method='get', headers=None, - payload=None, files=None): - if headers is None: - headers = {} - if files is None: - files = [] - - request_files = [] - for entry in files: - request_files[entry] = open(entry,'rb') - - if not urlparse.urlsplit(path).scheme: #pylint: disable=E1103 - full_url = urlparse.urljoin(SERVER, path) - else: - full_url = path - - try: - if method == 'get': - log('GET %s' % full_url) - response = requests.get(full_url, - data=json.dumps(payload), - headers=headers, - files=request_files, - timeout=HTTP_TIMEOUT) - elif method == 'post': - log('POST %s' % full_url) - response = requests.post(full_url, - data=json.dumps(payload), - headers=headers, - files=request_files, - timeout=HTTP_TIMEOUT) - else: - log('Unknown method %s' % method, - error=True) - except requests.exceptions.ConnectionError: - raise ZtpError('server connection error') - - return response - - def _get_request(self, url): - # resource or action - headers = {'content-type': CONTENT_TYPE_HTML} - result = self._http_request(url, - headers=headers) - log('Server response to GET request: status=%s' % result.status_code) - - return (result.status_code, - result.headers['content-type'].split(';')[0], - result) - - def _save_file_contents(self, contents, path, url=None): - if path.startswith('/mnt/flash'): - if not url: - raise ZtpError('attempting to save file to %s, but cannot' - 'retrieve content metadata.') - - _, _, metadata = self.get_metadata(url) - metadata = metadata.json() - - usage = flash_usage() - if (metadata['size'] > usage.free): - raise ZtpError('not enough memory on flash for saving %s to %s ' - '(free: %s bytes, required: %s bytes)' % - (url, path, usage.free, metadata['size'])) - elif (metadata['size'] + usage.used > 0.9 * usage.total): - percent = (metadata['size'] + usage.used) * 100.0 / usage.total - log('WARNING: flash disk usage will exceeed %s%% after ' - 'saving %s to %s' % (percent, url, path)) - - log('Writing %s...' % path) - - # Save contents to file - try: - with open(path, 'wb') as result: - for chunk in contents.iter_content(chunk_size=1024): - if chunk: - result.write(chunk) - result.close() - except IOError as err: - raise ZtpError('unable to write %s: %s' % (path, err)) - - # Set permissions - os.chmod(path, 0777) - - def get_config(self): - headers = {'content-type': CONTENT_TYPE_HTML} - result = self._http_request('bootstrap/config', - headers=headers) - log('Server response to GET config: contents=%s' % result.json()) - - status = result.status_code - content = result.headers['content-type'].split(';')[0] - if(status != HTTP_STATUS_OK or - content != CONTENT_TYPE_JSON): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - - return (status, content, result) - - def post_nodes(self, node): - headers = {'content-type': CONTENT_TYPE_JSON} - result = self._http_request('nodes', - method='post', - headers=headers, - payload=node) - location = result.headers['location'] \ - if 'location' in result.headers \ - else None - log('Server response to POST nodes: status=%s, location=%s' % - (result.status_code, location)) - - status = result.status_code - content = result.headers['content-type'].split(';')[0] - if(status not in [HTTP_STATUS_CREATED, - HTTP_STATUS_BAD_REQUEST, - HTTP_STATUS_CONFLICT] or - content != CONTENT_TYPE_HTML): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - elif status == HTTP_STATUS_BAD_REQUEST: - raise ZtpError('node not found on server (status=%s)' % status) - - return (status, content, location) - - def get_definition(self, location): - headers = {'content-type': CONTENT_TYPE_HTML} - result = self._http_request(location, - headers=headers) - - if result.status_code == HTTP_STATUS_OK: - log('Server response to GET definition: status=%s, contents=%s' % - (result.status_code, result.json())) - else: - log('Server response to GET definition: status=%s' % - result.status_code) - - status = result.status_code - content = result.headers['content-type'].split(';')[0] - if not ((status == HTTP_STATUS_OK and - content == CONTENT_TYPE_JSON) or - (status == HTTP_STATUS_BAD_REQUEST and - content == CONTENT_TYPE_HTML)): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - elif status == HTTP_STATUS_BAD_REQUEST: - raise ZtpError('server-side topology check failed (status=%s)' % - status) - - return (status, content, result) - - def get_action(self, action): - status, content, action_response = \ - self._get_request('actions/%s' % action) - - if not ((status == HTTP_STATUS_OK and - content == CONTENT_TYPE_PYTHON) or - (status == HTTP_STATUS_NOT_FOUND and - content == CONTENT_TYPE_HTML)): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - elif status == HTTP_STATUS_NOT_FOUND: - raise ZtpError('action not found on server (status=%s)' % status) - - filename = os.path.join(TEMP, action) - self._save_file_contents(action_response, filename) - return filename - - def get_metadata(self, url): - if urlparse.urlsplit(url).scheme: #pylint: disable=E1103 - aux = url.split('/') - if aux[3] != 'meta': - aux = aux[0:3] + ['meta'] + aux[3:] - url = '/'.join(aux) - else: - aux = [x for x in url.split('/') if x] - if aux[0] != 'meta': - url = '/'.join(['meta'] + aux) - - headers = {'content-type': CONTENT_TYPE_HTML} - result = self._http_request(url, - headers=headers) - log('Server response to GET meta: contents=%s' % result.json()) - - status = result.status_code - content = result.headers['content-type'].split(';')[0] - - if not ((status == HTTP_STATUS_OK and - content == CONTENT_TYPE_JSON) or - (status == HTTP_STATUS_NOT_FOUND and - content == CONTENT_TYPE_HTML) or - (status == HTTP_STATUS_INTERNAL_SERVER_ERROR and - content == CONTENT_TYPE_HTML)): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - elif status == HTTP_STATUS_NOT_FOUND: - raise ZtpError('metadata not found on server (status=%s)' % - status) - elif status == HTTP_STATUS_INTERNAL_SERVER_ERROR: - raise ZtpError( - 'unable to retrieve metadata from server (status=%s)' % - status) - - return (status, content, result) - - def get_resource(self, url, path): - if not urlparse.urlsplit(url).scheme: #pylint: disable=E1103 - url = urlparse.urljoin(SERVER, url) - - status, content, response = self._get_request(url) - if not ((status == HTTP_STATUS_OK and - content == CONTENT_TYPE_OTHER) or - (status == HTTP_STATUS_NOT_FOUND and - content == CONTENT_TYPE_HTML)): - raise ZtpUnexpectedServerResponseError( - 'unexpected reponse from server (status=%s; content-type=%s)' % - (status, content)) - elif status == HTTP_STATUS_NOT_FOUND: - raise ZtpError('resource not found on server (status=%s)' % status) - - self._save_file_contents(response, path, url) - - -class XmppClient(sleekxmpp.ClientXMPP): - #pylint: disable=W0613, R0904, R0201, R0924 - - def __init__(self, user, domain, password, rooms, - nick, xmpp_server, xmpp_port): - - self.xmpp_jid = '%s@%s' % (user, domain) - self.connected = False - - try: - sleekxmpp.ClientXMPP.__init__(self, self.xmpp_jid, - password) - except sleekxmpp.jid.InvalidJID: - log('Unable to connect XMPP client because of invalid jid: %s' % - self.xmpp_jid, xmpp=False) - return - - self.xmpp_nick = nick - self.xmpp_rooms = rooms - - self.xmpp_rooms = [] - for room in rooms: - self.xmpp_rooms.append('%s@conference.%s' % (room, domain)) - - self.add_event_handler('session_start', self._session_connected) - self.add_event_handler('connect', self._session_connected) - self.add_event_handler('disconnected', self._session_disconnected) - - # Multi-User Chat - self.register_plugin('xep_0045') - # XMPP Ping - self.register_plugin('xep_0199') - # Service Discovery - self.register_plugin('xep_0030') - - log('XmppClient connecting to server...', xmpp=False) - if xmpp_server != None: - self.connect((xmpp_server, xmpp_port), reattempt=False) - else: - self.connect(reattempt=False) - - self.process(block=False) - - retries = 3 - while not self.connected and retries: - # Wait to connect - time.sleep(1) - retries -= 1 - - def _session_connected(self, event): - log('XmppClient: Session connected (%s)' % self.xmpp_jid, - xmpp=False) - self.send_presence() - self.get_roster() - - self.connected = True - - # Joining rooms - for room in self.xmpp_rooms: - self.plugin['xep_0045'].joinMUC(room, - self.xmpp_nick, - wait=True) - log('XmppClient: Joined room %s as %s' % - (room, self.xmpp_nick), - xmpp=False) - - def _session_disconnected(self, event): - log('XmppClient: Session disconnected (%s)' % self.xmpp_jid, - xmpp=False) - self.connected = False - - def message(self, message): - for room in self.xmpp_rooms: - self.send_message(mto=room, - mbody=message, - mtype='groupchat') - -def apply_config(config, node): - global xmpp_client #pylint: disable=W0603 - - log('Applying server config') - - - # XMPP not configured yet - xmpp_config = config.get('xmpp', {}) - - global XMPP_MSG_TYPE #pylint: disable=W0603 - XMPP_MSG_TYPE = xmpp_config.get('msg_type', 'debug') - if XMPP_MSG_TYPE not in ['debug', 'info']: - log('XMPP configuration failed because of unexpected \'msg_type\': ' - '%s not in [\'debug\', \'info\']' % XMPP_MSG_TYPE, error=True, - xmpp=False) - else: - if xmpp_config: - log('Configuring XMPP', xmpp=False) - if ('username' in xmpp_config and - 'domain' in xmpp_config and - 'password' in xmpp_config and - 'rooms' in xmpp_config and - xmpp_config['rooms']): - nick = node.system()['serialnumber'] - if not nick: - # vEOS might not have a serial number configured - nick = node.system()['systemmac'] - xmpp_client = XmppClient(xmpp_config['username'], - xmpp_config['domain'], - xmpp_config['password'], - xmpp_config['rooms'], - nick, - xmpp_config.get('server', None), - xmpp_config.get('port', 5222)) - else: - # XMPP not configured yet - log('XMPP configuration failed because server response ' - 'is missing config details', - error=True, xmpp=False) - else: - log('No XMPP configuration received from server', xmpp=False) - - log_config = config.get('logging', []) - if log_config: - log('Configuring syslog') - syslog_manager.add_handlers(log_config) - else: - log('No XMPP configuration received from server') - - -def execute_action(server, action_details, special_attr): - action = action_details['action'] - - description = '' - if 'description'in action_details: - description = '(%s)' % action_details['description'] - - if action not in sys.modules: - log('Downloading action %s%s' % (action, description)) - filename = server.get_action(action) - - log('Executing action %s' % action) - if 'onstart' in action_details: - log('Action %s: %s' % (action, action_details['onstart']), - xmpp=True) - - try: - if action in sys.modules: - module = sys.modules[action] - else: - module = imp.load_source(action, filename) - - local_attr = action_details['attributes'] \ - if 'attributes' in action_details \ - else [] - ret = module.main(Attributes(local_attr, special_attr)) - if ret: - raise ZtpActionError('action returned %s' % ret) - log('Action executed succesfully (%s)' % action) - if 'onsuccess' in action_details: - log('Action %s: %s' % (action, action_details['onsuccess']), - xmpp=True) - except Exception as err: #pylint: disable=W0703 - if 'onfailure' in action_details: - log('Action %s: %s' % (action, action_details['onfailure']), - xmpp=True) - raise ZtpActionError('executing action failed (%s): %s' % (action, err)) - -def restore_factory_default(): - for filename in [RC_EOS, BOOT_EXTENSIONS]: - if os.path.exists(filename): - os.remove(filename) - - shutil.rmtree(BOOT_EXTENSIONS_FOLDER, ignore_errors=True) +def get_config(url): + response = requests.get(url) + file = response.text + return +def write_config(file_content): + with open('mnt/flash/startup-config', 'w') as file: + file.write(file_content) def main(): - #pylint: disable=W0603,R0912,R0915 - global syslog_manager - - restore_factory_default() - - syslog_manager = SyslogManager() - server = Server() - - # Retrieve and apply logging/XMPP configuration from server - # XMPP not configured yet - log('Retrieving config from server', xmpp=False) - _, _, config = server.get_config() - - # Creating node - node = Node(server) - - # XMPP not configured yet - log('Config retrieved from server', xmpp=False) - apply_config(config.json(), node) - - # Checking node on server - # XMPP not configured yet - log('Collecting node information', xmpp=False) - _, _, location = server.post_nodes(node.details()) - - # Get definition - _, _, definition = server.get_definition(location) - - # Execute actions - definition = definition.json() - - for attr in ['name', 'actions']: - if attr not in definition: - raise ZtpError('\'%s\' section missing from definition' % attr) - - definition_name = definition['name'] - log('Applying definition %s' % definition_name) - - - special_attr = {} - special_attr['NODE'] = node - for details in definition['actions']: - execute_action(server, details, special_attr) - - log('Definition %s applied successfully' % definition_name) - - # Check for startup-config - if not node.has_startup_config(): - raise ZtpError('startup configuration is missing at the end of the ' - 'bootstrap process') - - log('ZTP bootstrap completed successfully!') - - _exit(0) + mac_address = get_mac() + parsed_mac = mac_address.replace(":", "") + url_with_config = HTTP_SERVER_CONFIG_BASE_URL + parsed_mac + "startup-config" + startup_config = get_config(url_with_config) + write_config(startup_config) -if __name__ == '__main__': - try: - main() - except ZtpError as exception: - log('''Bootstrap process failed: - %s''' % str(exception), - error=True) - _exit(1) - except KeyboardInterrupt: - log('Bootstrap process keyboard-interrupted', - error=True) - log(sys.exc_info()[0]) - log(traceback.format_exc()) - _exit(1) - except Exception, exception: - log('''Bootstrap process failed because of unknown exception: - %s''' % - exception, error=True) - log(sys.exc_info()[0]) - log(traceback.format_exc()) - _exit(1) \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file