]> git.proxmox.com Git - mirror_ovs.git/blame - ovn/controller/chassis.c
ovn-controller: Create tunnels based on Chassis configuration.
[mirror_ovs.git] / ovn / controller / chassis.c
CommitLineData
717c7fc5
JP
1/* Copyright (c) 2015 Nicira, Inc.
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>
17#include "chassis.h"
18
e4901fe0 19#include "lib/hash.h"
717c7fc5 20#include "lib/poll-loop.h"
e4901fe0 21#include "lib/sset.h"
717c7fc5
JP
22#include "lib/util.h"
23#include "lib/vswitch-idl.h"
24#include "openvswitch/vlog.h"
e3df8838 25#include "ovn/lib/ovn-sb-idl.h"
717c7fc5
JP
26#include "ovn-controller.h"
27
28VLOG_DEFINE_THIS_MODULE(chassis);
29
30void
31chassis_init(struct controller_ctx *ctx)
32{
33 ovsdb_idl_add_table(ctx->ovs_idl, &ovsrec_table_open_vswitch);
34 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_open_vswitch_col_external_ids);
e4901fe0
JP
35 ovsdb_idl_add_table(ctx->ovs_idl, &ovsrec_table_bridge);
36 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_bridge_col_ports);
37 ovsdb_idl_add_table(ctx->ovs_idl, &ovsrec_table_port);
38 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_port_col_name);
39 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_port_col_interfaces);
40 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_port_col_external_ids);
41 ovsdb_idl_add_table(ctx->ovs_idl, &ovsrec_table_interface);
42 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_interface_col_name);
43 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_interface_col_type);
44 ovsdb_idl_add_column(ctx->ovs_idl, &ovsrec_interface_col_options);
717c7fc5
JP
45}
46
47static void
e4901fe0 48register_chassis(struct controller_ctx *ctx)
717c7fc5
JP
49{
50 const struct sbrec_chassis *chassis_rec;
51 const struct ovsrec_open_vswitch *cfg;
52 const char *encap_type, *encap_ip;
e4901fe0 53 struct sbrec_encap *encap_rec;
717c7fc5 54 static bool inited = false;
e4901fe0
JP
55 int retval = TXN_TRY_AGAIN;
56 struct ovsdb_idl_txn *txn;
717c7fc5
JP
57
58 SBREC_CHASSIS_FOR_EACH(chassis_rec, ctx->ovnsb_idl) {
3442ca9b 59 if (!strcmp(chassis_rec->name, ctx->chassis_id)) {
717c7fc5
JP
60 break;
61 }
62 }
63
64 /* xxx Need to support more than one encap. Also need to support
65 * xxx encap options. */
66 cfg = ovsrec_open_vswitch_first(ctx->ovs_idl);
67 if (!cfg) {
68 VLOG_INFO("No Open_vSwitch row defined.");
69 return;
70 }
71
72 encap_type = smap_get(&cfg->external_ids, "ovn-encap-type");
73 encap_ip = smap_get(&cfg->external_ids, "ovn-encap-ip");
74 if (!encap_type || !encap_ip) {
75 VLOG_INFO("Need to specify an encap type and ip");
76 return;
77 }
78
79 if (chassis_rec) {
80 int i;
81
82 for (i = 0; i < chassis_rec->n_encaps; i++) {
83 if (!strcmp(chassis_rec->encaps[i]->type, encap_type)
84 && !strcmp(chassis_rec->encaps[i]->ip, encap_ip)) {
85 /* Nothing changed. */
86 inited = true;
87 return;
88 } else if (!inited) {
89 VLOG_WARN("Chassis config changing on startup, make sure "
90 "multiple chassis are not configured : %s/%s->%s/%s",
91 chassis_rec->encaps[i]->type,
92 chassis_rec->encaps[i]->ip,
93 encap_type, encap_ip);
94 }
95
96 }
97 }
98
e4901fe0
JP
99 txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
100 ovsdb_idl_txn_add_comment(txn,
101 "ovn-controller: registering chassis '%s'",
102 ctx->chassis_id);
103
104 if (!chassis_rec) {
105 chassis_rec = sbrec_chassis_insert(txn);
106 sbrec_chassis_set_name(chassis_rec, ctx->chassis_id);
107 }
108
109 encap_rec = sbrec_encap_insert(txn);
110
111 sbrec_encap_set_type(encap_rec, encap_type);
112 sbrec_encap_set_ip(encap_rec, encap_ip);
113
114 sbrec_chassis_set_encaps(chassis_rec, &encap_rec, 1);
115
116 retval = ovsdb_idl_txn_commit_block(txn);
117 if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
118 VLOG_INFO("Problem registering chassis: %s",
119 ovsdb_idl_txn_status_to_string(retval));
120 poll_immediate_wake();
121 }
122 ovsdb_idl_txn_destroy(txn);
123
717c7fc5
JP
124 inited = true;
125}
126
e4901fe0
JP
127/* Enough context to create a new tunnel, using tunnel_add(). */
128struct tunnel_ctx {
129 /* Contains "struct port_hash_node"s. Used to figure out what
130 * existing tunnels should be deleted: we index all of the OVN encap
131 * rows into this data structure, then as existing rows are
132 * generated we remove them. After generating all the rows, any
133 * remaining in 'tunnel_hmap' must be deleted from the database. */
134 struct hmap tunnel_hmap;
135
136 /* Names of all ports in the bridge, to allow checking uniqueness when
137 * adding a new tunnel. */
138 struct sset port_names;
139
140 struct ovsdb_idl_txn *ovs_txn;
141 const struct ovsrec_bridge *br_int;
142};
143
144struct port_hash_node {
145 struct hmap_node node;
146 const struct ovsrec_port *port;
147 const struct ovsrec_bridge *bridge;
148};
149
150static size_t
151port_hash(const char *chassis_id, const char *type, const char *ip)
152{
153 size_t hash = hash_string(chassis_id, 0);
154 hash = hash_string(type, hash);
155 return hash_string(ip, hash);
156}
157
158static size_t
159port_hash_rec(const struct ovsrec_port *port)
160{
161 const char *chassis_id, *ip;
162 const struct ovsrec_interface *iface;
163
164 chassis_id = smap_get(&port->external_ids, "ovn-chassis-id");
165
166 if (!chassis_id || !port->n_interfaces) {
167 /* This should not happen for an OVN-created port. */
168 return 0;
169 }
170
171 iface = port->interfaces[0];
172 ip = smap_get(&iface->options, "remote_ip");
173
174 return port_hash(chassis_id, iface->type, ip);
175}
176
177static char *
178tunnel_create_name(struct tunnel_ctx *tc, const char *chassis_id)
179{
180 int i;
181
182 for (i = 0; i < UINT16_MAX; i++) {
183 char *port_name;
184 port_name = xasprintf("ovn-%.6s-%x", chassis_id, i);
185
186 if (!sset_contains(&tc->port_names, port_name)) {
187 return port_name;
188 }
189
190 free(port_name);
191 }
192
193 return NULL;
194}
195
196
197static void
198tunnel_add(struct tunnel_ctx *tc, const char *new_chassis_id,
199 const struct sbrec_encap *encap)
200{
201 struct port_hash_node *hash_node;
202
203 /* Check whether such a row already exists in OVS. If so, remove it
204 * from 'tc->tunnel_hmap' and we're done. */
205 HMAP_FOR_EACH_WITH_HASH (hash_node, node,
206 port_hash(new_chassis_id,
207 encap->type, encap->ip),
208 &tc->tunnel_hmap) {
209 const struct ovsrec_port *port = hash_node->port;
210 const char *chassis_id = smap_get(&port->external_ids,
211 "ovn-chassis-id");
212 const struct ovsrec_interface *iface;
213 const char *ip;
214
215 if (!chassis_id || !port->n_interfaces) {
216 continue;
217 }
218
219 iface = port->interfaces[0];
220 ip = smap_get(&iface->options, "remote_ip");
221 if (!ip) {
222 continue;
223 }
224
225 if (!strcmp(new_chassis_id, chassis_id)
226 && !strcmp(encap->type, iface->type)
227 && !strcmp(encap->ip, ip)) {
228 hmap_remove(&tc->tunnel_hmap, &hash_node->node);
229 free(hash_node);
230 return;
231 }
232 }
233
234 /* No such port, so add one. */
235 struct smap external_ids = SMAP_INITIALIZER(&external_ids);
236 struct smap options = SMAP_INITIALIZER(&options);
237 struct ovsrec_port *port, **ports;
238 struct ovsrec_interface *iface;
239 char *port_name;
240 size_t i;
241
242 port_name = tunnel_create_name(tc, new_chassis_id);
243 if (!port_name) {
244 VLOG_WARN("Unable to allocate unique name for '%s' tunnel",
245 new_chassis_id);
246 return;
247 }
248
249 iface = ovsrec_interface_insert(tc->ovs_txn);
250 ovsrec_interface_set_name(iface, port_name);
251 ovsrec_interface_set_type(iface, encap->type);
252 smap_add(&options, "remote_ip", encap->ip);
253 smap_add(&options, "key", "flow");
254 ovsrec_interface_set_options(iface, &options);
255 smap_destroy(&options);
256
257 port = ovsrec_port_insert(tc->ovs_txn);
258 ovsrec_port_set_name(port, port_name);
259 ovsrec_port_set_interfaces(port, &iface, 1);
260 smap_add(&external_ids, "ovn-chassis-id", new_chassis_id);
261 ovsrec_port_set_external_ids(port, &external_ids);
262 smap_destroy(&external_ids);
263
264 ports = xmalloc(sizeof *tc->br_int->ports * (tc->br_int->n_ports + 1));
265 for (i = 0; i < tc->br_int->n_ports; i++) {
266 ports[i] = tc->br_int->ports[i];
267 }
268 ports[tc->br_int->n_ports] = port;
269 ovsrec_bridge_set_ports(tc->br_int, ports, tc->br_int->n_ports + 1);
270
271 sset_add(&tc->port_names, port_name);
272 free(port_name);
273 free(ports);
274}
275
276static void
277bridge_delete_port(const struct ovsrec_bridge *br,
278 const struct ovsrec_port *port)
279{
280 struct ovsrec_port **ports;
281 size_t i, n;
282
283 ports = xmalloc(sizeof *br->ports * br->n_ports);
284 for (i = n = 0; i < br->n_ports; i++) {
285 if (br->ports[i] != port) {
286 ports[n++] = br->ports[i];
287 }
288 }
289 ovsrec_bridge_set_ports(br, ports, n);
290 free(ports);
291}
292
293static struct sbrec_encap *
294preferred_encap(const struct sbrec_chassis *chassis_rec)
295{
296 size_t i;
297
298 /* For hypervisors, we only support Geneve and STT encapsulations.
299 * Sets are returned alphabetically, so "geneve" will be preferred
300 * over "stt". */
301 for (i = 0; i < chassis_rec->n_encaps; i++) {
302 if (!strcmp(chassis_rec->encaps[i]->type, "geneve")
303 || !strcmp(chassis_rec->encaps[i]->type, "stt")) {
304 return chassis_rec->encaps[i];
305 }
306 }
307
308 return NULL;
309}
310
311static void
312update_encaps(struct controller_ctx *ctx)
313{
314 const struct sbrec_chassis *chassis_rec;
315 const struct ovsrec_bridge *br;
316 int retval;
317
318 struct tunnel_ctx tc = {
319 .tunnel_hmap = HMAP_INITIALIZER(&tc.tunnel_hmap),
320 .port_names = SSET_INITIALIZER(&tc.port_names),
321 .br_int = ctx->br_int
322 };
323
324 tc.ovs_txn = ovsdb_idl_txn_create(ctx->ovs_idl);
325 ovsdb_idl_txn_add_comment(tc.ovs_txn,
326 "ovn-controller: modifying OVS tunnels '%s'",
327 ctx->chassis_id);
328
329 /* Collect all port names into tc.port_names.
330 *
331 * Collect all the OVN-created tunnels into tc.tunnel_hmap. */
332 OVSREC_BRIDGE_FOR_EACH(br, ctx->ovs_idl) {
333 size_t i;
334
335 for (i = 0; i < br->n_ports; i++) {
336 const struct ovsrec_port *port = br->ports[i];
337
338 sset_add(&tc.port_names, port->name);
339
340 if (smap_get(&port->external_ids, "ovn-chassis-id")) {
341 struct port_hash_node *hash_node = xzalloc(sizeof *hash_node);
342 hash_node->bridge = br;
343 hash_node->port = port;
344 hmap_insert(&tc.tunnel_hmap, &hash_node->node,
345 port_hash_rec(port));
346 }
347 }
348 }
349
350 SBREC_CHASSIS_FOR_EACH(chassis_rec, ctx->ovnsb_idl) {
351 if (strcmp(chassis_rec->name, ctx->chassis_id)) {
352 /* Create tunnels to the other chassis. */
353 const struct sbrec_encap *encap = preferred_encap(chassis_rec);
354 if (!encap) {
355 VLOG_INFO("No supported encaps for '%s'", chassis_rec->name);
356 continue;
357 }
358 tunnel_add(&tc, chassis_rec->name, encap);
359 }
360 }
361
362 /* Delete any existing OVN tunnels that were not still around. */
363 struct port_hash_node *hash_node, *next_hash_node;
364 HMAP_FOR_EACH_SAFE (hash_node, next_hash_node, node, &tc.tunnel_hmap) {
365 hmap_remove(&tc.tunnel_hmap, &hash_node->node);
366 bridge_delete_port(hash_node->bridge, hash_node->port);
367 free(hash_node);
368 }
369 hmap_destroy(&tc.tunnel_hmap);
370 sset_destroy(&tc.port_names);
371
372 retval = ovsdb_idl_txn_commit_block(tc.ovs_txn);
373 if (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
374 VLOG_INFO("Problem modifying OVS tunnels: %s",
375 ovsdb_idl_txn_status_to_string(retval));
376 poll_immediate_wake();
377 }
378 ovsdb_idl_txn_destroy(tc.ovs_txn);
379}
380
381void
382chassis_run(struct controller_ctx *ctx)
383{
384 register_chassis(ctx);
385 update_encaps(ctx);
386}
387
717c7fc5
JP
388void
389chassis_destroy(struct controller_ctx *ctx)
390{
391 int retval = TXN_TRY_AGAIN;
392
393 ovs_assert(ctx->ovnsb_idl);
394
395 while (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
396 const struct sbrec_chassis *chassis_rec;
397 struct ovsdb_idl_txn *txn;
398
399 SBREC_CHASSIS_FOR_EACH(chassis_rec, ctx->ovnsb_idl) {
3442ca9b 400 if (!strcmp(chassis_rec->name, ctx->chassis_id)) {
717c7fc5
JP
401 break;
402 }
403 }
404
405 if (!chassis_rec) {
e4901fe0 406 break;
717c7fc5
JP
407 }
408
409 txn = ovsdb_idl_txn_create(ctx->ovnsb_idl);
410 ovsdb_idl_txn_add_comment(txn,
411 "ovn-controller: unregistering chassis '%s'",
3442ca9b 412 ctx->chassis_id);
717c7fc5
JP
413 sbrec_chassis_delete(chassis_rec);
414
415 retval = ovsdb_idl_txn_commit_block(txn);
416 if (retval == TXN_ERROR) {
417 VLOG_INFO("Problem unregistering chassis: %s",
418 ovsdb_idl_txn_status_to_string(retval));
419 }
420 ovsdb_idl_txn_destroy(txn);
421 }
e4901fe0
JP
422
423 retval = TXN_TRY_AGAIN;
424 while (retval != TXN_SUCCESS && retval != TXN_UNCHANGED) {
425 struct ovsrec_port **ports;
426 struct ovsdb_idl_txn *txn;
427 size_t i, n;
428
429 txn = ovsdb_idl_txn_create(ctx->ovs_idl);
430 ovsdb_idl_txn_add_comment(txn,
431 "ovn-controller: destroying tunnels");
432
433 /* Delete all the OVS-created tunnels from the integration
434 * bridge. */
435 ports = xmalloc(sizeof *ctx->br_int->ports * ctx->br_int->n_ports);
436 for (i = n = 0; i < ctx->br_int->n_ports; i++) {
437 if (!smap_get(&ctx->br_int->ports[i]->external_ids,
438 "ovn-chassis-id")) {
439 ports[n++] = ctx->br_int->ports[i];
440 }
441 }
442 ovsrec_bridge_set_ports(ctx->br_int, ports, n);
443 free(ports);
444
445 retval = ovsdb_idl_txn_commit_block(txn);
446 if (retval == TXN_ERROR) {
447 VLOG_INFO("Problem destroying tunnels: %s",
448 ovsdb_idl_txn_status_to_string(retval));
449 }
450 ovsdb_idl_txn_destroy(txn);
451 }
717c7fc5 452}