"""
Route map rules and match condition elements for dynamic routing policies.
A RouteMap can be created and subsequent rules can be inserted
within the route map policy.
A MatchCondition is the subject of the rule providing criteria to
specify how a match is made. Elements used in match conditions are
`next_hop`, `peer_address`, `access_list` and type `metric`.
.. seealso:: :class:`~MatchCondition` for more details on how to add match
conditions to a rule or modify an existing rule.
Example of creating a RouteMap and subsequent rule, specifying match condition
options as keyword arguments::
>>> from smc.routing.route_map import RouteMap
>>> from smc.routing.access_list import IPAccessList
>>> from smc.routing.bgp import ExternalBGPPeer
...
>>> rm = RouteMap.create(name='myroutemap')
>>> rm
RouteMap(name=myroutemap)
>>> rm.route_map_rules.create(name='rule1', action='permit',
next_hop=IPAccessList('myacl'), peer_address=ExternalBGPPeer('bgppeer'),
metric=20)
RouteMapRule(name=rule1)
...
>>> rule1 = rm.route_map_rules.get(0) # retrieve rule 1 from the route map
>>> for condition in rule1.match_condition:
... condition
...
Condition(rank=1, element=ExternalBGPPeer(name=bgppeer), type=u'peer_address')
Condition(rank=2, element=IPAccessList(name=myacl), type='access_list')
Condition(rank=3, element=Metric(value=20), type=u'metric')
Instead of providing singular match condition keywords to the `create` constructor,
you can also optionally provide a MatchCondition instance when creating a rule::
>>> from smc.routing.route_map import MatchCondition
>>> condition = MatchCondition()
>>> condition.add_access_list(IPAccessList('myacl'))
>>> condition.add_peer_address(ExternalBGPPeer('bgppeer'))
>>> condition.add_metric(20)
>>> condition
MatchCondition(entries=3)
>>> rm.route_map_rules.create(
... name='foo2',
... finish=False,
... match_condition=condition)
RouteMapRule(name=foo2)
To remove a match condition, first obtain it's rank. After making the modification
be sure to call update on the rule element::
>>> rule = rm.route_map_rules.get(0)
>>> rule.match_condition.remove_condition(rank=2)
>>> rule.update()
You can also delete a rule by obtaining the rule, either through the
route_map_rules collection reference or by iteration::
rule = rm.route_map_rules.get(1)
rule.delete()
Or by the name::
rule = rm.route_map_rules.get_exact('foo')
rule.delete()
.. seealso:: :class:`smc.base.collection.rule_collection`
"""
import collections
from smc.base.model import Element, ElementCreator, SubElement
from smc.base.collection import rule_collection
from smc.policy.rule import RuleCommon
from smc.api.exceptions import CreateRuleFailed
Metric = collections.namedtuple('Metric', 'value')
"""
A metric is a simple namedtuple for returning a Metric route map
element
:ivar int value: metric value for this BGP route
"""
Condition = collections.namedtuple('Condition', 'rank element type')
"""
A condition defines the type of dynamic element that is used in
the match condition field of a route map.
:ivar str rank: the rank in the match condition list
:ivar str element: the dynamic element type for this condition
:ivar str type: type defines the type of entry, i.e. metric, peer_address, next_hop,
access_list
"""
[docs]class MatchCondition(object):
"""
MatchCondition is an iterable container class that holds the
match conditions for the route map rule. The list of conditions
are ranked in order. You can add, remove and view conditions currently
configured in this rule. After making modifications, call update on
the rule to commit back to SMC.
When iterating over a match condition, a namedtuple is returned that
provides the rank and element type for the condition. It is then possible
to add by rank (ie: insert conditions in between others), or remove based
on rank. If not rank is provided when adding new conditions, the
condition is added to the bottom of the rank list.
:rtype: list(Condition)
"""
def __init__(self, rule=None):
self.conditions = rule.data.get(
'match_condition', []) if rule else []
def __iter__(self):
for condition in self.conditions:
condition_type = condition.get('type')
if 'element' in condition_type:
entry = Element.from_href(
condition.get('access_list_ref'))
condition_type = 'access_list'
elif 'metric' in condition_type:
entry = Metric(
condition.get('metric'))
elif 'peer_address' in condition_type:
ref = 'fwcluster_peer_address_ref' if 'fwcluster_peer_address_ref'\
in condition else 'external_bgp_peer_address_ref'
entry = Element.from_href(
condition.get(ref))
elif 'next_hop' in condition_type:
entry = Element.from_href(
condition.get('next_hop_ref'))
yield Condition(condition.get('rank'), entry, condition_type)
[docs] def add_access_list(self, accesslist, rank=None):
"""
Add an access list to the match condition. Valid
access list types are IPAccessList (v4 and v6),
IPPrefixList (v4 and v6), AS Path, CommunityAccessList,
ExtendedCommunityAccessList.
"""
self.conditions.append(
dict(access_list_ref=accesslist.href,
type='element',
rank=rank))
[docs] def add_metric(self, value, rank=None):
"""
Add a metric to this match condition
:param int value: metric value
"""
self.conditions.append(
dict(metric=value, type='metric', rank=rank))
[docs] def add_next_hop(self, access_or_prefix_list, rank=None):
"""
Add a next hop condition. Next hop elements must be
of type IPAccessList or IPPrefixList.
:raises ElementNotFound: If element specified does not exist
"""
self.conditions.append(dict(
next_hop_ref=access_or_prefix_list.href,
type='next_hop',
rank=rank))
[docs] def add_peer_address(self, ext_bgp_peer_or_fw, rank=None):
"""
Add a peer address. Peer address types are ExternalBGPPeer
or Engine.
:raises ElementNotFound: If element specified does not exist
"""
if ext_bgp_peer_or_fw.typeof == 'external_bgp_peer':
ref = 'external_bgp_peer_address_ref'
else: # engine
ref = 'fwcluster_peer_address_ref'
self.conditions.append(
{ref: ext_bgp_peer_or_fw.href,
'rank': rank,
'type': 'peer_address'})
[docs] def remove_condition(self, rank):
"""
Remove a condition element using it's rank. You can find the
rank and element for a match condition by iterating the match
condition::
>>> rule1 = rm.route_map_rules.get(0)
>>> for condition in rule1.match_condition:
... condition
...
Condition(rank=1, element=ExternalBGPPeer(name=bgppeer))
Condition(rank=2, element=IPAccessList(name=myacl))
Condition(rank=3, element=Metric(value=20))
Then delete by rank. Call update on the rule after making the
modification.
:param int rank: rank of the condition to remove
:raises UpdateElementFailed: failed to update rule
:return: None
"""
self.conditions[:] = [r for r in self.conditions
if r.get('rank') != rank]
def __repr__(self):
return 'MatchCondition(entries={})'.format(
len(self.conditions))
[docs]class RouteMapRule(RuleCommon, SubElement):
"""
A route map rule represents the rules to be processed for a route
map assigned to a specific BGP network. A match condition can be
provided which encapsulates using dynamic routing element types
such as IPAccessList, IPPrefixList, etc.
"""
typeof = 'route_map_rule'
[docs] def create(self, name, action='permit', goto=None, finish=False,
call=None, comment=None, add_pos=None, after=None,
before=None, **match_condition):
"""
Create a route map rule. You can provide match conditions
by using keyword arguments specifying the required types.
You can also create the route map rule and add match conditions
after.
:param str name: name for this rule
:param str action: permit or deny
:param str goto: specify a rule section to goto after if there
is a match condition. This will override the finish parameter
:param bool finish: finish stops the processing after a match condition.
If finish is False, processing will continue to the next rule.
:param RouteMap call: call another route map after matching.
:param str comment: optional comment for the rule
:param int add_pos: position to insert the rule, starting with position 1. If
the position value is greater than the number of rules, the rule is inserted at
the bottom. If add_pos is not provided, rule is inserted in position 1. Mutually
exclusive with ``after`` and ``before`` params.
:param str after: Rule tag to add this rule after. Mutually exclusive with ``add_pos``
and ``before`` params.
:param str before: Rule tag to add this rule before. Mutually exclusive with ``add_pos``
and ``after`` params.
:param match_condition: keyword values identifying initial
values for the match condition. Valid keyword arguments
are 'access_list', 'next_hop', 'metric' and 'peer_address'.
You can also optionally pass the keyword 'match_condition'
with an instance of MatchCondition.
:raises CreateRuleFailed: failure to insert rule with reason
:raises ElementNotFound: if references elements in a match condition
this can be raised when the element specified is not found.
.. seealso:: :class:`~MatchCondition` for valid elements and
expected values for each type.
"""
json = {'name': name,
'action': action,
'finish': finish,
'goto': goto.href if goto else None,
'call_route_map_ref': None if not call else call.href,
'comment': comment}
if not match_condition:
json.update(match_condition=[])
else:
if 'match_condition' in match_condition:
conditions = match_condition.pop('match_condition')
else:
conditions = MatchCondition()
if 'peer_address' in match_condition:
conditions.add_peer_address(
match_condition.pop('peer_address'))
if 'next_hop' in match_condition:
conditions.add_next_hop(
match_condition.pop('next_hop'))
if 'metric' in match_condition:
conditions.add_metric(
match_condition.pop('metric'))
if 'access_list' in match_condition:
conditions.add_access_list(
match_condition.pop('access_list'))
json.update(match_condition=conditions.conditions)
params = None
href = self.href
if add_pos is not None:
href = self.add_at_position(add_pos)
elif before or after:
params = self.add_before_after(before, after)
return ElementCreator(
self.__class__,
exception=CreateRuleFailed,
href=href,
params=params,
json=json)
@property
def comment(self):
"""
Get and set the comment for this rule.
:param str value: string comment
:rtype: str
"""
return self.data.get('comment')
@comment.setter
def comment(self, value):
self.data['comment'] = value
@property
def goto(self):
"""
If the rule is set to goto a rule section, return
the rule section, otherwise it will return None.
Check the value of finish to determine if the rule
is set to finish on match.
:return: RouteMap or None
"""
return Element.from_href(
self.data.get('goto'))
[docs] def goto_rule_section(self, rule_section):
"""
Set this rule to goto a specific rule section after
match. If goto is None, then check value of finish.
:param RouteMapRule rule_section: pass rule section
:return: None
"""
self.data.update(goto=rule_section.href)
@property
def action(self):
"""
Action for this route map rule. Valid actions
are 'permit' and 'deny'.
:rtype: str
"""
return self.data.get('action')
@property
def finish(self):
"""
Is rule action goto set to finish on this rule match.
If finish is False, then the policy will proceed to
the next rule.
:rtype: bool
"""
return self.data.get('finish')
@property
def is_disabled(self):
"""
Is the rule disabled
:rtype: bool
"""
return self.data.get('is_disabled')
@property
def is_rule_section(self):
return 'match_condition' not in self.data
[docs] def call_route_map(self, route_map):
"""
Call another route map after match of this rule.
Call update on the rule to save after modification.
:param RouteMap route_map: Pass the route map element
:raises ElementNotFound: invalid RouteMap reference passed
:return: None
"""
self.data.update(call_route_map_ref=route_map.href)
@property
def match_condition(self):
"""
Return the match condition for this rule. This
can then be modified in place. Be sure to call
update on the rule to save.
:rtype: MatchCondition
"""
return MatchCondition(self)
[docs]class RouteMap(Element):
"""
Use Route Map elements in more complex networks to control or manipulate
routes. You can use Access List elements as a Matching Condition in a
Route Map rule.
RouteMaps are rule lists similar to normal policies and can be iterated::
>>> from smc.routing.route_map import RouteMap
>>> rm = RouteMap('myroutemap')
>>> for rule in rm.route_map_rules:
... rule
...
RouteMapRule(name=Rule @115.13)
RouteMapRule(name=Rule @117.0)
"""
typeof = 'route_map'
[docs] @classmethod
def create(cls, name, comment=None):
"""
Create a new route map. After creation, you can add a rule
and subsequent match conditions.
:param str name: name of route map
:param str comment: optional comment
:raises CreateElementFailed: failed creating route map
:rtype: RouteMap
"""
json = {'name': name,
'comment': comment}
return ElementCreator(cls, json)
@property
def route_map_rules(self):
"""
IPv6NAT Rule entry point
:rtype: rule_collection(IPv6NATRule)
"""
return rule_collection(
self.get_relation('route_map_rules'), RouteMapRule)
[docs] def search_rule(self, search):
"""
Search the RouteMap policy using a search string
:param str search: search string for a contains match against
the rule name and comments field
:rtype: list(RouteMapRule)
"""
return [RouteMapRule(**rule) for rule in self.make_request(
resource='search_rule', params={'filter': search})]