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