Skip to content

Commit

Permalink
IPv6 support for router routes
Browse files Browse the repository at this point in the history
We can now configure IPv6 routes on our hardware routers, including
default gateways.

As we now return an empty dict if there are no routes we should no
longer see any route diffs when the router has no default route
configured.
  • Loading branch information
sebageek committed Jun 12, 2024
1 parent 950e4d6 commit 1176ca5
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 101 deletions.
193 changes: 139 additions & 54 deletions asr1k_neutron_l3/models/netconf_yang/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.

from collections import OrderedDict

from oslo_log import log as logging

from asr1k_neutron_l3.models.netconf_yang.ny_base import NyBase, execute_on_pair, NC_OPERATION
Expand All @@ -25,21 +23,23 @@


class RouteConstants(object):
DEFINITION = "vrf"

VRF = "vrf"
IP = "ip"
ROUTE = "route"
NAME = "name"

FOWARDING = "ip-route-interface-forwarding-list"
IPV6 = "ipv6"
IPV6_ROUTE_LIST = "ipv6-route-list"
IPV6_FWD_LIST = "ipv6-fwd-list"

FORWARDING = "ip-route-interface-forwarding-list"
FWD_LIST = "fwd-list"
FWD = "fwd"
PREFIX = "prefix"
MASK = "mask"


