#
# 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.
#
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#pylint: disable=C0103
import collections
import logging
import os
import ConfigParser
import ztpserver.types
log = logging.getLogger(__name__)
[docs]class Attr(object):
""" Base Attribute class for deriving all attributes for a Config object
:param name: required argument specifies attribute name
:param type: optional keyword argument specifies attribute type. the
default argument type is String
:param group: optional keyword argument specifies attribute group. All
attribute names must be unique within the group
:param default: optional keyword argument specifies the default value for
the attribute. The default value is None
"""
def __init__(self, name, **kwargs):
self.name = name
self.type = kwargs.get('type') or ztpserver.types.String()
self.group = kwargs.get('group') or 'default'
self.default = kwargs.get('default')
self.environ = kwargs.get('environ')
if self.environ is not None and self.environ in os.environ:
self.default = self.type(os.environ.get(self.environ))
elif self.default is not None:
self.default = self.type(self.default)
def __repr__(self):
return 'Attr(name=%s, group=%s, default=%s)' % \
(self.name, self.group, self.default)
[docs]class StrAttr(Attr):
""" String attribute class derived from Attr
:param choices: optional keyword argument specifies valid choices
"""
def __init__(self, name, choices=None, **kwargs):
self.choices = choices
attrtype = ztpserver.types.String(choices=choices)
super(StrAttr, self).__init__(name, type=attrtype, **kwargs)
[docs]class IntAttr(Attr):
""" Integer attribute class derived from Attr
:param min_value: specifies the min value. the default is None
:param max_value: specifies the max value. the default is None
"""
def __init__(self, name, min_value=None, max_value=None, **kwargs):
self.min_value = min_value
self.max_value = max_value
attrtype = ztpserver.types.Integer(min_value=min_value,
max_value=max_value)
super(IntAttr, self).__init__(name, type=attrtype, **kwargs)
[docs]class BoolAttr(Attr):
""" Boolean attribute class derived from Attr """
def __init__(self, name, **kwargs):
attrtype = ztpserver.types.Boolean()
super(BoolAttr, self).__init__(name, type=attrtype, **kwargs)
[docs]class ListAttr(Attr):
""" List attribute class derived from Attr
:param delimiter: specifies the delimiter character to split the string on
"""
def __init__(self, name, delimiter=',', **kwargs):
attrtype = ztpserver.types.List(delimiter=delimiter)
super(ListAttr, self).__init__(name, type=attrtype, **kwargs)
[docs]class Group(collections.Mapping):
""" The Group class provides a logical grouping of attributes in a
Config object. Group names must be unique for each Config instance
and cannot be assigned values.
:param name: the name of the group
:param config: the config object the group is associated with
"""
def __init__(self, name, config):
self.name = name
self.config = config
def __getattr__(self, name):
# pylint: disable=W0212
return self.config.__get_attribute__(name, self.name)
def __getitem__(self, name):
return self.__getattr__(name)
def __iter__(self):
return iter(self._keys())
def __len__(self):
return len(self._keys())
def __delitem__(self):
pass
def __setitem__(self):
pass
def _keys(self):
return [key[1] for key in self.config if key[0] == self.name]
[docs] def add_attribute(self, item):
self.config.add_attribute(item, self.name)
[docs]class Config(collections.Mapping):
""" The Config class represents the configuration for collection. """
def __init__(self):
self.attributes = dict()
self.groups = list()
def __getattr__(self, name):
return self.__get_attribute__(name)
def __getitem__(self, name):
return self.__get_attribute__(name)
def __iter__(self):
return iter(self.attributes)
def __len__(self):
return len(self.attributes)
def __repr__(self):
return 'Config'
def __delitem__(self):
pass
def __setitem__(self):
pass
def __get_attribute__(self, name, group=None):
if not group and name in self.groups:
return Group(name, self)
key = (group, name)
if key not in self.attributes:
raise AttributeError('Missing attribute: %s' % str(key))
item = self.attributes.get(key)
return item.get('value')
[docs] def add_attribute(self, item, group=None):
obj = dict(_metadata=item)
if group is None and hasattr(item, 'group'):
group = item.group
key = (group, item.name)
if group not in self.groups:
self.add_group(group)
if key in self.attributes:
raise AttributeError('Duplicate attribute: %s' % str(key))
self.attributes[key] = obj
if item.default is not None:
obj['value'] = self._transform(obj, item.default)
[docs] def add_group(self, group):
if isinstance(group, Group):
self.groups.append(group.name)
else:
group = str(group)
self.groups.append(group)
def _transform(self, item, value):
# pylint: disable=R0201
return item['_metadata'].type(value)
[docs] def set_value(self, name, value, group=None):
if not group and name in self.groups:
raise AttributeError('Failed to set value (name=%s, group=%s): '
'cannot set a value for a group' %
(name, group))
item = self.attributes.get((group, name))
if item is None:
raise AttributeError('Failed to set value (name=%s, group=%s): '
'missing item' %
(name, group))
item['value'] = self._transform(item, value)
[docs] def clear_value(self, name, group=None):
""" clears the attributes value and resets it to default """
if not group and name in self.groups:
raise AttributeError('Failed to clear value (name=%s, group=%s): '
'cannot clear values for a group' %
(name, group))
item = self.attributes.get((group, name))
if item['_metadata'].default is None: # pylint: disable=W0104
item['value'] = None
else:
item['value'] = self._transform(item, item['_metadata'].default)
[docs] def read(self, filename):
cp = ConfigParser.ConfigParser() #pylint: disable=C0103
cp.read(filename)
for section in cp.sections():
for key, value in cp.items(section):
try:
self.set_value(key, value, section)
except AttributeError as err:
log.warning('Error detected while reading %s: %s' %
(filename, err))
continue
runtime = Config()
# Group: default
runtime.add_attribute(StrAttr(
name='data_root',
default='/usr/share/ztpserver',
environ='ZTPS_DEFAULT_DATAROOT'
))
runtime.add_attribute(StrAttr(
name='identifier',
choices=['systemmac', 'serialnumber'],
default='serialnumber'
))
runtime.add_attribute(StrAttr(
name='server_url',
default='http://ztpserver:8080',
environ='ZTPS_DEFAULT_SERVER'
))
runtime.add_attribute(BoolAttr(
name='logging',
default=True,
environ='ZTPS_DEFAULT_LOGGING'
))
runtime.add_attribute(BoolAttr(
name='console_logging',
default=True
))
runtime.add_attribute(StrAttr(
name='console_logging_format',
default='%(levelname)s: [%(module)s:%(lineno)d] %(message)s',
environ='ZTPS_CONSOLE_LOGGING_FORMAT'
))
runtime.add_attribute(BoolAttr(
name='disable_topology_validation',
default=False
))
# Group: server
runtime.add_attribute(StrAttr(
name='interface',
group='server',
default='0.0.0.0'
))
runtime.add_attribute(IntAttr(
name='port',
group='server',
min_value=1,
max_value=65534,
default=8080
))
# Group: files
runtime.add_attribute(StrAttr(
name='folder',
group='files',
default='files',
environ='ZTPS_FILES_FOLDER'
))
runtime.add_attribute(StrAttr(
name='path_prefix',
group='files',
environ='ZTPS_FILES_PATH_PREFIX'
))
# Group: actions
runtime.add_attribute(StrAttr(
name='folder',
group='actions',
default='actions',
environ='ZTPS_ACTIONS_FOLDER'
))
runtime.add_attribute(StrAttr(
name='path_prefix',
group='actions',
environ='ZTPS_ACTIONS_PATH_PREFIX'
))
# Group: bootstrap
runtime.add_attribute(StrAttr(
name='folder',
group='bootstrap',
default='bootstrap',
environ='ZTPS_BOOTSTRAP_FOLDER'
))
runtime.add_attribute(StrAttr(
name='path_prefix',
group='bootstrap',
environ='ZTPS_BOOTSTRAP_PATH_PREFIX'
))
runtime.add_attribute(StrAttr(
name='filename',
group='bootstrap',
default='default',
environ='ZTPS_BOOTSTRAP_FILENAME'
))
# Group: neighbordb
runtime.add_attribute(StrAttr(
name='filename',
group='neighbordb',
default='neighbordb',
environ='ZTPS_NEIGHBORDB_FILENAME'
))