]> git.proxmox.com Git - ovs.git/blob - vtep/ovs-vtep
9a5aa3d680bb25ec2f48692ebbd41e22096561d0
[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
756 if __name__ == '__main__':
757 try:
758 main()
759 except SystemExit:
760 # Let system.exit() calls complete normally
761 raise
762 except:
763 vlog.exception("traceback")
764 sys.exit(ovs.daemon.RESTART_EXIT_CODE)