Source code for smc.routing.route_map

#  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  not use this file except in compliance with the License. You may obtain
#  a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  License for the specific language governing permissions and limitations
#  under the License.
"""
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}) ]