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"""
17This sample application performs as VTEP for EVPN VXLAN and constructs
18a 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
34Usage Example
35=============
36
37Environment
38-----------
39
40This 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
56Configuration steps
57-------------------
58
591. 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
772. 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
953. 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
1094. 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
129Testing
130-------
131
132If BGP (EVPN) connection between Ryu1 and Ryu2 has been established,
133pings between the client s1h1 and s2h1 should work.
134
135::
136
137    (s1h1)$ ping 10.0.0.21
138
139
140Troubleshooting
141---------------
142
143If connectivity between s1h1 and s2h1 isn't working,
144please check the followings.
145
1461. Make sure that Host A and Host B have full network connectivity.
147
148    ::
149
150        (Host A)$ ping 172.17.0.2
151
1522. 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
1703. 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
2064. Make sure that the IPv6 is enabled on your environment. Some Ryu BGP
207features require the IPv6 connectivity to bind sockets. Mininet seems to
208disable 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
2195. Make sure that your switch using the OpenFlow version 1.3. This application
220supports 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
246import json
247
248from ryu.app.ofctl import api as ofctl_api
249from ryu.app.wsgi import ControllerBase
250from ryu.app.wsgi import Response
251from ryu.app.wsgi import route
252from ryu.app.wsgi import WSGIApplication
253from ryu.base import app_manager
254from ryu.exception import RyuException
255from ryu.lib.ovs import bridge as ovs_bridge
256from ryu.lib.packet import arp
257from ryu.lib.packet import ether_types
258from ryu.lib.packet.bgp import _RouteDistinguisher
259from ryu.lib.packet.bgp import EvpnNLRI
260from ryu.lib.stringify import StringifyMixin
261from ryu.ofproto import ofproto_v1_3
262from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker
263from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN
264from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE
265from ryu.services.protocols.bgp.bgpspeaker import EVPN_MULTICAST_ETAG_ROUTE
266from ryu.services.protocols.bgp.info_base.evpn import EvpnPath
267
268
269API_NAME = 'restvtep'
270
271OVSDB_PORT = 6640  # The IANA registered port for OVSDB [RFC7047]
272
273PRIORITY_D_PLANE = 1
274PRIORITY_ARP_REPLAY = 2
275
276TABLE_ID_INGRESS = 0
277TABLE_ID_EGRESS = 1
278
279
280# Utility functions
281
282def to_int(i):
283    return int(str(i), 0)
284
285
286def 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
295class 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
306class DatapathNotFound(RestApiException):
307    message = 'No such datapath: %(dpid)s'
308
309
310class OFPortNotFound(RestApiException):
311    message = 'No such OFPort: %(port_name)s'
312
313
314# Exception classes related to BGP
315
316class BGPSpeakerNotFound(RestApiException):
317    message = 'BGPSpeaker could not be found'
318
319
320class NeighborNotFound(RestApiException):
321    message = 'No such neighbor: %(address)s'
322
323
324class VniNotFound(RestApiException):
325    message = 'No such VNI: %(vni)s'
326
327
328class ClientNotFound(RestApiException):
329    message = 'No such client: %(mac)s'
330
331
332class ClientNotLocal(RestApiException):
333    message = 'Specified client is not local: %(mac)s'
334
335
336# Utility classes related to EVPN
337
338class 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
363class 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
378class 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
403class 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
420class 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
1099def 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
1131def 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
1160delete_method = get_method
1161
1162
1163class 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))
1843