]> git.proxmox.com Git - mirror_ovs.git/blob - ovsdb/raft-private.c
ovn-nbctl: Fix the ovn-nbctl test "LBs - daemon" which fails during rpm build
[mirror_ovs.git] / ovsdb / raft-private.c
1 /*
2 * Copyright (c) 2017, 2018 Nicira, Inc.
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
17 #include <config.h>
18
19 #include "raft-private.h"
20
21 #include "openvswitch/dynamic-string.h"
22 #include "ovsdb-error.h"
23 #include "ovsdb-parser.h"
24 #include "socket-util.h"
25 #include "sset.h"
26 \f
27 /* Addresses of Raft servers. */
28
29 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
30 raft_address_validate(const char *address)
31 {
32 if (!strncmp(address, "unix:", 5)) {
33 return NULL;
34 } else if (!strncmp(address, "ssl:", 4) || !strncmp(address, "tcp:", 4)) {
35 struct sockaddr_storage ss;
36 if (!inet_parse_active(address + 4, -1, &ss, true)) {
37 return ovsdb_error(NULL, "%s: syntax error in address", address);
38 }
39 return NULL;
40 } else {
41 return ovsdb_error(NULL, "%s: expected \"tcp\" or \"ssl\" address",
42 address);
43 }
44 }
45
46 static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
47 raft_address_validate_json(const struct json *address)
48 {
49 if (address->type != JSON_STRING) {
50 return ovsdb_syntax_error(address, NULL,
51 "server address is not string");
52 }
53 return raft_address_validate(json_string(address));
54 }
55
56 /* Constructs and returns a "nickname" for a Raft server based on its 'address'
57 * and server ID 'sid'. The nickname is just a short name for the server to
58 * use in log messages, to make them more readable.
59 *
60 * The caller must eventually free the returned string. */
61 char *
62 raft_address_to_nickname(const char *address, const struct uuid *sid)
63 {
64 if (!strncmp(address, "unix:", 5)) {
65 const char *p = address + 5;
66
67 const char *slash = strrchr(p, '/');
68 if (slash) {
69 p = slash + 1;
70 }
71
72 int len = strcspn(p, ".");
73 if (len) {
74 return xmemdup0(p, len);
75 }
76 }
77
78 return xasprintf(SID_FMT, SID_ARGS(sid));
79 }
80 \f
81 /* Sets of Raft server addresses. */
82
83 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
84 raft_addresses_from_json(const struct json *json, struct sset *addresses)
85 {
86 sset_init(addresses);
87
88 const struct json_array *array = json_array(json);
89 if (!array->n) {
90 return ovsdb_syntax_error(json, NULL,
91 "at least one remote address is required");
92 }
93 for (size_t i = 0; i < array->n; i++) {
94 const struct json *address = array->elems[i];
95 struct ovsdb_error *error = raft_address_validate_json(address);
96 if (error) {
97 sset_destroy(addresses);
98 sset_init(addresses);
99 return error;
100 }
101 sset_add(addresses, json_string(address));
102 }
103 return NULL;
104 }
105
106 struct json *
107 raft_addresses_to_json(const struct sset *sset)
108 {
109 struct json *array;
110 const char *s;
111
112 array = json_array_create_empty();
113 SSET_FOR_EACH (s, sset) {
114 json_array_add(array, json_string_create(s));
115 }
116 return array;
117 }
118 \f
119 /* raft_server. */
120
121 const char *
122 raft_server_phase_to_string(enum raft_server_phase phase)
123 {
124 switch (phase) {
125 case RAFT_PHASE_STABLE: return "stable";
126 case RAFT_PHASE_CATCHUP: return "adding: catchup";
127 case RAFT_PHASE_CAUGHT_UP: return "adding: caught up";
128 case RAFT_PHASE_COMMITTING: return "adding: committing";
129 case RAFT_PHASE_REMOVE: return "removing";
130 default: return "<error>";
131 }
132 }
133
134 void
135 raft_server_destroy(struct raft_server *s)
136 {
137 if (s) {
138 free(s->address);
139 free(s->nickname);
140 free(s);
141 }
142 }
143
144 void
145 raft_servers_destroy(struct hmap *servers)
146 {
147 struct raft_server *s, *next;
148 HMAP_FOR_EACH_SAFE (s, next, hmap_node, servers) {
149 hmap_remove(servers, &s->hmap_node);
150 raft_server_destroy(s);
151 }
152 hmap_destroy(servers);
153 }
154
155 struct raft_server *
156 raft_server_add(struct hmap *servers, const struct uuid *sid,
157 const char *address)
158 {
159 struct raft_server *s = xzalloc(sizeof *s);
160 s->sid = *sid;
161 s->address = xstrdup(address);
162 s->nickname = raft_address_to_nickname(address, sid);
163 s->phase = RAFT_PHASE_STABLE;
164 hmap_insert(servers, &s->hmap_node, uuid_hash(sid));
165 return s;
166 }
167
168
169 struct raft_server *
170 raft_server_find(const struct hmap *servers, const struct uuid *sid)
171 {
172 struct raft_server *s;
173 HMAP_FOR_EACH_IN_BUCKET (s, hmap_node, uuid_hash(sid), servers) {
174 if (uuid_equals(sid, &s->sid)) {
175 return s;
176 }
177 }
178 return NULL;
179 }
180
181 const char *
182 raft_servers_get_nickname__(const struct hmap *servers, const struct uuid *sid)
183 {
184 const struct raft_server *s = raft_server_find(servers, sid);
185 return s ? s->nickname : NULL;
186 }
187
188 const char *
189 raft_servers_get_nickname(const struct hmap *servers,
190 const struct uuid *sid,
191 char buf[SID_LEN + 1], size_t bufsize)
192 {
193 const char *s = raft_servers_get_nickname__(servers, sid);
194 if (s) {
195 return s;
196 }
197 snprintf(buf, bufsize, SID_FMT, SID_ARGS(sid));
198 return buf;
199 }
200
201 static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
202 raft_servers_from_json__(const struct json *json, struct hmap *servers)
203 {
204 if (!json || json->type != JSON_OBJECT) {
205 return ovsdb_syntax_error(json, NULL, "servers must be JSON object");
206 } else if (shash_is_empty(json_object(json))) {
207 return ovsdb_syntax_error(json, NULL, "must have at least one server");
208 }
209
210 /* Parse new servers. */
211 struct shash_node *node;
212 SHASH_FOR_EACH (node, json_object(json)) {
213 /* Parse server UUID. */
214 struct uuid sid;
215 if (!uuid_from_string(&sid, node->name)) {
216 return ovsdb_syntax_error(json, NULL, "%s is not a UUID",
217 node->name);
218 }
219
220 const struct json *address = node->data;
221 struct ovsdb_error *error = raft_address_validate_json(address);
222 if (error) {
223 return error;
224 }
225
226 raft_server_add(servers, &sid, json_string(address));
227 }
228
229 return NULL;
230 }
231
232 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
233 raft_servers_from_json(const struct json *json, struct hmap *servers)
234 {
235 hmap_init(servers);
236 struct ovsdb_error *error = raft_servers_from_json__(json, servers);
237 if (error) {
238 raft_servers_destroy(servers);
239 }
240 return error;
241 }
242
243 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
244 raft_servers_validate_json(const struct json *json)
245 {
246 struct hmap servers = HMAP_INITIALIZER(&servers);
247 struct ovsdb_error *error = raft_servers_from_json__(json, &servers);
248 raft_servers_destroy(&servers);
249 return error;
250 }
251
252 struct json *
253 raft_servers_to_json(const struct hmap *servers)
254 {
255 struct json *json = json_object_create();
256 struct raft_server *s;
257 HMAP_FOR_EACH (s, hmap_node, servers) {
258 char sid_s[UUID_LEN + 1];
259 sprintf(sid_s, UUID_FMT, UUID_ARGS(&s->sid));
260 json_object_put_string(json, sid_s, s->address);
261 }
262 return json;
263 }
264
265 void
266 raft_servers_format(const struct hmap *servers, struct ds *ds)
267 {
268 int i = 0;
269 const struct raft_server *s;
270 HMAP_FOR_EACH (s, hmap_node, servers) {
271 if (i++) {
272 ds_put_cstr(ds, ", ");
273 }
274 ds_put_format(ds, SID_FMT"(%s)", SID_ARGS(&s->sid), s->address);
275 }
276 }
277 \f
278 /* Raft log entries. */
279
280 void
281 raft_entry_clone(struct raft_entry *dst, const struct raft_entry *src)
282 {
283 dst->term = src->term;
284 dst->data = json_nullable_clone(src->data);
285 dst->eid = src->eid;
286 dst->servers = json_nullable_clone(src->servers);
287 }
288
289 void
290 raft_entry_uninit(struct raft_entry *e)
291 {
292 if (e) {
293 json_destroy(e->data);
294 json_destroy(e->servers);
295 }
296 }
297
298 struct json *
299 raft_entry_to_json(const struct raft_entry *e)
300 {
301 struct json *json = json_object_create();
302 raft_put_uint64(json, "term", e->term);
303 if (e->data) {
304 json_object_put(json, "data", json_clone(e->data));
305 json_object_put_format(json, "eid", UUID_FMT, UUID_ARGS(&e->eid));
306 }
307 if (e->servers) {
308 json_object_put(json, "servers", json_clone(e->servers));
309 }
310 return json;
311 }
312
313 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
314 raft_entry_from_json(struct json *json, struct raft_entry *e)
315 {
316 memset(e, 0, sizeof *e);
317
318 struct ovsdb_parser p;
319 ovsdb_parser_init(&p, json, "raft log entry");
320 e->term = raft_parse_required_uint64(&p, "term");
321 e->data = json_nullable_clone(
322 ovsdb_parser_member(&p, "data", OP_OBJECT | OP_ARRAY | OP_OPTIONAL));
323 e->eid = e->data ? raft_parse_required_uuid(&p, "eid") : UUID_ZERO;
324 e->servers = json_nullable_clone(
325 ovsdb_parser_member(&p, "servers", OP_OBJECT | OP_OPTIONAL));
326 if (e->servers) {
327 ovsdb_parser_put_error(&p, raft_servers_validate_json(e->servers));
328 }
329
330 struct ovsdb_error *error = ovsdb_parser_finish(&p);
331 if (error) {
332 raft_entry_uninit(e);
333 }
334 return error;
335 }
336
337 bool
338 raft_entry_equals(const struct raft_entry *a, const struct raft_entry *b)
339 {
340 return (a->term == b->term
341 && json_equal(a->data, b->data)
342 && uuid_equals(&a->eid, &b->eid)
343 && json_equal(a->servers, b->servers));
344 }
345 \f
346 void
347 raft_header_uninit(struct raft_header *h)
348 {
349 if (!h) {
350 return;
351 }
352
353 free(h->name);
354 free(h->local_address);
355 sset_destroy(&h->remote_addresses);
356 raft_entry_uninit(&h->snap);
357 }
358
359 static void
360 raft_header_from_json__(struct raft_header *h, struct ovsdb_parser *p)
361 {
362 /* Parse always-required fields. */
363 h->sid = raft_parse_required_uuid(p, "server_id");
364 h->name = nullable_xstrdup(raft_parse_required_string(p, "name"));
365 h->local_address = nullable_xstrdup(
366 raft_parse_required_string(p, "local_address"));
367
368 /* Parse "remote_addresses", if present.
369 *
370 * If this is present, then this database file is for the special case of a
371 * server that was created with "ovsdb-tool join-cluster" and has not yet
372 * joined its cluster, */
373 const struct json *remote_addresses
374 = ovsdb_parser_member(p, "remote_addresses", OP_ARRAY | OP_OPTIONAL);
375 h->joining = remote_addresses != NULL;
376 if (h->joining) {
377 struct ovsdb_error *error = raft_addresses_from_json(
378 remote_addresses, &h->remote_addresses);
379 if (error) {
380 ovsdb_parser_put_error(p, error);
381 } else if (sset_find_and_delete(&h->remote_addresses, h->local_address)
382 && sset_is_empty(&h->remote_addresses)) {
383 ovsdb_parser_raise_error(p, "at least one remote address (other "
384 "than the local address) is required");
385 }
386 } else {
387 /* The set of servers is mandatory. */
388 h->snap.servers = json_nullable_clone(
389 ovsdb_parser_member(p, "prev_servers", OP_OBJECT));
390 if (h->snap.servers) {
391 ovsdb_parser_put_error(p, raft_servers_validate_json(
392 h->snap.servers));
393 }
394
395 /* Term, index, and snapshot are optional, but if any of them is
396 * present, all of them must be. */
397 h->snap_index = raft_parse_optional_uint64(p, "prev_index");
398 if (h->snap_index) {
399 h->snap.data = json_nullable_clone(
400 ovsdb_parser_member(p, "prev_data", OP_ANY));
401 h->snap.eid = raft_parse_required_uuid(p, "prev_eid");
402 h->snap.term = raft_parse_required_uint64(p, "prev_term");
403 }
404 }
405
406 /* Parse cluster ID. If we're joining a cluster, this is optional,
407 * otherwise it is mandatory. */
408 raft_parse_uuid(p, "cluster_id", h->joining, &h->cid);
409 }
410
411 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
412 raft_header_from_json(struct raft_header *h, const struct json *json)
413 {
414 struct ovsdb_parser p;
415 ovsdb_parser_init(&p, json, "raft header");
416 memset(h, 0, sizeof *h);
417 sset_init(&h->remote_addresses);
418 raft_header_from_json__(h, &p);
419 struct ovsdb_error *error = ovsdb_parser_finish(&p);
420 if (error) {
421 raft_header_uninit(h);
422 }
423 return error;
424 }
425
426 struct json *
427 raft_header_to_json(const struct raft_header *h)
428 {
429 struct json *json = json_object_create();
430
431 json_object_put_format(json, "server_id", UUID_FMT, UUID_ARGS(&h->sid));
432 if (!uuid_is_zero(&h->cid)) {
433 json_object_put_format(json, "cluster_id",
434 UUID_FMT, UUID_ARGS(&h->cid));
435 }
436 json_object_put_string(json, "local_address", h->local_address);
437 json_object_put_string(json, "name", h->name);
438
439 if (!sset_is_empty(&h->remote_addresses)) {
440 json_object_put(json, "remote_addresses",
441 raft_addresses_to_json(&h->remote_addresses));
442 }
443
444 if (h->snap.servers) {
445 json_object_put(json, "prev_servers", json_clone(h->snap.servers));
446 }
447 if (h->snap_index) {
448 raft_put_uint64(json, "prev_index", h->snap_index);
449 raft_put_uint64(json, "prev_term", h->snap.term);
450 if (h->snap.data) {
451 json_object_put(json, "prev_data", json_clone(h->snap.data));
452 }
453 json_object_put_format(json, "prev_eid",
454 UUID_FMT, UUID_ARGS(&h->snap.eid));
455 }
456
457 return json;
458 }
459 \f
460 void
461 raft_record_uninit(struct raft_record *r)
462 {
463 if (!r) {
464 return;
465 }
466
467 free(r->comment);
468
469 switch (r->type) {
470 case RAFT_REC_ENTRY:
471 json_destroy(r->entry.data);
472 json_destroy(r->entry.servers);
473 break;
474
475 case RAFT_REC_NOTE:
476 free(r->note);
477 break;
478
479 case RAFT_REC_TERM:
480 case RAFT_REC_VOTE:
481 case RAFT_REC_COMMIT_INDEX:
482 case RAFT_REC_LEADER:
483 break;
484 }
485 }
486
487 static void
488 raft_record_from_json__(struct raft_record *r, struct ovsdb_parser *p)
489 {
490 r->comment = nullable_xstrdup(raft_parse_optional_string(p, "comment"));
491
492 /* Parse "note". */
493 const char *note = raft_parse_optional_string(p, "note");
494 if (note) {
495 r->type = RAFT_REC_NOTE;
496 r->term = 0;
497 r->note = xstrdup(note);
498 return;
499 }
500
501 /* Parse "commit_index". */
502 r->commit_index = raft_parse_optional_uint64(p, "commit_index");
503 if (r->commit_index) {
504 r->type = RAFT_REC_COMMIT_INDEX;
505 r->term = 0;
506 return;
507 }
508
509 /* All remaining types of log records include "term", plus at most one of:
510 *
511 * - "index" plus zero or more of "data", "eid", and "servers". "data"
512 * and "eid" must be both present or both absent.
513 *
514 * - "vote".
515 *
516 * - "leader".
517 */
518
519 /* Parse "term".
520 *
521 * A Raft leader can replicate entries from previous terms to the other
522 * servers in the cluster, retaining the original terms on those entries
523 * (see section 3.6.2 "Committing entries from previous terms" for more
524 * information), so it's OK for the term in a log record to precede the
525 * current term. */
526 r->term = raft_parse_required_uint64(p, "term");
527
528 /* Parse "leader". */
529 if (raft_parse_optional_uuid(p, "leader", &r->sid)) {
530 r->type = RAFT_REC_LEADER;
531 if (uuid_is_zero(&r->sid)) {
532 ovsdb_parser_raise_error(p, "record says leader is all-zeros SID");
533 }
534 return;
535 }
536
537 /* Parse "vote". */
538 if (raft_parse_optional_uuid(p, "vote", &r->sid)) {
539 r->type = RAFT_REC_VOTE;
540 if (uuid_is_zero(&r->sid)) {
541 ovsdb_parser_raise_error(p, "record votes for all-zeros SID");
542 }
543 return;
544 }
545
546 /* If "index" is present parse the rest of the entry, otherwise it's just a
547 * term update. */
548 r->entry.index = raft_parse_optional_uint64(p, "index");
549 if (!r->entry.index) {
550 r->type = RAFT_REC_TERM;
551 } else {
552 r->type = RAFT_REC_ENTRY;
553 r->entry.servers = json_nullable_clone(
554 ovsdb_parser_member(p, "servers", OP_OBJECT | OP_OPTIONAL));
555 if (r->entry.servers) {
556 ovsdb_parser_put_error(
557 p, raft_servers_validate_json(r->entry.servers));
558 }
559 r->entry.data = json_nullable_clone(
560 ovsdb_parser_member(p, "data",
561 OP_OBJECT | OP_ARRAY | OP_OPTIONAL));
562 r->entry.eid = (r->entry.data
563 ? raft_parse_required_uuid(p, "eid")
564 : UUID_ZERO);
565 }
566 }
567
568 struct ovsdb_error * OVS_WARN_UNUSED_RESULT
569 raft_record_from_json(struct raft_record *r, const struct json *json)
570 {
571 struct ovsdb_parser p;
572 ovsdb_parser_init(&p, json, "raft log record");
573 raft_record_from_json__(r, &p);
574 struct ovsdb_error *error = ovsdb_parser_finish(&p);
575 if (error) {
576 raft_record_uninit(r);
577 }
578 return error;
579 }
580
581 struct json *
582 raft_record_to_json(const struct raft_record *r)
583 {
584 struct json *json = json_object_create();
585
586 if (r->comment && *r->comment) {
587 json_object_put_string(json, "comment", r->comment);
588 }
589
590 switch (r->type) {
591 case RAFT_REC_ENTRY:
592 raft_put_uint64(json, "term", r->term);
593 raft_put_uint64(json, "index", r->entry.index);
594 if (r->entry.data) {
595 json_object_put(json, "data", json_clone(r->entry.data));
596 }
597 if (r->entry.servers) {
598 json_object_put(json, "servers", json_clone(r->entry.servers));
599 }
600 if (!uuid_is_zero(&r->entry.eid)) {
601 json_object_put_format(json, "eid",
602 UUID_FMT, UUID_ARGS(&r->entry.eid));
603 }
604 break;
605
606 case RAFT_REC_TERM:
607 raft_put_uint64(json, "term", r->term);
608 break;
609
610 case RAFT_REC_VOTE:
611 raft_put_uint64(json, "term", r->term);
612 json_object_put_format(json, "vote", UUID_FMT, UUID_ARGS(&r->sid));
613 break;
614
615 case RAFT_REC_NOTE:
616 json_object_put(json, "note", json_string_create(r->note));
617 break;
618
619 case RAFT_REC_COMMIT_INDEX:
620 raft_put_uint64(json, "commit_index", r->commit_index);
621 break;
622
623 case RAFT_REC_LEADER:
624 raft_put_uint64(json, "term", r->term);
625 json_object_put_format(json, "leader", UUID_FMT, UUID_ARGS(&r->sid));
626 break;
627
628 default:
629 OVS_NOT_REACHED();
630 }
631 return json;
632 }
633 \f
634 /* Puts 'integer' into JSON 'object' with the given 'name'.
635 *
636 * The OVS JSON implementation only supports integers in the range
637 * INT64_MIN...INT64_MAX, which causes trouble for values from INT64_MAX+1 to
638 * UINT64_MAX. We map those into the negative range. */
639 void
640 raft_put_uint64(struct json *object, const char *name, uint64_t integer)
641 {
642 json_object_put(object, name, json_integer_create(integer));
643 }
644
645 /* Parses an integer from parser 'p' with the given 'name'.
646 *
647 * The OVS JSON implementation only supports integers in the range
648 * INT64_MIN...INT64_MAX, which causes trouble for values from INT64_MAX+1 to
649 * UINT64_MAX. We map the negative range back into positive numbers. */
650 static uint64_t
651 raft_parse_uint64__(struct ovsdb_parser *p, const char *name, bool optional)
652 {
653 enum ovsdb_parser_types types = OP_INTEGER | (optional ? OP_OPTIONAL : 0);
654 const struct json *json = ovsdb_parser_member(p, name, types);
655 return json ? json_integer(json) : 0;
656 }
657
658 uint64_t
659 raft_parse_optional_uint64(struct ovsdb_parser *p, const char *name)
660 {
661 return raft_parse_uint64__(p, name, true);
662 }
663
664 uint64_t
665 raft_parse_required_uint64(struct ovsdb_parser *p, const char *name)
666 {
667 return raft_parse_uint64__(p, name, false);
668 }
669
670 static int
671 raft_parse_boolean__(struct ovsdb_parser *p, const char *name, bool optional)
672 {
673 enum ovsdb_parser_types types = OP_BOOLEAN | (optional ? OP_OPTIONAL : 0);
674 const struct json *json = ovsdb_parser_member(p, name, types);
675 return json ? json_boolean(json) : -1;
676 }
677
678 bool
679 raft_parse_required_boolean(struct ovsdb_parser *p, const char *name)
680 {
681 return raft_parse_boolean__(p, name, false);
682 }
683
684 /* Returns true or false if present, -1 if absent. */
685 int
686 raft_parse_optional_boolean(struct ovsdb_parser *p, const char *name)
687 {
688 return raft_parse_boolean__(p, name, true);
689 }
690
691 static const char *
692 raft_parse_string__(struct ovsdb_parser *p, const char *name, bool optional)
693 {
694 enum ovsdb_parser_types types = OP_STRING | (optional ? OP_OPTIONAL : 0);
695 const struct json *json = ovsdb_parser_member(p, name, types);
696 return json ? json_string(json) : NULL;
697 }
698
699 const char *
700 raft_parse_required_string(struct ovsdb_parser *p, const char *name)
701 {
702 return raft_parse_string__(p, name, false);
703 }
704
705 const char *
706 raft_parse_optional_string(struct ovsdb_parser *p, const char *name)
707 {
708 return raft_parse_string__(p, name, true);
709 }
710
711 bool
712 raft_parse_uuid(struct ovsdb_parser *p, const char *name, bool optional,
713 struct uuid *uuid)
714 {
715 const char *s = raft_parse_string__(p, name, optional);
716 if (s) {
717 if (uuid_from_string(uuid, s)) {
718 return true;
719 }
720 ovsdb_parser_raise_error(p, "%s is not a valid UUID", name);
721 }
722 *uuid = UUID_ZERO;
723 return false;
724 }
725
726 struct uuid
727 raft_parse_required_uuid(struct ovsdb_parser *p, const char *name)
728 {
729 struct uuid uuid;
730 raft_parse_uuid(p, name, false, &uuid);
731 return uuid;
732 }
733
734 bool
735 raft_parse_optional_uuid(struct ovsdb_parser *p, const char *name,
736 struct uuid *uuid)
737 {
738 return raft_parse_uuid(p, name, true, uuid);
739 }
740