1 # Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
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].
22 This app will invoke OVSDB request to the switches.
23 Please set the manager address before calling the API of this app.
27 $ sudo ovs-vsctl set-manager ptcp:6640
40 This example supposes the following environment::
42 Host A (172.17.0.1) Host B (172.17.0.2)
43 +--------------------+ +--------------------+
44 | Ryu1 | --- BGP(EVPN) --- | Ryu2 |
45 +--------------------+ +--------------------+
47 +--------------------+ +--------------------+
48 | s1 (OVS) | ===== vxlan ===== | s2 (OVS) |
49 +--------------------+ +--------------------+
50 (s1-eth1) (s1-eth2) (s2-eth1) (s2-eth2)
52 +--------+ +--------+ +--------+ +--------+
53 | s1h1 | | s1h2 | | s2h1 | | s2h2 |
54 +--------+ +--------+ +--------+ +--------+
59 1. Creates a new BGPSpeaker instance on each host.
63 (Host A)$ curl -X POST -d '{
66 "router_id": "172.17.0.1"
67 }' http://localhost:8080/vtep/speakers | python -m json.tool
71 (Host B)$ curl -X POST -d '{
74 "router_id": "172.17.0.2"
75 }' http://localhost:8080/vtep/speakers | python -m json.tool
77 2. Registers the neighbor for the speakers on each host.
81 (Host A)$ curl -X POST -d '{
82 "address": "172.17.0.2",
84 }' http://localhost:8080/vtep/neighbors |
89 (Host B)$ curl -X POST -d '{
90 "address": "172.17.0.1",
92 }' http://localhost:8080/vtep/neighbors |
95 3. Defines a new VXLAN network(VNI=10) on the Host A/B.
99 (Host A)$ curl -X POST -d '{
101 }' http://localhost:8080/vtep/networks | python -m json.tool
105 (Host B)$ curl -X POST -d '{
107 }' http://localhost:8080/vtep/networks | python -m json.tool
109 4. Registers the clients to the VXLAN network.
111 For "s1h1"(ip="10.0.0.11", mac="aa:bb:cc:00:00:11") on Host A::
113 (Host A)$ curl -X POST -d '{
115 "mac": "aa:bb:cc:00:00:11",
117 } ' http://localhost:8080/vtep/networks/10/clients |
120 For "s2h1"(ip="10.0.0.21", mac="aa:bb:cc:00:00:21") on Host B::
122 (Host B)$ curl -X POST -d '{
124 "mac": "aa:bb:cc:00:00:21",
126 } ' http://localhost:8080/vtep/networks/10/clients |
132 If BGP (EVPN) connection between Ryu1 and Ryu2 has been established,
133 pings between the client s1h1 and s2h1 should work.
137 (s1h1)$ ping 10.0.0.21
143 If connectivity between s1h1 and s2h1 isn't working,
144 please check the followings.
146 1. Make sure that Host A and Host B have full network connectivity.
150 (Host A)$ ping 172.17.0.2
152 2. Make sure that BGP(EVPN) connection has been established.
156 (Host A)$ curl -X GET http://localhost:8080/vtep/neighbors |
163 "address": "172.17.0.2",
165 "state": "up" # "up" shows the connection established
170 3. Make sure that BGP(EVPN) routes have been advertised.
174 (Host A)$ curl -X GET http://localhost:8080/vtep/networks |
182 "aa:bb:cc:00:00:11": {
185 "mac": "aa:bb:cc:00:00:11",
186 "next_hop": "172.17.0.1",
190 "aa:bb:cc:00:00:21": { # route for "s2h1" on Host B
193 "mac": "aa:bb:cc:00:00:21",
194 "next_hop": "172.17.0.2",
199 "ethernet_tag_id": 0,
200 "route_dist": "65000:10",
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.
212 $ sysctl net.ipv6.conf.all.disable_ipv6
213 net.ipv6.conf.all.disable_ipv6 = 0 # should NOT be enabled
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"
219 5. Make sure that your switch using the OpenFlow version 1.3. This application
220 supports only the OpenFlow version 1.3.
224 $ ovs-vsctl get Bridge s1 protocols
229 At the time of this writing, we use the the following version of Ryu,
230 Open vSwitch and Mininet.
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
243 2.2.1 # APT packaged version of Ubuntu 16.04
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
269 API_NAME = 'restvtep'
271 OVSDB_PORT = 6640 # The IANA registered port for OVSDB [RFC7047]
274 PRIORITY_ARP_REPLAY = 2
283 return int(str(i), 0)
289 str_list.append(str(s))
293 # Exception classes related to OpenFlow and OVSDB
295 class RestApiException(RyuException):
297 def to_response(self, status):
302 return Response(content_type='application/json',
303 body=json.dumps(body), status=status)
306 class DatapathNotFound(RestApiException):
307 message = 'No such datapath: %(dpid)s'
310 class OFPortNotFound(RestApiException):
311 message = 'No such OFPort: %(port_name)s'
314 # Exception classes related to BGP
316 class BGPSpeakerNotFound(RestApiException):
317 message = 'BGPSpeaker could not be found'
320 class NeighborNotFound(RestApiException):
321 message = 'No such neighbor: %(address)s'
324 class VniNotFound(RestApiException):
325 message = 'No such VNI: %(vni)s'
328 class ClientNotFound(RestApiException):
329 message = 'No such client: %(mac)s'
332 class ClientNotLocal(RestApiException):
333 message = 'Specified client is not local: %(mac)s'
336 # Utility classes related to EVPN
338 class EvpnSpeaker(BGPSpeaker, StringifyMixin):
345 def __init__(self, dpid, as_number, router_id,
346 best_path_change_handler,
347 peer_down_handler, peer_up_handler,
349 super(EvpnSpeaker, self).__init__(
352 best_path_change_handler=best_path_change_handler,
353 peer_down_handler=peer_down_handler,
354 peer_up_handler=peer_up_handler,
358 self.as_number = as_number
359 self.router_id = router_id
360 self.neighbors = neighbors or {}
363 class EvpnNeighbor(StringifyMixin):
371 def __init__(self, address, remote_as, state='down'):
372 super(EvpnNeighbor, self).__init__()
373 self.address = address
374 self.remote_as = remote_as
378 class EvpnNetwork(StringifyMixin):
385 def __init__(self, vni, route_dist, ethernet_tag_id, clients=None):
386 super(EvpnNetwork, self).__init__()
388 self.route_dist = route_dist
389 self.ethernet_tag_id = ethernet_tag_id
390 self.clients = clients or {}
392 def get_clients(self, **kwargs):
394 for _, c in self.clients.items():
395 for k, v in kwargs.items():
396 if getattr(c, k) != v:
403 class EvpnClient(StringifyMixin):
412 def __init__(self, port, mac, ip, next_hop):
413 super(EvpnClient, self).__init__()
417 self.next_hop = next_hop
420 class RestVtep(app_manager.RyuApp):
421 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
422 _CONTEXTS = {'wsgi': WSGIApplication}
424 def __init__(self, *args, **kwargs):
425 super(RestVtep, self).__init__(*args, **kwargs)
426 wsgi = kwargs['wsgi']
427 wsgi.register(RestVtepController, {RestVtep.__name__: self})
429 # EvpnSpeaker instance instantiated later
432 # OVSBridge instance instantiated later
435 # Dictionary for retrieving the EvpnNetwork instance by VNI
437 # <vni>: <instance 'EvpnNetwork'>,
442 # Utility methods related to OpenFlow
444 def _get_datapath(self, dpid):
445 return ofctl_api.get_datapath(self, dpid)
448 def _add_flow(datapath, priority, match, instructions,
449 table_id=TABLE_ID_INGRESS):
450 parser = datapath.ofproto_parser
452 mod = parser.OFPFlowMod(
457 instructions=instructions)
459 datapath.send_msg(mod)
462 def _del_flow(datapath, priority, match, table_id=TABLE_ID_INGRESS):
463 ofproto = datapath.ofproto
464 parser = datapath.ofproto_parser
466 mod = parser.OFPFlowMod(
469 command=ofproto.OFPFC_DELETE,
471 out_port=ofproto.OFPP_ANY,
472 out_group=ofproto.OFPG_ANY,
475 datapath.send_msg(mod)
477 def _add_network_ingress_flow(self, datapath, tag, in_port, eth_src=None):
478 parser = datapath.ofproto_parser
481 match = parser.OFPMatch(in_port=in_port)
483 match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
485 parser.OFPInstructionWriteMetadata(
486 metadata=tag, metadata_mask=parser.UINT64_MAX),
487 parser.OFPInstructionGotoTable(1)]
489 self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions)
491 def _del_network_ingress_flow(self, datapath, in_port, eth_src=None):
492 parser = datapath.ofproto_parser
495 match = parser.OFPMatch(in_port=in_port)
497 match = parser.OFPMatch(in_port=in_port, eth_src=eth_src)
499 self._del_flow(datapath, PRIORITY_D_PLANE, match)
501 def _add_arp_reply_flow(self, datapath, tag, arp_tpa, arp_tha):
502 ofproto = datapath.ofproto
503 parser = datapath.ofproto_parser
505 match = parser.OFPMatch(
506 metadata=(tag, parser.UINT64_MAX),
507 eth_type=ether_types.ETH_TYPE_ARP,
508 arp_op=arp.ARP_REQUEST,
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)]
524 parser.OFPInstructionActions(
525 ofproto.OFPIT_APPLY_ACTIONS, actions)]
527 self._add_flow(datapath, PRIORITY_ARP_REPLAY, match, instructions,
528 table_id=TABLE_ID_EGRESS)
530 def _del_arp_reply_flow(self, datapath, tag, arp_tpa):
531 parser = datapath.ofproto_parser
533 match = parser.OFPMatch(
534 metadata=(tag, parser.UINT64_MAX),
535 eth_type=ether_types.ETH_TYPE_ARP,
536 arp_op=arp.ARP_REQUEST,
539 self._del_flow(datapath, PRIORITY_ARP_REPLAY, match,
540 table_id=TABLE_ID_EGRESS)
542 def _add_l2_switching_flow(self, datapath, tag, eth_dst, out_port):
543 ofproto = datapath.ofproto
544 parser = datapath.ofproto_parser
546 match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
548 actions = [parser.OFPActionOutput(out_port)]
550 parser.OFPInstructionActions(
551 ofproto.OFPIT_APPLY_ACTIONS, actions)]
553 self._add_flow(datapath, PRIORITY_D_PLANE, match, instructions,
554 table_id=TABLE_ID_EGRESS)
556 def _del_l2_switching_flow(self, datapath, tag, eth_dst):
557 parser = datapath.ofproto_parser
559 match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX),
562 self._del_flow(datapath, PRIORITY_D_PLANE, match,
563 table_id=TABLE_ID_EGRESS)
565 def _del_network_egress_flow(self, datapath, tag):
566 parser = datapath.ofproto_parser
568 match = parser.OFPMatch(metadata=(tag, parser.UINT64_MAX))
570 self._del_flow(datapath, PRIORITY_D_PLANE, match,
571 table_id=TABLE_ID_EGRESS)
573 # Utility methods related to OVSDB
575 def _get_ovs_bridge(self, dpid):
576 datapath = self._get_datapath(dpid)
578 self.logger.debug('No such datapath: %s', dpid)
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):
588 self.ovs = ovs_bridge.OVSBridge(
590 datapath_id=datapath.id,
591 ovsdb_addr=ovsdb_addr)
593 except Exception as e:
594 self.logger.exception('Cannot initiate OVSDB connection: %s', e)
599 def _get_ofport(self, dpid, port_name):
600 ovs = self._get_ovs_bridge(dpid)
605 return ovs.get_ofport(port_name)
606 except Exception as e:
607 self.logger.debug('Cannot get port number for %s: %s',
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))
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:
621 ovs = self._get_ovs_bridge(dpid)
625 # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
627 name='vxlan_%s_%s' % (remote_ip, key),
631 # Returns VXLAN port number
632 return self._get_vxlan_port(dpid, remote_ip, key)
634 def _del_vxlan_port(self, dpid, remote_ip, key):
635 ovs = self._get_ovs_bridge(dpid)
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:
644 # Adds VXLAN port named 'vxlan_<remote_ip>_<key>'
645 ovs.del_port('vxlan_%s_%s' % (remote_ip, key))
647 # Returns deleted VXLAN port number
650 # Event handlers for BGP
652 def _evpn_mac_ip_adv_route_handler(self, ev):
653 network = self.networks.get(ev.path.nlri.vni, None)
655 self.logger.debug('No such VNI registered: %s', ev.path.nlri)
658 datapath = self._get_datapath(self.speaker.dpid)
660 self.logger.debug('No such datapath: %s', self.speaker.dpid)
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))
672 self._add_l2_switching_flow(
675 eth_dst=ev.path.nlri.mac_addr,
678 self._add_arp_reply_flow(
681 arp_tpa=ev.path.nlri.ip_addr,
682 arp_tha=ev.path.nlri.mac_addr)
684 network.clients[ev.path.nlri.mac_addr] = EvpnClient(
686 mac=ev.path.nlri.mac_addr,
687 ip=ev.path.nlri.ip_addr,
690 def _evpn_incl_mcast_etag_route_handler(self, ev):
691 # Note: For the VLAN Based service, we use RT(=RD) assigned
693 vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
695 network = self.networks.get(vni, None)
697 self.logger.debug('No such VNI registered: %s', vni)
700 datapath = self._get_datapath(self.speaker.dpid)
702 self.logger.debug('No such datapath: %s', self.speaker.dpid)
705 vxlan_port = self._add_vxlan_port(
706 dpid=self.speaker.dpid,
707 remote_ip=ev.nexthop,
709 if vxlan_port is None:
710 self.logger.debug('Cannot create a new VXLAN port: %s',
711 'vxlan_%s_%s' % (ev.nexthop, vni))
714 self._add_network_ingress_flow(
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)
725 def _evpn_withdraw_mac_ip_adv_route_handler(self, ev):
726 network = self.networks.get(ev.path.nlri.vni, None)
728 self.logger.debug('No such VNI registered: %s', ev.path.nlri)
731 datapath = self._get_datapath(self.speaker.dpid)
733 self.logger.debug('No such datapath: %s', self.speaker.dpid)
736 client = network.clients.get(ev.path.nlri.mac_addr, None)
738 self.logger.debug('No such client: %s', ev.path.nlri.mac_addr)
741 self._del_l2_switching_flow(
744 eth_dst=ev.path.nlri.mac_addr)
746 self._del_arp_reply_flow(
749 arp_tpa=ev.path.nlri.ip_addr)
751 network.clients.pop(ev.path.nlri.mac_addr)
753 def _evpn_withdraw_incl_mcast_etag_route_handler(self, ev):
754 # Note: For the VLAN Based service, we use RT(=RD) assigned
756 vni = _RouteDistinguisher.from_str(ev.path.nlri.route_dist).assigned
758 network = self.networks.get(vni, None)
760 self.logger.debug('No such VNI registered: %s', vni)
763 datapath = self._get_datapath(self.speaker.dpid)
765 self.logger.debug('No such datapath: %s', self.speaker.dpid)
768 vxlan_port = self._get_vxlan_port(
769 dpid=self.speaker.dpid,
770 remote_ip=ev.nexthop,
772 if vxlan_port is None:
773 self.logger.debug('No such VXLAN port: %s',
774 'vxlan_%s_%s' % (ev.nexthop, vni))
777 self._del_network_ingress_flow(
781 vxlan_port = self._del_vxlan_port(
782 dpid=self.speaker.dpid,
783 remote_ip=ev.nexthop,
785 if vxlan_port is None:
786 self.logger.debug('Cannot delete VXLAN port: %s',
787 'vxlan_%s_%s' % (ev.nexthop, vni))
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)
796 def _best_path_change_handler(self, ev):
797 if not isinstance(ev.path, EvpnPath):
798 # Ignores non-EVPN routes
800 elif ev.nexthop == self.speaker.router_id:
801 # Ignore local connected routes
804 self._evpn_withdraw_route_handler(ev)
806 self._evpn_route_handler(ev)
808 def _peer_down_handler(self, remote_ip, remote_as):
809 neighbor = self.speaker.neighbors.get(remote_ip, None)
811 self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
812 remote_ip, remote_as)
815 neighbor.state = 'down'
817 def _peer_up_handler(self, remote_ip, remote_as):
818 neighbor = self.speaker.neighbors.get(remote_ip, None)
820 self.logger.debug('No such neighbor: remote_ip=%s, remote_as=%s',
821 remote_ip, remote_as)
824 neighbor.state = 'up'
826 # API methods for REST controller
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)
832 raise DatapathNotFound(dpid=dpid)
834 self.speaker = EvpnSpeaker(
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)
842 return {self.speaker.router_id: self.speaker.to_jsondict()}
844 def get_speaker(self):
845 if self.speaker is None:
846 return BGPSpeakerNotFound()
848 return {self.speaker.router_id: self.speaker.to_jsondict()}
850 def del_speaker(self):
851 if self.speaker is None:
852 return BGPSpeakerNotFound()
854 for vni in list(self.networks.keys()):
855 self.del_network(vni=vni)
857 for address in list(self.speaker.neighbors.keys()):
858 self.del_neighbor(address=address)
860 self.speaker.shutdown()
861 speaker = self.speaker
864 return {speaker.router_id: speaker.to_jsondict()}
866 def add_neighbor(self, address, remote_as):
867 if self.speaker is None:
868 raise BGPSpeakerNotFound()
870 self.speaker.neighbor_add(
875 neighbor = EvpnNeighbor(
878 self.speaker.neighbors[address] = neighbor
880 return {address: neighbor.to_jsondict()}
882 def get_neighbors(self, address=None):
883 if self.speaker is None:
884 raise BGPSpeakerNotFound()
886 if address is not None:
887 neighbor = self.speaker.neighbors.get(address, None)
889 raise NeighborNotFound(address=address)
890 return {address: neighbor.to_jsondict()}
893 for address, neighbor in self.speaker.neighbors.items():
894 neighbors[address] = neighbor.to_jsondict()
898 def del_neighbor(self, address):
899 if self.speaker is None:
900 raise BGPSpeakerNotFound()
902 neighbor = self.speaker.neighbors.get(address, None)
904 raise NeighborNotFound(address=address)
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)
911 self.speaker.neighbor_del(address=address)
913 neighbor = self.speaker.neighbors.pop(address)
915 return {address: neighbor.to_jsondict()}
917 def add_network(self, vni):
918 if self.speaker is None:
919 raise BGPSpeakerNotFound()
921 # Constructs type 0 RD with as_number and vni
922 route_dist = "%s:%d" % (self.speaker.as_number, vni)
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)
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,
936 ip_addr=self.speaker.router_id,
937 next_hop=self.speaker.router_id)
939 network = EvpnNetwork(
941 route_dist=route_dist,
943 self.networks[vni] = network
945 return {vni: network.to_jsondict()}
947 def get_networks(self, vni=None):
948 if self.speaker is None:
949 raise BGPSpeakerNotFound()
952 network = self.networks.get(vni, None)
954 raise VniNotFound(vni=vni)
955 return {vni: network.to_jsondict()}
958 for vni, network in self.networks.items():
959 networks[vni] = network.to_jsondict()
963 def del_network(self, vni):
964 if self.speaker is None:
965 raise BGPSpeakerNotFound()
967 datapath = self._get_datapath(self.speaker.dpid)
969 raise DatapathNotFound(dpid=self.speaker.dpid)
971 network = self.networks.get(vni, None)
973 raise VniNotFound(vni=vni)
975 for client in network.get_clients(next_hop=self.speaker.router_id):
980 self._del_network_egress_flow(
984 for address in self.speaker.neighbors:
985 self._del_vxlan_port(
986 dpid=self.speaker.dpid,
990 self.speaker.evpn_prefix_del(
991 route_type=EVPN_MULTICAST_ETAG_ROUTE,
992 route_dist=network.route_dist,
994 ip_addr=self.speaker.router_id)
996 self.speaker.vrf_del(route_dist=network.route_dist)
998 network = self.networks.pop(vni)
1000 return {vni: network.to_jsondict()}
1002 def add_client(self, vni, port, mac, ip):
1003 if self.speaker is None:
1004 raise BGPSpeakerNotFound()
1006 datapath = self._get_datapath(self.speaker.dpid)
1007 if datapath is None:
1008 raise DatapathNotFound(dpid=self.speaker.dpid)
1010 network = self.networks.get(vni, None)
1012 raise VniNotFound(vni=vni)
1014 port = self._get_ofport(self.speaker.dpid, port)
1019 raise OFPortNotFound(port_name=port)
1021 self._add_network_ingress_flow(
1027 self._add_l2_switching_flow(
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,
1043 next_hop=self.speaker.router_id,
1044 tunnel_type='vxlan')
1046 # Stores local client info
1047 client = EvpnClient(
1051 next_hop=self.speaker.router_id)
1052 network.clients[mac] = client
1054 return {vni: client.to_jsondict()}
1056 def del_client(self, vni, mac):
1057 if self.speaker is None:
1058 raise BGPSpeakerNotFound()
1060 datapath = self._get_datapath(self.speaker.dpid)
1061 if datapath is None:
1062 raise DatapathNotFound(dpid=self.speaker.dpid)
1064 network = self.networks.get(vni, None)
1066 raise VniNotFound(vni=vni)
1068 client = network.clients.get(mac, None)
1070 raise ClientNotFound(mac=mac)
1071 elif client.next_hop != self.speaker.router_id:
1072 raise ClientNotLocal(mac=mac)
1074 self._del_network_ingress_flow(
1076 in_port=client.port,
1079 self._del_l2_switching_flow(
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,
1094 client = network.clients.pop(mac)
1096 return {vni: client.to_jsondict()}
1099 def post_method(keywords):
1100 def _wrapper(method):
1101 def __wrapper(self, req, **kwargs):
1104 body = req.json if req.body else {}
1106 raise ValueError('Invalid syntax %s', req.body)
1108 for key, converter in keywords.items():
1109 value = kwargs.get(key, 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)
1117 return method(self, **kwargs)
1118 except Exception as e:
1124 return Response(content_type='application/json',
1125 body=json.dumps(body), status=status)
1126 __wrapper.__doc__ = method.__doc__
1131 def get_method(keywords=None):
1132 keywords = keywords or {}
1134 def _wrapper(method):
1135 def __wrapper(self, _, **kwargs):
1137 for key, converter in keywords.items():
1138 value = kwargs.get(key, None)
1141 kwargs[key] = converter(value)
1142 except ValueError as e:
1143 return Response(content_type='application/json',
1144 body={"error": str(e)}, status=400)
1146 return method(self, **kwargs)
1147 except Exception as e:
1153 return Response(content_type='application/json',
1154 body=json.dumps(body), status=status)
1155 __wrapper.__doc__ = method.__doc__
1160 delete_method = get_method
1163 class RestVtepController(ControllerBase):
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
1170 @route(API_NAME, '/vtep/speakers', methods=['POST'])
1174 "as_number": to_int,
1177 def add_speaker(self, **kwargs):
1179 Creates a new BGPSpeaker instance.
1183 ======= ================
1185 ======= ================
1187 ======= ================
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 ========== ============================================
1201 $ curl -X POST -d '{
1204 "router_id": "172.17.0.1"
1205 }' http://localhost:8080/vtep/speakers | python -m json.tool
1215 "router_id": "172.17.0.1"
1221 body = self.vtep_app.add_speaker(**kwargs)
1222 except DatapathNotFound as e:
1223 return e.to_response(status=404)
1225 return Response(content_type='application/json',
1226 body=json.dumps(body))
1228 @route(API_NAME, '/vtep/speakers', methods=['GET'])
1230 def get_speakers(self, **kwargs):
1232 Gets the info of BGPSpeaker instance.
1236 ======= ================
1238 ======= ================
1240 ======= ================
1244 $ curl -X GET http://localhost:8080/vtep/speakers |
1257 "address": "172.17.0.2",
1263 "router_id": "172.17.0.1"
1269 body = self.vtep_app.get_speaker()
1270 except BGPSpeakerNotFound as e:
1271 return e.to_response(status=404)
1273 return Response(content_type='application/json',
1274 body=json.dumps(body))
1276 @route(API_NAME, '/vtep/speakers', methods=['DELETE'])
1278 def del_speaker(self, **kwargs):
1280 Shutdowns BGPSpeaker instance.
1284 ======= ================
1286 ======= ================
1287 DELETE /vtep/speakers
1288 ======= ================
1292 $ curl -X DELETE http://localhost:8080/vtep/speakers |
1303 "router_id": "172.17.0.1"
1309 body = self.vtep_app.del_speaker()
1310 except BGPSpeakerNotFound as e:
1311 return e.to_response(status=404)
1313 return Response(content_type='application/json',
1314 body=json.dumps(body))
1316 @route(API_NAME, '/vtep/neighbors', methods=['POST'])
1320 "remote_as": to_int,
1322 def add_neighbor(self, **kwargs):
1324 Registers a new neighbor to the speaker.
1328 ======= ========================
1330 ======= ========================
1331 POST /vtep/neighbors
1332 ======= ========================
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 ========== ================================================
1345 $ curl -X POST -d '{
1346 "address": "172.17.0.2",
1348 }' http://localhost:8080/vtep/neighbors |
1356 "address": "172.17.0.2",
1364 body = self.vtep_app.add_neighbor(**kwargs)
1365 except BGPSpeakerNotFound as e:
1366 return e.to_response(status=400)
1368 return Response(content_type='application/json',
1369 body=json.dumps(body))
1371 def _get_neighbors(self, **kwargs):
1373 body = self.vtep_app.get_neighbors(**kwargs)
1374 except (BGPSpeakerNotFound, NeighborNotFound) as e:
1375 return e.to_response(status=404)
1377 return Response(content_type='application/json',
1378 body=json.dumps(body))
1380 @route(API_NAME, '/vtep/neighbors', methods=['GET'])
1382 def get_neighbors(self, **kwargs):
1384 Gets a list of all neighbors.
1388 ======= ========================
1390 ======= ========================
1392 ======= ========================
1396 $ curl -X GET http://localhost:8080/vtep/neighbors |
1404 "address": "172.17.0.2",
1411 return self._get_neighbors(**kwargs)
1413 @route(API_NAME, '/vtep/neighbors/{address}', methods=['GET'])
1418 def get_neighbor(self, **kwargs):
1420 Gets the neighbor for the specified address.
1424 ======= ==================================
1426 ======= ==================================
1427 GET /vtep/neighbors/{address}
1428 ======= ==================================
1432 ========== ================================================
1433 Attribute Description
1434 ========== ================================================
1435 address IP address of neighbor. (e.g. "172.17.0.2")
1436 ========== ================================================
1440 $ curl -X GET http://localhost:8080/vtep/neighbors/172.17.0.2 |
1448 "address": "172.17.0.2",
1455 return self._get_neighbors(**kwargs)
1457 @route(API_NAME, '/vtep/neighbors/{address}', methods=['DELETE'])
1462 def del_neighbor(self, **kwargs):
1464 Unregister the specified neighbor from the speaker.
1468 ======= ==================================
1470 ======= ==================================
1471 DELETE /vtep/speaker/neighbors/{address}
1472 ======= ==================================
1476 ========== ================================================
1477 Attribute Description
1478 ========== ================================================
1479 address IP address of neighbor. (e.g. "172.17.0.2")
1480 ========== ================================================
1484 $ curl -X DELETE http://localhost:8080/vtep/speaker/neighbors/172.17.0.2 |
1492 "address": "172.17.0.2",
1500 body = self.vtep_app.del_neighbor(**kwargs)
1501 except (BGPSpeakerNotFound, NeighborNotFound) as e:
1502 return e.to_response(status=404)
1504 return Response(content_type='application/json',
1505 body=json.dumps(body))
1507 @route(API_NAME, '/vtep/networks', methods=['POST'])
1512 def add_network(self, **kwargs):
1514 Defines a new network.
1518 ======= ===============
1520 ======= ===============
1522 ======= ===============
1526 ================ ========================================
1527 Attribute Description
1528 ================ ========================================
1529 vni Virtual Network Identifier. (e.g. 10)
1530 ================ ========================================
1534 $ curl -X POST -d '{
1536 }' http://localhost:8080/vtep/networks | python -m json.tool
1544 "ethernet_tag_id": 0,
1545 "route_dist": "65000:10",
1552 body = self.vtep_app.add_network(**kwargs)
1553 except BGPSpeakerNotFound as e:
1554 return e.to_response(status=404)
1556 return Response(content_type='application/json',
1557 body=json.dumps(body))
1559 def _get_networks(self, **kwargs):
1561 body = self.vtep_app.get_networks(**kwargs)
1562 except (BGPSpeakerNotFound, VniNotFound) as e:
1563 return e.to_response(status=404)
1565 return Response(content_type='application/json',
1566 body=json.dumps(body))
1568 @route(API_NAME, '/vtep/networks', methods=['GET'])
1570 def get_networks(self, **kwargs):
1572 Gets a list of all networks.
1576 ======= ===============
1578 ======= ===============
1580 ======= ===============
1584 $ curl -X GET http://localhost:8080/vtep/networks |
1593 "aa:bb:cc:dd:ee:ff": {
1596 "mac": "aa:bb:cc:dd:ee:ff",
1597 "next_hop": "172.17.0.1",
1602 "ethernet_tag_id": 0,
1603 "route_dist": "65000:10",
1609 return self._get_networks(**kwargs)
1611 @route(API_NAME, '/vtep/networks/{vni}', methods=['GET'])
1616 def get_network(self, **kwargs):
1618 Gets the network for the specified VNI.
1622 ======= =====================
1624 ======= =====================
1625 GET /vtep/networks/{vni}
1626 ======= =====================
1630 ================ ========================================
1631 Attribute Description
1632 ================ ========================================
1633 vni Virtual Network Identifier. (e.g. 10)
1634 ================ ========================================
1638 $ curl -X GET http://localhost:8080/vtep/networks/10 |
1647 "aa:bb:cc:dd:ee:ff": {
1650 "mac": "aa:bb:cc:dd:ee:ff",
1651 "next_hop": "172.17.0.1",
1656 "ethernet_tag_id": 0,
1657 "route_dist": "65000:10",
1663 return self._get_networks(**kwargs)
1665 @route(API_NAME, '/vtep/networks/{vni}', methods=['DELETE'])
1670 def del_network(self, **kwargs):
1672 Deletes the network for the specified VNI.
1676 ======= =====================
1678 ======= =====================
1679 DELETE /vtep/networks/{vni}
1680 ======= =====================
1684 ================ ========================================
1685 Attribute Description
1686 ================ ========================================
1687 vni Virtual Network Identifier. (e.g. 10)
1688 ================ ========================================
1692 $ curl -X DELETE http://localhost:8080/vtep/networks/10 |
1700 "ethernet_tag_id": 10,
1705 "mac": "e2:b1:0c:ba:42:ed",
1710 "route_dist": "65000:100",
1717 body = self.vtep_app.del_network(**kwargs)
1718 except (BGPSpeakerNotFound, DatapathNotFound, VniNotFound) as e:
1719 return e.to_response(status=404)
1721 return Response(content_type='application/json',
1722 body=json.dumps(body))
1724 @route(API_NAME, '/vtep/networks/{vni}/clients', methods=['POST'])
1732 def add_client(self, **kwargs):
1734 Registers a new client to the specified network.
1738 ======= =============================
1740 ======= =============================
1741 POST /vtep/networks/{vni}/clients
1742 ======= =============================
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 =========== ===============================================
1761 $ curl -X POST -d '{
1763 "mac": "aa:bb:cc:dd:ee:ff",
1765 }' http://localhost:8080/vtep/networks/10/clients |
1774 "mac": "aa:bb:cc:dd:ee:ff",
1775 "next_hop": "172.17.0.1",
1782 body = self.vtep_app.add_client(**kwargs)
1783 except (BGPSpeakerNotFound, DatapathNotFound,
1784 VniNotFound, OFPortNotFound) as e:
1785 return e.to_response(status=404)
1787 return Response(content_type='application/json',
1788 body=json.dumps(body))
1790 @route(API_NAME, '/vtep/networks/{vni}/clients/{mac}', methods=['DELETE'])
1796 def del_client(self, **kwargs):
1798 Registers a new client to the specified network.
1802 ======= ===================================
1804 ======= ===================================
1805 DELETE /vtep/networks/{vni}/clients/{mac}
1806 ======= ===================================
1810 =========== ===============================================
1811 Attribute Description
1812 =========== ===============================================
1813 vni Virtual Network Identifier. (e.g. 10)
1814 mac Client MAC address to register.
1815 =========== ===============================================
1819 $ curl -X DELETE http://localhost:8080/vtep/networks/10/clients/aa:bb:cc:dd:ee:ff |
1828 "mac": "aa:bb:cc:dd:ee:ff",
1829 "next_hop": "172.17.0.1",
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)
1841 return Response(content_type='application/json',
1842 body=json.dumps(body))