rhel/openvswitch.spec: Add SELinux policy.
[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.setup_ls()
95         self.replication_mode = "service_node"
96
97     def __del__(self):
98         vlog.info("destroying lswitch %s" % self.name)
99
100     def setup_ls(self):
101
102         if ps_type:
103             ovs_vsctl("--may-exist add-br %s -- set Bridge %s datapath_type=%s"
104                       % (self.short_name, self.short_name, ps_type))
105         else:
106             ovs_vsctl("--may-exist add-br %s" % self.short_name)
107
108         ovs_vsctl("br-set-external-id %s vtep_logical_switch true"
109                   % self.short_name)
110         ovs_vsctl("br-set-external-id %s logical_switch_name %s"
111                   % (self.short_name, self.name))
112
113         vtep_ctl("clear-local-macs %s" % self.name)
114         vtep_ctl("add-mcast-local %s unknown-dst %s" % (self.name, Tunnel_Ip))
115
116         ovs_ofctl("del-flows %s" % self.short_name)
117         ovs_ofctl("add-flow %s priority=0,action=drop" % self.short_name)
118
119     def cleanup_ls(self):
120         for port_no, tun_name, remote_ip in six.itervalues(self.tunnels):
121             del_bfd(remote_ip)
122
123     def update_flood(self):
124         flood_ports = list(self.ports.values())
125
126         # Traffic flowing from one 'unknown-dst' should not be flooded to
127         # port belonging to another 'unknown-dst'.
128         for tunnel in self.unknown_dsts:
129             port_no = self.tunnels[tunnel][0]
130             ovs_ofctl("add-flow %s table=1,priority=1,in_port=%s,action=%s"
131                       % (self.short_name, port_no, ",".join(flood_ports)))
132
133         # Traffic coming from a VTEP physical port should always be flooded to
134         # all the other physical ports that belong to that VTEP device and
135         # this logical switch.  If the replication mode is service node then
136         # send to one unknown_dst node (the first one here); else we assume the
137         # replication mode is source node and we send the packet to all
138         # unknown_dst nodes.
139         for tunnel in self.unknown_dsts:
140             port_no = self.tunnels[tunnel][0]
141             flood_ports.append(port_no)
142             if self.replication_mode == "service_node":
143                 break
144
145         ovs_ofctl("add-flow %s table=1,priority=0,action=%s"
146                   % (self.short_name, ",".join(flood_ports)))
147
148     def add_lbinding(self, lbinding):
149         vlog.info("adding %s binding to %s" % (lbinding, self.name))
150         port_no = ovs_vsctl("get Interface %s ofport" % lbinding)
151         self.ports[lbinding] = port_no
152         ovs_ofctl("add-flow %s in_port=%s,action=learn(table=1,"
153                   "priority=1000,idle_timeout=15,cookie=0x5000,"
154                   "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
155                   "output:NXM_OF_IN_PORT[]),resubmit(,1)"
156                   % (self.short_name, port_no))
157
158         self.update_flood()
159
160     def del_lbinding(self, lbinding):
161         vlog.info("removing %s binding from %s" % (lbinding, self.name))
162         port_no = self.ports[lbinding]
163         ovs_ofctl("del-flows %s in_port=%s" % (self.short_name, port_no))
164         del self.ports[lbinding]
165         self.update_flood()
166
167     def add_tunnel(self, tunnel, tunnel_key):
168         global tun_id
169         vlog.info("adding tunnel %s" % tunnel)
170         encap, ip = tunnel.split("/")
171
172         if encap != "vxlan_over_ipv4":
173             vlog.warn("unsupported tunnel format %s" % encap)
174             return
175
176         tun_id += 1
177         tun_name = "vx" + str(tun_id)
178
179         ovs_vsctl("add-port %s %s -- set Interface %s type=vxlan "
180                   "options:key=%s options:remote_ip=%s"
181                   % (self.short_name, tun_name, tun_name, tunnel_key, ip))
182
183         for i in range(10):
184             port_no = ovs_vsctl("get Interface %s ofport" % tun_name)
185             if port_no != "-1":
186                 break
187             elif i == 9:
188                 vlog.warn("couldn't create tunnel %s" % tunnel)
189                 ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
190                 return
191
192             # Give the system a moment to allocate the port number
193             time.sleep(0.5)
194
195         self.tunnels[tunnel] = (port_no, tun_name, ip)
196
197         add_bfd(ip)
198
199         ovs_ofctl("add-flow %s table=0,priority=1000,in_port=%s,"
200                   "actions=resubmit(,1)"
201                   % (self.short_name, port_no))
202
203     def del_tunnel(self, tunnel):
204         vlog.info("removing tunnel %s" % tunnel)
205
206         port_no, tun_name, remote_ip = self.tunnels[tunnel]
207         ovs_ofctl("del-flows %s table=0,in_port=%s"
208                   % (self.short_name, port_no))
209         ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
210
211         del_bfd(remote_ip)
212
213         del self.tunnels[tunnel]
214
215     def update_local_macs(self):
216         flows = ovs_ofctl("dump-flows %s cookie=0x5000/-1,table=1"
217                           % self.short_name).splitlines()
218         macs = set()
219         for f in flows:
220             mac = re.split(r'.*dl_dst=(.*) .*', f)
221             if len(mac) == 3:
222                 macs.add(mac[1])
223
224         for mac in macs.difference(self.local_macs):
225             vlog.info("adding local ucast %s to %s" % (mac, self.name))
226             vtep_ctl("add-ucast-local %s %s %s" % (self.name, mac, Tunnel_Ip))
227
228         for mac in self.local_macs.difference(macs):
229             vlog.info("removing local ucast %s from %s" % (mac, self.name))
230             vtep_ctl("del-ucast-local %s %s" % (self.name, mac))
231
232         self.local_macs = macs
233
234     def add_remote_mac(self, mac, tunnel):
235         port_no = self.tunnels.get(tunnel, (0, ""))[0]
236         if not port_no:
237             return
238
239         ovs_ofctl("add-flow %s table=1,priority=1000,dl_dst=%s,action=%s"
240                   % (self.short_name, mac, port_no))
241
242     def del_remote_mac(self, mac):
243         ovs_ofctl("del-flows %s table=1,dl_dst=%s" % (self.short_name, mac))
244
245     def update_remote_macs(self):
246         remote_macs = {}
247         unknown_dsts = set()
248         tunnels = set()
249         parse_ucast = True
250
251         column = vtep_ctl("--columns=tunnel_key find logical_switch "
252                           "name=%s" % self.name)
253         tunnel_key = column.partition(":")[2].strip()
254         if tunnel_key and isinstance(eval(tunnel_key), six.integer_types):
255             vlog.info("update_remote_macs: using tunnel key %s in %s"
256                       % (tunnel_key, self.name))
257         else:
258             vlog.info("Invalid tunnel key %s in %s post VTEP DB requery"
259                       % (tunnel_key, self.name))
260             return
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, tunnel_key)
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)