Skip to content
Snippets Groups Projects
Commit 2d7e7108 authored by Neil Schark's avatar Neil Schark
Browse files

arista bootstrap skript

parent 58dff43c
No related branches found
No related tags found
No related merge requests found
#!/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 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
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'
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 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)
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment