Cli Config Builder
CliConfigBuilder
This section demonstrates how to use CliConfigBuilder
, which allows for
clearer, and faster development time. CliConfigBuilder
handles building
configuration and unconfiguration string for each Device
. It follows these
main ideas :
One object that handles everything related to cli building, in a simple manner without redundant code for the user. It creates its own indentation, knows how to configure and unconfig for specific attributes, deals with appending block of configuration together and much more. These functionality are available with the help of a few new class/methods.
submode_context
creates new a new block of configuration. Every configuration
following it will contains an indentation and will automatically add an exit
command. Submode_context
is used with the keyword with
. Everything under
the with
indentation is configured inside this block. To get many block of
configuration, many submode_context
can be chained together.
attributes.format
allows to remove many if
statement, and will match
{value}
with self.value
, which reduces the chances of typo and further
minimalize the amount of typing required. Please see an example below.
configuration.submode_unconfig
allows to quickly remove the whole block
of configurations if unconfiguration is wanted and no other attributes
is
given.
Here are the difference in the code between the previous examples:
Import
CliConfigBuilder
.Replace cfgs for configurations, which is an instance of
CliConfigBuilder
.configurations.submode_context
to create a new block of indendated configurations.attributes.format
to help with formatting the string in combination withconfigurations.append_line
to append the configuration.Add
unconfig
argumentAdd submode_unconfig
#### Imports ####
from genie.conf import Genie
from genie.conf.base import Device
from genie.conf.base import Testbed
from genie.conf.base.base import DeviceFeature
from genie.conf.base.cli import CliConfigBuilder
from genie.conf.base.attributes import DeviceSubAttributes,\
SubAttributesDict,\
AttributesHelper
#### Vrf class ####
class Vrf(DeviceFeature):
class DeviceAttributes(DeviceSubAttributes):
def build_config(self, devices=None, apply=True, attributes=None,
unconfig=False):
# List containing configuration for this loop
# Instantiate configurations
configurations = CliConfigBuilder(unconfig=unconfig)
# Create Vrf Submode context
with configurations.submode_context(
attributes.format('vrf {name}',
force=True)):
if unconfig and attributes.iswildcard:
configurations.submode_unconfig()
configurations.append_line(attributes.format('description {description}'))
configurations.append_line(attributes.format('rd {rd}'))
return str(configurations)
# __init__ of Vrf
def __init__(self, name, *args, **kwargs):
self.device_attr = SubAttributesDict(self.DeviceAttributes,
parent=self)
self.name = name
super().__init__(*args, **kwargs)
# Adding a new build_config, to call
def build_config(self, devices=None, apply=True, attributes=None,
unconfig=False):
cfgs = {}
attributes = AttributesHelper(self, attributes)
if devices is None:
devices = self.devices
devices = set(dev.name for dev in 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,
unconfig=unconfig)
if apply:
for device_name, cfg in sorted(cfgs.items()):
if cfg:
device = self.testbed.devices[device_name]
device.configure(cfg)
else:
return cfgs
#### Main section ####
# Set Genie Tb
testbed = Testbed()
Genie.testbed = testbed
dev1 = Device(name='pe1', testbed=testbed, os='nxos')
dev2 = Device(name='pe2', testbed=testbed, os='nxos')
vrf1 = Vrf(name='blue')
print(vrf1.build_config(devices=[dev1, dev2], apply=False))
# {'pe2': 'vrf blue\n exit',
# 'pe1': 'vrf blue\n exit'}
# Let's add a different description for both device
vrf1.device_attr[dev1.name].description = 'Pe1 blue vrf'
vrf1.device_attr[dev2.name].description = 'Pe2 super blue vrf'
# And same RD for both, we can set it at the parent level as we want it
# to be of the same value
vrf1.rd = '800:1'
print(vrf1.build_config(devices=[dev1, dev2], apply=False))
# {'pe2': 'vrf blue\n description Pe2 super blue vrf\n rd 800:1\n exit',
# 'pe1': 'vrf blue\n description Pe1 blue vrf\n rd 800:1\n exit'}
# Let's see our new unconfig power that was given for free
# As no attributes were given, it then removes the whole block
vrf1.build_config(devices=[dev1, dev2], apply=False, unconfig=True)
# {'pe2': 'no vrf blue',
# 'pe1': 'no vrf blue'}
# Let's only remove a particular attribute configuration
vrf1.build_config(apply=False, devices=[dev1, dev2],
attributes={'device_attr':{'*':{'description':{''}}}}, unconfig=True)
# {'pe1': 'vrf blue\n no description Pe1 blue vrf\n exit',
# 'pe2': 'vrf blue\n no description Pe2 super blue vrf\n exit'}
# And remove both description and rd
vrf1.build_config(apply=False, devices=[dev1, dev2],
attributes={'device_attr':{'*':{'description':None 'rd':None}}}, unconfig=True)
# {'pe1': 'vrf blue\n no description Pe1 blue vrf\n no rd 800:1\n exit',
# 'pe2': 'vrf blue\n no description Pe2 super blue vrf\n no rd 800:1\n exit'}
So far we’ve seen it saves development time, and gives cleaner code. One new
functionality is that it includes unconfiguration with it. It takes advantage
of the fact that most configuration can be removed with no
in front of it.
In corner cases, users can provide unconfig_cmd
to provide the cli to remove
the configuration to use. For example :
with configurations.submode_context(
attributes.format('vrf {name}', force=True),
unconfig_cmd='no vrf'):
configurations.append_line(attributes.format('rd {rd}'),
unconfig_cmd='no rd')
One last useful api that can be useful is append_block
. It appends a block of
line into configurations.
raw
is another argument which can be given. It guarantee that the string
given will not be modified. So no automatic string substitution, and no
automatic unconfiguration. raw
accepts True
or False
as value.
CliConfig
So far, at the end of the build_config
, we’ve always returned the
configuration string. However, this mean to apply the configuration to the
device, we would need to keep track which configuration goes with which device.
Instead, let’s create an object that does this for us.
CliConfig
accepts the following arguments:
device
must be adevice object
unconfig
must beTrue
/False
, depending if it is configuring or notcli_config
must be the configurationstr
__str__
has been overwritten to return the cli_config
string.
from genie.conf.base.config import CliConfig
if apply:
for device_name, cfg in sorted(cfgs.items()):
if cfg:
device = self.testbed.devices[device_name]
device.configure(cfg)
else:
return CliConfig(device=self.device, unconfig=unconfig,
cli_config=cfgs)
YangConfig
It is exactly the same a CliConfig
, but related to Yang
and Ydk
.
YangConfig
accepts the following arguments:
device
must be adevice object
unconfig
must beTrue
/False
, depending if it is configuring or notncp
must be aNetconfServiceProvider
objectcrud_service
must be aCrudService
object
from genie.conf.base.config import YangConfig
if apply:
# create netconf connection
ncp = NetconfServiceProvider(self.device)
if unconfig:
crud_service.delete(ncp, ydk_obj)
else:
crud_service.create(ncp, ydk_obj)
else:
if unconfig:
return YangConfig(device=self.device,
ydk_obj=ydk_obj,
ncp=NetconfServiceProvider,
crud_service=crud_service.delete)
else:
return YangConfig(device=self.device,
ydk_obj=ydk_obj,
ncp=NetconfServiceProvider,
crud_service=crud_service.create)