backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / app / rest_vtep.py
1 # Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #    http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """
17 This sample application performs as VTEP for EVPN VXLAN and constructs
18 a Single Subnet per EVI corresponding to the VLAN Based service in [RFC7432].
19
20 .. NOTE::
21
22     This app will invoke OVSDB request to the switches.
23     Please set the manager address before calling the API of this app.
24
25     ::
26
27         $ sudo ovs-vsctl set-manager ptcp:6640
28         $ sudo ovs-vsctl show
29             ...(snip)
30             Manager "ptcp:6640"
31             ...(snip)
32
33
34 Usage Example
35 =============
36
37 Environment
38 -----------
39
40 This example supposes the following environment::
41
42      Host A (172.17.0.1)                      Host B (172.17.0.2)
43     +--------------------+                   +--------------------+
44     |   Ryu1             | --- BGP(EVPN) --- |   Ryu2             |
45     +--------------------+                   +--------------------+
46             |                                       |
47     +--------------------+                   +--------------------+
48     |   s1 (OVS)         | ===== vxlan ===== |   s2 (OVS)         |
49     +--------------------+                   +--------------------+
50     (s1-eth1)    (s1-eth2)                   (s2-eth1)    (s2-eth2)
51        |            |                           |            |
52     +--------+  +--------+                   +--------+  +--------+
53     | s1h1   |  | s1h2   |                   | s2h1   |  | s2h2   |
54     +--------+  +--------+                   +--------+  +--------+
55
56 Configuration steps
57 -------------------
58
59 1. Creates a new BGPSpeaker instance on each host.
60
61     On Host A::
62
63         (Host A)$ curl -X POST -d '{
64          "dpid": 1,
65          "as_number": 65000,
66          "router_id": "172.17.0.1"
67          }' http://localhost:8080/vtep/speakers | python -m json.tool
68
69     On Host B::
70
71         (Host B)$ curl -X POST -d '{
72          "dpid": 1,
73          "as_number": 65000,
74          "router_id": "172.17.0.2"
75          }' http://localhost:8080/vtep/speakers | python -m json.tool
76
77 2. Registers the neighbor for the speakers on each host.
78
79     On Host A::
80
81         (Host A)$ curl -X POST -d '{
82          "address": "172.17.0.2",
83          "remote_as": 65000
84          }' http://localhost:8080/vtep/neighbors |
85          python -m json.tool
86
87     On Host B::
88
89         (Host B)$ curl -X POST -d '{
90          "address": "172.17.0.1",
91          "remote_as": 65000
92          }' http://localhost:8080/vtep/neighbors |
93          python -m json.tool
94
95 3. Defines a new VXLAN network(VNI=10) on the Host A/B.
96
97     On Host A::
98
99         (Host A)$ curl -X POST -d '{
100          "vni": 10
101          }' http://localhost:8080/vtep/networks | python -m json.tool
102
103     On Host B::
104
105         (Host B)$ curl -X POST -d '{
106          "vni": 10
107          }' http://localhost:8080/vtep/networks | python -m json.tool
108
109 4. Registers the clients to the VXLAN network.
110
111     For "s1h1"(ip="10.0.0.11", mac="aa:bb:cc:00:00:11") on Host A::
112
113         (Host A)$ curl -X POST -d '{
114          "port": "s1-eth1",
115          "mac": "aa:bb:cc:00:00:11",
116          "ip": "10.0.0.11"
117          } ' http://localhost:8080/vtep/networks/10/clients |
118          python -m json.tool
119
120     For "s2h1"(ip="10.0.0.21", mac="aa:bb:cc:00:00:21") on Host B::
121
122         (Host B)$ curl -X POST -d '{
123          "port": "s2-eth1",
124          "mac": "aa:bb:cc:00:00:21",
125          "ip": "10.0.0.21"
126          } ' http://localhost:8080/vtep/networks/10/clients |
127          python -m json.tool
128
129 Testing
130 -------
131
132 If BGP (EVPN) connection between Ryu1 and Ryu2 has been established,
133 pings between the client s1h1 and s2h1 should work.
134
135 ::
136
137     (s1h1)$ ping 10.0.0.21
138
139
140 Troubleshooting
141 ---------------
142
143 If connectivity between s1h1 and s2h1 isn't working,
144 please check the followings.
145
146 1. Make sure that Host A and Host B have full network connectivity.
147
148     ::
149
150         (Host A)$ ping 172.17.0.2
151
152 2. Make sure that BGP(EVPN) connection has been established.
153
154     ::
155
156         (Host A)$ curl -X GET http://localhost:8080/vtep/neighbors |
157          python -m json.tool
158
159         ...
160         {
161             "172.17.0.2": {
162                 "EvpnNeighbor": {
163                     "address": "172.17.0.2",
164                     "remote_as": 65000,
165                     "state": "up"  # "up" shows the connection established
166                 }
167             }
168         }
169
170 3. Make sure that BGP(EVPN) routes have been advertised.
171
172     ::
173
174         (Host A)$ curl -X GET http://localhost:8080/vtep/networks |
175          python -m json.tool
176
177          ...
178         {
179             "10": {
180                 "EvpnNetwork": {
181                     "clients": {
182                         "aa:bb:cc:00:00:11": {
183                             "EvpnClient": {
184                                 "ip": "10.0.0.11",
185                                 "mac": "aa:bb:cc:00:00:11",
186                                 "next_hop": "172.17.0.1",
187                                 "port": 1
188                             }
189                         },
190                         "aa:bb:cc:00:00:21": {  # route for "s2h1" on Host B
191                             "EvpnClient": {
192                                 "ip": "10.0.0.21",
193                                 "mac": "aa:bb:cc:00:00:21",
194                                 "next_hop": "172.17.0.2",
195                                 "port": 3
196                             }
197                         }
198                     },
199                     "ethernet_tag_id": 0,
200                     "route_dist": "65000:10",
201                     "vni": 10
202                 }
203             }
204         }
205
206 4. Make sure that the IPv6 is enabled on your environment. Some Ryu BGP
207 features require the IPv6 connectivity to bind sockets. Mininet seems to
208 disable IPv6 on its installation.
209
210     For example::
211
212         $ sysctl net.ipv6.conf.all.disable_ipv6
213         net.ipv6.conf.all.disable_ipv6 = 0  # should NOT be enabled
214
215         $ grep GRUB_CMDLINE_LINUX_DEFAULT /etc/default/grub
216         # please remove "ipv6.disable=1" and reboot
217         GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 quiet splash"
218
219 5. Make sure that your switch using the OpenFlow version 1.3. This application
220 supports only the OpenFlow version 1.3.
221
222     For example::
223
224         $ ovs-vsctl get Bridge s1 protocols
225         ["OpenFlow13"]
226
227 .. Note::
228
229     At the time of this writing, we use the the following version of Ryu,
230     Open vSwitch and Mininet.
231
232     ::
233
234         $ ryu --version
235         ryu 4.19
236
237         $ ovs-vsctl --version
238         ovs-vsctl (Open vSwitch) 2.5.2  # APT packaged version of Ubuntu 16.04
239         Compiled Oct 17 2017 16:38:57
240         DB Schema 7.12.1
241
242         $ mn --version
243         2.2.1  # APT packaged version of Ubuntu 16.04
244 """
245
246 import json
247
248 from ryu.app.ofctl import api as ofctl_api
249 from ryu.app.wsgi import ControllerBase
250 from ryu.app.wsgi import Response
251 from ryu.app.wsgi import route
252 from ryu.app.wsgi import WSGIApplication
253 from ryu.base import app_manager
254 from ryu.exception import RyuException
255 from ryu.lib.ovs import bridge as ovs_bridge
256 from ryu.lib.packet import arp
257 from ryu.lib.packet import ether_types
258 from ryu.lib.packet.bgp import _RouteDistinguisher
259 from ryu.lib.packet.bgp import EvpnNLRI
260 from ryu.lib.stringify import StringifyMixin
261 from ryu.ofproto import ofproto_v1_3
262 from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker
263 from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN
264 from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE
265 from ryu.services.protocols.bgp.bgpspeaker import EVPN_MULTICAST_ETAG_ROUTE
266 from ryu.services.protocols.bgp.info_base.evpn import EvpnPath
267
268
269 API_NAME = 'restvtep'
270
271 OVSDB_PORT = 6640  # The IANA registered port for OVSDB [RFC7047]
272
273 PRIORITY_D_PLANE = 1
274 PRIORITY_ARP_REPLAY = 2
275
276 TABLE_ID_INGRESS = 0
277 TABLE_ID_EGRESS = 1
278
279
280 # Utility functions
281
282 def to_int(i):
283     return int(str(i), 0)
284
285
286 def to_str_list(l):
287     str_list = []
288     for s in l:
289         str_list.append(str(s))
290     return str_list
291
292
293 # Exception classes related to OpenFlow and OVSDB
294
295 class RestApiException(RyuException):
296
297     def to_response(self, status):
298         body = {
299             "error": str(self),
300             "status": status,
301         }
302         return Response(content_type='application/json',
303                         body=json.dumps(body), status=status)
304
305
306 class DatapathNotFound(RestApiException):
307     message = 'No such datapath: %(dpid)s'
308
309
310 class OFPortNotFound(RestApiException):
311     message = 'No such OFPort: %(port_name)s'
312
313
314 # Exception classes related to BGP
315
316 class BGPSpeakerNotFound(RestApiException):
317     message = 'BGPSpeaker could not be found'
318
319
320 class NeighborNotFound(RestApiException):
321     message = 'No such neighbor: %(address)s'
322
323
324 class VniNotFound(RestApiException):
325     message = 'No such VNI: %(vni)s'
326
327
328 class ClientNotFound(RestApiException):
329     message = 'No such client: %(mac)s'
330
331
332 class ClientNotLocal(RestApiException):
333     message = 'Specified client is not local: %(mac)s'
334
335
336 # Utility classes related to EVPN
337
338 class EvpnSpeaker(BGPSpeaker, StringifyMixin):
339     _TYPE = {
340         'ascii': [
341             'router_id',
342         ],
343     }
344
345     def __init__(self, dpid, as_number, router_id,
346                  best_path_change_handler,
347                  peer_down_handler, peer_up_handler,
348                  neighbors=None):
349         super(EvpnSpeaker, self).__init__(
350             as_number=as_number,
351             router_id=router_id,
352             best_path_change_handler=best_path_change_handler,
353             peer_down_handler=peer_down_handler,
354             peer_up_handler=peer_up_handler,
355             ssh_console=True)
356
357         self.dpid = dpid
358         self.as_number = as_number
359         self.router_id = router_id
360         self.neighbors = neighbors or {}
361
362
363 class EvpnNeighbor(StringifyMixin):
364     _TYPE = {
365         'ascii': [
366             'address',
367             'state',
368         ],
369     }
370
371     def __init__(self, address, remote_as, state='down'):
372         super(EvpnNeighbor, self).__init__()
373         self.address = address
374         self.remote_as = remote_as
375         self.state = state
376
377
378 class EvpnNetwork(StringifyMixin):
379     _TYPE = {
380         'ascii': [
381             'route_dist',
382         ],
383     }
384
385     def __init__(self, vni, route_dist, ethernet_tag_id, clients=None):
386         super(EvpnNetwork, self).__init__()
387         self.vni = vni
388         self.route_dist = route_dist
389         self.ethernet_tag_id = ethernet_tag_id
390         self.clients = clients or {}
391
392     def get_clients(self, **kwargs):
393         l = []
394         for _, c in self.clients.items():
395             for k, v in kwargs.items():
396                 if getattr(c, k) != v:
397                     break
398             else:
399                 l.append(c)
400         return l
401
402
403 class EvpnClient(StringifyMixin):
404     _TYPE = {
405         'ascii': [
406             'mac',
407             'ip',
408             'next_hop'
409         ],
410     }
411
412     def __init__(self, port, mac, ip, next_hop):
413         super(EvpnClient, self).__init__()
414         self.port = port
415         self.mac = mac
416         self.ip = ip
417         self.next_hop = next_hop
418
419
420 class RestVtep(app_manager.RyuApp):
421     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
422     _CONTEXTS = {'wsgi': WSGIApplication}
423
424     def __init__(self, *args, **kwargs):
425         super(RestVtep, self).__init__(*args, **kwargs)
426         wsgi = kwargs['wsgi']
427         wsgi.register(RestVtepController, {RestVtep.__name__: self})
428
429         # EvpnSpeaker instance instantiated later
430         self.speaker = None
431
432         # OVSBridge instance instantiated later
433         self.ovs = None
434
435         # Dictionary for retrieving the EvpnNetwork instance by VNI
436         # self.networks = {
437         #     <vni>: <instance 'EvpnNetwork'>,
438         #     ...
439         # }
440         self.networks = {}
441
442     # Utility methods related to OpenFlow
443
444     def _get_datapath(self, dpid):
445         return ofctl_api.get_datapath(self, dpid)
446
447     @staticmethod
448     def _add_flow(datapath, priority, match, instructions,
449                   table_id=TABLE_ID_INGRESS):
450         parser = datapath.ofproto_parser
451
452         mod = parser.OFPFlowMod(
453             datapath=datapath,
454             table_id=table_id,
455             priority=priority,
456             match=match,
457             instructions=instructions)
458
459         datapath.send_msg(mod)
460
461     @staticmethod
462     def _del_flow(datapath, priority, match, table_id=TABLE_ID_INGRESS):
463         ofproto = datapath.ofproto
464         parser = datapath.ofproto_parser
465
466         mod = parser.OFPFlowMod(
467             datapath=datapath,
468             table_id=table_id,
469             command=ofproto.OFPFC_DELETE,
470             priority=priority,
471             out_port=ofproto.OFPP_ANY,
472             out_group=ofproto.OFPG_ANY,
473             match=match)
474
475         datapath.send_msg(mod)
476
477     def _add_network_ingress_flow(self, datapath, tag, in_port, eth_src=None):
478         parser = datapath.ofproto_parser
479
480         if eth_src is None:
481             match = parser.OFPMatch(in_port=in_port)
482         else:
483             match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
484         instructions = [
485             parser.OFPInstructionWriteMetadata(
486                 metadata=tag, metadata_mask=parser.UINT64_MAX),
487             parser.OFPInstructionGotoTable(1)]
488
489         self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions)
490
491     def _del_network_ingress_flow(self, datapath, in_port, eth_src=None):
492         parser = datapath.ofproto_parser
493
494         if eth_src is None:
495             match = parser.OFPMatch(in_port=in_port)
496         else:
497             match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
498
499         self._del_flow(datapath, PRIORITY_D_PLANE, match)
500
501     def _add_arp_reply_flow(self, datapath, tag, arp_tpa, arp_tha):
502         ofproto = datapath.ofproto
503         parser = datapath.ofproto_parser
504
505         match = parser.OFPMatch(
506             metadata=(tag, parser.UINT64_MAX),
507             eth_type=ether_types.ETH_TYPE_ARP,
508             arp_op=arp.ARP_REQUEST,
509             arp_tpa=arp_tpa)
510
511         actions = [
512             parser.NXActionRegMove(
513                 src_field="eth_src", dst_field="eth_dst", n_bits=48),
514             parser.OFPActionSetField(eth_src=arp_tha),
515             parser.OFPActionSetField(arp_op=arp.ARP_REPLY),
516             parser.NXActionRegMove(
517                 src_field="arp_sha", dst_field="arp_tha", n_bits=48),
518             parser.NXActionRegMove(
519                 src_field="arp_spa", dst_field="arp_tpa", n_bits=32),
520             parser.OFPActionSetField(arp_sha=arp_tha),
521             parser.OFPActionSetField(arp_spa=arp_tpa),
522             parser.OFPActionOutput(ofproto.OFPP_IN_PORT)]
523         instructions = [
524             parser.OFPInstructionActions(
525                 ofproto.OFPIT_APPLY_ACTIONS, actions)]
526
527         self._add_flow(datapath, PRIORITY_ARP_REPLAY, match, instructions,
528                        table_id=TABLE_ID_EGRESS)
529
530     def _del_arp_reply_flow(self, datapath, tag, arp_tpa):
531         parser = datapath.ofproto_parser
532
533         match = parser.OFPMatch(
534             metadata=(tag, parser.UINT64_MAX),
535             eth_type=ether_types.ETH_TYPE_ARP,
536             arp_op=arp.ARP_REQUEST,
537             arp_tpa=arp_tpa)
538
539         self._del_flow(datapath, PRIORITY_ARP_REPLAY, match,
540                        table_id=TABLE_ID_EGRESS)
541
542     def _add_l2_switching_flow(self, datapath, tag, eth_dst, out_port):
543         ofproto = datapath.ofproto
544         parser = datapath.ofproto_parser
545
546         match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
547                                 eth_dst=eth_dst)
548         actions = [parser.OFPActionOutput(out_port)]
549         instructions = [
550             parser.OFPInstructionActions(
551                 ofproto.OFPIT_APPLY_ACTIONS, actions)]
552
553         self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions,
554                        table_id=TABLE_ID_EGRESS)
555
556     def _del_l2_switching_flow(self, datapath, tag, eth_dst):
557         parser = datapath.ofproto_parser
558
559         match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
560                                 eth_dst=eth_dst)
561
562         self._del_flow(datapath, PRIORITY_D_PLANE, match,
563                        table_id=TABLE_ID_EGRESS)
564
565     def _del_network_egress_flow(self, datapath, tag):
566         parser = datapath.ofproto_parser
567
568         match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX))
569
570         self._del_flow(datapath, PRIORITY_D_PLANE, match,
571                        table_id=TABLE_ID_EGRESS)
572
573     # Utility methods related to OVSDB
574
575     def _get_ovs_bridge(self, dpid):
576         datapath = self._get_datapath(dpid)
577         if datapath is None:
578             self.logger.debug('No such datapath: %s', dpid)
579             return None
580
581         ovsdb_addr = 'tcp:%s:%d' % (datapath.address[0], OVSDB_PORT)
582         if (self.ovs is not None
583                 and self.ovs.datapath_id == dpid
584                 and self.ovs.vsctl.remote == ovsdb_addr):
585             return self.ovs
586
587         try:
588             self.ovs = ovs_bridge.OVSBridge(
589                 CONF=self.CONF,
590                 datapath_id=datapath.id,
591                 ovsdb_addr=ovsdb_addr)
592             self.ovs.init()
593         except Exception as e:
594             self.logger.exception('Cannot initiate OVSDB connection: %s', e)
595             return None
596
597         return self.ovs
598
599     def _get_ofport(self, dpid, port_name):
600         ovs = self._get_ovs_bridge(dpid)
601         if ovs is None:
602             return None
603
604         try:
605             return ovs.get_ofport(port_name)
606         except Exception as e:
607             self.logger.debug('Cannot get port number for %s: %s',
608                               port_name, e)
609             return None
610
611     def _get_vxlan_port(self, dpid, remote_ip, key):
612         # Searches VXLAN port named 'vxlan_<remote_ip>_<key>'
613         return self._get_ofport(dpid, 'vxlan_%s_%s' % (remote_ip, key))
614
615     def _add_vxlan_port(self, dpid, remote_ip, key):
616         # If VXLAN port already exists, returns OFPort number
617         vxlan_port = self._get_vxlan_port(dpid, remote_ip, key)
618         if vxlan_port is not None:
619             return vxlan_port
620
621         ovs = self._get_ovs_bridge(dpid)
622         if ovs is None:
623             return None
624
625         # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
626         ovs.add_vxlan_port(
627             name='vxlan_%s_%s' % (remote_ip, key),
628             remote_ip=remote_ip,
629             key=key)
630
631         # Returns VXLAN port number
632         return self._get_vxlan_port(dpid, remote_ip, key)
633
634     def _del_vxlan_port(self, dpid, remote_ip, key):
635         ovs = self._get_ovs_bridge(dpid)
636         if ovs is None:
637             return None
638
639         # If VXLAN port does not exist, returns None
640         vxlan_port = self._get_vxlan_port(dpid, remote_ip, key)
641         if vxlan_port is None:
642             return None
643
644         # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
645         ovs.del_port('vxlan_%s_%s' % (remote_ip, key))
646
647         # Returns deleted VXLAN port number
648         return vxlan_port
649
650     # Event handlers for BGP
651
652     def _evpn_mac_ip_adv_route_handler(self, ev):
653         network = self.networks.get(ev.path.nlri.vni, None)
654         if network is None:
655             self.logger.debug('No such VNI registered: %s', ev.path.nlri)
656             return
657
658         datapath = self._get_datapath(self.speaker.dpid)
659         if datapath is None:
660             self.logger.debug('No such datapath: %s', self.speaker.dpid)
661             return
662
663         vxlan_port = self._add_vxlan_port(
664             dpid=self.speaker.dpid,
665             remote_ip=ev.nexthop,
666             key=ev.path.nlri.vni)
667         if vxlan_port is None:
668             self.logger.debug('Cannot create a new VXLAN port: %s',
669                               'vxlan_%s_%s' % (ev.nexthop, ev.path.nlri.vni))
670             return
671
672         self._add_l2_switching_flow(
673             datapath=datapath,
674             tag=network.vni,
675             eth_dst=ev.path.nlri.mac_addr,
676             out_port=vxlan_port)
677
678         self._add_arp_reply_flow(
679             datapath=datapath,
680             tag=network.vni,
681             arp_tpa=ev.path.nlri.ip_addr,
682             arp_tha=ev.path.nlri.mac_addr)
683
684         network.clients[ev.path.nlri.mac_addr] = EvpnClient(
685             port=vxlan_port,
686             mac=ev.path.nlri.mac_addr,
687             ip=ev.path.nlri.ip_addr,
688             next_hop=ev.nexthop)
689
690     def _evpn_incl_mcast_etag_route_handler(self, ev):
691         # Note: For the VLAN Based service, we use RT(=RD) assigned
692         # field as vid.
693         vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
694
695         network = self.networks.get(vni, None)
696         if network is None:
697             self.logger.debug('No such VNI registered: %s', vni)
698             return
699
700         datapath = self._get_datapath(self.speaker.dpid)
701         if datapath is None:
702             self.logger.debug('No such datapath: %s', self.speaker.dpid)
703             return
704
705         vxlan_port = self._add_vxlan_port(
706             dpid=self.speaker.dpid,
707             remote_ip=ev.nexthop,
708             key=vni)
709         if vxlan_port is None:
710             self.logger.debug('Cannot create a new VXLAN port: %s',
711                               'vxlan_%s_%s' % (ev.nexthop, vni))
712             return
713
714         self._add_network_ingress_flow(
715             datapath=datapath,
716             tag=vni,
717             in_port=vxlan_port)
718
719     def _evpn_route_handler(self, ev):
720         if ev.path.nlri.type == EvpnNLRI.MAC_IP_ADVERTISEMENT:
721             self._evpn_mac_ip_adv_route_handler(ev)
722         elif ev.path.nlri.type == EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG:
723             self._evpn_incl_mcast_etag_route_handler(ev)
724
725     def _evpn_withdraw_mac_ip_adv_route_handler(self, ev):
726         network = self.networks.get(ev.path.nlri.vni, None)
727         if network is None:
728             self.logger.debug('No such VNI registered: %s', ev.path.nlri)
729             return
730
731         datapath = self._get_datapath(self.speaker.dpid)
732         if datapath is None:
733             self.logger.debug('No such datapath: %s', self.speaker.dpid)
734             return
735
736         client = network.clients.get(ev.path.nlri.mac_addr, None)
737         if client is None:
738             self.logger.debug('No such client: %s', ev.path.nlri.mac_addr)
739             return
740
741         self._del_l2_switching_flow(
742             datapath=datapath,
743             tag=network.vni,
744             eth_dst=ev.path.nlri.mac_addr)
745
746         self._del_arp_reply_flow(
747             datapath=datapath,
748             tag=network.vni,
749             arp_tpa=ev.path.nlri.ip_addr)
750
751         network.clients.pop(ev.path.nlri.mac_addr)
752
753     def _evpn_withdraw_incl_mcast_etag_route_handler(self, ev):
754         # Note: For the VLAN Based service, we use RT(=RD) assigned
755         # field as vid.
756         vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
757
758         network = self.networks.get(vni, None)
759         if network is None:
760             self.logger.debug('No such VNI registered: %s', vni)
761             return
762
763         datapath = self._get_datapath(self.speaker.dpid)
764         if datapath is None:
765             self.logger.debug('No such datapath: %s', self.speaker.dpid)
766             return
767
768         vxlan_port = self._get_vxlan_port(
769             dpid=self.speaker.dpid,
770             remote_ip=ev.nexthop,
771             key=vni)
772         if vxlan_port is None:
773             self.logger.debug('No such VXLAN port: %s',
774                               'vxlan_%s_%s' % (ev.nexthop, vni))
775             return
776
777         self._del_network_ingress_flow(
778             datapath=datapath,
779             in_port=vxlan_port)
780
781         vxlan_port = self._del_vxlan_port(
782             dpid=self.speaker.dpid,
783             remote_ip=ev.nexthop,
784             key=vni)
785         if vxlan_port is None:
786             self.logger.debug('Cannot delete VXLAN port: %s',
787                               'vxlan_%s_%s' % (ev.nexthop, vni))
788             return
789
790     def _evpn_withdraw_route_handler(self, ev):
791         if ev.path.nlri.type == EvpnNLRI.MAC_IP_ADVERTISEMENT:
792             self._evpn_withdraw_mac_ip_adv_route_handler(ev)
793         elif ev.path.nlri.type == EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG:
794             self._evpn_withdraw_incl_mcast_etag_route_handler(ev)
795
796     def _best_path_change_handler(self, ev):
797         if not isinstance(ev.path, EvpnPath):
798             # Ignores non-EVPN routes
799             return
800         elif ev.nexthop == self.speaker.router_id:
801             # Ignore local connected routes
802             return
803         elif ev.is_withdraw:
804             self._evpn_withdraw_route_handler(ev)
805         else:
806             self._evpn_route_handler(ev)
807
808     def _peer_down_handler(self, remote_ip, remote_as):
809         neighbor = self.speaker.neighbors.get(remote_ip, None)
810         if neighbor is None:
811             self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
812                               remote_ip, remote_as)
813             return
814
815         neighbor.state = 'down'
816
817     def _peer_up_handler(self, remote_ip, remote_as):
818         neighbor = self.speaker.neighbors.get(remote_ip, None)
819         if neighbor is None:
820             self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
821                               remote_ip, remote_as)
822             return
823
824         neighbor.state = 'up'
825
826     # API methods for REST controller
827
828     def add_speaker(self, dpid, as_number, router_id):
829         # Check if the datapath for the specified dpid exist or not
830         datapath = self._get_datapath(dpid)
831         if datapath is None:
832             raise DatapathNotFound(dpid=dpid)
833
834         self.speaker = EvpnSpeaker(
835             dpid=dpid,
836             as_number=as_number,
837             router_id=router_id,
838             best_path_change_handler=self._best_path_change_handler,
839             peer_down_handler=self._peer_down_handler,
840             peer_up_handler=self._peer_up_handler)
841
842         return {self.speaker.router_id: self.speaker.to_jsondict()}
843
844     def get_speaker(self):
845         if self.speaker is None:
846             return BGPSpeakerNotFound()
847
848         return {self.speaker.router_id: self.speaker.to_jsondict()}
849
850     def del_speaker(self):
851         if self.speaker is None:
852             return BGPSpeakerNotFound()
853
854         for vni in list(self.networks.keys()):
855             self.del_network(vni=vni)
856
857         for address in list(self.speaker.neighbors.keys()):
858             self.del_neighbor(address=address)
859
860         self.speaker.shutdown()
861         speaker = self.speaker
862         self.speaker = None
863
864         return {speaker.router_id: speaker.to_jsondict()}
865
866     def add_neighbor(self, address, remote_as):
867         if self.speaker is None:
868             raise BGPSpeakerNotFound()
869
870         self.speaker.neighbor_add(
871             address=address,
872             remote_as=remote_as,
873             enable_evpn=True)
874
875         neighbor = EvpnNeighbor(
876             address=address,
877             remote_as=remote_as)
878         self.speaker.neighbors[address] = neighbor
879
880         return {address: neighbor.to_jsondict()}
881
882     def get_neighbors(self, address=None):
883         if self.speaker is None:
884             raise BGPSpeakerNotFound()
885
886         if address is not None:
887             neighbor = self.speaker.neighbors.get(address, None)
888             if neighbor is None:
889                 raise NeighborNotFound(address=address)
890             return {address: neighbor.to_jsondict()}
891
892         neighbors = {}
893         for address, neighbor in self.speaker.neighbors.items():
894             neighbors[address] = neighbor.to_jsondict()
895
896         return neighbors
897
898     def del_neighbor(self, address):
899         if self.speaker is None:
900             raise BGPSpeakerNotFound()
901
902         neighbor = self.speaker.neighbors.get(address, None)
903         if neighbor is None:
904             raise NeighborNotFound(address=address)
905
906         for network in self.networks.values():
907             for mac, client in list(network.clients.items()):
908                 if client.next_hop == address:
909                     network.clients.pop(mac)
910
911         self.speaker.neighbor_del(address=address)
912
913         neighbor = self.speaker.neighbors.pop(address)
914
915         return {address: neighbor.to_jsondict()}
916
917     def add_network(self, vni):
918         if self.speaker is None:
919             raise BGPSpeakerNotFound()
920
921         # Constructs type 0 RD with as_number and vni
922         route_dist = "%s:%d" % (self.speaker.as_number, vni)
923
924         self.speaker.vrf_add(
925             route_dist=route_dist,
926             import_rts=[route_dist],
927             export_rts=[route_dist],
928             route_family=RF_L2_EVPN)
929
930         # Note: For the VLAN Based service, ethernet_tag_id
931         # must be set to zero.
932         self.speaker.evpn_prefix_add(
933             route_type=EVPN_MULTICAST_ETAG_ROUTE,
934             route_dist=route_dist,
935             ethernet_tag_id=vni,
936             ip_addr=self.speaker.router_id,
937             next_hop=self.speaker.router_id)
938
939         network = EvpnNetwork(
940             vni=vni,
941             route_dist=route_dist,
942             ethernet_tag_id=0)
943         self.networks[vni] = network
944
945         return {vni: network.to_jsondict()}
946
947     def get_networks(self, vni=None):
948         if self.speaker is None:
949             raise BGPSpeakerNotFound()
950
951         if vni is not None:
952             network = self.networks.get(vni, None)
953             if network is None:
954                 raise VniNotFound(vni=vni)
955             return {vni: network.to_jsondict()}
956
957         networks = {}
958         for vni, network in self.networks.items():
959             networks[vni] = network.to_jsondict()
960
961         return networks
962
963     def del_network(self, vni):
964         if self.speaker is None:
965             raise BGPSpeakerNotFound()
966
967         datapath = self._get_datapath(self.speaker.dpid)
968         if datapath is None:
969             raise DatapathNotFound(dpid=self.speaker.dpid)
970
971         network = self.networks.get(vni, None)
972         if network is None:
973             raise VniNotFound(vni=vni)
974
975         for client in network.get_clients(next_hop=self.speaker.router_id):
976             self.del_client(
977                 vni=vni,
978                 mac=client.mac)
979
980         self._del_network_egress_flow(
981             datapath=datapath,
982             tag=vni)
983
984         for address in self.speaker.neighbors:
985             self._del_vxlan_port(
986                 dpid=self.speaker.dpid,
987                 remote_ip=address,
988                 key=vni)
989
990         self.speaker.evpn_prefix_del(
991             route_type=EVPN_MULTICAST_ETAG_ROUTE,
992             route_dist=network.route_dist,
993             ethernet_tag_id=vni,
994             ip_addr=self.speaker.router_id)
995
996         self.speaker.vrf_del(route_dist=network.route_dist)
997
998         network = self.networks.pop(vni)
999
1000         return {vni: network.to_jsondict()}
1001
1002     def add_client(self, vni, port, mac, ip):
1003         if self.speaker is None:
1004             raise BGPSpeakerNotFound()
1005
1006         datapath = self._get_datapath(self.speaker.dpid)
1007         if datapath is None:
1008             raise DatapathNotFound(dpid=self.speaker.dpid)
1009
1010         network = self.networks.get(vni, None)
1011         if network is None:
1012             raise VniNotFound(vni=vni)
1013
1014         port = self._get_ofport(self.speaker.dpid, port)
1015         if port is None:
1016             try:
1017                 port = to_int(port)
1018             except ValueError:
1019                 raise OFPortNotFound(port_name=port)
1020
1021         self._add_network_ingress_flow(
1022             datapath=datapath,
1023             tag=network.vni,
1024             in_port=port,
1025             eth_src=mac)
1026
1027         self._add_l2_switching_flow(
1028             datapath=datapath,
1029             tag=network.vni,
1030             eth_dst=mac,
1031             out_port=port)
1032
1033         # Note: For the VLAN Based service, ethernet_tag_id
1034         # must be set to zero.
1035         self.speaker.evpn_prefix_add(
1036             route_type=EVPN_MAC_IP_ADV_ROUTE,
1037             route_dist=network.route_dist,
1038             esi=0,
1039             ethernet_tag_id=0,
1040             mac_addr=mac,
1041             ip_addr=ip,
1042             vni=vni,
1043             next_hop=self.speaker.router_id,
1044             tunnel_type='vxlan')
1045
1046         # Stores local client info
1047         client = EvpnClient(
1048             port=port,
1049             mac=mac,
1050             ip=ip,
1051             next_hop=self.speaker.router_id)
1052         network.clients[mac] = client
1053
1054         return {vni: client.to_jsondict()}
1055
1056     def del_client(self, vni, mac):
1057         if self.speaker is None:
1058             raise BGPSpeakerNotFound()
1059
1060         datapath = self._get_datapath(self.speaker.dpid)
1061         if datapath is None:
1062             raise DatapathNotFound(dpid=self.speaker.dpid)
1063
1064         network = self.networks.get(vni, None)
1065         if network is None:
1066             raise VniNotFound(vni=vni)
1067
1068         client = network.clients.get(mac, None)
1069         if client is None:
1070             raise ClientNotFound(mac=mac)
1071         elif client.next_hop != self.speaker.router_id:
1072             raise ClientNotLocal(mac=mac)
1073
1074         self._del_network_ingress_flow(
1075             datapath=datapath,
1076             in_port=client.port,
1077             eth_src=mac)
1078
1079         self._del_l2_switching_flow(
1080             datapath=datapath,
1081             tag=network.vni,
1082             eth_dst=mac)
1083
1084         # Note: For the VLAN Based service, ethernet_tag_id
1085         # must be set to zero.
1086         self.speaker.evpn_prefix_del(
1087             route_type=EVPN_MAC_IP_ADV_ROUTE,
1088             route_dist=network.route_dist,
1089             esi=0,
1090             ethernet_tag_id=0,
1091             mac_addr=mac,
1092             ip_addr=client.ip)
1093
1094         client = network.clients.pop(mac)
1095
1096         return {vni: client.to_jsondict()}
1097
1098
1099 def post_method(keywords):
1100     def _wrapper(method):
1101         def __wrapper(self, req, **kwargs):
1102             try:
1103                 try:
1104                     body = req.json if req.body else {}
1105                 except ValueError:
1106                     raise ValueError('Invalid syntax %s', req.body)
1107                 kwargs.update(body)
1108                 for key, converter in keywords.items():
1109                     value = kwargs.get(key, None)
1110                     if value is None:
1111                         raise ValueError('%s not specified' % key)
1112                     kwargs[key] = converter(value)
1113             except ValueError as e:
1114                 return Response(content_type='application/json',
1115                                 body={"error": str(e)}, status=400)
1116             try:
1117                 return method(self, **kwargs)
1118             except Exception as e:
1119                 status = 500
1120                 body = {
1121                     "error": str(e),
1122                     "status": status,
1123                 }
1124                 return Response(content_type='application/json',
1125                                 body=json.dumps(body), status=status)
1126         __wrapper.__doc__ = method.__doc__
1127         return __wrapper
1128     return _wrapper
1129
1130
1131 def get_method(keywords=None):
1132     keywords = keywords or {}
1133
1134     def _wrapper(method):
1135         def __wrapper(self, _, **kwargs):
1136             try:
1137                 for key, converter in keywords.items():
1138                     value = kwargs.get(key, None)
1139                     if value is None:
1140                         continue
1141                     kwargs[key] = converter(value)
1142             except ValueError as e:
1143                 return Response(content_type='application/json',
1144                                 body={"error": str(e)}, status=400)
1145             try:
1146                 return method(self, **kwargs)
1147             except Exception as e:
1148                 status = 500
1149                 body = {
1150                     "error": str(e),
1151                     "status": status,
1152                 }
1153                 return Response(content_type='application/json',
1154                                 body=json.dumps(body), status=status)
1155         __wrapper.__doc__ = method.__doc__
1156         return __wrapper
1157     return _wrapper
1158
1159
1160 delete_method = get_method
1161
1162
1163 class RestVtepController(ControllerBase):
1164
1165     def __init__(self, req, link, data, **config):
1166         super(RestVtepController, self).__init__(req, link, data, **config)
1167         self.vtep_app = data[RestVtep.__name__]
1168         self.logger = self.vtep_app.logger
1169
1170     @route(API_NAME, '/vtep/speakers', methods=['POST'])
1171     @post_method(
1172         keywords={
1173             "dpid": to_int,
1174             "as_number": to_int,
1175             "router_id": str,
1176         })
1177     def add_speaker(self, **kwargs):
1178         """
1179         Creates a new BGPSpeaker instance.
1180
1181         Usage:
1182
1183             ======= ================
1184             Method  URI
1185             ======= ================
1186             POST    /vtep/speakers
1187             ======= ================
1188
1189         Request parameters:
1190
1191             ========== ============================================
1192             Attribute  Description
1193             ========== ============================================
1194             dpid       ID of Datapath binding to speaker. (e.g. 1)
1195             as_number  AS number. (e.g. 65000)
1196             router_id  Router ID. (e.g. "172.17.0.1")
1197             ========== ============================================
1198
1199         Example::
1200
1201             $ curl -X POST -d '{
1202              "dpid": 1,
1203              "as_number": 65000,
1204              "router_id": "172.17.0.1"
1205              }' http://localhost:8080/vtep/speakers | python -m json.tool
1206
1207         ::
1208
1209             {
1210                 "172.17.0.1": {
1211                     "EvpnSpeaker": {
1212                         "as_number": 65000,
1213                         "dpid": 1,
1214                         "neighbors": {},
1215                         "router_id": "172.17.0.1"
1216                     }
1217                 }
1218             }
1219         """
1220         try:
1221             body = self.vtep_app.add_speaker(**kwargs)
1222         except DatapathNotFound as e:
1223             return e.to_response(status=404)
1224
1225         return Response(content_type='application/json',
1226                         body=json.dumps(body))
1227
1228     @route(API_NAME, '/vtep/speakers', methods=['GET'])
1229     @get_method()
1230     def get_speakers(self, **kwargs):
1231         """
1232         Gets the info of BGPSpeaker instance.
1233
1234         Usage:
1235
1236             ======= ================
1237             Method  URI
1238             ======= ================
1239             GET     /vtep/speakers
1240             ======= ================
1241
1242         Example::
1243
1244             $ curl -X GET http://localhost:8080/vtep/speakers |
1245              python -m json.tool
1246
1247         ::
1248
1249             {
1250                 "172.17.0.1": {
1251                     "EvpnSpeaker": {
1252                         "as_number": 65000,
1253                         "dpid": 1,
1254                         "neighbors": {
1255                             "172.17.0.2": {
1256                                 "EvpnNeighbor": {
1257                                     "address": "172.17.0.2",
1258                                     "remote_as": 65000,
1259                                     "state": "up"
1260                                 }
1261                             }
1262                         },
1263                         "router_id": "172.17.0.1"
1264                     }
1265                 }
1266             }
1267         """
1268         try:
1269             body = self.vtep_app.get_speaker()
1270         except BGPSpeakerNotFound as e:
1271             return e.to_response(status=404)
1272
1273         return Response(content_type='application/json',
1274                         body=json.dumps(body))
1275
1276     @route(API_NAME, '/vtep/speakers', methods=['DELETE'])
1277     @delete_method()
1278     def del_speaker(self, **kwargs):
1279         """
1280         Shutdowns BGPSpeaker instance.
1281
1282         Usage:
1283
1284             ======= ================
1285             Method  URI
1286             ======= ================
1287             DELETE  /vtep/speakers
1288             ======= ================
1289
1290         Example::
1291
1292             $ curl -X DELETE http://localhost:8080/vtep/speakers |
1293              python -m json.tool
1294
1295         ::
1296
1297             {
1298                 "172.17.0.1": {
1299                     "EvpnSpeaker": {
1300                         "as_number": 65000,
1301                         "dpid": 1,
1302                         "neighbors": {},
1303                         "router_id": "172.17.0.1"
1304                     }
1305                 }
1306             }
1307         """
1308         try:
1309             body = self.vtep_app.del_speaker()
1310         except BGPSpeakerNotFound as e:
1311             return e.to_response(status=404)
1312
1313         return Response(content_type='application/json',
1314                         body=json.dumps(body))
1315
1316     @route(API_NAME, '/vtep/neighbors', methods=['POST'])
1317     @post_method(
1318         keywords={
1319             "address": str,
1320             "remote_as": to_int,
1321         })
1322     def add_neighbor(self, **kwargs):
1323         """
1324         Registers a new neighbor to the speaker.
1325
1326         Usage:
1327
1328             ======= ========================
1329             Method  URI
1330             ======= ========================
1331             POST    /vtep/neighbors
1332             ======= ========================
1333
1334         Request parameters:
1335
1336             ========== ================================================
1337             Attribute  Description
1338             ========== ================================================
1339             address    IP address of neighbor. (e.g. "172.17.0.2")
1340             remote_as  AS number of neighbor. (e.g. 65000)
1341             ========== ================================================
1342
1343         Example::
1344
1345             $ curl -X POST -d '{
1346              "address": "172.17.0.2",
1347              "remote_as": 65000
1348              }' http://localhost:8080/vtep/neighbors |
1349              python -m json.tool
1350
1351         ::
1352
1353             {
1354                 "172.17.0.2": {
1355                     "EvpnNeighbor": {
1356                         "address": "172.17.0.2",
1357                         "remote_as": 65000,
1358                         "state": "down"
1359                     }
1360                 }
1361             }
1362         """
1363         try:
1364             body = self.vtep_app.add_neighbor(**kwargs)
1365         except BGPSpeakerNotFound as e:
1366             return e.to_response(status=400)
1367
1368         return Response(content_type='application/json',
1369                         body=json.dumps(body))
1370
1371     def _get_neighbors(self, **kwargs):
1372         try:
1373             body = self.vtep_app.get_neighbors(**kwargs)
1374         except (BGPSpeakerNotFound, NeighborNotFound) as e:
1375             return e.to_response(status=404)
1376
1377         return Response(content_type='application/json',
1378                         body=json.dumps(body))
1379
1380     @route(API_NAME, '/vtep/neighbors', methods=['GET'])
1381     @get_method()
1382     def get_neighbors(self, **kwargs):
1383         """
1384         Gets a list of all neighbors.
1385
1386         Usage:
1387
1388             ======= ========================
1389             Method  URI
1390             ======= ========================
1391             GET     /vtep/neighbors
1392             ======= ========================
1393
1394         Example::
1395
1396             $ curl -X GET http://localhost:8080/vtep/neighbors |
1397              python -m json.tool
1398
1399         ::
1400
1401             {
1402                 "172.17.0.2": {
1403                     "EvpnNeighbor": {
1404                         "address": "172.17.0.2",
1405                         "remote_as": 65000,
1406                         "state": "up"
1407                     }
1408                 }
1409             }
1410         """
1411         return self._get_neighbors(**kwargs)
1412
1413     @route(API_NAME, '/vtep/neighbors/{address}', methods=['GET'])
1414     @get_method(
1415         keywords={
1416             "address": str,
1417         })
1418     def get_neighbor(self, **kwargs):
1419         """
1420         Gets the neighbor for the specified address.
1421
1422         Usage:
1423
1424             ======= ==================================
1425             Method  URI
1426             ======= ==================================
1427             GET     /vtep/neighbors/{address}
1428             ======= ==================================
1429
1430         Request parameters:
1431
1432             ========== ================================================
1433             Attribute  Description
1434             ========== ================================================
1435             address    IP address of neighbor. (e.g. "172.17.0.2")
1436             ========== ================================================
1437
1438         Example::
1439
1440             $ curl -X GET http://localhost:8080/vtep/neighbors/172.17.0.2 |
1441              python -m json.tool
1442
1443         ::
1444
1445             {
1446                 "172.17.0.2": {
1447                     "EvpnNeighbor": {
1448                         "address": "172.17.0.2",
1449                         "remote_as": 65000,
1450                         "state": "up"
1451                     }
1452                 }
1453             }
1454         """
1455         return self._get_neighbors(**kwargs)
1456
1457     @route(API_NAME, '/vtep/neighbors/{address}', methods=['DELETE'])
1458     @delete_method(
1459         keywords={
1460             "address": str,
1461         })
1462     def del_neighbor(self, **kwargs):
1463         """
1464         Unregister the specified neighbor from the speaker.
1465
1466         Usage:
1467
1468             ======= ==================================
1469             Method  URI
1470             ======= ==================================
1471             DELETE  /vtep/speaker/neighbors/{address}
1472             ======= ==================================
1473
1474         Request parameters:
1475
1476             ========== ================================================
1477             Attribute  Description
1478             ========== ================================================
1479             address    IP address of neighbor. (e.g. "172.17.0.2")
1480             ========== ================================================
1481
1482         Example::
1483
1484             $ curl -X DELETE http://localhost:8080/vtep/speaker/neighbors/172.17.0.2 |
1485              python -m json.tool
1486
1487         ::
1488
1489             {
1490                 "172.17.0.2": {
1491                     "EvpnNeighbor": {
1492                         "address": "172.17.0.2",
1493                         "remote_as": 65000,
1494                         "state": "up"
1495                     }
1496                 }
1497             }
1498         """
1499         try:
1500             body = self.vtep_app.del_neighbor(**kwargs)
1501         except (BGPSpeakerNotFound, NeighborNotFound) as e:
1502             return e.to_response(status=404)
1503
1504         return Response(content_type='application/json',
1505                         body=json.dumps(body))
1506
1507     @route(API_NAME, '/vtep/networks', methods=['POST'])
1508     @post_method(
1509         keywords={
1510             "vni": to_int,
1511         })
1512     def add_network(self, **kwargs):
1513         """
1514         Defines a new network.
1515
1516         Usage:
1517
1518             ======= ===============
1519             Method  URI
1520             ======= ===============
1521             POST    /vtep/networks
1522             ======= ===============
1523
1524         Request parameters:
1525
1526             ================ ========================================
1527             Attribute        Description
1528             ================ ========================================
1529             vni              Virtual Network Identifier. (e.g. 10)
1530             ================ ========================================
1531
1532         Example::
1533
1534             $ curl -X POST -d '{
1535              "vni": 10
1536              }' http://localhost:8080/vtep/networks | python -m json.tool
1537
1538         ::
1539
1540             {
1541                 "10": {
1542                     "EvpnNetwork": {
1543                         "clients": {},
1544                         "ethernet_tag_id": 0,
1545                         "route_dist": "65000:10",
1546                         "vni": 10
1547                     }
1548                 }
1549             }
1550         """
1551         try:
1552             body = self.vtep_app.add_network(**kwargs)
1553         except BGPSpeakerNotFound as e:
1554             return e.to_response(status=404)
1555
1556         return Response(content_type='application/json',
1557                         body=json.dumps(body))
1558
1559     def _get_networks(self, **kwargs):
1560         try:
1561             body = self.vtep_app.get_networks(**kwargs)
1562         except (BGPSpeakerNotFound, VniNotFound) as e:
1563             return e.to_response(status=404)
1564
1565         return Response(content_type='application/json',
1566                         body=json.dumps(body))
1567
1568     @route(API_NAME, '/vtep/networks', methods=['GET'])
1569     @get_method()
1570     def get_networks(self, **kwargs):
1571         """
1572         Gets a list of all networks.
1573
1574         Usage:
1575
1576             ======= ===============
1577             Method  URI
1578             ======= ===============
1579             GET     /vtep/networks
1580             ======= ===============
1581
1582         Example::
1583
1584             $ curl -X GET http://localhost:8080/vtep/networks |
1585              python -m json.tool
1586
1587         ::
1588
1589             {
1590                 "10": {
1591                     "EvpnNetwork": {
1592                         "clients": {
1593                             "aa:bb:cc:dd:ee:ff": {
1594                                 "EvpnClient": {
1595                                     "ip": "10.0.0.1",
1596                                     "mac": "aa:bb:cc:dd:ee:ff",
1597                                     "next_hop": "172.17.0.1",
1598                                     "port": 1
1599                                 }
1600                             }
1601                         },
1602                         "ethernet_tag_id": 0,
1603                         "route_dist": "65000:10",
1604                         "vni": 10
1605                     }
1606                 }
1607             }
1608         """
1609         return self._get_networks(**kwargs)
1610
1611     @route(API_NAME, '/vtep/networks/{vni}', methods=['GET'])
1612     @get_method(
1613         keywords={
1614             "vni": to_int,
1615         })
1616     def get_network(self, **kwargs):
1617         """
1618         Gets the network for the specified VNI.
1619
1620         Usage:
1621
1622             ======= =====================
1623             Method  URI
1624             ======= =====================
1625             GET     /vtep/networks/{vni}
1626             ======= =====================
1627
1628         Request parameters:
1629
1630             ================ ========================================
1631             Attribute        Description
1632             ================ ========================================
1633             vni              Virtual Network Identifier. (e.g. 10)
1634             ================ ========================================
1635
1636         Example::
1637
1638             $ curl -X GET http://localhost:8080/vtep/networks/10 |
1639              python -m json.tool
1640
1641         ::
1642
1643             {
1644                 "10": {
1645                     "EvpnNetwork": {
1646                         "clients": {
1647                             "aa:bb:cc:dd:ee:ff": {
1648                                 "EvpnClient": {
1649                                     "ip": "10.0.0.1",
1650                                     "mac": "aa:bb:cc:dd:ee:ff",
1651                                     "next_hop": "172.17.0.1",
1652                                     "port": 1
1653                                 }
1654                             }
1655                         },
1656                         "ethernet_tag_id": 0,
1657                         "route_dist": "65000:10",
1658                         "vni": 10
1659                     }
1660                 }
1661             }
1662         """
1663         return self._get_networks(**kwargs)
1664
1665     @route(API_NAME, '/vtep/networks/{vni}', methods=['DELETE'])
1666     @delete_method(
1667         keywords={
1668             "vni": to_int,
1669         })
1670     def del_network(self, **kwargs):
1671         """
1672         Deletes the network for the specified VNI.
1673
1674         Usage:
1675
1676             ======= =====================
1677             Method  URI
1678             ======= =====================
1679             DELETE  /vtep/networks/{vni}
1680             ======= =====================
1681
1682         Request parameters:
1683
1684             ================ ========================================
1685             Attribute        Description
1686             ================ ========================================
1687             vni              Virtual Network Identifier. (e.g. 10)
1688             ================ ========================================
1689
1690         Example::
1691
1692             $ curl -X DELETE http://localhost:8080/vtep/networks/10 |
1693              python -m json.tool
1694
1695         ::
1696
1697             {
1698                 "10": {
1699                     "EvpnNetwork": {
1700                         "ethernet_tag_id": 10,
1701                         "clients": [
1702                             {
1703                                 "EvpnClient": {
1704                                     "ip": "10.0.0.11",
1705                                     "mac": "e2:b1:0c:ba:42:ed",
1706                                     "port": 1
1707                                 }
1708                             }
1709                         ],
1710                         "route_dist": "65000:100",
1711                         "vni": 10
1712                     }
1713                 }
1714             }
1715         """
1716         try:
1717             body = self.vtep_app.del_network(**kwargs)
1718         except (BGPSpeakerNotFound, DatapathNotFound, VniNotFound) as e:
1719             return e.to_response(status=404)
1720
1721         return Response(content_type='application/json',
1722                         body=json.dumps(body))
1723
1724     @route(API_NAME, '/vtep/networks/{vni}/clients', methods=['POST'])
1725     @post_method(
1726         keywords={
1727             "vni": to_int,
1728             "port": str,
1729             "mac": str,
1730             "ip": str,
1731         })
1732     def add_client(self, **kwargs):
1733         """
1734         Registers a new client to the specified network.
1735
1736         Usage:
1737
1738             ======= =============================
1739             Method  URI
1740             ======= =============================
1741             POST    /vtep/networks/{vni}/clients
1742             ======= =============================
1743
1744         Request parameters:
1745
1746             =========== ===============================================
1747             Attribute   Description
1748             =========== ===============================================
1749             vni         Virtual Network Identifier. (e.g. 10)
1750             port        Port number to connect client.
1751                         For convenience, port name can be specified
1752                         and automatically translated to port number.
1753                         (e.g. "s1-eth1" or 1)
1754             mac         Client MAC address to register.
1755                         (e.g. "aa:bb:cc:dd:ee:ff")
1756             ip          Client IP address. (e.g. "10.0.0.1")
1757             =========== ===============================================
1758
1759         Example::
1760
1761             $ curl -X POST -d '{
1762              "port": "s1-eth1",
1763              "mac": "aa:bb:cc:dd:ee:ff",
1764              "ip": "10.0.0.1"
1765              }' http://localhost:8080/vtep/networks/10/clients |
1766              python -m json.tool
1767
1768         ::
1769
1770             {
1771                 "10": {
1772                     "EvpnClient": {
1773                         "ip": "10.0.0.1",
1774                         "mac": "aa:bb:cc:dd:ee:ff",
1775                         "next_hop": "172.17.0.1",
1776                         "port": 1
1777                     }
1778                 }
1779             }
1780         """
1781         try:
1782             body = self.vtep_app.add_client(**kwargs)
1783         except (BGPSpeakerNotFound, DatapathNotFound,
1784                 VniNotFound, OFPortNotFound) as e:
1785             return e.to_response(status=404)
1786
1787         return Response(content_type='application/json',
1788                         body=json.dumps(body))
1789
1790     @route(API_NAME, '/vtep/networks/{vni}/clients/{mac}', methods=['DELETE'])
1791     @delete_method(
1792         keywords={
1793             "vni": to_int,
1794             "mac": str,
1795         })
1796     def del_client(self, **kwargs):
1797         """
1798         Registers a new client to the specified network.
1799
1800         Usage:
1801
1802             ======= ===================================
1803             Method  URI
1804             ======= ===================================
1805             DELETE  /vtep/networks/{vni}/clients/{mac}
1806             ======= ===================================
1807
1808         Request parameters:
1809
1810             =========== ===============================================
1811             Attribute   Description
1812             =========== ===============================================
1813             vni         Virtual Network Identifier. (e.g. 10)
1814             mac         Client MAC address to register.
1815             =========== ===============================================
1816
1817         Example::
1818
1819             $ curl -X DELETE http://localhost:8080/vtep/networks/10/clients/aa:bb:cc:dd:ee:ff |
1820              python -m json.tool
1821
1822         ::
1823
1824             {
1825                 "10": {
1826                     "EvpnClient": {
1827                         "ip": "10.0.0.1",
1828                         "mac": "aa:bb:cc:dd:ee:ff",
1829                         "next_hop": "172.17.0.1",
1830                         "port": 1
1831                     }
1832                 }
1833             }
1834         """
1835         try:
1836             body = self.vtep_app.del_client(**kwargs)
1837         except (BGPSpeakerNotFound, DatapathNotFound,
1838                 VniNotFound, ClientNotFound, ClientNotLocal) as e:
1839             return Response(body=str(e), status=500)
1840
1841         return Response(content_type='application/json',
1842                         body=json.dumps(body))