]> git.proxmox.com Git - mirror_ovs.git/blob - vtep/ovs-vtep
man: Document common options in ovs-vsctl and vtep-ctl.
[mirror_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 subprocess
22 import sys
23 import time
24 import types
25
26 import ovs.dirs
27 import ovs.util
28 import ovs.daemon
29 import ovs.unixctl.server
30 import ovs.vlog
31
32
33 VERSION = "0.99"
34
35 root_prefix = ""
36
37 __pychecker__ = 'no-reuseattr' # Remove in pychecker >= 0.8.19.
38 vlog = ovs.vlog.Vlog("ovs-vtep")
39 exiting = False
40
41 Tunnel_Ip = ""
42 Lswitches = {}
43 Bindings = {}
44 ls_count = 0
45 tun_id = 0
46
47 def call_prog(prog, args_list):
48 cmd = [prog, "-vconsole:off"] + args_list
49 output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
50 if len(output) == 0 or output[0] == None:
51 output = ""
52 else:
53 output = output[0].strip()
54 return output
55
56 def ovs_vsctl(args):
57 return call_prog("ovs-vsctl", args.split())
58
59 def ovs_ofctl(args):
60 return call_prog("ovs-ofctl", args.split())
61
62 def vtep_ctl(args):
63 return call_prog("vtep-ctl", args.split())
64
65
66 def unixctl_exit(conn, unused_argv, unused_aux):
67 global exiting
68 exiting = True
69 conn.reply(None)
70
71
72 class Logical_Switch(object):
73 def __init__(self, ls_name):
74 global ls_count
75 self.name = ls_name
76 ls_count += 1
77 self.short_name = "vtep_ls" + str(ls_count)
78 vlog.info("creating lswitch %s (%s)" % (self.name, self.short_name))
79 self.ports = {}
80 self.tunnels = {}
81 self.local_macs = set()
82 self.remote_macs = {}
83 self.unknown_dsts = set()
84 self.tunnel_key = 0
85 self.setup_ls()
86
87 def __del__(self):
88 vlog.info("destroying lswitch %s" % self.name)
89
90 def setup_ls(self):
91 column = vtep_ctl("--columns=tunnel_key find logical_switch "
92 "name=%s" % self.name)
93 tunnel_key = column.partition(":")[2].strip()
94 if (tunnel_key and type(eval(tunnel_key)) == types.IntType):
95 self.tunnel_key = tunnel_key
96 vlog.info("using tunnel key %s in %s"
97 % (self.tunnel_key, self.name))
98 else:
99 self.tunnel_key = 0
100 vlog.warn("invalid tunnel key for %s, using 0" % self.name)
101
102 ovs_vsctl("--may-exist add-br %s" % self.short_name)
103 ovs_vsctl("br-set-external-id %s vtep_logical_switch true"
104 % self.short_name)
105 ovs_vsctl("br-set-external-id %s logical_switch_name %s"
106 % (self.short_name, self.name))
107
108 vtep_ctl("clear-local-macs %s" % self.name)
109 vtep_ctl("add-mcast-local %s unknown-dst %s" % (self.name, Tunnel_Ip))
110
111 ovs_ofctl("del-flows %s" % self.short_name)
112 ovs_ofctl("add-flow %s priority=0,action=drop" % self.short_name)
113
114 def update_flood(self):
115 flood_ports = self.ports.values()
116
117 # Traffic flowing from one 'unknown-dst' should not be flooded to
118 # port belonging to another 'unknown-dst'.
119 for tunnel in self.unknown_dsts:
120 port_no = self.tunnels[tunnel][0]
121 ovs_ofctl("add-flow %s table=1,priority=1,in_port=%s,action=%s"
122 % (self.short_name, port_no, ",".join(flood_ports)))
123
124 # Traffic coming from a VTEP physical port should only be flooded to
125 # one 'unknown-dst' and to all other physical ports that belong to that
126 # VTEP device and this logical switch.
127 for tunnel in self.unknown_dsts:
128 port_no = self.tunnels[tunnel][0]
129 flood_ports.append(port_no)
130 break
131
132 ovs_ofctl("add-flow %s table=1,priority=0,action=%s"
133 % (self.short_name, ",".join(flood_ports)))
134
135 def add_lbinding(self, lbinding):
136 vlog.info("adding %s binding to %s" % (lbinding, self.name))
137 port_no = ovs_vsctl("get Interface %s ofport" % lbinding)
138 self.ports[lbinding] = port_no
139 ovs_ofctl("add-flow %s in_port=%s,action=learn(table=1,"
140 "priority=1000,idle_timeout=15,cookie=0x5000,"
141 "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
142 "output:NXM_OF_IN_PORT[]),resubmit(,1)"
143 % (self.short_name, port_no))
144
145 self.update_flood()
146
147 def del_lbinding(self, lbinding):
148 vlog.info("removing %s binding from %s" % (lbinding, self.name))
149 port_no = self.ports[lbinding]
150 ovs_ofctl("del-flows %s in_port=%s" % (self.short_name, port_no));
151 del self.ports[lbinding]
152 self.update_flood()
153
154 def add_tunnel(self, tunnel):
155 global tun_id
156 vlog.info("adding tunnel %s" % tunnel)
157 encap, ip = tunnel.split("/")
158
159 if encap != "vxlan_over_ipv4":
160 vlog.warn("unsupported tunnel format %s" % encap)
161 return
162
163 tun_id += 1
164 tun_name = "vx" + str(tun_id)
165
166 ovs_vsctl("add-port %s %s -- set Interface %s type=vxlan "
167 "options:key=%s options:remote_ip=%s"
168 % (self.short_name, tun_name, tun_name, self.tunnel_key, ip))
169
170 for i in range(10):
171 port_no = ovs_vsctl("get Interface %s ofport" % tun_name)
172 if port_no != "-1":
173 break
174 elif i == 9:
175 vlog.warn("couldn't create tunnel %s" % tunnel)
176 ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
177 return
178
179 # Give the system a moment to allocate the port number
180 time.sleep(0.5)
181
182 self.tunnels[tunnel] = (port_no, tun_name)
183
184 ovs_ofctl("add-flow %s table=0,priority=1000,in_port=%s,"
185 "actions=resubmit(,1)"
186 % (self.short_name, port_no))
187
188 def del_tunnel(self, tunnel):
189 vlog.info("removing tunnel %s" % tunnel)
190
191 port_no, tun_name = self.tunnels[tunnel]
192 ovs_ofctl("del-flows %s table=0,in_port=%s"
193 % (self.short_name, port_no))
194 ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
195
196 del self.tunnels[tunnel]
197
198 def update_local_macs(self):
199 flows = ovs_ofctl("dump-flows %s cookie=0x5000/-1,table=1"
200 % self.short_name).splitlines()
201 macs = set()
202 for f in flows:
203 mac = re.split(r'.*dl_dst=(.*) .*', f)
204 if len(mac) == 3:
205 macs.add(mac[1])
206
207 for mac in macs.difference(self.local_macs):
208 vlog.info("adding local ucast %s to %s" % (mac, self.name))
209 vtep_ctl("add-ucast-local %s %s %s" % (self.name, mac, Tunnel_Ip))
210
211 for mac in self.local_macs.difference(macs):
212 vlog.info("removing local ucast %s from %s" % (mac, self.name))
213 vtep_ctl("del-ucast-local %s %s" % (self.name, mac))
214
215 self.local_macs = macs
216
217 def add_remote_mac(self, mac, tunnel):
218 port_no = self.tunnels.get(tunnel, (0,""))[0]
219 if not port_no:
220 return
221
222 ovs_ofctl("add-flow %s table=1,priority=1000,dl_dst=%s,action=%s"
223 % (self.short_name, mac, port_no))
224
225 def del_remote_mac(self, mac):
226 ovs_ofctl("del-flows %s table=1,dl_dst=%s" % (self.short_name, mac))
227
228 def update_remote_macs(self):
229 remote_macs = {}
230 unknown_dsts = set()
231 tunnels = set()
232 parse_ucast = True
233
234 mac_list = vtep_ctl("list-remote-macs %s" % self.name).splitlines()
235 for line in mac_list:
236 if (line.find("mcast-mac-remote") != -1):
237 parse_ucast = False
238 continue
239
240 entry = re.split(r' (.*) -> (.*)', line)
241 if len(entry) != 4:
242 continue
243
244 if parse_ucast:
245 remote_macs[entry[1]] = entry[2]
246 else:
247 if entry[1] != "unknown-dst":
248 continue
249
250 unknown_dsts.add(entry[2])
251
252 tunnels.add(entry[2])
253
254 old_tunnels = set(self.tunnels.keys())
255
256 for tunnel in tunnels.difference(old_tunnels):
257 self.add_tunnel(tunnel)
258
259 for tunnel in old_tunnels.difference(tunnels):
260 self.del_tunnel(tunnel)
261
262 for mac in remote_macs.keys():
263 if (self.remote_macs.get(mac) != remote_macs[mac]):
264 self.add_remote_mac(mac, remote_macs[mac])
265
266 for mac in self.remote_macs.keys():
267 if not remote_macs.has_key(mac):
268 self.del_remote_mac(mac)
269
270 self.remote_macs = remote_macs
271
272 if (self.unknown_dsts != unknown_dsts):
273 self.unknown_dsts = unknown_dsts
274 self.update_flood()
275
276 def update_stats(self):
277 # Map Open_vSwitch's "interface:statistics" to columns of
278 # vtep's logical_binding_stats. Since we are using the 'interface' from
279 # the logical switch to collect stats, packets transmitted from it
280 # is received in the physical switch and vice versa.
281 stats_map = {'tx_packets':'packets_to_local',
282 'tx_bytes':'bytes_to_local',
283 'rx_packets':'packets_from_local',
284 'rx_bytes':'bytes_from_local'}
285
286 # Go through all the logical switch's interfaces that end with "-l"
287 # and copy the statistics to logical_binding_stats.
288 for interface in self.ports.iterkeys():
289 if not interface.endswith("-l"):
290 continue
291 vlan, pp_name, logical = interface.split("-")
292 uuid = vtep_ctl("get physical_port %s vlan_stats:%s"
293 % (pp_name, vlan))
294 if not uuid:
295 continue
296
297 for (mapfrom, mapto) in stats_map.iteritems():
298 value = ovs_vsctl("get interface %s statistics:%s"
299 % (interface, mapfrom)).strip('"')
300 vtep_ctl("set logical_binding_stats %s %s=%s"
301 % (uuid, mapto, value))
302
303 def run(self):
304 self.update_local_macs()
305 self.update_remote_macs()
306 self.update_stats()
307
308 def add_binding(ps_name, binding, ls):
309 vlog.info("adding binding %s" % binding)
310
311 vlan, pp_name = binding.split("-")
312 pbinding = binding+"-p"
313 lbinding = binding+"-l"
314
315 # Create a patch port that connects the VLAN+port to the lswitch.
316 # Do them as two separate calls so if one side already exists, the
317 # other side is created.
318 ovs_vsctl("add-port %s %s "
319 " -- set Interface %s type=patch options:peer=%s"
320 % (ps_name, pbinding, pbinding, lbinding))
321 ovs_vsctl("add-port %s %s "
322 " -- set Interface %s type=patch options:peer=%s"
323 % (ls.short_name, lbinding, lbinding, pbinding))
324
325 port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
326 patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
327 vlan_ = vlan.lstrip('0')
328 if vlan_:
329 ovs_ofctl("add-flow %s in_port=%s,dl_vlan=%s,action=strip_vlan,%s"
330 % (ps_name, port_no, vlan_, patch_no))
331 ovs_ofctl("add-flow %s in_port=%s,action=mod_vlan_vid:%s,%s"
332 % (ps_name, patch_no, vlan_, port_no))
333 else:
334 ovs_ofctl("add-flow %s in_port=%s,action=%s"
335 % (ps_name, port_no, patch_no))
336 ovs_ofctl("add-flow %s in_port=%s,action=%s"
337 % (ps_name, patch_no, port_no))
338
339 # Create a logical_bindings_stats record.
340 if not vlan_:
341 vlan_ = "0"
342 vtep_ctl("set physical_port %s vlan_stats:%s=@stats --\
343 --id=@stats create logical_binding_stats packets_from_local=0"\
344 % (pp_name, vlan_))
345
346 ls.add_lbinding(lbinding)
347 Bindings[binding] = ls.name
348
349 def del_binding(ps_name, binding, ls):
350 vlog.info("removing binding %s" % binding)
351
352 vlan, pp_name = binding.split("-")
353 pbinding = binding+"-p"
354 lbinding = binding+"-l"
355
356 port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
357 patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
358 vlan_ = vlan.lstrip('0')
359 if vlan_:
360 ovs_ofctl("del-flows %s in_port=%s,dl_vlan=%s"
361 % (ps_name, port_no, vlan_))
362 ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
363 else:
364 ovs_ofctl("del-flows %s in_port=%s" % (ps_name, port_no))
365 ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
366
367 ls.del_lbinding(lbinding)
368
369 # Destroy the patch port that connects the VLAN+port to the lswitch
370 ovs_vsctl("del-port %s %s -- del-port %s %s"
371 % (ps_name, pbinding, ls.short_name, lbinding))
372
373 # Remove the record that links vlan with stats in logical_binding_stats.
374 vtep_ctl("remove physical_port %s vlan_stats %s" % (pp_name, vlan))
375
376 del Bindings[binding]
377
378 def handle_physical(ps_name):
379 # Gather physical ports except the patch ports we created
380 ovs_ports = ovs_vsctl("list-ports %s" % ps_name).split()
381 ovs_port_set = set([port for port in ovs_ports if port[-2:] != "-p"])
382
383 vtep_pp_set = set(vtep_ctl("list-ports %s" % ps_name).split())
384
385 for pp_name in ovs_port_set.difference(vtep_pp_set):
386 vlog.info("adding %s to %s" % (pp_name, ps_name))
387 vtep_ctl("add-port %s %s" % (ps_name, pp_name))
388
389 for pp_name in vtep_pp_set.difference(ovs_port_set):
390 vlog.info("deleting %s from %s" % (pp_name, ps_name))
391 vtep_ctl("del-port %s %s" % (ps_name, pp_name))
392
393 new_bindings = set()
394 for pp_name in vtep_pp_set:
395 binding_set = set(vtep_ctl("list-bindings %s %s"
396 % (ps_name, pp_name)).splitlines())
397
398 for b in binding_set:
399 vlan, ls_name = b.split()
400 if ls_name not in Lswitches:
401 Lswitches[ls_name] = Logical_Switch(ls_name)
402
403 binding = "%s-%s" % (vlan, pp_name)
404 ls = Lswitches[ls_name]
405 new_bindings.add(binding)
406
407 if Bindings.has_key(binding):
408 if Bindings[binding] == ls_name:
409 continue
410 else:
411 del_binding(ps_name, binding, Lswitches[Bindings[binding]])
412
413 add_binding(ps_name, binding, ls)
414
415
416 dead_bindings = set(Bindings.keys()).difference(new_bindings)
417 for binding in dead_bindings:
418 ls_name = Bindings[binding]
419 ls = Lswitches[ls_name]
420
421 del_binding(ps_name, binding, ls)
422
423 if not len(ls.ports):
424 ovs_vsctl("del-br %s" % Lswitches[ls_name].short_name)
425 del Lswitches[ls_name]
426
427 def setup(ps_name):
428 br_list = ovs_vsctl("list-br").split()
429 if (ps_name not in br_list):
430 ovs.util.ovs_fatal(0, "couldn't find OVS bridge %s" % ps_name, vlog)
431
432 call_prog("vtep-ctl", ["set", "physical_switch", ps_name,
433 'description="OVS VTEP Emulator"'])
434
435 tunnel_ips = vtep_ctl("get physical_switch %s tunnel_ips"
436 % ps_name).strip('[]"').split(", ")
437 if len(tunnel_ips) != 1 or not tunnel_ips[0]:
438 ovs.util.ovs_fatal(0, "exactly one 'tunnel_ips' should be set", vlog)
439
440 global Tunnel_Ip
441 Tunnel_Ip = tunnel_ips[0]
442
443 ovs_ofctl("del-flows %s" % ps_name)
444
445 # Remove any logical bridges from the previous run
446 for br in br_list:
447 if ovs_vsctl("br-get-external-id %s vtep_logical_switch"
448 % br) == "true":
449 # Remove the remote side of any logical switch
450 ovs_ports = ovs_vsctl("list-ports %s" % br).split()
451 for port in ovs_ports:
452 port_type = ovs_vsctl("get Interface %s type"
453 % port).strip('"')
454 if port_type != "patch":
455 continue
456
457 peer = ovs_vsctl("get Interface %s options:peer"
458 % port).strip('"')
459 if (peer):
460 ovs_vsctl("del-port %s" % peer)
461
462 ovs_vsctl("del-br %s" % br)
463
464
465 def main():
466 parser = argparse.ArgumentParser()
467 parser.add_argument("ps_name", metavar="PS-NAME",
468 help="Name of physical switch.")
469 parser.add_argument("--root-prefix", metavar="DIR",
470 help="Use DIR as alternate root directory"
471 " (for testing).")
472 parser.add_argument("--version", action="version",
473 version="%s %s" % (ovs.util.PROGRAM_NAME, VERSION))
474
475 ovs.vlog.add_args(parser)
476 ovs.daemon.add_args(parser)
477 args = parser.parse_args()
478 ovs.vlog.handle_args(args)
479 ovs.daemon.handle_args(args)
480
481 global root_prefix
482 if args.root_prefix:
483 root_prefix = args.root_prefix
484
485 ps_name = args.ps_name
486
487 ovs.daemon.daemonize()
488
489 ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
490 error, unixctl = ovs.unixctl.server.UnixctlServer.create(None,
491 version=VERSION)
492 if error:
493 ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
494
495 setup(ps_name)
496
497 while True:
498 unixctl.run()
499 if exiting:
500 break
501
502 handle_physical(ps_name)
503
504 for ls_name, ls in Lswitches.items():
505 ls.run()
506
507 poller = ovs.poller.Poller()
508 unixctl.wait(poller)
509 poller.timer_wait(1000)
510 poller.block()
511
512 unixctl.close()
513
514 if __name__ == '__main__':
515 try:
516 main()
517 except SystemExit:
518 # Let system.exit() calls complete normally
519 raise
520 except:
521 vlog.exception("traceback")
522 sys.exit(ovs.daemon.RESTART_EXIT_CODE)