|
| 1 | +import traceback |
| 2 | +from .log import log_crit, log_err, log_debug |
| 3 | +from .manager import Manager |
| 4 | +from .template import TemplateFabric |
| 5 | +import socket |
| 6 | + |
| 7 | +class StaticRouteMgr(Manager): |
| 8 | + """ This class updates static routes when STATIC_ROUTE table is updated """ |
| 9 | + def __init__(self, common_objs, db, table): |
| 10 | + """ |
| 11 | + Initialize the object |
| 12 | + :param common_objs: common object dictionary |
| 13 | + :param db: name of the db |
| 14 | + :param table: name of the table in the db |
| 15 | + """ |
| 16 | + super(StaticRouteMgr, self).__init__( |
| 17 | + common_objs, |
| 18 | + [], |
| 19 | + db, |
| 20 | + table, |
| 21 | + ) |
| 22 | + |
| 23 | + self.static_routes = {} |
| 24 | + |
| 25 | + OP_DELETE = 'DELETE' |
| 26 | + OP_ADD = 'ADD' |
| 27 | + |
| 28 | + def set_handler(self, key, data): |
| 29 | + vrf, ip_prefix = self.split_key(key) |
| 30 | + is_ipv6 = TemplateFabric.is_ipv6(ip_prefix) |
| 31 | + |
| 32 | + arg_list = lambda v: v.split(',') if len(v.strip()) != 0 else None |
| 33 | + bkh_list = arg_list(data['blackhole']) if 'blackhole' in data else None |
| 34 | + nh_list = arg_list(data['nexthop']) if 'nexthop' in data else None |
| 35 | + intf_list = arg_list(data['ifname']) if 'ifname' in data else None |
| 36 | + dist_list = arg_list(data['distance']) if 'distance' in data else None |
| 37 | + nh_vrf_list = arg_list(data['nexthop-vrf']) if 'nexthop-vrf' in data else None |
| 38 | + |
| 39 | + try: |
| 40 | + ip_nh_set = IpNextHopSet(is_ipv6, bkh_list, nh_list, intf_list, dist_list, nh_vrf_list) |
| 41 | + cur_nh_set = self.static_routes.get(vrf, {}).get(ip_prefix, IpNextHopSet(is_ipv6)) |
| 42 | + cmd_list = self.static_route_commands(ip_nh_set, cur_nh_set, ip_prefix, vrf) |
| 43 | + except Exception as exc: |
| 44 | + log_crit("Got an exception %s: Traceback: %s" % (str(exc), traceback.format_exc())) |
| 45 | + return False |
| 46 | + |
| 47 | + if cmd_list: |
| 48 | + self.cfg_mgr.push_list(cmd_list) |
| 49 | + log_debug("Static route {} is scheduled for updates".format(key)) |
| 50 | + else: |
| 51 | + log_debug("Nothing to update for static route {}".format(key)) |
| 52 | + |
| 53 | + self.static_routes.setdefault(vrf, {})[ip_prefix] = ip_nh_set |
| 54 | + |
| 55 | + return True |
| 56 | + |
| 57 | + |
| 58 | + def del_handler(self, key): |
| 59 | + vrf, ip_prefix = self.split_key(key) |
| 60 | + is_ipv6 = TemplateFabric.is_ipv6(ip_prefix) |
| 61 | + |
| 62 | + ip_nh_set = IpNextHopSet(is_ipv6) |
| 63 | + cur_nh_set = self.static_routes.get(vrf, {}).get(ip_prefix, IpNextHopSet(is_ipv6)) |
| 64 | + cmd_list = self.static_route_commands(ip_nh_set, cur_nh_set, ip_prefix, vrf) |
| 65 | + |
| 66 | + if cmd_list: |
| 67 | + self.cfg_mgr.push_list(cmd_list) |
| 68 | + log_debug("Static route {} is scheduled for updates".format(key)) |
| 69 | + else: |
| 70 | + log_debug("Nothing to update for static route {}".format(key)) |
| 71 | + |
| 72 | + self.static_routes.setdefault(vrf, {}).pop(ip_prefix, None) |
| 73 | + |
| 74 | + @staticmethod |
| 75 | + def split_key(key): |
| 76 | + """ |
| 77 | + Split key into vrf name and prefix. |
| 78 | + :param key: key to split |
| 79 | + :return: vrf name extracted from the key, ip prefix extracted from the key |
| 80 | + """ |
| 81 | + if '|' not in key: |
| 82 | + return 'default', key |
| 83 | + else: |
| 84 | + return tuple(key.split('|', 1)) |
| 85 | + |
| 86 | + def static_route_commands(self, ip_nh_set, cur_nh_set, ip_prefix, vrf): |
| 87 | + diff_set = ip_nh_set.symmetric_difference(cur_nh_set) |
| 88 | + |
| 89 | + op_cmd_list = {} |
| 90 | + for ip_nh in diff_set: |
| 91 | + if ip_nh in cur_nh_set: |
| 92 | + op = self.OP_DELETE |
| 93 | + else: |
| 94 | + op = self.OP_ADD |
| 95 | + |
| 96 | + op_cmds = op_cmd_list.setdefault(op, []) |
| 97 | + op_cmds.append(self.generate_command(op, ip_nh, ip_prefix, vrf)) |
| 98 | + |
| 99 | + cmd_list = op_cmd_list.get(self.OP_DELETE, []) |
| 100 | + cmd_list += op_cmd_list.get(self.OP_ADD, []) |
| 101 | + |
| 102 | + return cmd_list |
| 103 | + |
| 104 | + def generate_command(self, op, ip_nh, ip_prefix, vrf): |
| 105 | + return '{}{} route {}{}{}'.format( |
| 106 | + 'no ' if op == self.OP_DELETE else '', |
| 107 | + 'ipv6' if ip_nh.af == socket.AF_INET6 else 'ip', |
| 108 | + ip_prefix, |
| 109 | + ip_nh, |
| 110 | + ' vrf {}'.format(vrf) if vrf != 'default' else '' |
| 111 | + ) |
| 112 | + |
| 113 | +class IpNextHop: |
| 114 | + def __init__(self, af_id, blackhole, dst_ip, if_name, dist, vrf): |
| 115 | + zero_ip = lambda af: '0.0.0.0' if af == socket.AF_INET else '::' |
| 116 | + self.af = af_id |
| 117 | + self.blackhole = 'false' if blackhole is None or blackhole == '' else blackhole |
| 118 | + self.distance = 0 if dist is None else int(dist) |
| 119 | + if self.blackhole == 'true': |
| 120 | + dst_ip = if_name = vrf = None |
| 121 | + self.ip = zero_ip(af_id) if dst_ip is None or dst_ip == '' else dst_ip |
| 122 | + self.interface = '' if if_name is None else if_name |
| 123 | + self.nh_vrf = '' if vrf is None else vrf |
| 124 | + if self.blackhole != 'true' and self.is_zero_ip() and len(self.interface.strip()) == 0: |
| 125 | + log_err('Mandatory attribute not found for nexthop') |
| 126 | + raise ValueError |
| 127 | + def __eq__(self, other): |
| 128 | + return (self.af == other.af and self.blackhole == other.blackhole and |
| 129 | + self.ip == other.ip and self.interface == other.interface and |
| 130 | + self.distance == other.distance and self.nh_vrf == other.nh_vrf) |
| 131 | + def __ne__(self, other): |
| 132 | + return (self.af != other.af or self.blackhole != other.blackhole or |
| 133 | + self.ip != other.ip or self.interface != other.interface or |
| 134 | + self.distance != other.distance or self.nh_vrf != other.nh_vrf) |
| 135 | + def __hash__(self): |
| 136 | + return hash((self.af, self.blackhole, self.ip, self.interface, self.distance, self.nh_vrf)) |
| 137 | + def is_zero_ip(self): |
| 138 | + return sum([x for x in socket.inet_pton(self.af, self.ip)]) == 0 |
| 139 | + def __format__(self, format): |
| 140 | + ret_val = '' |
| 141 | + if self.blackhole == 'true': |
| 142 | + ret_val += ' blackhole' |
| 143 | + if not (self.ip is None or self.is_zero_ip()): |
| 144 | + ret_val += ' %s' % self.ip |
| 145 | + if not (self.interface is None or self.interface == ''): |
| 146 | + ret_val += ' %s' % self.interface |
| 147 | + if not (self.distance is None or self.distance == 0): |
| 148 | + ret_val += ' %d' % self.distance |
| 149 | + if not (self.nh_vrf is None or self.nh_vrf == ''): |
| 150 | + ret_val += ' nexthop-vrf %s' % self.nh_vrf |
| 151 | + return ret_val |
| 152 | + |
| 153 | +class IpNextHopSet(set): |
| 154 | + def __init__(self, is_ipv6, bkh_list = None, ip_list = None, intf_list = None, dist_list = None, vrf_list = None): |
| 155 | + super(IpNextHopSet, self).__init__() |
| 156 | + af = socket.AF_INET6 if is_ipv6 else socket.AF_INET |
| 157 | + if bkh_list is None and ip_list is None and intf_list is None: |
| 158 | + # empty set, for delete case |
| 159 | + return |
| 160 | + nums = {len(x) for x in [bkh_list, ip_list, intf_list, dist_list, vrf_list] if x is not None} |
| 161 | + if len(nums) != 1: |
| 162 | + log_err("Lists of next-hop attribute have different sizes: %s" % nums) |
| 163 | + for x in [bkh_list, ip_list, intf_list, dist_list, vrf_list]: |
| 164 | + log_debug("List: %s" % x) |
| 165 | + raise ValueError |
| 166 | + nh_cnt = nums.pop() |
| 167 | + item = lambda lst, i: lst[i] if lst is not None else None |
| 168 | + for idx in range(nh_cnt): |
| 169 | + try: |
| 170 | + self.add(IpNextHop(af, item(bkh_list, idx), item(ip_list, idx), item(intf_list, idx), |
| 171 | + item(dist_list, idx), item(vrf_list, idx), )) |
| 172 | + except ValueError: |
| 173 | + continue |
0 commit comments