ovn-controller: reload configured SB probe timer
[cascardo/ovs.git] / ovn / controller / ovn-controller.c
1 /* Copyright (c) 2015, 2016 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
18 #include "ovn-controller.h"
19
20 #include <errno.h>
21 #include <getopt.h>
22 #include <signal.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "binding.h"
27 #include "chassis.h"
28 #include "command-line.h"
29 #include "compiler.h"
30 #include "daemon.h"
31 #include "dirs.h"
32 #include "openvswitch/dynamic-string.h"
33 #include "encaps.h"
34 #include "fatal-signal.h"
35 #include "hmap.h"
36 #include "lflow.h"
37 #include "lib/vswitch-idl.h"
38 #include "lport.h"
39 #include "ofctrl.h"
40 #include "openvswitch/vconn.h"
41 #include "openvswitch/vlog.h"
42 #include "ovn/lib/ovn-sb-idl.h"
43 #include "ovn/lib/ovn-util.h"
44 #include "patch.h"
45 #include "physical.h"
46 #include "pinctrl.h"
47 #include "poll-loop.h"
48 #include "lib/bitmap.h"
49 #include "lib/hash.h"
50 #include "smap.h"
51 #include "sset.h"
52 #include "stream-ssl.h"
53 #include "stream.h"
54 #include "unixctl.h"
55 #include "util.h"
56
57 VLOG_DEFINE_THIS_MODULE(main);
58
59 static unixctl_cb_func ovn_controller_exit;
60 static unixctl_cb_func ct_zone_list;
61
62 #define DEFAULT_BRIDGE_NAME "br-int"
63 #define DEFAULT_PROBE_INTERVAL_MSEC 5000
64
65 static void update_probe_interval(struct controller_ctx *);
66 static void parse_options(int argc, char *argv[]);
67 OVS_NO_RETURN static void usage(void);
68
69 static char *ovs_remote;
70
71 struct local_datapath *
72 get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
73 {
74     struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
75     return (node
76             ? CONTAINER_OF(node, struct local_datapath, hmap_node)
77             : NULL);
78 }
79
80 struct patched_datapath *
81 get_patched_datapath(const struct hmap *patched_datapaths, uint32_t tunnel_key)
82 {
83     struct hmap_node *node = hmap_first_with_hash(patched_datapaths,
84                                                   tunnel_key);
85     return (node
86             ? CONTAINER_OF(node, struct patched_datapath, hmap_node)
87             : NULL);
88 }
89
90 const struct sbrec_chassis *
91 get_chassis(struct ovsdb_idl *ovnsb_idl, const char *chassis_id)
92 {
93     const struct sbrec_chassis *chassis_rec;
94
95     SBREC_CHASSIS_FOR_EACH(chassis_rec, ovnsb_idl) {
96         if (!strcmp(chassis_rec->name, chassis_id)) {
97             break;
98         }
99     }
100
101     return chassis_rec;
102 }
103
104 uint32_t
105 get_tunnel_type(const char *name)
106 {
107     if (!strcmp(name, "geneve")) {
108         return GENEVE;
109     } else if (!strcmp(name, "stt")) {
110         return STT;
111     } else if (!strcmp(name, "vxlan")) {
112         return VXLAN;
113     }
114
115     return 0;
116 }
117
118 const struct ovsrec_bridge *
119 get_bridge(struct ovsdb_idl *ovs_idl, const char *br_name)
120 {
121     const struct ovsrec_bridge *br;
122     OVSREC_BRIDGE_FOR_EACH (br, ovs_idl) {
123         if (!strcmp(br->name, br_name)) {
124             return br;
125         }
126     }
127     return NULL;
128 }
129
130 static const struct ovsrec_bridge *
131 create_br_int(struct controller_ctx *ctx,
132               const struct ovsrec_open_vswitch *cfg,
133               const char *bridge_name)
134 {
135     if (!ctx->ovs_idl_txn) {
136         return NULL;
137     }
138
139     ovsdb_idl_txn_add_comment(ctx->ovs_idl_txn,
140             "ovn-controller: creating integration bridge '%s'", bridge_name);
141
142     struct ovsrec_interface *iface;
143     iface = ovsrec_interface_insert(ctx->ovs_idl_txn);
144     ovsrec_interface_set_name(iface, bridge_name);
145     ovsrec_interface_set_type(iface, "internal");
146
147     struct ovsrec_port *port;
148     port = ovsrec_port_insert(ctx->ovs_idl_txn);
149     ovsrec_port_set_name(port, bridge_name);
150     ovsrec_port_set_interfaces(port, &iface, 1);
151
152     struct ovsrec_bridge *bridge;
153     bridge = ovsrec_bridge_insert(ctx->ovs_idl_txn);
154     ovsrec_bridge_set_name(bridge, bridge_name);
155     ovsrec_bridge_set_fail_mode(bridge, "secure");
156     const struct smap oc = SMAP_CONST1(&oc, "disable-in-band", "true");
157     ovsrec_bridge_set_other_config(bridge, &oc);
158     ovsrec_bridge_set_ports(bridge, &port, 1);
159
160     struct ovsrec_bridge **bridges;
161     size_t bytes = sizeof *bridges * cfg->n_bridges;
162     bridges = xmalloc(bytes + sizeof *bridges);
163     memcpy(bridges, cfg->bridges, bytes);
164     bridges[cfg->n_bridges] = bridge;
165     ovsrec_open_vswitch_verify_bridges(cfg);
166     ovsrec_open_vswitch_set_bridges(cfg, bridges, cfg->n_bridges + 1);
167
168     return bridge;
169 }
170
171 static const struct ovsrec_bridge *
172 get_br_int(struct controller_ctx *ctx)
173 {
174     const struct ovsrec_open_vswitch *cfg;
175     cfg = ovsrec_open_vswitch_first(ctx->ovs_idl);
176     if (!cfg) {
177         return NULL;
178     }
179
180     const char *br_int_name = smap_get(&cfg->external_ids, "ovn-bridge");
181     if (!br_int_name) {
182         br_int_name = DEFAULT_BRIDGE_NAME;
183     }
184
185     const struct ovsrec_bridge *br;
186     br = get_bridge(ctx->ovs_idl, br_int_name);
187     if (!br) {
188         return create_br_int(ctx, cfg, br_int_name);
189     }
190     return br;
191 }
192
193 static const char *
194 get_chassis_id(const struct ovsdb_idl *ovs_idl)
195 {
196     const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl);
197     const char *chassis_id = cfg ? smap_get(&cfg->external_ids, "system-id") : NULL;
198
199     if (!chassis_id) {
200         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
201         VLOG_WARN_RL(&rl, "'system-id' in Open_vSwitch database is missing.");
202     }
203
204     return chassis_id;
205 }
206
207 /* Retrieves the OVN Southbound remote location from the
208  * "external-ids:ovn-remote" key in 'ovs_idl' and returns a copy of it.
209  *
210  * XXX ovn-controller does not support this changing mid-run, but that should
211  * be addressed later. */
212 static char *
213 get_ovnsb_remote(struct ovsdb_idl *ovs_idl)
214 {
215     while (1) {
216         ovsdb_idl_run(ovs_idl);
217
218         const struct ovsrec_open_vswitch *cfg
219             = ovsrec_open_vswitch_first(ovs_idl);
220         if (cfg) {
221             const char *remote = smap_get(&cfg->external_ids, "ovn-remote");
222             if (remote) {
223                 return xstrdup(remote);
224             }
225         }
226
227         VLOG_INFO("OVN OVSDB remote not specified.  Waiting...");
228         ovsdb_idl_wait(ovs_idl);
229         poll_block();
230     }
231 }
232
233 static void
234 update_ct_zones(struct sset *lports, struct hmap *patched_datapaths,
235                 struct simap *ct_zones, unsigned long *ct_zone_bitmap)
236 {
237     struct simap_node *ct_zone, *ct_zone_next;
238     int scan_start = 1;
239     struct patched_datapath *pd;
240     const char *user;
241     struct sset all_users = SSET_INITIALIZER(&all_users);
242
243     SSET_FOR_EACH(user, lports) {
244         sset_add(&all_users, user);
245     }
246
247     /* Local patched datapath (gateway routers) need zones assigned. */
248     HMAP_FOR_EACH(pd, hmap_node, patched_datapaths) {
249         if (!pd->local) {
250             continue;
251         }
252
253         char *dnat = alloc_nat_zone_key(pd->port_binding, "dnat");
254         char *snat = alloc_nat_zone_key(pd->port_binding, "snat");
255         sset_add(&all_users, dnat);
256         sset_add(&all_users, snat);
257         free(dnat);
258         free(snat);
259     }
260
261     /* Delete zones that do not exist in above sset. */
262     SIMAP_FOR_EACH_SAFE(ct_zone, ct_zone_next, ct_zones) {
263         if (!sset_contains(&all_users, ct_zone->name)) {
264             bitmap_set0(ct_zone_bitmap, ct_zone->data);
265             simap_delete(ct_zones, ct_zone);
266         }
267     }
268
269     /* xxx This is wasteful to assign a zone to each port--even if no
270      * xxx security policy is applied. */
271
272     /* Assign a unique zone id for each logical port and two zones
273      * to a gateway router. */
274     SSET_FOR_EACH(user, &all_users) {
275         size_t zone;
276
277         if (simap_contains(ct_zones, user)) {
278             continue;
279         }
280
281         /* We assume that there are 64K zones and that we own them all. */
282         zone = bitmap_scan(ct_zone_bitmap, 0, scan_start, MAX_CT_ZONES + 1);
283         if (zone == MAX_CT_ZONES + 1) {
284             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
285             VLOG_WARN_RL(&rl, "exhausted all ct zones");
286             return;
287         }
288         scan_start = zone + 1;
289
290         bitmap_set1(ct_zone_bitmap, zone);
291         simap_put(ct_zones, user, zone);
292
293         /* xxx We should erase any old entries for this
294          * xxx zone, but we need a generic interface to the conntrack
295          * xxx table. */
296     }
297
298     sset_destroy(&all_users);
299 }
300
301 /* Contains "struct local_datapath" nodes whose hash values are the
302  * tunnel_key of datapaths with at least one local port binding. */
303 static struct hmap local_datapaths = HMAP_INITIALIZER(&local_datapaths);
304 static struct hmap patched_datapaths = HMAP_INITIALIZER(&patched_datapaths);
305
306 static struct lport_index lports;
307 static struct mcgroup_index mcgroups;
308
309 int
310 main(int argc, char *argv[])
311 {
312     struct unixctl_server *unixctl;
313     bool exiting;
314     int retval;
315
316     ovs_cmdl_proctitle_init(argc, argv);
317     set_program_name(argv[0]);
318     service_start(&argc, &argv);
319     parse_options(argc, argv);
320     fatal_ignore_sigpipe();
321
322     daemonize_start(false);
323
324     retval = unixctl_server_create(NULL, &unixctl);
325     if (retval) {
326         exit(EXIT_FAILURE);
327     }
328     unixctl_command_register("exit", "", 0, 0, ovn_controller_exit, &exiting);
329
330     daemonize_complete();
331
332     ovsrec_init();
333     sbrec_init();
334
335     ofctrl_init();
336     pinctrl_init();
337     lflow_init();
338
339     lport_index_init(&lports);
340     mcgroup_index_init(&mcgroups);
341
342     /* Connect to OVS OVSDB instance.  We do not monitor all tables by
343      * default, so modules must register their interest explicitly.  */
344     struct ovsdb_idl_loop ovs_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
345         ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true));
346     ovsdb_idl_add_table(ovs_idl_loop.idl, &ovsrec_table_open_vswitch);
347     ovsdb_idl_add_column(ovs_idl_loop.idl,
348                          &ovsrec_open_vswitch_col_external_ids);
349     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_open_vswitch_col_bridges);
350     ovsdb_idl_add_table(ovs_idl_loop.idl, &ovsrec_table_interface);
351     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_interface_col_name);
352     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_interface_col_type);
353     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_interface_col_options);
354     ovsdb_idl_add_table(ovs_idl_loop.idl, &ovsrec_table_port);
355     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_port_col_name);
356     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_port_col_interfaces);
357     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_port_col_external_ids);
358     ovsdb_idl_add_table(ovs_idl_loop.idl, &ovsrec_table_bridge);
359     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_bridge_col_ports);
360     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_bridge_col_name);
361     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_bridge_col_fail_mode);
362     ovsdb_idl_add_column(ovs_idl_loop.idl, &ovsrec_bridge_col_other_config);
363     chassis_register_ovs_idl(ovs_idl_loop.idl);
364     encaps_register_ovs_idl(ovs_idl_loop.idl);
365     binding_register_ovs_idl(ovs_idl_loop.idl);
366     physical_register_ovs_idl(ovs_idl_loop.idl);
367     ovsdb_idl_get_initial_snapshot(ovs_idl_loop.idl);
368
369     /* Connect to OVN SB database. */
370     char *ovnsb_remote = get_ovnsb_remote(ovs_idl_loop.idl);
371     struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
372         ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true));
373
374     /* Track the southbound idl. */
375     ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
376
377     ovsdb_idl_get_initial_snapshot(ovnsb_idl_loop.idl);
378
379     /* Initialize connection tracking zones. */
380     struct simap ct_zones = SIMAP_INITIALIZER(&ct_zones);
381     unsigned long ct_zone_bitmap[BITMAP_N_LONGS(MAX_CT_ZONES)];
382     memset(ct_zone_bitmap, 0, sizeof ct_zone_bitmap);
383     bitmap_set1(ct_zone_bitmap, 0); /* Zone 0 is reserved. */
384     unixctl_command_register("ct-zone-list", "", 0, 0,
385                              ct_zone_list, &ct_zones);
386
387     /* Main loop. */
388     exiting = false;
389     while (!exiting) {
390         /* Check OVN SB database. */
391         char *new_ovnsb_remote = get_ovnsb_remote(ovs_idl_loop.idl);
392         if (strcmp(ovnsb_remote, new_ovnsb_remote)) {
393             free(ovnsb_remote);
394             ovnsb_remote = new_ovnsb_remote;
395             ovsdb_idl_set_remote(ovnsb_idl_loop.idl, ovnsb_remote, true);
396             binding_reset_processing();
397             lport_index_clear(&lports);
398             mcgroup_index_clear(&mcgroups);
399         } else {
400             free(new_ovnsb_remote);
401         }
402
403         struct controller_ctx ctx = {
404             .ovs_idl = ovs_idl_loop.idl,
405             .ovs_idl_txn = ovsdb_idl_loop_run(&ovs_idl_loop),
406             .ovnsb_idl = ovnsb_idl_loop.idl,
407             .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
408         };
409
410         update_probe_interval(&ctx);
411
412         struct sset all_lports = SSET_INITIALIZER(&all_lports);
413
414         const struct ovsrec_bridge *br_int = get_br_int(&ctx);
415         const char *chassis_id = get_chassis_id(ctx.ovs_idl);
416
417         if (chassis_id) {
418             chassis_run(&ctx, chassis_id);
419             encaps_run(&ctx, br_int, chassis_id);
420             binding_run(&ctx, br_int, chassis_id, &local_datapaths);
421         }
422
423         if (br_int && chassis_id) {
424             patch_run(&ctx, br_int, chassis_id, &local_datapaths,
425                       &patched_datapaths);
426
427             lport_index_fill(&lports, ctx.ovnsb_idl);
428             mcgroup_index_fill(&mcgroups, ctx.ovnsb_idl);
429
430             enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int);
431
432             pinctrl_run(&ctx, &lports, br_int, chassis_id, &local_datapaths);
433             update_ct_zones(&all_lports, &patched_datapaths, &ct_zones,
434                             ct_zone_bitmap);
435
436             struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
437             lflow_run(&ctx, &lports, &mcgroups, &local_datapaths,
438                       &patched_datapaths, &ct_zones, &flow_table);
439             if (chassis_id) {
440                 physical_run(&ctx, mff_ovn_geneve,
441                              br_int, chassis_id, &ct_zones, &flow_table,
442                              &local_datapaths, &patched_datapaths);
443             }
444             ofctrl_put(&flow_table);
445             hmap_destroy(&flow_table);
446         }
447
448         sset_destroy(&all_lports);
449
450         unixctl_server_run(unixctl);
451
452         unixctl_server_wait(unixctl);
453         if (exiting) {
454             poll_immediate_wake();
455         }
456
457         if (br_int) {
458             ofctrl_wait();
459             pinctrl_wait(&ctx);
460         }
461         ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
462         ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
463         ovsdb_idl_track_clear(ovnsb_idl_loop.idl);
464         poll_block();
465         if (should_service_stop()) {
466             exiting = true;
467         }
468     }
469
470     /* It's time to exit.  Clean up the databases. */
471     bool done = false;
472     while (!done) {
473         struct controller_ctx ctx = {
474             .ovs_idl = ovs_idl_loop.idl,
475             .ovs_idl_txn = ovsdb_idl_loop_run(&ovs_idl_loop),
476             .ovnsb_idl = ovnsb_idl_loop.idl,
477             .ovnsb_idl_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
478         };
479
480         const struct ovsrec_bridge *br_int = get_br_int(&ctx);
481         const char *chassis_id = get_chassis_id(ctx.ovs_idl);
482
483         /* Run all of the cleanup functions, even if one of them returns false.
484          * We're done if all of them return true. */
485         done = binding_cleanup(&ctx, chassis_id);
486         done = chassis_cleanup(&ctx, chassis_id) && done;
487         done = encaps_cleanup(&ctx, br_int) && done;
488         if (done) {
489             poll_immediate_wake();
490         }
491
492         ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
493         ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
494         poll_block();
495     }
496
497     unixctl_server_destroy(unixctl);
498     lflow_destroy();
499     ofctrl_destroy();
500     pinctrl_destroy();
501
502     simap_destroy(&ct_zones);
503
504     ovsdb_idl_loop_destroy(&ovs_idl_loop);
505     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
506
507     free(ovnsb_remote);
508     free(ovs_remote);
509     service_stop();
510
511     exit(retval);
512 }
513
514 static void
515 parse_options(int argc, char *argv[])
516 {
517     enum {
518         OPT_PEER_CA_CERT = UCHAR_MAX + 1,
519         OPT_BOOTSTRAP_CA_CERT,
520         VLOG_OPTION_ENUMS,
521         DAEMON_OPTION_ENUMS
522     };
523
524     static struct option long_options[] = {
525         {"help", no_argument, NULL, 'h'},
526         {"version", no_argument, NULL, 'V'},
527         VLOG_LONG_OPTIONS,
528         DAEMON_LONG_OPTIONS,
529         STREAM_SSL_LONG_OPTIONS,
530         {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
531         {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
532         {NULL, 0, NULL, 0}
533     };
534     char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
535
536     for (;;) {
537         int c;
538
539         c = getopt_long(argc, argv, short_options, long_options, NULL);
540         if (c == -1) {
541             break;
542         }
543
544         switch (c) {
545         case 'h':
546             usage();
547
548         case 'V':
549             ovs_print_version(OFP13_VERSION, OFP13_VERSION);
550             exit(EXIT_SUCCESS);
551
552         VLOG_OPTION_HANDLERS
553         DAEMON_OPTION_HANDLERS
554         STREAM_SSL_OPTION_HANDLERS
555
556         case OPT_PEER_CA_CERT:
557             stream_ssl_set_peer_ca_cert_file(optarg);
558             break;
559
560         case OPT_BOOTSTRAP_CA_CERT:
561             stream_ssl_set_ca_cert_file(optarg, true);
562             break;
563
564         case '?':
565             exit(EXIT_FAILURE);
566
567         default:
568             abort();
569         }
570     }
571     free(short_options);
572
573     argc -= optind;
574     argv += optind;
575
576     if (argc == 0) {
577         ovs_remote = xasprintf("unix:%s/db.sock", ovs_rundir());
578     } else if (argc == 1) {
579         ovs_remote = xstrdup(argv[0]);
580     } else {
581         VLOG_FATAL("exactly zero or one non-option argument required; "
582                    "use --help for usage");
583     }
584 }
585
586 static void
587 usage(void)
588 {
589     printf("%s: OVN controller\n"
590            "usage %s [OPTIONS] [OVS-DATABASE]\n"
591            "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n",
592                program_name, program_name);
593     stream_usage("OVS-DATABASE", true, false, false);
594     daemon_usage();
595     vlog_usage();
596     printf("\nOther options:\n"
597            "  -h, --help              display this help message\n"
598            "  -V, --version           display version information\n");
599     exit(EXIT_SUCCESS);
600 }
601
602 static void
603 ovn_controller_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
604              const char *argv[] OVS_UNUSED, void *exiting_)
605 {
606     bool *exiting = exiting_;
607     *exiting = true;
608
609     unixctl_command_reply(conn, NULL);
610 }
611
612 static void
613 ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
614              const char *argv[] OVS_UNUSED, void *ct_zones_)
615 {
616     struct simap *ct_zones = ct_zones_;
617     struct ds ds = DS_EMPTY_INITIALIZER;
618     struct simap_node *zone;
619
620     SIMAP_FOR_EACH(zone, ct_zones) {
621         ds_put_format(&ds, "%s %d\n", zone->name, zone->data);
622     }
623
624     unixctl_command_reply(conn, ds_cstr(&ds));
625     ds_destroy(&ds);
626 }
627
628 /* Get the desired SB probe timer from the OVS database and configure it into
629  * the SB database. */
630 static void
631 update_probe_interval(struct controller_ctx *ctx)
632 {
633     const struct ovsrec_open_vswitch *cfg
634         = ovsrec_open_vswitch_first(ctx->ovs_idl);
635     int interval = (cfg
636                     ? smap_get_int(&cfg->external_ids,
637                                    "ovn-remote-probe-interval",
638                                    DEFAULT_PROBE_INTERVAL_MSEC)
639                     : DEFAULT_PROBE_INTERVAL_MSEC);
640     ovsdb_idl_set_probe_interval(ctx->ovnsb_idl, interval);
641 }