Abstraction
In the previous sections, we’ve learn how to create a new Feature
, though
the subject of abstraction hasn’t been approached yet.
First make sure you have read pyats
abstract, especially the section on
:lookupdecorator:`Lookup Decorator <http>` as it is the core of abstraction in Genie
conf
.
Strategy
The strategy is as follow:
Create a base class which holds the structure of the
Feature
Create another version of this class for a or some tokens (could be OS, cli/yang/…) and implement the specific
build_config
for these tokens.
This allows to have the same structure and variable for all implementations, while supporting different implementation for each token, or a series of token.
Let’s give an example, which is really close to the actual implementation
in Genie
.
from genie.abstract import lookup
class Base(object):
@lookup('os')
def build_config(self, *args, **kwargs):
'''Abstract method to build_config'''
raise NotImplementedError
@lookup('os')
def build_unconfig(self, *args, **kwargs):
'''Abstract method to build_unconfig'''
raise NotImplementedError
from genie.conf.base import ConfigurableBase
from genie.decorator import managedattribute
from genie.conf.base.config import CliConfig
from genie.conf.base.attributes import DeviceSubAttributes,\
SubAttributesDict,\
AttributesHelper
class MyFeature(ConfigurableBase):
# Inheriting from Base (As all feature inherits from atleast one the
Features classes)
name = managedattribute(
name='name',
read_only=True,
doc='Name of the Feature')
description = managedattribute(
name='description',
type=managedattribute.test_istype(str),
doc='Description of the Feature')
class DeviceAttributes(DeviceSubAttributes):
# And any other structure wanted
pass
def __init__(self, name, **kwargs):
self._name = name
self.device_attr = SubAttributesDict(self.DeviceAttributes,
parent=self)
super().__init__(*kwargs)
def build_config(self, devices=None, apply=True, attributes=None):
cfgs = {}
attributes = AttributesHelper(self, attributes)
if devices is None:
devices = self.devices
devices = set(devices)
# Loop over all the items of 'self.device_attr', sort them,
# and only care about the keys which are in keys.
for key, sub, attributes2 in attributes.mapping_items(
'device_attr', keys=devices, sort=True):
# For each, call their build_config with attributes as an argument.
# attributes2 is only the attributes related to this particular
# device, and its parent attributes. (To allow parent default
# values)
cfgs[key] = sub.build_config(apply=False,
attributes=attributes2)
if apply:
for device_name, cfg in sorted(cfgs.items()):
if cfg:
device = self.testbed.devices[device_name]
device.configure(cfg)
else:
return cfgs
def build_unconfig(self, devices=None, apply=True, attributes=None):
cfgs = {}
attributes = AttributesHelper(self, attributes)
if devices is None:
devices = self.devices
devices = set(devices)
# Loop over all the items of 'self.device_attr', sort them,
# and only care about the keys which are in keys.
for key, sub, attributes2 in attributes.mapping_items(
'device_attr', keys=devices, sort=True):
# For each, call their build_config with attributes as an argument.
# attributes2 is only the attributes related to this particular
# device, and its parent attributes. (To allow parent default
# values)
cfgs[key] = sub.build_unconfig(apply=False,
attributes=attributes2)
if apply:
for device_name, cfg in sorted(cfgs.items()):
if cfg:
device = self.testbed.devices[device_name]
device.configure(cfg)
else:
return cfgs
So far, we have only created the structure of the Feature. It has
DeviceAttributes
, it also contains 2 managedattributes
. However
no configuration is part of it.
The configuration is inside a different file, inside a diferent directory, with
the token as directory name. For Genie
, the first token is OS
name, so
nxos
, iosxr, `iosxe
and so on.
Let’s now implement the configuration section of this Feature
.
from genie.conf.base.config import CliConfig
from genie.conf.base.cli import CliConfigBuilder
from genie.conf.base.attributes import AttributesHelper
class myFeature(object):
class DeviceAttributes(object):
def build_config(self, attributes=None, unconfig=False):
attributes=AttributesHelper(self, attributes)
configurations = CliConfigBuilder(unconfig=unconfig)
with configurations.submode_context(
attributes.format('feature {name}',
force=True)):
if unconfig and attributes.iswildcard:
configurations.submode_unconfig()
configurations.append_line(attributes.format('description {description}'))
return CliConfig(device=self.device, unconfig=unconfig,
cli_config=configurations)
def build_unconfig(self, apply=True, attributes=None, **kwargs):
return self.build_config(apply=apply, attributes=attributes,
unconfig=True, **kwargs)
Hint
This code is not executable, please see next section. This section only explains the concepts and the strategy.
This represents the configuration part of the code, which is in a different files than the structure itself.
Let’s revisit the structure of genie_libs
now that we have this new
understanding.
genie_libs
`-- conf
|-- __init__.py <-- Package declaration
`-- vrf <-- Feature Directory
|-- __init__.py
|-- vrf.py <-- Structure file
|-- iosxe <-- Token
| |-- __init__.py <-- Token declaration
| `-- vrf.py <-- Configuration implementation
|-- nxos <-- Token
| |-- __init__.py <-- Token declaration
| `-- vrf.py <-- Configuration implementation
|-- iosxr <-- Token
| |-- __init__.py <-- Token declaration
| `-- vrf.py <-- Configuration implementation
`-- tests
`-- test_vrf.py