datapath:backport: openvswitch: use flow protocol when recalculating ipv6 checksums
[cascardo/ovs.git] / vtep / ovs-vtep
1 #! /usr/bin/env python
2 # Copyright (C) 2013 Nicira, Inc. All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 # Limitations:
17 #     - Doesn't support multicast other than "unknown-dst"
18
19 import argparse
20 import re
21 import shlex
22 import subprocess
23 import sys
24 import time
25
26 import ovs.dirs
27 import ovs.util
28 import ovs.daemon
29 import ovs.unixctl.server
30 import ovs.vlog
31 from six.moves import range
32 import six
33
34
35 VERSION = "0.99"
36
37 root_prefix = ""
38
39 __pychecker__ = 'no-reuseattr'  # Remove in pychecker >= 0.8.19.
40 vlog = ovs.vlog.Vlog("ovs-vtep")
41 exiting = False
42
43 ps_name = ""
44 ps_type = ""
45 Tunnel_Ip = ""
46 Lswitches = {}
47 Bindings = {}
48 ls_count = 0
49 tun_id = 0
50 bfd_bridge = "vtep_bfd"
51 bfd_ref = {}
52
53
54 def call_prog(prog, args_list):
55     cmd = [prog, "-vconsole:off"] + args_list
56     output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
57     if len(output) == 0 or output[0] is None:
58         output = ""
59     else:
60         output = output[0].decode().strip()
61     return output
62
63
64 def ovs_vsctl(args):
65     return call_prog("ovs-vsctl", shlex.split(args))
66
67
68 def ovs_ofctl(args):
69     return call_prog("ovs-ofctl", shlex.split(args))
70
71
72 def vtep_ctl(args):
73     return call_prog("vtep-ctl", shlex.split(args))
74
75
76 def unixctl_exit(conn, unused_argv, unused_aux):
77     global exiting
78     exiting = True
79     conn.reply(None)
80
81
82 class Logical_Switch(object):
83     def __init__(self, ls_name, ps_name):
84         global ls_count
85         self.name = ls_name
86         ls_count += 1
87         self.short_name = ps_name + "_vtep_ls" + str(ls_count)
88         vlog.info("creating lswitch %s (%s)" % (self.name, self.short_name))
89         self.ports = {}
90         self.tunnels = {}
91         self.local_macs = set()
92         self.remote_macs = {}
93         self.unknown_dsts = set()
94         self.tunnel_key = 0
95         self.setup_ls()
96         self.replication_mode = "service_node"
97
98     def __del__(self):
99         vlog.info("destroying lswitch %s" % self.name)
100
101     def setup_ls(self):
102         column = vtep_ctl("--columns=tunnel_key find logical_switch "
103                           "name=%s" % self.name)
104         tunnel_key = column.partition(":")[2].strip()
105         if tunnel_key and isinstance(eval(tunnel_key), six.integer_types):
106             self.tunnel_key = tunnel_key
107             vlog.info("using tunnel key %s in %s"
108                       % (self.tunnel_key, self.name))
109         else:
110             self.tunnel_key = 0
111             vlog.warn("invalid tunnel key for %s, using 0" % self.name)
112
113         if ps_type:
114             ovs_vsctl("--may-exist add-br %s -- set Bridge %s datapath_type=%s"
115                       % (self.short_name, self.short_name, ps_type))
116         else:
117             ovs_vsctl("--may-exist add-br %s" % self.short_name)
118
119         ovs_vsctl("br-set-external-id %s vtep_logical_switch true"
120                   % self.short_name)
121         ovs_vsctl("br-set-external-id %s logical_switch_name %s"
122                   % (self.short_name, self.name))
123
124         vtep_ctl("clear-local-macs %s" % self.name)
125         vtep_ctl("add-mcast-local %s unknown-dst %s" % (self.name, Tunnel_Ip))
126
127         ovs_ofctl("del-flows %s" % self.short_name)
128         ovs_ofctl("add-flow %s priority=0,action=drop" % self.short_name)
129
130     def cleanup_ls(self):
131         for port_no, tun_name, remote_ip in six.itervalues(self.tunnels):
132             del_bfd(remote_ip)
133
134     def update_flood(self):
135         flood_ports = list(self.ports.values())
136
137         # Traffic flowing from one 'unknown-dst' should not be flooded to
138         # port belonging to another 'unknown-dst'.
139         for tunnel in self.unknown_dsts:
140             port_no = self.tunnels[tunnel][0]
141             ovs_ofctl("add-flow %s table=1,priority=1,in_port=%s,action=%s"
142                       % (self.short_name, port_no, ",".join(flood_ports)))
143
144         # Traffic coming from a VTEP physical port should always be flooded to
145         # all the other physical ports that belong to that VTEP device and
146         # this logical switch.  If the replication mode is service node then
147         # send to one unknown_dst node (the first one here); else we assume the
148         # replication mode is source node and we send the packet to all
149         # unknown_dst nodes.
150         for tunnel in self.unknown_dsts:
151             port_no = self.tunnels[tunnel][0]
152             flood_ports.append(port_no)
153             if self.replication_mode == "service_node":
154                 break
155
156         ovs_ofctl("add-flow %s table=1,priority=0,action=%s"
157                   % (self.short_name, ",".join(flood_ports)))
158
159     def add_lbinding(self, lbinding):
160         vlog.info("adding %s binding to %s" % (lbinding, self.name))
161         port_no = ovs_vsctl("get Interface %s ofport" % lbinding)
162         self.ports[lbinding] = port_no
163         ovs_ofctl("add-flow %s in_port=%s,action=learn(table=1,"
164                   "priority=1000,idle_timeout=15,cookie=0x5000,"
165                   "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
166                   "output:NXM_OF_IN_PORT[]),resubmit(,1)"
167                   % (self.short_name, port_no))
168
169         self.update_flood()
170
171     def del_lbinding(self, lbinding):
172         vlog.info("removing %s binding from %s" % (lbinding, self.name))
173         port_no = self.ports[lbinding]
174         ovs_ofctl("del-flows %s in_port=%s" % (self.short_name, port_no))
175         del self.ports[lbinding]
176         self.update_flood()
177
178     def add_tunnel(self, tunnel):
179         global tun_id
180         vlog.info("adding tunnel %s" % tunnel)
181         encap, ip = tunnel.split("/")
182
183         if encap != "vxlan_over_ipv4":
184             vlog.warn("unsupported tunnel format %s" % encap)
185             return
186
187         tun_id += 1
188         tun_name = "vx" + str(tun_id)
189
190         ovs_vsctl("add-port %s %s -- set Interface %s type=vxlan "
191                   "options:key=%s options:remote_ip=%s"
192                   % (self.short_name, tun_name, tun_name, self.tunnel_key, ip))
193
194         for i in range(10):
195             port_no = ovs_vsctl("get Interface %s ofport" % tun_name)
196             if port_no != "-1":
197                 break
198             elif i == 9:
199                 vlog.warn("couldn't create tunnel %s" % tunnel)
200                 ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
201                 return
202
203             # Give the system a moment to allocate the port number
204             time.sleep(0.5)
205
206         self.tunnels[tunnel] = (port_no, tun_name, ip)
207
208         add_bfd(ip)
209
210         ovs_ofctl("add-flow %s table=0,priority=1000,in_port=%s,"
211                   "actions=resubmit(,1)"
212                   % (self.short_name, port_no))
213
214     def del_tunnel(self, tunnel):
215         vlog.info("removing tunnel %s" % tunnel)
216
217         port_no, tun_name, remote_ip = self.tunnels[tunnel]
218         ovs_ofctl("del-flows %s table=0,in_port=%s"
219                   % (self.short_name, port_no))
220         ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
221
222         del_bfd(remote_ip)
223
224         del self.tunnels[tunnel]
225
226     def update_local_macs(self):
227         flows = ovs_ofctl("dump-flows %s cookie=0x5000/-1,table=1"
228                           % self.short_name).splitlines()
229         macs = set()
230         for f in flows:
231             mac = re.split(r'.*dl_dst=(.*) .*', f)
232             if len(mac) == 3:
233                 macs.add(mac[1])
234
235         for mac in macs.difference(self.local_macs):
236             vlog.info("adding local ucast %s to %s" % (mac, self.name))
237             vtep_ctl("add-ucast-local %s %s %s" % (self.name, mac, Tunnel_Ip))
238
239         for mac in self.local_macs.difference(macs):
240             vlog.info("removing local ucast %s from %s" % (mac, self.name))
241             vtep_ctl("del-ucast-local %s %s" % (self.name, mac))
242
243         self.local_macs = macs
244
245     def add_remote_mac(self, mac, tunnel):
246         port_no = self.tunnels.get(tunnel, (0, ""))[0]
247         if not port_no:
248             return
249
250         ovs_ofctl("add-flow %s table=1,priority=1000,dl_dst=%s,action=%s"
251                   % (self.short_name, mac, port_no))
252
253     def del_remote_mac(self, mac):
254         ovs_ofctl("del-flows %s table=1,dl_dst=%s" % (self.short_name, mac))
255
256     def update_remote_macs(self):
257         remote_macs = {}
258         unknown_dsts = set()
259         tunnels = set()
260         parse_ucast = True
261
262         mac_list = vtep_ctl("list-remote-macs %s" % self.name).splitlines()
263         for line in mac_list:
264             if (line.find("mcast-mac-remote") != -1):
265                 parse_ucast = False
266                 continue
267
268             entry = re.split(r'  (.*) -> (.*)', line)
269             if len(entry) != 4:
270                 continue
271
272             if parse_ucast:
273                 remote_macs[entry[1]] = entry[2]
274             else:
275                 if entry[1] != "unknown-dst":
276                     continue
277
278                 unknown_dsts.add(entry[2])
279
280             tunnels.add(entry[2])
281
282         old_tunnels = set(self.tunnels.keys())
283
284         for tunnel in tunnels.difference(old_tunnels):
285             self.add_tunnel(tunnel)
286
287         for tunnel in old_tunnels.difference(tunnels):
288             self.del_tunnel(tunnel)
289
290         for mac in six.iterkeys(remote_macs):
291             if (self.remote_macs.get(mac) != remote_macs[mac]):
292                 self.add_remote_mac(mac, remote_macs[mac])
293
294         for mac in six.iterkeys(self.remote_macs):
295             if mac not in remote_macs:
296                 self.del_remote_mac(mac)
297
298         self.remote_macs = remote_macs
299
300         replication_mode = vtep_ctl("get logical_switch %s replication_mode"
301                                     % self.name)
302
303         # Replication mode is an optional column and if it is not set,
304         # replication mode defaults to service_node.
305         if replication_mode == "[]":
306             replication_mode = "service_node"
307
308         # If the logical switch level replication mode has changed then
309         # update to that value.
310         update_flood_set = False
311         if replication_mode != self.replication_mode:
312             self.replication_mode = replication_mode
313             vlog.info("%s replication mode changed to %s" %
314                       (self.name, self.replication_mode))
315             update_flood_set = True
316
317         if (self.unknown_dsts != unknown_dsts):
318             self.unknown_dsts = unknown_dsts
319             update_flood_set = True
320
321         # If either the replication mode has changed or the unknown
322         # destinations set has changed, update the flooding decision.
323         if update_flood_set is True:
324             self.update_flood()
325
326     def update_stats(self):
327         # Map Open_vSwitch's "interface:statistics" to columns of
328         # vtep's logical_binding_stats. Since we are using the 'interface' from
329         # the logical switch to collect stats, packets transmitted from it
330         # is received in the physical switch and vice versa.
331         stats_map = {'tx_packets': 'packets_to_local',
332                      'tx_bytes': 'bytes_to_local',
333                      'rx_packets': 'packets_from_local',
334                      'rx_bytes': 'bytes_from_local'}
335
336         # Go through all the logical switch's interfaces that end with "-l"
337         # and copy the statistics to logical_binding_stats.
338         for interface in six.iterkeys(self.ports):
339             if not interface.endswith("-l"):
340                 continue
341             # Physical ports can have a '-' as part of its name.
342             vlan, remainder = interface.split("-", 1)
343             pp_name, logical = remainder.rsplit("-", 1)
344             uuid = vtep_ctl("get physical_port %s vlan_stats:%s"
345                             % (pp_name, vlan))
346             if not uuid:
347                 continue
348
349             for mapfrom, mapto in six.iteritems(stats_map):
350                 value = ovs_vsctl("get interface %s statistics:%s"
351                                   % (interface, mapfrom)).strip('"')
352                 vtep_ctl("set logical_binding_stats %s %s=%s"
353                          % (uuid, mapto, value))
354
355     def run(self):
356         self.update_local_macs()
357         self.update_remote_macs()
358         self.update_stats()
359
360
361 def get_vtep_tunnel(remote_ip):
362     # Get the physical_locator record for the local tunnel end point.
363     column = vtep_ctl("--columns=_uuid find physical_locator "
364                       "dst_ip=%s" % Tunnel_Ip)
365     local = column.partition(":")[2].strip()
366     if not local:
367         return (None, None, None)
368
369     # Get the physical_locator record for the remote tunnel end point.
370     column = vtep_ctl("--columns=_uuid find physical_locator "
371                       "dst_ip=%s" % remote_ip)
372     remote = column.partition(":")[2].strip()
373     if not remote:
374         return (None, None, None)
375
376     column = vtep_ctl("--columns=_uuid find tunnel "
377                       "local=%s remote=%s" % (local, remote))
378     tunnel = column.partition(":")[2].strip()
379
380     return (local, remote, tunnel)
381
382
383 def create_vtep_tunnel(remote_ip):
384     local, remote, tunnel = get_vtep_tunnel(remote_ip)
385     if not local or not remote:
386         return None
387
388     if not tunnel:
389         vlog.info("creating tunnel record in vtep for remote_ip:%s"
390                   % remote_ip)
391         tunnel = vtep_ctl("add physical_switch %s tunnels @tun -- "
392                           "--id=@tun create Tunnel local=%s remote=%s"
393                           % (ps_name, local, remote))
394     return tunnel
395
396
397 def destroy_vtep_tunnel(remote_ip):
398     local, remote, tunnel = get_vtep_tunnel(remote_ip)
399     if tunnel:
400         vlog.info("destroying tunnel record in vtep for remote_ip:%s"
401                   % remote_ip)
402         vtep_ctl("remove physical_switch %s tunnels %s "
403                  "-- --if-exists destroy tunnel %s"
404                  % (ps_name, tunnel, tunnel))
405
406
407 def add_bfd(remote_ip):
408     # The VTEP emulator creates one OVS bridge for every logical switch.
409     # Multiple logical switches can have multiple OVS tunnels to the
410     # same machine (with different tunnel ids). But VTEP schema expects
411     # a single BFD session between two physical locators. Therefore
412     # create a separate bridge ('bfd_bridge') and create a single OVS tunnel
413     # between two phsyical locators (using reference counter).
414     if remote_ip in bfd_ref:
415         bfd_ref[remote_ip] += 1
416         return
417
418     vlog.info("adding bfd tunnel for remote_ip:%s" % remote_ip)
419
420     port_name = "bfd" + remote_ip
421     # Don't enable BFD yet. Enabling or disabling BFD is based on
422     # the controller setting a value in VTEP DB's tunnel record.
423     ovs_vsctl("--may-exist add-port %s %s "
424               " -- set Interface %s type=vxlan options:remote_ip=%s"
425               % (bfd_bridge, port_name, port_name, remote_ip))
426     bfd_ref[remote_ip] = 1
427
428     # Ideally, we should create a 'tunnel' record in the VTEP DB here.
429     # To create a 'tunnel' record, we need 2 entries in 'physical_locator'
430     # table (one for local and one for remote). But, 'physical_locator'
431     # can be created/destroyed asynchronously when the remote controller
432     # adds/removes entries in Ucast_Macs_Remote table. To prevent race
433     # conditions, pass the responsibility of creating a 'tunnel' record
434     # to run_bfd() which runs more often.
435
436
437 def del_bfd(remote_ip):
438     if remote_ip in bfd_ref:
439         if bfd_ref[remote_ip] == 1:
440             port_name = "bfd" + remote_ip
441             vlog.info("deleting bfd tunnel for remote_ip:%s" % remote_ip)
442             ovs_vsctl("--if-exists del-port %s" % port_name)
443             destroy_vtep_tunnel(remote_ip)
444             del bfd_ref[remote_ip]
445         else:
446             bfd_ref[remote_ip] -= 1
447
448
449 def run_bfd():
450     bfd_ports = ovs_vsctl("list-ports %s" % bfd_bridge).split()
451     for port in bfd_ports:
452         remote_ip = ovs_vsctl("get interface %s options:remote_ip" % port)
453         tunnel = create_vtep_tunnel(remote_ip)
454         if not tunnel:
455             continue
456
457         bfd_params_default = {'bfd_params:enable': 'false',
458                               'bfd_params:min_rx': 1000,
459                               'bfd_params:min_tx': 100,
460                               'bfd_params:decay_min_rx': 0,
461                               'bfd_params:cpath_down': 'false',
462                               'bfd_params:check_tnl_key': 'false'}
463         bfd_params_values = {}
464
465         for key, default in six.iteritems(bfd_params_default):
466             column = vtep_ctl("--if-exists get tunnel %s %s"
467                               % (tunnel, key))
468             if not column:
469                 bfd_params_values[key] = default
470             else:
471                 bfd_params_values[key] = column
472
473         for key, value in six.iteritems(bfd_params_values):
474             new_key = key.replace('_params', '')
475             ovs_vsctl("set interface %s %s=%s" % (port, new_key, value))
476
477         bfd_status = ['bfd_status:state', 'bfd_status:forwarding',
478                       'bfd_status:diagnostic', 'bfd_status:remote_state',
479                       'bfd_status:remote_diagnostic']
480         for key in bfd_status:
481             value = ovs_vsctl("--if-exists get interface %s %s" % (port, key))
482             if value:
483                 vtep_ctl("set tunnel %s %s=%s" % (tunnel, key, value))
484             else:
485                 new_key = key.replace('bfd_status:', '')
486                 vtep_ctl("remove tunnel %s bfd_status %s" % (tunnel, new_key))
487
488         vtep_ctl("set tunnel %s bfd_status:enabled=%s"
489                  % (tunnel, bfd_params_values['bfd_params:enable']))
490
491         # Add the defaults as described in VTEP schema to make it explicit.
492         bfd_lconf_default = {'bfd_config_local:bfd_dst_ip': '169.254.1.0',
493                              'bfd_config_local:bfd_dst_mac':
494                              '00:23:20:00:00:01'}
495         for key, value in six.iteritems(bfd_lconf_default):
496             vtep_ctl("set tunnel %s %s=%s" % (tunnel, key, value))
497
498         # bfd_config_remote options from VTEP DB should be populated to
499         # corresponding OVS DB values.
500         bfd_dst_ip = vtep_ctl("--if-exists get tunnel %s "
501                               "bfd_config_remote:bfd_dst_ip" % (tunnel))
502         if not bfd_dst_ip:
503             bfd_dst_ip = "169.254.1.1"
504
505         bfd_dst_mac = vtep_ctl("--if-exists get tunnel %s "
506                                "bfd_config_remote:bfd_dst_mac" % (tunnel))
507         if not bfd_dst_mac:
508             bfd_dst_mac = "00:23:20:00:00:01"
509
510         ovs_vsctl("set interface %s bfd:bfd_dst_ip=%s "
511                   "bfd:bfd_remote_dst_mac=%s bfd:bfd_local_dst_mac=%s"
512                   % (port, bfd_dst_ip,
513                      bfd_lconf_default['bfd_config_local:bfd_dst_mac'],
514                      bfd_dst_mac))
515
516
517 def add_binding(binding, ls):
518     vlog.info("adding binding %s" % binding)
519
520     vlan, pp_name = binding.split("-", 1)
521     pbinding = binding + "-p"
522     lbinding = binding + "-l"
523
524     # Create a patch port that connects the VLAN+port to the lswitch.
525     # Do them as two separate calls so if one side already exists, the
526     # other side is created.
527     ovs_vsctl("add-port %s %s "
528               " -- set Interface %s type=patch options:peer=%s"
529               % (ps_name, pbinding, pbinding, lbinding))
530     ovs_vsctl("add-port %s %s "
531               " -- set Interface %s type=patch options:peer=%s"
532               % (ls.short_name, lbinding, lbinding, pbinding))
533
534     port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
535     patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
536     vlan_ = vlan.lstrip('0')
537     if vlan_:
538         ovs_ofctl("add-flow %s in_port=%s,dl_vlan=%s,action=strip_vlan,%s"
539                   % (ps_name, port_no, vlan_, patch_no))
540         ovs_ofctl("add-flow %s in_port=%s,action=mod_vlan_vid:%s,%s"
541                   % (ps_name, patch_no, vlan_, port_no))
542     else:
543         ovs_ofctl("add-flow %s in_port=%s,action=%s"
544                   % (ps_name, port_no, patch_no))
545         ovs_ofctl("add-flow %s in_port=%s,action=%s"
546                   % (ps_name, patch_no, port_no))
547
548     # Create a logical_bindings_stats record.
549     if not vlan_:
550         vlan_ = "0"
551     vtep_ctl("set physical_port %s vlan_stats:%s=@stats -- "
552              "--id=@stats create logical_binding_stats packets_from_local=0"
553              % (pp_name, vlan_))
554
555     ls.add_lbinding(lbinding)
556     Bindings[binding] = ls.name
557
558
559 def del_binding(binding, ls):
560     vlog.info("removing binding %s" % binding)
561
562     vlan, pp_name = binding.split("-", 1)
563     pbinding = binding + "-p"
564     lbinding = binding + "-l"
565
566     port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
567     patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
568     vlan_ = vlan.lstrip('0')
569     if vlan_:
570         ovs_ofctl("del-flows %s in_port=%s,dl_vlan=%s"
571                   % (ps_name, port_no, vlan_))
572         ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
573     else:
574         ovs_ofctl("--strict del-flows %s in_port=%s" % (ps_name, port_no))
575         ovs_ofctl("--strict del-flows %s in_port=%s" % (ps_name, patch_no))
576
577     ls.del_lbinding(lbinding)
578
579     # Destroy the patch port that connects the VLAN+port to the lswitch
580     ovs_vsctl("del-port %s %s -- del-port %s %s"
581               % (ps_name, pbinding, ls.short_name, lbinding))
582
583     # Remove the record that links vlan with stats in logical_binding_stats.
584     vtep_ctl("remove physical_port %s vlan_stats %s" % (pp_name, vlan))
585
586     del Bindings[binding]
587
588
589 def handle_physical():
590     # Gather physical ports except the patch ports we created
591     ovs_ports = ovs_vsctl("list-ports %s" % ps_name).split()
592     ovs_port_set = set([port for port in ovs_ports if port[-2:] != "-p"])
593
594     vtep_pp_set = set(vtep_ctl("list-ports %s" % ps_name).split())
595
596     for pp_name in ovs_port_set.difference(vtep_pp_set):
597         vlog.info("adding %s to %s" % (pp_name, ps_name))
598         vtep_ctl("add-port %s %s" % (ps_name, pp_name))
599
600     for pp_name in vtep_pp_set.difference(ovs_port_set):
601         vlog.info("deleting %s from %s" % (pp_name, ps_name))
602         vtep_ctl("del-port %s %s" % (ps_name, pp_name))
603
604     new_bindings = set()
605     for pp_name in vtep_pp_set:
606         binding_set = set(vtep_ctl("list-bindings %s %s"
607                                    % (ps_name, pp_name)).splitlines())
608
609         for b in binding_set:
610             vlan, ls_name = b.split()
611             if ls_name not in Lswitches:
612                 Lswitches[ls_name] = Logical_Switch(ls_name, ps_name)
613
614             binding = "%s-%s" % (vlan, pp_name)
615             ls = Lswitches[ls_name]
616             new_bindings.add(binding)
617
618             if binding in Bindings:
619                 if Bindings[binding] == ls_name:
620                     continue
621                 else:
622                     del_binding(binding, Lswitches[Bindings[binding]])
623
624             add_binding(binding, ls)
625
626     dead_bindings = set(Bindings.keys()).difference(new_bindings)
627     for binding in dead_bindings:
628         ls_name = Bindings[binding]
629         ls = Lswitches[ls_name]
630
631         del_binding(binding, ls)
632
633         if not len(ls.ports):
634             ls.cleanup_ls()
635             ovs_vsctl("del-br %s" % Lswitches[ls_name].short_name)
636             vtep_ctl("clear-local-macs %s" % Lswitches[ls_name].name)
637             del Lswitches[ls_name]
638
639
640 def setup():
641     br_list = ovs_vsctl("list-br").split()
642     if (ps_name not in br_list):
643         ovs.util.ovs_fatal(0, "couldn't find OVS bridge %s" % ps_name, vlog)
644
645     global ps_type
646     ps_type = ovs_vsctl("get Bridge %s datapath_type" % ps_name).strip('"')
647
648     call_prog("vtep-ctl", ["set", "physical_switch", ps_name,
649                            'description="OVS VTEP Emulator"'])
650
651     tunnel_ips = vtep_ctl("get physical_switch %s tunnel_ips"
652                           % ps_name).strip('[]"').split(", ")
653     if len(tunnel_ips) != 1 or not tunnel_ips[0]:
654         ovs.util.ovs_fatal(0, "exactly one 'tunnel_ips' should be set", vlog)
655
656     global Tunnel_Ip
657     Tunnel_Ip = tunnel_ips[0]
658
659     ovs_ofctl("del-flows %s" % ps_name)
660
661     # Remove any logical bridges from the previous run
662     for br in br_list:
663         if ovs_vsctl("br-get-external-id %s vtep_logical_switch"
664                      % br) == "true":
665             # Remove the remote side of any logical switch
666             ovs_ports = ovs_vsctl("list-ports %s" % br).split()
667             for port in ovs_ports:
668                 port_type = ovs_vsctl("get Interface %s type"
669                                       % port).strip('"')
670                 if port_type != "patch":
671                     continue
672
673                 peer = ovs_vsctl("get Interface %s options:peer"
674                                  % port).strip('"')
675                 if (peer):
676                     ovs_vsctl("del-port %s" % peer)
677
678             ovs_vsctl("del-br %s" % br)
679
680         if br == bfd_bridge:
681             bfd_ports = ovs_vsctl("list-ports %s" % bfd_bridge).split()
682             for port in bfd_ports:
683                 remote_ip = ovs_vsctl("get interface %s options:remote_ip"
684                                       % port)
685                 destroy_vtep_tunnel(remote_ip)
686
687             ovs_vsctl("del-br %s" % br)
688
689     if ps_type:
690         ovs_vsctl("add-br %s -- set Bridge %s datapath_type=%s"
691                   % (bfd_bridge, bfd_bridge, ps_type))
692     else:
693         ovs_vsctl("add-br %s" % bfd_bridge)
694
695     # Remove local-mac entries from the previous run.  Otherwise, if a vlan
696     # binding is removed while the emulator is *not* running, the corresponding
697     # local-mac entries are never cleaned up.
698     vtep_ls = set(vtep_ctl("list-ls").split())
699     for ls_name in vtep_ls:
700         vtep_ctl("clear-local-macs %s" % ls_name)
701
702
703 def main():
704     parser = argparse.ArgumentParser()
705     parser.add_argument("ps_name", metavar="PS-NAME",
706                         help="Name of physical switch.")
707     parser.add_argument("--root-prefix", metavar="DIR",
708                         help="Use DIR as alternate root directory"
709                         " (for testing).")
710     parser.add_argument("--version", action="version",
711                         version="%s %s" % (ovs.util.PROGRAM_NAME, VERSION))
712
713     ovs.vlog.add_args(parser)
714     ovs.daemon.add_args(parser)
715     args = parser.parse_args()
716     ovs.vlog.handle_args(args)
717     ovs.daemon.handle_args(args)
718
719     global root_prefix
720     if args.root_prefix:
721         root_prefix = args.root_prefix
722
723     global ps_name
724     ps_name = args.ps_name
725
726     ovs.daemon.daemonize()
727
728     ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
729     error, unixctl = ovs.unixctl.server.UnixctlServer.create(None,
730                                                              version=VERSION)
731     if error:
732         ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
733
734     setup()
735
736     while True:
737         unixctl.run()
738         if exiting:
739             break
740
741         handle_physical()
742
743         for ls_name, ls in six.iteritems(Lswitches):
744             ls.run()
745
746         run_bfd()
747
748         poller = ovs.poller.Poller()
749         unixctl.wait(poller)
750         poller.timer_wait(1000)
751         poller.block()
752
753     unixctl.close()
754
755 if __name__ == '__main__':
756     try:
757         main()
758     except SystemExit:
759         # Let system.exit() calls complete normally
760         raise
761     except:
762         vlog.exception("traceback")
763         sys.exit(ovs.daemon.RESTART_EXIT_CODE)