class VrfRoute(NyBase):
class VrfRouteBase(NyBase):
ID_FILTER = """
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native"
xmlns:ios-eth="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
Expand Down Expand Up @@ -68,7 +68,10 @@ class VrfRoute(NyBase):
VRF_XPATH_FILTER = "/native/ip/route/vrf[name='{vrf}']"

LIST_KEY = RouteConstants.ROUTE
ITEM_KEY = RouteConstants.DEFINITION
ITEM_KEY = RouteConstants.VRF

IP_ROUTE_CLASS = None
IP_KEY = None

@classmethod
def get_for_vrf(cls, context, vrf=None):
Expand All @@ -78,7 +81,7 @@ def get_for_vrf(cls, context, vrf=None):
def __parameters__(cls):
return [
{'key': 'name', 'id': True},
{'key': 'routes', 'yang-key': RouteConstants.FOWARDING, 'type': [IpRoute], 'default': []}
{'key': 'routes', 'yang-key': cls.IP_ROUTE_CLASS.LIST_KEY, 'type': [cls.IP_ROUTE_CLASS], 'default': []}
]

@classmethod
Expand All @@ -87,23 +90,21 @@ def get_primary_filter(cls, **kwargs):

@classmethod
def remove_wrapper(cls, dict, context):
dict = super(VrfRoute, cls)._remove_base_wrapper(dict, context)
dict = cls._remove_base_wrapper(dict, context)
if dict is None:
return

dict = dict.get(RouteConstants.IP, dict)
dict = dict.get(cls.IP_KEY, dict)
dict = dict.get(cls.LIST_KEY, dict)

return dict

def _wrapper_preamble(self, dict, context):
result = {}
result[self.LIST_KEY] = dict
result = {RouteConstants.IP: result}
return result

def __init__(self, **kwargs):
super(VrfRoute, self).__init__(**kwargs)
return {
self.IP_KEY: {
self.LIST_KEY: dict,
}
}

@property
def neutron_router_id(self):
Expand All @@ -113,37 +114,41 @@ def neutron_router_id(self):
@execute_on_pair()
def update(self, context):
if len(self.routes) > 0:
return super(VrfRoute, self)._update(context=context, method=NC_OPERATION.PUT)
return self._update(context=context, method=NC_OPERATION.PUT)
else:
return self._delete(context=context)

def to_single_dict(self, context):
raise NotImplementedError

def to_dict(self, context):
vrf_route = OrderedDict()
vrf_route[RouteConstants.NAME] = self.name
vrf_route[RouteConstants.FOWARDING] = []
if not self.routes:
# no routes --> empty container
return {}

if isinstance(self.routes, list):
vrf_routes = []
if self.routes:
for route in sorted(self.routes, key=lambda route: route.prefix):
vrf_route[RouteConstants.FOWARDING].append(route.to_single_dict(context))

result = OrderedDict()
result[RouteConstants.DEFINITION] = vrf_route
vrf_routes.append(route.to_single_dict(context))

return dict(result)
return {
RouteConstants.VRF: {
RouteConstants.NAME: self.name,
self.IP_ROUTE_CLASS.LIST_KEY: vrf_routes,
}
}

def to_delete_dict(self, context):
vrf_route = OrderedDict()
vrf_route[RouteConstants.NAME] = self.name
vrf_route[RouteConstants.FOWARDING] = []

result = OrderedDict()
result[RouteConstants.DEFINITION] = vrf_route

return dict(result)
return {
RouteConstants.VRF: {
RouteConstants.NAME: self.name,
self.IP_ROUTE_CLASS.LIST_KEY: [],
}
}


class IpRoute(NyBase):
LIST_KEY = RouteConstants.FOWARDING
class IpRouteV4(NyBase):
LIST_KEY = RouteConstants.FORWARDING

@classmethod
def __parameters__(cls):
Expand All @@ -153,27 +158,107 @@ def __parameters__(cls):
{'key': 'fwd_list', 'yang-key': RouteConstants.FWD_LIST, 'default': []}
]

def __init__(self, **kwargs):
super(IpRoute, self).__init__(**kwargs)
def to_single_dict(self, context):
return {
RouteConstants.PREFIX: self.prefix,
RouteConstants.MASK: self.mask,
RouteConstants.FWD_LIST: self.fwd_list,
}

@property
def vrf(self):
if self.parent:
self.parent.get(RouteConstants.VRF)
def to_dict(self, context):
return {self.LIST_KEY: self.to_single_dict(context)}

def __id_function__(self, id_field, **kwargs):
self.id = "{},{}".format(self.prefix, self.mask)

def to_single_dict(self, context):
ip_route = OrderedDict()
ip_route[RouteConstants.PREFIX] = self.prefix
ip_route[RouteConstants.MASK] = self.mask
ip_route[RouteConstants.FWD_LIST] = self.fwd_list
class IpRouteV6(NyBase):
LIST_KEY = RouteConstants.IPV6_ROUTE_LIST

@classmethod
def __parameters__(cls):
return [
{'key': 'prefix', 'mandatory': True},
{'key': 'fwd_list', 'yang-key': RouteConstants.IPV6_FWD_LIST, 'default': []}
]

return ip_route
def to_single_dict(self, context):
return {
RouteConstants.PREFIX: self.prefix,
RouteConstants.IPV6_FWD_LIST: self.fwd_list,
}

def to_dict(self, context):
result = OrderedDict()
result[RouteConstants.FOWARDING] = self.to_single_dict(context)
return {self.LIST_KEY: self.to_single_dict(context)}


return dict(result)
class VrfRouteV4(VrfRouteBase):
ID_FILTER = """
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native"
xmlns:ios-eth="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
<ip>
<route>
<vrf>
<name>{id}</name>
</vrf>
</route>
</ip>
</native>
"""

GET_ALL_STUB = """
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<ip>
<route>
<vrf>
<name/>
</vrf>
</route>
</ip>
</native>
"""

VRF_XPATH_FILTER = "/native/ip/route/vrf[name='{vrf}']"
IP_ROUTE_CLASS = IpRouteV4
IP_KEY = RouteConstants.IP

def to_single_dict(self, context):
return {
RouteConstants.PREFIX: self.prefix,
RouteConstants.MASK: self.mask,
RouteConstants.FWD_LIST: self.fwd_list
}


class VrfRouteV6(VrfRouteBase):
ID_FILTER = """
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native"
xmlns:ios-eth="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
<ipv6>
<route>
<vrf>
<name>{id}</name>
</vrf>
</route>
</ipv6>
</native>
"""

GET_ALL_STUB = """
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<ipv6>
<route>
<vrf>
<name/>
</vrf>
</route>
</ipv6>
</native>
"""

VRF_XPATH_FILTER = "/native/ipv6/route/vrf[name='{vrf}']"
IP_ROUTE_CLASS = IpRouteV6
IP_KEY = RouteConstants.IPV6

def to_single_dict(self, context):
return {
RouteConstants.PREFIX: self.prefix,
RouteConstants.IPV6_FWD_LIST: self.fwd_list
}
18 changes: 10 additions & 8 deletions asr1k_neutron_l3/models/netconf_yang/vrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from asr1k_neutron_l3.models.netconf_yang.nat import InterfaceDynamicNat
from asr1k_neutron_l3.models.netconf_yang.ny_base import NyBase, Requeable, NC_OPERATION, execute_on_pair, \
retry_on_failure, YANG_TYPE
from asr1k_neutron_l3.models.netconf_yang.route import VrfRoute
from asr1k_neutron_l3.models.netconf_yang.route import VrfRouteV4, VrfRouteV6

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -241,16 +241,18 @@ def postflight(self, context, method):

routes = []
try:
routes = VrfRoute.get_for_vrf(context=context, vrf=self.id)
if len(routes) == 0:
LOG.info("No routes to clean")
routes_v4 = VrfRouteV4.get_for_vrf(context=context, vrf=self.id)
routes_v6 = VrfRouteV6.get_for_vrf(context=context, vrf=self.id)
routes = routes_v4 + routes_v6
if routes:
LOG.info("No routes to clean for %s", self.name)

for route in routes:
LOG.info("Deleting hanging route {} in vrf {} postflight.".format(route.name, self.name))
LOG.info("Deleting hanging route %s in vrf %s postflight.", route.name, self.name)
route._delete(context=context)
LOG.info("Deleted hanging route {} in vrf {} postflight.".format(route.name, self.name))
except BaseException as e:
LOG.error("Failed to delete {} routes in VRF {} postlight : {}".format(len(routes), self.id, e))
LOG.debug("Deleted hanging route %s in vrf %s postflight.", route.name, self.name)
except Exception as e:
LOG.error("Failed to delete %s routes in VRF %s postlight: %s", len(routes), self.id, e)

LOG.debug("Processing Interfaces")
vbis = []
Expand Down
50 changes: 35 additions & 15 deletions asr1k_neutron_l3/models/neutron/l3/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,61 @@
from asr1k_neutron_l3.models.neutron.l3 import base


class RouteCollection(base.Base):
class BaseRouteCollection(base.Base):
def __init__(self, router_id):
super(RouteCollection, self).__init__()
super().__init__()
self.router_id = utils.uuid_to_vrf_id(router_id)
self.routes = []

@property
def _route_collection_model(self):
raise NotImplementedError

@property
def _rest_definition(self):
rest_routes = []
for route in self.routes:
rest_routes.append(route._rest_definition)
return l3_route.VrfRoute(name=self.router_id, routes=rest_routes)
rest_routes = [route._rest_definition for route in self.routes]
return self._route_collection_model(name=self.router_id, routes=rest_routes)

def append(self, route):
self.routes.append(route)

def get(self):
return l3_route.VrfRoute.get(self.router_id)

def delete(self):
rc = l3_route.VrfRoute(name=self.router_id)

return rc.delete()
class RouteCollectionV4(BaseRouteCollection):
@property
def _route_collection_model(self):
return l3_route.VrfRouteV4


class Route(base.Base):
class RouteV4(base.Base):
def __init__(self, router_id, destination, mask, nexthop):
self.router_id = router_id
self.destination = destination
self.mask = mask
self.nexthop = nexthop

self._rest_definition = l3_route.IpRoute(vrf=self.router_id, prefix=self.destination, mask=self.mask,
fwd_list={"fwd": self.nexthop})
self._rest_definition = l3_route.IpRouteV4(vrf=self.router_id, prefix=self.destination, mask=self.mask,
fwd_list={"fwd": self.nexthop})

@property
def cidr(self):
return utils.to_cidr(self.destination, self.mask)


class RouteCollectionV6(BaseRouteCollection):
@property
def _route_collection_model(self):
return l3_route.VrfRouteV6


class RouteV6(base.Base):
def __init__(self, router_id, destination, nexthop):
self.router_id = router_id
self.destination = destination
self.nexthop = nexthop

self._rest_definition = l3_route.IpRouteV6(vrf=self.router_id, prefix=self.destination,
fwd_list={"fwd": self.nexthop})

@property
def cidr(self):
return self.destination
Loading

0 comments on commit 1176ca5

Please sign in to comment.