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