tests: handle removed interface
[vpp.git] / test / vpp_ip_route.py
1 """
2   IP Routes
3
4   object abstractions for representing IP routes in VPP
5 """
6
7 from vpp_object import VppObject
8 from vpp_ip import DpoProto, INVALID_INDEX, VppIpAddressUnion, \
9     VppIpMPrefix
10 from ipaddress import ip_network, ip_address, IPv4Network, IPv6Network
11 from vpp_papi_exceptions import UnexpectedApiReturnValueError
12
13 # from vnet/vnet/mpls/mpls_types.h
14 MPLS_IETF_MAX_LABEL = 0xfffff
15 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
16
17 try:
18     text_type = unicode
19 except NameError:
20     text_type = str
21
22
23 class FibPathProto:
24     FIB_PATH_NH_PROTO_IP4 = 0
25     FIB_PATH_NH_PROTO_IP6 = 1
26     FIB_PATH_NH_PROTO_MPLS = 2
27     FIB_PATH_NH_PROTO_ETHERNET = 3
28     FIB_PATH_NH_PROTO_BIER = 4
29     FIB_PATH_NH_PROTO_NSH = 5
30
31
32 class FibPathType:
33     FIB_PATH_TYPE_NORMAL = 0
34     FIB_PATH_TYPE_LOCAL = 1
35     FIB_PATH_TYPE_DROP = 2
36     FIB_PATH_TYPE_UDP_ENCAP = 3
37     FIB_PATH_TYPE_BIER_IMP = 4
38     FIB_PATH_TYPE_ICMP_UNREACH = 5
39     FIB_PATH_TYPE_ICMP_PROHIBIT = 6
40     FIB_PATH_TYPE_SOURCE_LOOKUP = 7
41     FIB_PATH_TYPE_DVR = 8
42     FIB_PATH_TYPE_INTERFACE_RX = 9
43     FIB_PATH_TYPE_CLASSIFY = 10
44
45
46 class FibPathFlags:
47     FIB_PATH_FLAG_NONE = 0
48     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
49     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
50     FIB_PATH_FLAG_POP_PW_CW = 4
51
52
53 class MplsLspMode:
54     PIPE = 0
55     UNIFORM = 1
56
57
58 def mk_network(addr, len):
59     if ip_address(text_type(addr)).version == 4:
60         return IPv4Network("%s/%d" % (addr, len), strict=False)
61     else:
62         return IPv6Network("%s/%d" % (addr, len), strict=False)
63
64
65 def ip_to_dpo_proto(addr):
66     if addr.version == 6:
67         return DpoProto.DPO_PROTO_IP6
68     else:
69         return DpoProto.DPO_PROTO_IP4
70
71
72 def address_proto(ip_addr):
73     if ip_addr.ip_addr.version == 4:
74         return FibPathProto.FIB_PATH_NH_PROTO_IP4
75     else:
76         return FibPathProto.FIB_PATH_NH_PROTO_IP6
77
78
79 def find_route(test, addr, len, table_id=0, sw_if_index=None):
80     prefix = mk_network(addr, len)
81
82     if 4 == prefix.version:
83         routes = test.vapi.ip_route_dump(table_id, False)
84     else:
85         routes = test.vapi.ip_route_dump(table_id, True)
86
87     for e in routes:
88         if table_id == e.route.table_id \
89            and str(e.route.prefix) == str(prefix):
90             if not sw_if_index:
91                 return True
92             else:
93                 # should be only one path if the user is looking
94                 # for the interface the route is reachable through
95                 if e.route.n_paths != 1:
96                     return False
97                 else:
98                     return (e.route.paths[0].sw_if_index == sw_if_index)
99
100     return False
101
102
103 def find_route_in_dump(dump, route, table):
104     for r in dump:
105         if table.table_id == r.route.table_id \
106            and route.prefix == r.route.prefix:
107             if len(route.paths) == r.route.n_paths:
108                 return True
109     return False
110
111
112 def find_mroute_in_dump(dump, route, table):
113     for r in dump:
114         if table.table_id == r.route.table_id \
115            and route.prefix == r.route.prefix:
116             return True
117     return False
118
119
120 def find_mroute(test, grp_addr, src_addr, grp_addr_len,
121                 table_id=0):
122     ip_mprefix = VppIpMPrefix(text_type(src_addr),
123                               text_type(grp_addr),
124                               grp_addr_len)
125
126     if 4 == ip_mprefix.version:
127         routes = test.vapi.ip_mroute_dump(table_id, False)
128     else:
129         routes = test.vapi.ip_mroute_dump(table_id, True)
130
131     for e in routes:
132         if table_id == e.route.table_id and ip_mprefix == e.route.prefix:
133             return True
134     return False
135
136
137 def find_mpls_route(test, table_id, label, eos_bit, paths=None):
138     dump = test.vapi.mpls_route_dump(table_id)
139     for e in dump:
140         if label == e.mr_route.mr_label \
141            and eos_bit == e.mr_route.mr_eos \
142            and table_id == e.mr_route.mr_table_id:
143             if not paths:
144                 return True
145             else:
146                 if (len(paths) != len(e.mr_route.mr_paths)):
147                     return False
148                 for i in range(len(paths)):
149                     if (paths[i] != e.mr_route.mr_paths[i]):
150                         return False
151                 return True
152     return False
153
154
155 def fib_interface_ip_prefix(test, addr, len, sw_if_index):
156     # can't use python net here since we need the host bits in the prefix
157     prefix = "%s/%d" % (addr, len)
158     addrs = test.vapi.ip_address_dump(
159         sw_if_index,
160         is_ipv6=(6 == ip_address(addr).version))
161
162     for a in addrs:
163         if a.sw_if_index == sw_if_index and \
164            str(a.prefix) == prefix:
165             return True
166     return False
167
168
169 class VppIpTable(VppObject):
170
171     def __init__(self,
172                  test,
173                  table_id,
174                  is_ip6=0,
175                  register=True):
176         self._test = test
177         self.table_id = table_id
178         self.is_ip6 = is_ip6
179         self.register = register
180
181     def add_vpp_config(self):
182         self._test.vapi.ip_table_add_del(is_add=1,
183                                          table={'is_ip6': self.is_ip6,
184                                                 'table_id': self.table_id})
185         if self.register:
186             self._test.registry.register(self, self._test.logger)
187         return self
188
189     def remove_vpp_config(self):
190         self._test.vapi.ip_table_add_del(is_add=0,
191                                          table={'is_ip6': self.is_ip6,
192                                                 'table_id': self.table_id})
193
194     def replace_begin(self):
195         self._test.vapi.ip_table_replace_begin(
196             table={'is_ip6': self.is_ip6,
197                    'table_id': self.table_id})
198
199     def replace_end(self):
200         self._test.vapi.ip_table_replace_end(
201             table={'is_ip6': self.is_ip6,
202                    'table_id': self.table_id})
203
204     def flush(self):
205         self._test.vapi.ip_table_flush(table={'is_ip6': self.is_ip6,
206                                               'table_id': self.table_id})
207
208     def dump(self):
209         return self._test.vapi.ip_route_dump(self.table_id, self.is_ip6)
210
211     def mdump(self):
212         return self._test.vapi.ip_mroute_dump(self.table_id, self.is_ip6)
213
214     def query_vpp_config(self):
215         if self.table_id == 0:
216             # the default table always exists
217             return False
218         # find the default route
219         return find_route(self._test,
220                           "::" if self.is_ip6 else "0.0.0.0",
221                           0,
222                           self.table_id)
223
224     def object_id(self):
225         return ("table-%s-%d" %
226                 ("v6" if self.is_ip6 == 1 else "v4",
227                  self.table_id))
228
229
230 class VppIpInterfaceAddress(VppObject):
231
232     def __init__(self, test, intf, addr, len, bind=None):
233         self._test = test
234         self.intf = intf
235         self.addr = addr
236         self.len = len
237         self.prefix = "%s/%d" % (addr, len)
238         self.host_len = ip_network(self.prefix, strict=False).max_prefixlen
239         self.table_id = 0
240         if bind:
241             self.table_id = bind.table.table_id
242
243     def add_vpp_config(self):
244         self._test.vapi.sw_interface_add_del_address(
245             sw_if_index=self.intf.sw_if_index, prefix=self.prefix,
246             is_add=1)
247         self._test.registry.register(self, self._test.logger)
248         return self
249
250     def remove_vpp_config(self):
251         self._test.vapi.sw_interface_add_del_address(
252             sw_if_index=self.intf.sw_if_index, prefix=self.prefix,
253             is_add=0)
254
255     def query_vpp_config(self):
256         # search for the IP address mapping and the two expected
257         # FIB entries
258         v = ip_address(self.addr).version
259
260         if ((v == 4 and self.len < 31) or (v == 6 and self.len < 127)):
261             return (fib_interface_ip_prefix(self._test,
262                                             self.addr,
263                                             self.len,
264                                             self.intf.sw_if_index) &
265                     find_route(self._test,
266                                self.addr,
267                                self.len,
268                                table_id=self.table_id,
269                                sw_if_index=self.intf.sw_if_index) &
270                     find_route(self._test,
271                                self.addr,
272                                self.host_len,
273                                table_id=self.table_id,
274                                sw_if_index=self.intf.sw_if_index))
275         else:
276             return (fib_interface_ip_prefix(self._test,
277                                             self.addr,
278                                             self.len,
279                                             self.intf.sw_if_index) &
280                     find_route(self._test,
281                                self.addr,
282                                self.host_len,
283                                table_id=self.table_id,
284                                sw_if_index=self.intf.sw_if_index))
285
286     def object_id(self):
287         return "interface-ip-%s-%d-%s" % (self.intf,
288                                           self.table_id,
289                                           self.prefix)
290
291
292 class VppIp6LinkLocalAddress(VppObject):
293
294     def __init__(self, test, intf, addr):
295         self._test = test
296         self.intf = intf
297         self.addr = addr
298
299     def add_vpp_config(self):
300         self._test.vapi.sw_interface_ip6_set_link_local_address(
301             sw_if_index=self.intf.sw_if_index, ip=self.addr)
302         self._test.registry.register(self, self._test.logger)
303         return self
304
305     def remove_vpp_config(self):
306         # link locals can't be removed, only changed
307         pass
308
309     def query_vpp_config(self):
310         # no API to query
311         return False
312
313     def object_id(self):
314         return "ip6-link-local-%s-%s" % (self.intf, self.addr)
315
316
317 class VppIpInterfaceBind(VppObject):
318
319     def __init__(self, test, intf, table):
320         self._test = test
321         self.intf = intf
322         self.table = table
323
324     def add_vpp_config(self):
325         if self.table.is_ip6:
326             self.intf.set_table_ip6(self.table.table_id)
327         else:
328             self.intf.set_table_ip4(self.table.table_id)
329         self._test.registry.register(self, self._test.logger)
330         return self
331
332     def remove_vpp_config(self):
333         if 0 == self.table.table_id:
334             return
335         if self.table.is_ip6:
336             self.intf.set_table_ip6(0)
337         else:
338             self.intf.set_table_ip4(0)
339
340     def query_vpp_config(self):
341         if 0 == self.table.table_id:
342             return False
343         try:
344             return self._test.vapi.sw_interface_get_table(
345                 self.intf.sw_if_index,
346                 self.table.is_ip6).vrf_id == self.table.table_id
347         except UnexpectedApiReturnValueError as e:
348             if e.retval == -2:  # INVALID_SW_IF_INDEX
349                 return False
350             raise
351
352     def object_id(self):
353         return "interface-bind-%s-%s" % (self.intf, self.table)
354
355
356 class VppMplsLabel:
357     def __init__(self, value, mode=MplsLspMode.PIPE, ttl=64, exp=0):
358         self.value = value
359         self.mode = mode
360         self.ttl = ttl
361         self.exp = exp
362
363     def encode(self):
364         is_uniform = 0 if self.mode is MplsLspMode.PIPE else 1
365         return {'label': self.value,
366                 'ttl': self.ttl,
367                 'exp': self.exp,
368                 'is_uniform': is_uniform}
369
370     def __eq__(self, other):
371         if isinstance(other, self.__class__):
372             return (self.value == other.value and
373                     self.ttl == other.ttl and
374                     self.exp == other.exp and
375                     self.mode == other.mode)
376         elif hasattr(other, 'label'):
377             return (self.value == other.label and
378                     self.ttl == other.ttl and
379                     self.exp == other.exp and
380                     (self.mode == MplsLspMode.UNIFORM) == other.is_uniform)
381         else:
382             return False
383
384     def __ne__(self, other):
385         return not (self == other)
386
387
388 class VppFibPathNextHop:
389     def __init__(self, addr,
390                  via_label=MPLS_LABEL_INVALID,
391                  next_hop_id=INVALID_INDEX):
392         self.addr = VppIpAddressUnion(addr)
393         self.via_label = via_label
394         self.obj_id = next_hop_id
395
396     def encode(self):
397         if self.via_label is not MPLS_LABEL_INVALID:
398             return {'via_label': self.via_label}
399         if self.obj_id is not INVALID_INDEX:
400             return {'obj_id': self.obj_id}
401         else:
402             return {'address': self.addr.encode()}
403
404     def proto(self):
405         if self.via_label is MPLS_LABEL_INVALID:
406             return address_proto(self.addr)
407         else:
408             return FibPathProto.FIB_PATH_NH_PROTO_MPLS
409
410     def __eq__(self, other):
411         if not isinstance(other, self.__class__):
412             # try the other instance's __eq__.
413             return NotImplemented
414         return (self.addr == other.addr and
415                 self.via_label == other.via_label and
416                 self.obj_id == other.obj_id)
417
418
419 class VppRoutePath:
420
421     def __init__(
422             self,
423             nh_addr,
424             nh_sw_if_index,
425             nh_table_id=0,
426             labels=[],
427             nh_via_label=MPLS_LABEL_INVALID,
428             rpf_id=0,
429             next_hop_id=INVALID_INDEX,
430             proto=None,
431             flags=FibPathFlags.FIB_PATH_FLAG_NONE,
432             type=FibPathType.FIB_PATH_TYPE_NORMAL):
433         self.nh_itf = nh_sw_if_index
434         self.nh_table_id = nh_table_id
435         self.nh_labels = labels
436         self.weight = 1
437         self.rpf_id = rpf_id
438         self.proto = proto
439         self.flags = flags
440         self.type = type
441         self.nh = VppFibPathNextHop(nh_addr, nh_via_label, next_hop_id)
442         if proto is None:
443             self.proto = self.nh.proto()
444         else:
445             self.proto = proto
446         self.next_hop_id = next_hop_id
447
448     def encode_labels(self):
449         lstack = []
450         for l in self.nh_labels:
451             if type(l) == VppMplsLabel:
452                 lstack.append(l.encode())
453             else:
454                 lstack.append({'label': l,
455                                'ttl': 255})
456         while (len(lstack) < 16):
457             lstack.append({})
458
459         return lstack
460
461     def encode(self):
462         return {'weight': 1,
463                 'preference': 0,
464                 'table_id': self.nh_table_id,
465                 'nh': self.nh.encode(),
466                 'next_hop_id': self.next_hop_id,
467                 'sw_if_index': self.nh_itf,
468                 'rpf_id': self.rpf_id,
469                 'proto': self.proto,
470                 'type': self.type,
471                 'flags': self.flags,
472                 'n_labels': len(self.nh_labels),
473                 'label_stack': self.encode_labels()}
474
475     def __eq__(self, other):
476         if isinstance(other, self.__class__):
477             return self.nh == other.nh
478         elif hasattr(other, 'sw_if_index'):
479             # vl_api_fib_path_t
480             if (len(self.nh_labels) != other.n_labels):
481                 return False
482             for i in range(len(self.nh_labels)):
483                 if (self.nh_labels[i] != other.label_stack[i]):
484                     return False
485             return self.nh_itf == other.sw_if_index
486         else:
487             return False
488
489     def __ne__(self, other):
490         return not (self == other)
491
492
493 class VppMRoutePath(VppRoutePath):
494
495     def __init__(self, nh_sw_if_index, flags,
496                  nh=None,
497                  proto=FibPathProto.FIB_PATH_NH_PROTO_IP4,
498                  type=FibPathType.FIB_PATH_TYPE_NORMAL,
499                  bier_imp=INVALID_INDEX):
500         if not nh:
501             nh = "::" if proto is FibPathProto.FIB_PATH_NH_PROTO_IP6 \
502                  else "0.0.0.0"
503         super(VppMRoutePath, self).__init__(nh,
504                                             nh_sw_if_index,
505                                             proto=proto,
506                                             type=type,
507                                             next_hop_id=bier_imp)
508         self.nh_i_flags = flags
509         self.bier_imp = bier_imp
510
511     def encode(self):
512         return {'path': super(VppMRoutePath, self).encode(),
513                 'itf_flags': self.nh_i_flags}
514
515
516 class VppIpRoute(VppObject):
517     """
518     IP Route
519     """
520
521     def __init__(self, test, dest_addr,
522                  dest_addr_len, paths, table_id=0, register=True):
523         self._test = test
524         self.paths = paths
525         self.table_id = table_id
526         self.prefix = mk_network(dest_addr, dest_addr_len)
527         self.register = register
528         self.stats_index = None
529         self.modified = False
530
531         self.encoded_paths = []
532         for path in self.paths:
533             self.encoded_paths.append(path.encode())
534
535     def __eq__(self, other):
536         if self.table_id == other.table_id and \
537            self.prefix == other.prefix:
538             return True
539         return False
540
541     def modify(self, paths):
542         self.paths = paths
543         self.encoded_paths = []
544         for path in self.paths:
545             self.encoded_paths.append(path.encode())
546         self.modified = True
547
548         self._test.vapi.ip_route_add_del(route={'table_id': self.table_id,
549                                                 'prefix': self.prefix,
550                                                 'n_paths': len(
551                                                     self.encoded_paths),
552                                                 'paths': self.encoded_paths,
553                                                 },
554                                          is_add=1,
555                                          is_multipath=0)
556
557     def add_vpp_config(self):
558         r = self._test.vapi.ip_route_add_del(
559             route={'table_id': self.table_id,
560                    'prefix': self.prefix,
561                    'n_paths': len(self.encoded_paths),
562                    'paths': self.encoded_paths,
563                    },
564             is_add=1,
565             is_multipath=0)
566         self.stats_index = r.stats_index
567         if self.register:
568             self._test.registry.register(self, self._test.logger)
569         return self
570
571     def remove_vpp_config(self):
572         # there's no need to issue different deletes for modified routes
573         # we do this only to test the two different ways to delete routes
574         # eiter by passing all the paths to remove and mutlipath=1 or
575         # passing no paths and multipath=0
576         if self.modified:
577             self._test.vapi.ip_route_add_del(
578                 route={'table_id': self.table_id,
579                        'prefix': self.prefix,
580                        'n_paths': len(
581                            self.encoded_paths),
582                        'paths': self.encoded_paths},
583                 is_add=0,
584                 is_multipath=1)
585         else:
586             self._test.vapi.ip_route_add_del(
587                 route={'table_id': self.table_id,
588                        'prefix': self.prefix,
589                        'n_paths': 0},
590                 is_add=0,
591                 is_multipath=0)
592
593     def query_vpp_config(self):
594         return find_route(self._test,
595                           self.prefix.network_address,
596                           self.prefix.prefixlen,
597                           self.table_id)
598
599     def object_id(self):
600         return ("%s:table-%d-%s" % (
601             'ip6-route' if self.prefix.version == 6 else 'ip-route',
602                 self.table_id,
603                 self.prefix))
604
605     def get_stats_to(self):
606         c = self._test.statistics.get_counter("/net/route/to")
607         return c[0][self.stats_index]
608
609     def get_stats_via(self):
610         c = self._test.statistics.get_counter("/net/route/via")
611         return c[0][self.stats_index]
612
613
614 class VppIpRouteV2(VppObject):
615     """
616     IP Route V2
617     """
618
619     def __init__(self, test, dest_addr,
620                  dest_addr_len, paths, table_id=0,
621                  register=True, src=0):
622         self._test = test
623         self.paths = paths
624         self.table_id = table_id
625         self.prefix = mk_network(dest_addr, dest_addr_len)
626         self.register = register
627         self.stats_index = None
628         self.modified = False
629         self.src = src
630
631         self.encoded_paths = []
632         for path in self.paths:
633             self.encoded_paths.append(path.encode())
634
635     def __eq__(self, other):
636         if self.table_id == other.table_id and \
637            self.prefix == other.prefix:
638             return True
639         return False
640
641     def modify(self, paths):
642         self.paths = paths
643         self.encoded_paths = []
644         for path in self.paths:
645             self.encoded_paths.append(path.encode())
646         self.modified = True
647
648         self._test.vapi.ip_route_add_del_v2(route={'table_id': self.table_id,
649                                                    'prefix': self.prefix,
650                                                    'src': self.src,
651                                                    'n_paths': len(
652                                                        self.encoded_paths),
653                                                    'paths': self.encoded_paths,
654                                                    },
655                                             is_add=1,
656                                             is_multipath=0)
657
658     def add_vpp_config(self):
659         r = self._test.vapi.ip_route_add_del_v2(
660             route={'table_id': self.table_id,
661                    'prefix': self.prefix,
662                    'n_paths': len(self.encoded_paths),
663                    'paths': self.encoded_paths,
664                    'src': self.src,
665                    },
666             is_add=1,
667             is_multipath=0)
668         self.stats_index = r.stats_index
669         if self.register:
670             self._test.registry.register(self, self._test.logger)
671         return self
672
673     def remove_vpp_config(self):
674         # there's no need to issue different deletes for modified routes
675         # we do this only to test the two different ways to delete routes
676         # eiter by passing all the paths to remove and mutlipath=1 or
677         # passing no paths and multipath=0
678         if self.modified:
679             self._test.vapi.ip_route_add_del_v2(
680                 route={'table_id': self.table_id,
681                        'prefix': self.prefix,
682                        'src': self.src,
683                        'n_paths': len(
684                            self.encoded_paths),
685                        'paths': self.encoded_paths},
686                 is_add=0,
687                 is_multipath=1)
688         else:
689             self._test.vapi.ip_route_add_del_v2(
690                 route={'table_id': self.table_id,
691                        'prefix': self.prefix,
692                        'src': self.src,
693                        'n_paths': 0},
694                 is_add=0,
695                 is_multipath=0)
696
697     def query_vpp_config(self):
698         return find_route(self._test,
699                           self.prefix.network_address,
700                           self.prefix.prefixlen,
701                           self.table_id)
702
703     def object_id(self):
704         return ("%s:table-%d-%s" % (
705             'ip6-route' if self.prefix.version == 6 else 'ip-route',
706                 self.table_id,
707                 self.prefix))
708
709     def get_stats_to(self):
710         c = self._test.statistics.get_counter("/net/route/to")
711         return c[0][self.stats_index]
712
713     def get_stats_via(self):
714         c = self._test.statistics.get_counter("/net/route/via")
715         return c[0][self.stats_index]
716
717
718 class VppIpMRoute(VppObject):
719     """
720     IP Multicast Route
721     """
722
723     def __init__(self, test, src_addr, grp_addr,
724                  grp_addr_len, e_flags, paths, table_id=0,
725                  rpf_id=0):
726         self._test = test
727         self.paths = paths
728         self.table_id = table_id
729         self.e_flags = e_flags
730         self.rpf_id = rpf_id
731
732         self.prefix = VppIpMPrefix(src_addr, grp_addr, grp_addr_len)
733         self.encoded_paths = []
734         for path in self.paths:
735             self.encoded_paths.append(path.encode())
736
737     def encode(self, paths=None):
738         _paths = self.encoded_paths if paths is None else paths
739         return {'table_id': self.table_id,
740                 'entry_flags': self.e_flags,
741                 'rpf_id': self.rpf_id,
742                 'prefix': self.prefix.encode(),
743                 'n_paths': len(_paths),
744                 'paths': _paths,
745                 }
746
747     def add_vpp_config(self):
748         r = self._test.vapi.ip_mroute_add_del(route=self.encode(),
749                                               is_multipath=1,
750                                               is_add=1)
751         self.stats_index = r.stats_index
752         self._test.registry.register(self, self._test.logger)
753         return self
754
755     def remove_vpp_config(self):
756         self._test.vapi.ip_mroute_add_del(route=self.encode(),
757                                           is_multipath=1,
758                                           is_add=0)
759
760     def update_entry_flags(self, flags):
761         self.e_flags = flags
762         self._test.vapi.ip_mroute_add_del(route=self.encode(paths=[]),
763                                           is_multipath=1,
764                                           is_add=1)
765
766     def update_rpf_id(self, rpf_id):
767         self.rpf_id = rpf_id
768         self._test.vapi.ip_mroute_add_del(route=self.encode(paths=[]),
769                                           is_multipath=1,
770                                           is_add=1)
771
772     def update_path_flags(self, itf, flags):
773         for p in range(len(self.paths)):
774             if self.paths[p].nh_itf == itf:
775                 self.paths[p].nh_i_flags = flags
776                 self.encoded_paths[p] = self.paths[p].encode()
777                 break
778
779         self._test.vapi.ip_mroute_add_del(
780             route=self.encode(
781                 paths=[self.encoded_paths[p]]),
782             is_add=1,
783             is_multipath=0)
784
785     def query_vpp_config(self):
786         return find_mroute(self._test,
787                            self.prefix.gaddr,
788                            self.prefix.saddr,
789                            self.prefix.length,
790                            self.table_id)
791
792     def object_id(self):
793         return ("%d:(%s,%s/%d)" % (self.table_id,
794                                    self.prefix.saddr,
795                                    self.prefix.gaddr,
796                                    self.prefix.length))
797
798     def get_stats(self):
799         c = self._test.statistics.get_counter("/net/mroute")
800         return c[0][self.stats_index]
801
802
803 class VppMFibSignal:
804     def __init__(self, test, route, interface, packet):
805         self.route = route
806         self.interface = interface
807         self.packet = packet
808         self.test = test
809
810     def compare(self, signal):
811         self.test.assertEqual(self.interface, signal.sw_if_index)
812         self.test.assertEqual(self.route.table_id, signal.table_id)
813         self.test.assertEqual(self.route.prefix, signal.prefix)
814
815
816 class VppMplsIpBind(VppObject):
817     """
818     MPLS to IP Binding
819     """
820
821     def __init__(self, test, local_label, dest_addr, dest_addr_len,
822                  table_id=0, ip_table_id=0, is_ip6=0):
823         self._test = test
824         self.dest_addr_len = dest_addr_len
825         self.dest_addr = dest_addr
826         self.ip_addr = ip_address(text_type(dest_addr))
827         self.local_label = local_label
828         self.table_id = table_id
829         self.ip_table_id = ip_table_id
830         self.prefix = mk_network(dest_addr, dest_addr_len)
831
832     def add_vpp_config(self):
833         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
834                                             self.prefix,
835                                             table_id=self.table_id,
836                                             ip_table_id=self.ip_table_id)
837         self._test.registry.register(self, self._test.logger)
838
839     def remove_vpp_config(self):
840         self._test.vapi.mpls_ip_bind_unbind(self.local_label,
841                                             self.prefix,
842                                             table_id=self.table_id,
843                                             ip_table_id=self.ip_table_id,
844                                             is_bind=0)
845
846     def query_vpp_config(self):
847         dump = self._test.vapi.mpls_route_dump(self.table_id)
848         for e in dump:
849             if self.local_label == e.mr_route.mr_label \
850                and self.table_id == e.mr_route.mr_table_id:
851                 return True
852         return False
853
854     def object_id(self):
855         return ("%d:%s binds %d:%s/%d"
856                 % (self.table_id,
857                    self.local_label,
858                    self.ip_table_id,
859                    self.dest_addr,
860                    self.dest_addr_len))
861
862
863 class VppMplsTable(VppObject):
864
865     def __init__(self,
866                  test,
867                  table_id):
868         self._test = test
869         self.table_id = table_id
870
871     def add_vpp_config(self):
872         self._test.vapi.mpls_table_add_del(
873             self.table_id,
874             is_add=1)
875         self._test.registry.register(self, self._test.logger)
876
877     def remove_vpp_config(self):
878         self._test.vapi.mpls_table_add_del(
879             self.table_id,
880             is_add=0)
881
882     def query_vpp_config(self):
883         dump = self._test.vapi.mpls_table_dump()
884         for d in dump:
885             if d.mt_table.mt_table_id == self.table_id:
886                 return True
887         return False
888
889     def object_id(self):
890         return ("table-mpls-%d" % (self.table_id))
891
892
893 class VppMplsRoute(VppObject):
894     """
895     MPLS Route/LSP
896     """
897
898     def __init__(self, test, local_label, eos_bit, paths, table_id=0,
899                  is_multicast=0,
900                  eos_proto=FibPathProto.FIB_PATH_NH_PROTO_IP4):
901         self._test = test
902         self.paths = paths
903         self.local_label = local_label
904         self.eos_bit = eos_bit
905         self.eos_proto = eos_proto
906         self.table_id = table_id
907         self.is_multicast = is_multicast
908
909     def add_vpp_config(self):
910         paths = []
911         for path in self.paths:
912             paths.append(path.encode())
913
914         r = self._test.vapi.mpls_route_add_del(self.table_id,
915                                                self.local_label,
916                                                self.eos_bit,
917                                                self.eos_proto,
918                                                self.is_multicast,
919                                                paths, 1, 0)
920         self.stats_index = r.stats_index
921         self._test.registry.register(self, self._test.logger)
922
923     def remove_vpp_config(self):
924         paths = []
925         for path in self.paths:
926             paths.append(path.encode())
927
928         self._test.vapi.mpls_route_add_del(self.table_id,
929                                            self.local_label,
930                                            self.eos_bit,
931                                            self.eos_proto,
932                                            self.is_multicast,
933                                            paths, 0, 0)
934
935     def query_vpp_config(self):
936         return find_mpls_route(self._test, self.table_id,
937                                self.local_label, self.eos_bit)
938
939     def object_id(self):
940         return ("mpls-route-%d:%s/%d"
941                 % (self.table_id,
942                    self.local_label,
943                    20 + self.eos_bit))
944
945     def get_stats_to(self):
946         c = self._test.statistics.get_counter("/net/route/to")
947         return c[0][self.stats_index]
948
949     def get_stats_via(self):
950         c = self._test.statistics.get_counter("/net/route/via")
951         return c[0][self.stats_index]