]>
Commit | Line | Data |
---|---|---|
aef5f431 | 1 | /* Copyright (c) 2015, 2016 Nicira, Inc. |
717c7fc5 JP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
e387e3e8 | 17 | #include "binding.h" |
70c7cfef RM |
18 | #include "lflow.h" |
19 | #include "lport.h" | |
717c7fc5 | 20 | |
78aab811 | 21 | #include "lib/bitmap.h" |
fdb3c70c | 22 | #include "lib/poll-loop.h" |
717c7fc5 JP |
23 | #include "lib/sset.h" |
24 | #include "lib/util.h" | |
a6095f81 | 25 | #include "lib/netdev.h" |
717c7fc5 | 26 | #include "lib/vswitch-idl.h" |
ee89ea7b | 27 | #include "openvswitch/hmap.h" |
717c7fc5 | 28 | #include "openvswitch/vlog.h" |
e3df8838 | 29 | #include "ovn/lib/ovn-sb-idl.h" |
717c7fc5 JP |
30 | #include "ovn-controller.h" |
31 | ||
e387e3e8 | 32 | VLOG_DEFINE_THIS_MODULE(binding); |
717c7fc5 | 33 | |
a6095f81 BS |
34 | #define OVN_QOS_TYPE "linux-htb" |
35 | ||
36 | struct qos_queue { | |
37 | struct hmap_node node; | |
38 | uint32_t queue_id; | |
39 | uint32_t max_rate; | |
40 | uint32_t burst; | |
41 | }; | |
42 | ||
717c7fc5 | 43 | void |
4a5a9e06 | 44 | binding_register_ovs_idl(struct ovsdb_idl *ovs_idl) |
717c7fc5 | 45 | { |
4a5a9e06 BP |
46 | ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); |
47 | ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges); | |
717c7fc5 | 48 | |
4a5a9e06 BP |
49 | ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge); |
50 | ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name); | |
51 | ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports); | |
717c7fc5 | 52 | |
4a5a9e06 BP |
53 | ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port); |
54 | ovsdb_idl_add_column(ovs_idl, &ovsrec_port_col_name); | |
55 | ovsdb_idl_add_column(ovs_idl, &ovsrec_port_col_interfaces); | |
a6095f81 | 56 | ovsdb_idl_add_column(ovs_idl, &ovsrec_port_col_qos); |
717c7fc5 | 57 | |
4a5a9e06 BP |
58 | ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); |
59 | ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_name); | |
60 | ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_external_ids); | |
a6095f81 BS |
61 | ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_status); |
62 | ||
63 | ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos); | |
64 | ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type); | |
717c7fc5 JP |
65 | } |
66 | ||
926c34fd | 67 | static void |
6f783c06 | 68 | get_local_iface_ids(const struct ovsrec_bridge *br_int, |
c5f346a5 | 69 | struct shash *lport_to_iface, |
a6095f81 BS |
70 | struct sset *all_lports, |
71 | struct sset *egress_ifaces) | |
717c7fc5 | 72 | { |
717c7fc5 JP |
73 | int i; |
74 | ||
422a9f73 BP |
75 | for (i = 0; i < br_int->n_ports; i++) { |
76 | const struct ovsrec_port *port_rec = br_int->ports[i]; | |
717c7fc5 JP |
77 | const char *iface_id; |
78 | int j; | |
79 | ||
422a9f73 | 80 | if (!strcmp(port_rec->name, br_int->name)) { |
717c7fc5 JP |
81 | continue; |
82 | } | |
83 | ||
84 | for (j = 0; j < port_rec->n_interfaces; j++) { | |
85 | const struct ovsrec_interface *iface_rec; | |
86 | ||
87 | iface_rec = port_rec->interfaces[j]; | |
88 | iface_id = smap_get(&iface_rec->external_ids, "iface-id"); | |
a6095f81 BS |
89 | |
90 | if (iface_id) { | |
91 | shash_add(lport_to_iface, iface_id, iface_rec); | |
92 | sset_add(all_lports, iface_id); | |
93 | } | |
94 | ||
95 | /* Check if this is a tunnel interface. */ | |
96 | if (smap_get(&iface_rec->options, "remote_ip")) { | |
97 | const char *tunnel_iface | |
98 | = smap_get(&iface_rec->status, "tunnel_egress_iface"); | |
99 | if (tunnel_iface) { | |
100 | sset_add(egress_ifaces, tunnel_iface); | |
101 | } | |
717c7fc5 | 102 | } |
263064ae RM |
103 | } |
104 | } | |
263064ae RM |
105 | } |
106 | ||
bda5a056 RB |
107 | static void |
108 | add_local_datapath(struct hmap *local_datapaths, | |
3a0d92b3 | 109 | const struct sbrec_port_binding *binding_rec) |
bda5a056 | 110 | { |
e4426e34 BP |
111 | if (get_local_datapath(local_datapaths, |
112 | binding_rec->datapath->tunnel_key)) { | |
6e6c3f91 | 113 | return; |
bda5a056 | 114 | } |
6e6c3f91 HZ |
115 | |
116 | struct local_datapath *ld = xzalloc(sizeof *ld); | |
263064ae | 117 | ld->logical_port = xstrdup(binding_rec->logical_port); |
8e9f1c13 | 118 | memcpy(&ld->uuid, &binding_rec->header_.uuid, sizeof ld->uuid); |
6e6c3f91 HZ |
119 | hmap_insert(local_datapaths, &ld->hmap_node, |
120 | binding_rec->datapath->tunnel_key); | |
bda5a056 RB |
121 | } |
122 | ||
aef5f431 | 123 | static void |
a6095f81 | 124 | get_qos_params(const struct sbrec_port_binding *pb, struct hmap *queue_map) |
aef5f431 | 125 | { |
a6095f81 BS |
126 | uint32_t max_rate = smap_get_int(&pb->options, "qos_max_rate", 0); |
127 | uint32_t burst = smap_get_int(&pb->options, "qos_burst", 0); | |
128 | uint32_t queue_id = smap_get_int(&pb->options, "qdisc_queue_id", 0); | |
aef5f431 | 129 | |
a6095f81 BS |
130 | if ((!max_rate && !burst) || !queue_id) { |
131 | /* Qos is not configured for this port. */ | |
132 | return; | |
133 | } | |
134 | ||
135 | struct qos_queue *node = xzalloc(sizeof *node); | |
136 | hmap_insert(queue_map, &node->node, hash_int(queue_id, 0)); | |
137 | node->max_rate = max_rate; | |
138 | node->burst = burst; | |
139 | node->queue_id = queue_id; | |
140 | } | |
141 | ||
142 | static const struct ovsrec_qos * | |
143 | get_noop_qos(struct controller_ctx *ctx) | |
144 | { | |
145 | const struct ovsrec_qos *qos; | |
146 | OVSREC_QOS_FOR_EACH (qos, ctx->ovs_idl) { | |
147 | if (!strcmp(qos->type, "linux-noop")) { | |
148 | return qos; | |
149 | } | |
150 | } | |
151 | ||
152 | if (!ctx->ovs_idl_txn) { | |
153 | return NULL; | |
154 | } | |
155 | qos = ovsrec_qos_insert(ctx->ovs_idl_txn); | |
156 | ovsrec_qos_set_type(qos, "linux-noop"); | |
157 | return qos; | |
158 | } | |
159 | ||
160 | static bool | |
161 | set_noop_qos(struct controller_ctx *ctx, struct sset *egress_ifaces) | |
162 | { | |
163 | if (!ctx->ovs_idl_txn) { | |
164 | return false; | |
165 | } | |
166 | ||
167 | const struct ovsrec_qos *noop_qos = get_noop_qos(ctx); | |
168 | if (!noop_qos) { | |
169 | return false; | |
170 | } | |
171 | ||
172 | const struct ovsrec_port *port; | |
173 | size_t count = 0; | |
174 | ||
175 | OVSREC_PORT_FOR_EACH (port, ctx->ovs_idl) { | |
176 | if (sset_contains(egress_ifaces, port->name)) { | |
177 | ovsrec_port_set_qos(port, noop_qos); | |
178 | count++; | |
179 | } | |
180 | if (sset_count(egress_ifaces) == count) { | |
181 | break; | |
182 | } | |
183 | } | |
184 | return true; | |
185 | } | |
186 | ||
187 | static void | |
188 | setup_qos(const char *egress_iface, struct hmap *queue_map) | |
189 | { | |
190 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); | |
191 | struct netdev *netdev_phy; | |
192 | ||
193 | if (!egress_iface) { | |
194 | /* Queues cannot be configured. */ | |
195 | return; | |
196 | } | |
197 | ||
198 | int error = netdev_open(egress_iface, NULL, &netdev_phy); | |
199 | if (error) { | |
200 | VLOG_WARN_RL(&rl, "%s: could not open netdev (%s)", | |
201 | egress_iface, ovs_strerror(error)); | |
202 | return; | |
203 | } | |
204 | ||
205 | /* Check and configure qdisc. */ | |
206 | const char *qdisc_type; | |
207 | struct smap qdisc_details; | |
208 | ||
209 | smap_init(&qdisc_details); | |
210 | if (netdev_get_qos(netdev_phy, &qdisc_type, &qdisc_details) != 0 || | |
211 | qdisc_type[0] == '\0') { | |
212 | /* Qos is not supported. */ | |
213 | return; | |
214 | } | |
215 | if (strcmp(qdisc_type, OVN_QOS_TYPE)) { | |
216 | error = netdev_set_qos(netdev_phy, OVN_QOS_TYPE, &qdisc_details); | |
217 | if (error) { | |
218 | VLOG_WARN_RL(&rl, "%s: could not configure QoS (%s)", | |
219 | egress_iface, ovs_strerror(error)); | |
220 | } | |
221 | } | |
222 | ||
223 | /* Check and delete if needed. */ | |
224 | struct netdev_queue_dump dump; | |
225 | unsigned int queue_id; | |
226 | struct smap queue_details; | |
227 | struct qos_queue *sb_info; | |
228 | struct hmap consistent_queues; | |
229 | ||
230 | smap_init(&queue_details); | |
231 | hmap_init(&consistent_queues); | |
232 | NETDEV_QUEUE_FOR_EACH (&queue_id, &queue_details, &dump, netdev_phy) { | |
233 | bool is_queue_needed = false; | |
234 | ||
235 | HMAP_FOR_EACH_WITH_HASH (sb_info, node, hash_int(queue_id, 0), | |
236 | queue_map) { | |
237 | is_queue_needed = true; | |
238 | if (sb_info->max_rate == | |
239 | smap_get_int(&queue_details, "max-rate", 0) | |
240 | && sb_info->burst == smap_get_int(&queue_details, "burst", 0)) { | |
241 | /* This queue is consistent. */ | |
242 | hmap_insert(&consistent_queues, &sb_info->node, | |
243 | hash_int(queue_id, 0)); | |
244 | break; | |
245 | } | |
246 | } | |
247 | ||
248 | if (!is_queue_needed) { | |
249 | error = netdev_delete_queue(netdev_phy, queue_id); | |
250 | if (error) { | |
251 | VLOG_WARN_RL(&rl, "%s: could not delete queue %u (%s)", | |
252 | egress_iface, queue_id, ovs_strerror(error)); | |
253 | } | |
254 | } | |
255 | } | |
256 | ||
257 | /* Create/Update queues. */ | |
258 | HMAP_FOR_EACH (sb_info, node, queue_map) { | |
259 | if (hmap_contains(&consistent_queues, &sb_info->node)) { | |
260 | hmap_remove(&consistent_queues, &sb_info->node); | |
261 | continue; | |
262 | } | |
263 | ||
264 | smap_clear(&queue_details); | |
265 | smap_add_format(&queue_details, "max-rate", "%d", sb_info->max_rate); | |
266 | smap_add_format(&queue_details, "burst", "%d", sb_info->burst); | |
267 | error = netdev_set_queue(netdev_phy, sb_info->queue_id, | |
268 | &queue_details); | |
269 | if (error) { | |
270 | VLOG_WARN_RL(&rl, "%s: could not configure queue %u (%s)", | |
271 | egress_iface, sb_info->queue_id, ovs_strerror(error)); | |
272 | } | |
273 | } | |
274 | smap_destroy(&queue_details); | |
275 | hmap_destroy(&consistent_queues); | |
276 | netdev_close(netdev_phy); | |
aef5f431 BP |
277 | } |
278 | ||
263064ae | 279 | static void |
6f783c06 | 280 | consider_local_datapath(struct controller_ctx *ctx, |
263064ae RM |
281 | const struct sbrec_chassis *chassis_rec, |
282 | const struct sbrec_port_binding *binding_rec, | |
a6095f81 | 283 | struct hmap *qos_map, |
6f783c06 | 284 | struct hmap *local_datapaths, |
c5f346a5 BS |
285 | struct shash *lport_to_iface, |
286 | struct sset *all_lports) | |
263064ae RM |
287 | { |
288 | const struct ovsrec_interface *iface_rec | |
6f783c06 RB |
289 | = shash_find_data(lport_to_iface, binding_rec->logical_port); |
290 | ||
263064ae RM |
291 | if (iface_rec |
292 | || (binding_rec->parent_port && binding_rec->parent_port[0] && | |
926c34fd RM |
293 | sset_contains(all_lports, binding_rec->parent_port))) { |
294 | if (binding_rec->parent_port && binding_rec->parent_port[0]) { | |
295 | /* Add child logical port to the set of all local ports. */ | |
296 | sset_add(all_lports, binding_rec->logical_port); | |
297 | } | |
3a0d92b3 | 298 | add_local_datapath(local_datapaths, binding_rec); |
a6095f81 BS |
299 | if (iface_rec && qos_map && ctx->ovs_idl_txn) { |
300 | get_qos_params(binding_rec, qos_map); | |
263064ae RM |
301 | } |
302 | if (binding_rec->chassis == chassis_rec) { | |
303 | return; | |
304 | } | |
305 | if (ctx->ovnsb_idl_txn) { | |
306 | if (binding_rec->chassis) { | |
307 | VLOG_INFO("Changing chassis for lport %s from %s to %s.", | |
308 | binding_rec->logical_port, | |
309 | binding_rec->chassis->name, | |
310 | chassis_rec->name); | |
311 | } else { | |
312 | VLOG_INFO("Claiming lport %s for this chassis.", | |
313 | binding_rec->logical_port); | |
9240e9ab RR |
314 | for (int i = 0; i < binding_rec->n_mac; i++) { |
315 | VLOG_INFO("Claiming %s", binding_rec->mac[i]); | |
316 | } | |
263064ae RM |
317 | } |
318 | sbrec_port_binding_set_chassis(binding_rec, chassis_rec); | |
319 | } | |
62b87eab NS |
320 | } else if (!strcmp(binding_rec->type, "l2gateway")) { |
321 | const char *chassis_id = smap_get(&binding_rec->options, | |
322 | "l2gateway-chassis"); | |
323 | if (!chassis_id || strcmp(chassis_id, chassis_rec->name)) { | |
324 | if (binding_rec->chassis == chassis_rec && ctx->ovnsb_idl_txn) { | |
325 | VLOG_INFO("Releasing l2gateway port %s from this chassis.", | |
326 | binding_rec->logical_port); | |
327 | sbrec_port_binding_set_chassis(binding_rec, NULL); | |
328 | } | |
329 | return; | |
330 | } | |
331 | ||
926c34fd RM |
332 | sset_add(all_lports, binding_rec->logical_port); |
333 | add_local_datapath(local_datapaths, binding_rec); | |
62b87eab NS |
334 | if (binding_rec->chassis == chassis_rec) { |
335 | return; | |
336 | } | |
337 | ||
338 | if (!strcmp(chassis_id, chassis_rec->name) && ctx->ovnsb_idl_txn) { | |
339 | VLOG_INFO("Claiming l2gateway port %s for this chassis.", | |
340 | binding_rec->logical_port); | |
341 | sbrec_port_binding_set_chassis(binding_rec, chassis_rec); | |
62b87eab | 342 | } |
8439c2eb CSV |
343 | } else if (!strcmp(binding_rec->type, "l3gateway")) { |
344 | const char *chassis = smap_get(&binding_rec->options, | |
345 | "l3gateway-chassis"); | |
346 | if (!strcmp(chassis, chassis_rec->name) && ctx->ovnsb_idl_txn) { | |
347 | add_local_datapath(local_datapaths, binding_rec); | |
348 | } | |
349 | } else if (chassis_rec && binding_rec->chassis == chassis_rec) { | |
263064ae RM |
350 | if (ctx->ovnsb_idl_txn) { |
351 | VLOG_INFO("Releasing lport %s from this chassis.", | |
352 | binding_rec->logical_port); | |
9240e9ab RR |
353 | for (int i = 0; i < binding_rec->n_mac; i++) { |
354 | VLOG_INFO("Releasing %s", binding_rec->mac[i]); | |
355 | } | |
263064ae | 356 | sbrec_port_binding_set_chassis(binding_rec, NULL); |
c5f346a5 | 357 | sset_find_and_delete(all_lports, binding_rec->logical_port); |
263064ae | 358 | } |
c5f346a5 BS |
359 | } else if (!binding_rec->chassis |
360 | && !strcmp(binding_rec->type, "localnet")) { | |
361 | /* Add all localnet ports to all_lports so that we allocate ct zones | |
362 | * for them. */ | |
363 | sset_add(all_lports, binding_rec->logical_port); | |
263064ae RM |
364 | } |
365 | } | |
366 | ||
717c7fc5 | 367 | void |
4acc496e | 368 | binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int, |
c5f346a5 BS |
369 | const char *chassis_id, struct hmap *local_datapaths, |
370 | struct sset *all_lports) | |
717c7fc5 | 371 | { |
71332231 | 372 | const struct sbrec_chassis *chassis_rec; |
dcda6e0d | 373 | const struct sbrec_port_binding *binding_rec; |
6f783c06 | 374 | struct shash lport_to_iface = SHASH_INITIALIZER(&lport_to_iface); |
a6095f81 BS |
375 | struct sset egress_ifaces = SSET_INITIALIZER(&egress_ifaces); |
376 | struct hmap qos_map; | |
f1fd7657 | 377 | |
2ddf7558 | 378 | chassis_rec = get_chassis(ctx->ovnsb_idl, chassis_id); |
263064ae RM |
379 | if (!chassis_rec) { |
380 | return; | |
381 | } | |
71332231 | 382 | |
a6095f81 | 383 | hmap_init(&qos_map); |
37c7a694 | 384 | if (br_int) { |
a6095f81 BS |
385 | get_local_iface_ids(br_int, &lport_to_iface, all_lports, |
386 | &egress_ifaces); | |
37c7a694 | 387 | } |
aef5f431 | 388 | |
7eccc741 JP |
389 | /* Run through each binding record to see if it is resident on this |
390 | * chassis and update the binding accordingly. This includes both | |
391 | * directly connected logical ports and children of those ports. */ | |
926c34fd RM |
392 | SBREC_PORT_BINDING_FOR_EACH(binding_rec, ctx->ovnsb_idl) { |
393 | consider_local_datapath(ctx, chassis_rec, binding_rec, | |
a6095f81 BS |
394 | sset_is_empty(&egress_ifaces) ? NULL : |
395 | &qos_map, local_datapaths, &lport_to_iface, | |
926c34fd | 396 | all_lports); |
a6095f81 BS |
397 | |
398 | } | |
399 | ||
400 | if (!sset_is_empty(&egress_ifaces) | |
401 | && set_noop_qos(ctx, &egress_ifaces)) { | |
402 | const char *entry; | |
403 | SSET_FOR_EACH (entry, &egress_ifaces) { | |
404 | setup_qos(entry, &qos_map); | |
405 | } | |
717c7fc5 | 406 | } |
6f783c06 RB |
407 | |
408 | shash_destroy(&lport_to_iface); | |
a6095f81 BS |
409 | sset_destroy(&egress_ifaces); |
410 | hmap_destroy(&qos_map); | |
717c7fc5 JP |
411 | } |
412 | ||
f1fd7657 BP |
413 | /* Returns true if the database is all cleaned up, false if more work is |
414 | * required. */ | |
415 | bool | |
4acc496e | 416 | binding_cleanup(struct controller_ctx *ctx, const char *chassis_id) |
717c7fc5 | 417 | { |
f1fd7657 BP |
418 | if (!ctx->ovnsb_idl_txn) { |
419 | return false; | |
420 | } | |
717c7fc5 | 421 | |
30a4256f BP |
422 | if (!chassis_id) { |
423 | return true; | |
424 | } | |
78aab811 | 425 | |
f1fd7657 | 426 | const struct sbrec_chassis *chassis_rec |
2ddf7558 | 427 | = get_chassis(ctx->ovnsb_idl, chassis_id); |
71332231 | 428 | if (!chassis_rec) { |
f1fd7657 | 429 | return true; |
71332231 AW |
430 | } |
431 | ||
dcda6e0d BP |
432 | ovsdb_idl_txn_add_comment( |
433 | ctx->ovnsb_idl_txn, | |
434 | "ovn-controller: removing all port bindings for '%s'", chassis_id); | |
717c7fc5 | 435 | |
dcda6e0d | 436 | const struct sbrec_port_binding *binding_rec; |
f1fd7657 | 437 | bool any_changes = false; |
dcda6e0d | 438 | SBREC_PORT_BINDING_FOR_EACH(binding_rec, ctx->ovnsb_idl) { |
f1fd7657 | 439 | if (binding_rec->chassis == chassis_rec) { |
dcda6e0d | 440 | sbrec_port_binding_set_chassis(binding_rec, NULL); |
f1fd7657 | 441 | any_changes = true; |
717c7fc5 | 442 | } |
717c7fc5 | 443 | } |
f1fd7657 | 444 | return !any_changes; |
717c7fc5 | 445 | } |