2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-21 17:37:37 +00:00
ovs/ovsdb/raft-private.c
Ilya Maximets 1de4a08c22 json: Use functions to access json arrays.
Internal implementation of JSON array will be changed in the future
commits.  Add access functions that users can rely on instead of
accessing the internals of 'struct json' directly and convert all the
users.  Structure fields are intentionally renamed to make sure that
no code is using the old fields directly.

json_array() function is removed, as not needed anymore.  Added new
functions:  json_array_size(), json_array_at(), json_array_set()
and json_array_pop().  These are enough to cover all the use cases
within OVS.

The change is fairly large, however, IMO, it's a much overdue cleanup
that we need even without changing the underlying implementation.

Acked-by: Mike Pattrick <mkp@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2025-06-30 16:53:56 +02:00

830 lines
24 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2017, 2018 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <config.h>
#include "raft-private.h"
#include "coverage.h"
#include "openvswitch/dynamic-string.h"
#include "ovsdb-error.h"
#include "ovsdb-parser.h"
#include "socket-util.h"
#include "sset.h"
COVERAGE_DEFINE(raft_entry_serialize);
/* Addresses of Raft servers. */
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_address_validate(const char *address)
{
if (!strncmp(address, "unix:", 5)) {
return NULL;
} else if (!strncmp(address, "ssl:", 4) || !strncmp(address, "tcp:", 4)) {
struct sockaddr_storage ss;
bool dns_failure = false;
if (!inet_parse_active(address + 4, -1, &ss, true, &dns_failure)
&& !dns_failure) {
return ovsdb_error(NULL, "%s: syntax error in address", address);
}
return NULL;
} else {
return ovsdb_error(NULL, "%s: expected \"tcp\" or \"ssl\" address",
address);
}
}
static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_address_validate_json(const struct json *address)
{
if (address->type != JSON_STRING) {
return ovsdb_syntax_error(address, NULL,
"server address is not string");
}
return raft_address_validate(json_string(address));
}
/* Constructs and returns a "nickname" for a Raft server based on its 'address'
* and server ID 'sid'. The nickname is just a short name for the server to
* use in log messages, to make them more readable.
*
* The caller must eventually free the returned string. */
char *
raft_address_to_nickname(const char *address, const struct uuid *sid)
{
if (!strncmp(address, "unix:", 5)) {
const char *p = address + 5;
const char *slash = strrchr(p, '/');
if (slash) {
p = slash + 1;
}
int len = strcspn(p, ".");
if (len) {
return xmemdup0(p, len);
}
}
return xasprintf(SID_FMT, SID_ARGS(sid));
}
/* Sets of Raft server addresses. */
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_addresses_from_json(const struct json *json, struct sset *addresses)
{
sset_init(addresses);
size_t n = json_array_size(json);
if (!n) {
return ovsdb_syntax_error(json, NULL,
"at least one remote address is required");
}
for (size_t i = 0; i < n; i++) {
const struct json *address = json_array_at(json, i);
struct ovsdb_error *error = raft_address_validate_json(address);
if (error) {
sset_destroy(addresses);
sset_init(addresses);
return error;
}
sset_add(addresses, json_string(address));
}
return NULL;
}
struct json *
raft_addresses_to_json(const struct sset *sset)
{
struct json *array;
const char *s;
array = json_array_create_empty();
SSET_FOR_EACH (s, sset) {
json_array_add(array, json_string_create(s));
}
return array;
}
/* raft_server. */
const char *
raft_server_phase_to_string(enum raft_server_phase phase)
{
switch (phase) {
case RAFT_PHASE_STABLE: return "stable";
case RAFT_PHASE_CATCHUP: return "adding: catchup";
case RAFT_PHASE_CAUGHT_UP: return "adding: caught up";
case RAFT_PHASE_COMMITTING: return "adding: committing";
case RAFT_PHASE_REMOVE: return "removing";
default: return "<error>";
}
}
void
raft_server_destroy(struct raft_server *s)
{
if (s) {
free(s->address);
free(s->nickname);
free(s);
}
}
void
raft_servers_destroy(struct hmap *servers)
{
struct raft_server *s;
HMAP_FOR_EACH_SAFE (s, hmap_node, servers) {
hmap_remove(servers, &s->hmap_node);
raft_server_destroy(s);
}
hmap_destroy(servers);
}
struct raft_server *
raft_server_add(struct hmap *servers, const struct uuid *sid,
const char *address)
{
struct raft_server *s = xzalloc(sizeof *s);
s->sid = *sid;
s->address = xstrdup(address);
s->nickname = raft_address_to_nickname(address, sid);
s->phase = RAFT_PHASE_STABLE;
hmap_insert(servers, &s->hmap_node, uuid_hash(sid));
return s;
}
struct raft_server *
raft_server_find(const struct hmap *servers, const struct uuid *sid)
{
struct raft_server *s;
HMAP_FOR_EACH_IN_BUCKET (s, hmap_node, uuid_hash(sid), servers) {
if (uuid_equals(sid, &s->sid)) {
return s;
}
}
return NULL;
}
const char *
raft_servers_get_nickname__(const struct hmap *servers, const struct uuid *sid)
{
const struct raft_server *s = raft_server_find(servers, sid);
return s ? s->nickname : NULL;
}
const char *
raft_servers_get_nickname(const struct hmap *servers,
const struct uuid *sid,
char buf[SID_LEN + 1], size_t bufsize)
{
const char *s = raft_servers_get_nickname__(servers, sid);
if (s) {
return s;
}
snprintf(buf, bufsize, SID_FMT, SID_ARGS(sid));
return buf;
}
static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_servers_from_json__(const struct json *json, struct hmap *servers)
{
if (!json || json->type != JSON_OBJECT) {
return ovsdb_syntax_error(json, NULL, "servers must be JSON object");
} else if (shash_is_empty(json_object(json))) {
return ovsdb_syntax_error(json, NULL, "must have at least one server");
}
/* Parse new servers. */
struct shash_node *node;
SHASH_FOR_EACH (node, json_object(json)) {
/* Parse server UUID. */
struct uuid sid;
if (!uuid_from_string(&sid, node->name)) {
return ovsdb_syntax_error(json, NULL, "%s is not a UUID",
node->name);
}
const struct json *address = node->data;
struct ovsdb_error *error = raft_address_validate_json(address);
if (error) {
return error;
}
raft_server_add(servers, &sid, json_string(address));
}
return NULL;
}
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_servers_from_json(const struct json *json, struct hmap *servers)
{
hmap_init(servers);
struct ovsdb_error *error = raft_servers_from_json__(json, servers);
if (error) {
raft_servers_destroy(servers);
}
return error;
}
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_servers_validate_json(const struct json *json)
{
struct hmap servers = HMAP_INITIALIZER(&servers);
struct ovsdb_error *error = raft_servers_from_json__(json, &servers);
raft_servers_destroy(&servers);
return error;
}
struct json *
raft_servers_to_json(const struct hmap *servers)
{
struct json *json = json_object_create();
struct raft_server *s;
HMAP_FOR_EACH (s, hmap_node, servers) {
char sid_s[UUID_LEN + 1];
sprintf(sid_s, UUID_FMT, UUID_ARGS(&s->sid));
json_object_put_string(json, sid_s, s->address);
}
return json;
}
void
raft_servers_format(const struct hmap *servers, struct ds *ds)
{
int i = 0;
const struct raft_server *s;
HMAP_FOR_EACH (s, hmap_node, servers) {
if (i++) {
ds_put_cstr(ds, ", ");
}
ds_put_format(ds, SID_FMT"(%s)", SID_ARGS(&s->sid), s->address);
}
}
/* Raft log entries. */
void
raft_entry_clone(struct raft_entry *dst, const struct raft_entry *src)
{
dst->term = src->term;
dst->data.full_json = json_nullable_clone(src->data.full_json);
dst->data.serialized = json_nullable_clone(src->data.serialized);
dst->eid = src->eid;
dst->servers = json_nullable_clone(src->servers);
dst->election_timer = src->election_timer;
}
void
raft_entry_uninit(struct raft_entry *e)
{
if (e) {
json_destroy(e->data.full_json);
json_destroy(e->data.serialized);
json_destroy(e->servers);
}
}
struct json *
raft_entry_to_json(const struct raft_entry *e)
{
struct json *json = json_object_create();
raft_put_uint64(json, "term", e->term);
if (raft_entry_has_data(e)) {
json_object_put(json, "data",
json_clone(raft_entry_get_serialized_data(e)));
json_object_put_uuid(json, "eid", &e->eid);
}
if (e->servers) {
json_object_put(json, "servers", json_clone(e->servers));
}
if (e->election_timer) {
raft_put_uint64(json, "election_timer", e->election_timer);
}
return json;
}
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_entry_from_json(const struct json *json, struct raft_entry *e)
{
memset(e, 0, sizeof *e);
struct ovsdb_parser p;
ovsdb_parser_init(&p, json, "raft log entry");
e->term = raft_parse_required_uint64(&p, "term");
raft_entry_set_parsed_data(e,
ovsdb_parser_member(&p, "data", OP_OBJECT | OP_ARRAY | OP_OPTIONAL));
e->eid = raft_entry_has_data(e)
? raft_parse_required_uuid(&p, "eid") : UUID_ZERO;
e->servers = json_nullable_clone(
ovsdb_parser_member(&p, "servers", OP_OBJECT | OP_OPTIONAL));
if (e->servers) {
ovsdb_parser_put_error(&p, raft_servers_validate_json(e->servers));
}
e->election_timer = raft_parse_optional_uint64(&p, "election_timer");
struct ovsdb_error *error = ovsdb_parser_finish(&p);
if (error) {
raft_entry_uninit(e);
}
return error;
}
bool
raft_entry_equals(const struct raft_entry *a, const struct raft_entry *b)
{
return (a->term == b->term
&& uuid_equals(&a->eid, &b->eid)
&& json_equal(a->servers, b->servers)
&& json_equal(raft_entry_get_parsed_data(a),
raft_entry_get_parsed_data(b)));
}
bool
raft_entry_has_data(const struct raft_entry *e)
{
return e->data.full_json || e->data.serialized;
}
static void
raft_entry_data_serialize(struct raft_entry *e)
{
if (!raft_entry_has_data(e) || e->data.serialized) {
return;
}
COVERAGE_INC(raft_entry_serialize);
e->data.serialized = json_serialized_object_create(e->data.full_json);
}
void
raft_entry_set_parsed_data_nocopy(struct raft_entry *e, struct json *json)
{
ovs_assert(!json || json->type != JSON_SERIALIZED_OBJECT);
e->data.full_json = json;
e->data.serialized = NULL;
}
void
raft_entry_set_parsed_data(struct raft_entry *e, const struct json *json)
{
raft_entry_set_parsed_data_nocopy(e, json_nullable_clone(json));
}
/* Returns a pointer to the fully parsed json object of the data.
* Caller takes the ownership of the result.
*
* Entry will no longer contain a fully parsed json object.
* Subsequent calls for the same raft entry will return NULL. */
struct json * OVS_WARN_UNUSED_RESULT
raft_entry_steal_parsed_data(struct raft_entry *e)
{
/* Ensure that serialized version exists. */
raft_entry_data_serialize(e);
struct json *json = e->data.full_json;
e->data.full_json = NULL;
return json;
}
/* Returns a pointer to the fully parsed json object of the data, if any. */
const struct json *
raft_entry_get_parsed_data(const struct raft_entry *e)
{
return e->data.full_json;
}
/* Returns a pointer to the JSON_SERIALIZED_OBJECT of the data. */
const struct json *
raft_entry_get_serialized_data(const struct raft_entry *e)
{
raft_entry_data_serialize(CONST_CAST(struct raft_entry *, e));
return e->data.serialized;
}
void
raft_header_uninit(struct raft_header *h)
{
if (!h) {
return;
}
free(h->name);
free(h->local_address);
sset_destroy(&h->remote_addresses);
raft_entry_uninit(&h->snap);
}
static void
raft_header_from_json__(struct raft_header *h, struct ovsdb_parser *p)
{
/* Parse always-required fields. */
h->sid = raft_parse_required_uuid(p, "server_id");
h->name = nullable_xstrdup(raft_parse_required_string(p, "name"));
h->local_address = nullable_xstrdup(
raft_parse_required_string(p, "local_address"));
/* Parse "remote_addresses", if present.
*
* If this is present, then this database file is for the special case of a
* server that was created with "ovsdb-tool join-cluster" and has not yet
* joined its cluster, */
const struct json *remote_addresses
= ovsdb_parser_member(p, "remote_addresses", OP_ARRAY | OP_OPTIONAL);
h->joining = remote_addresses != NULL;
if (h->joining) {
struct ovsdb_error *error = raft_addresses_from_json(
remote_addresses, &h->remote_addresses);
if (error) {
ovsdb_parser_put_error(p, error);
} else if (sset_find_and_delete(&h->remote_addresses, h->local_address)
&& sset_is_empty(&h->remote_addresses)) {
ovsdb_parser_raise_error(p, "at least one remote address (other "
"than the local address) is required");
}
} else {
/* The set of servers is mandatory. */
h->snap.servers = json_nullable_clone(
ovsdb_parser_member(p, "prev_servers", OP_OBJECT));
if (h->snap.servers) {
ovsdb_parser_put_error(p, raft_servers_validate_json(
h->snap.servers));
}
/* Term, index, and snapshot are optional, but if any of them is
* present, all of them must be. */
h->snap_index = raft_parse_optional_uint64(p, "prev_index");
if (h->snap_index) {
raft_entry_set_parsed_data(
&h->snap, ovsdb_parser_member(p, "prev_data", OP_ANY));
h->snap.eid = raft_parse_required_uuid(p, "prev_eid");
h->snap.term = raft_parse_required_uint64(p, "prev_term");
h->snap.election_timer = raft_parse_optional_uint64(
p, "prev_election_timer");
}
}
/* Parse cluster ID. If we're joining a cluster, this is optional,
* otherwise it is mandatory. */
raft_parse_uuid(p, "cluster_id", h->joining, &h->cid);
}
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_header_from_json(struct raft_header *h, const struct json *json)
{
struct ovsdb_parser p;
ovsdb_parser_init(&p, json, "raft header");
memset(h, 0, sizeof *h);
sset_init(&h->remote_addresses);
raft_header_from_json__(h, &p);
struct ovsdb_error *error = ovsdb_parser_finish(&p);
if (error) {
raft_header_uninit(h);
}
return error;
}
struct json *
raft_header_to_json(const struct raft_header *h)
{
struct json *json = json_object_create();
json_object_put_uuid(json, "server_id", &h->sid);
if (!uuid_is_zero(&h->cid)) {
json_object_put_uuid(json, "cluster_id", &h->cid);
}
json_object_put_string(json, "local_address", h->local_address);
json_object_put_string(json, "name", h->name);
if (!sset_is_empty(&h->remote_addresses)) {
json_object_put(json, "remote_addresses",
raft_addresses_to_json(&h->remote_addresses));
}
if (h->snap.servers) {
json_object_put(json, "prev_servers", json_clone(h->snap.servers));
}
if (h->snap_index) {
raft_put_uint64(json, "prev_index", h->snap_index);
raft_put_uint64(json, "prev_term", h->snap.term);
if (raft_entry_has_data(&h->snap)) {
json_object_put(json, "prev_data",
json_clone(raft_entry_get_serialized_data(&h->snap)));
}
json_object_put_uuid(json, "prev_eid", &h->snap.eid);
if (h->snap.election_timer) {
raft_put_uint64(json, "prev_election_timer",
h->snap.election_timer);
}
}
return json;
}
void
raft_record_uninit(struct raft_record *r)
{
if (!r) {
return;
}
free(r->comment);
switch (r->type) {
case RAFT_REC_ENTRY:
json_destroy(r->entry.data);
json_destroy(r->entry.servers);
break;
case RAFT_REC_NOTE:
free(r->note);
break;
case RAFT_REC_TERM:
case RAFT_REC_VOTE:
case RAFT_REC_COMMIT_INDEX:
case RAFT_REC_LEADER:
break;
}
}
static void
raft_record_from_json__(struct raft_record *r, struct ovsdb_parser *p)
{
r->comment = nullable_xstrdup(raft_parse_optional_string(p, "comment"));
/* Parse "note". */
const char *note = raft_parse_optional_string(p, "note");
if (note) {
r->type = RAFT_REC_NOTE;
r->term = 0;
r->note = xstrdup(note);
return;
}
/* Parse "commit_index". */
r->commit_index = raft_parse_optional_uint64(p, "commit_index");
if (r->commit_index) {
r->type = RAFT_REC_COMMIT_INDEX;
r->term = 0;
return;
}
/* All remaining types of log records include "term", plus at most one of:
*
* - "index" plus zero or more of "data", "eid", and "servers". "data"
* and "eid" must be both present or both absent.
*
* - "vote".
*
* - "leader".
*/
/* Parse "term".
*
* A Raft leader can replicate entries from previous terms to the other
* servers in the cluster, retaining the original terms on those entries
* (see section 3.6.2 "Committing entries from previous terms" for more
* information), so it's OK for the term in a log record to precede the
* current term. */
r->term = raft_parse_required_uint64(p, "term");
/* Parse "leader". */
if (raft_parse_optional_uuid(p, "leader", &r->sid)) {
r->type = RAFT_REC_LEADER;
if (uuid_is_zero(&r->sid)) {
ovsdb_parser_raise_error(p, "record says leader is all-zeros SID");
}
return;
}
/* Parse "vote". */
if (raft_parse_optional_uuid(p, "vote", &r->sid)) {
r->type = RAFT_REC_VOTE;
if (uuid_is_zero(&r->sid)) {
ovsdb_parser_raise_error(p, "record votes for all-zeros SID");
}
return;
}
/* If "index" is present parse the rest of the entry, otherwise it's just a
* term update. */
r->entry.index = raft_parse_optional_uint64(p, "index");
if (!r->entry.index) {
r->type = RAFT_REC_TERM;
} else {
r->type = RAFT_REC_ENTRY;
r->entry.servers = json_nullable_clone(
ovsdb_parser_member(p, "servers", OP_OBJECT | OP_OPTIONAL));
if (r->entry.servers) {
ovsdb_parser_put_error(
p, raft_servers_validate_json(r->entry.servers));
}
r->entry.election_timer = raft_parse_optional_uint64(
p, "election_timer");
r->entry.data = json_nullable_clone(
ovsdb_parser_member(p, "data",
OP_OBJECT | OP_ARRAY | OP_OPTIONAL));
r->entry.eid = (r->entry.data
? raft_parse_required_uuid(p, "eid")
: UUID_ZERO);
}
}
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
raft_record_from_json(struct raft_record *r, const struct json *json)
{
struct ovsdb_parser p;
ovsdb_parser_init(&p, json, "raft log record");
raft_record_from_json__(r, &p);
struct ovsdb_error *error = ovsdb_parser_finish(&p);
if (error) {
raft_record_uninit(r);
}
return error;
}
struct json *
raft_record_to_json(const struct raft_record *r)
{
struct json *json = json_object_create();
if (r->comment && *r->comment) {
json_object_put_string(json, "comment", r->comment);
}
switch (r->type) {
case RAFT_REC_ENTRY:
raft_put_uint64(json, "term", r->term);
raft_put_uint64(json, "index", r->entry.index);
if (r->entry.data) {
json_object_put(json, "data", json_clone(r->entry.data));
}
if (r->entry.servers) {
json_object_put(json, "servers", json_clone(r->entry.servers));
}
if (r->entry.election_timer) {
raft_put_uint64(json, "election_timer", r->entry.election_timer);
}
if (!uuid_is_zero(&r->entry.eid)) {
json_object_put_uuid(json, "eid", &r->entry.eid);
}
break;
case RAFT_REC_TERM:
raft_put_uint64(json, "term", r->term);
break;
case RAFT_REC_VOTE:
raft_put_uint64(json, "term", r->term);
json_object_put_uuid(json, "vote", &r->sid);
break;
case RAFT_REC_NOTE:
json_object_put(json, "note", json_string_create(r->note));
break;
case RAFT_REC_COMMIT_INDEX:
raft_put_uint64(json, "commit_index", r->commit_index);
break;
case RAFT_REC_LEADER:
raft_put_uint64(json, "term", r->term);
json_object_put_uuid(json, "leader", &r->sid);
break;
default:
OVS_NOT_REACHED();
}
return json;
}
/* Puts 'integer' into JSON 'object' with the given 'name'.
*
* The OVS JSON implementation only supports integers in the range
* INT64_MIN...INT64_MAX, which causes trouble for values from INT64_MAX+1 to
* UINT64_MAX. We map those into the negative range. */
void
raft_put_uint64(struct json *object, const char *name, uint64_t integer)
{
json_object_put(object, name, json_integer_create(integer));
}
/* Parses an integer from parser 'p' with the given 'name'.
*
* The OVS JSON implementation only supports integers in the range
* INT64_MIN...INT64_MAX, which causes trouble for values from INT64_MAX+1 to
* UINT64_MAX. We map the negative range back into positive numbers. */
static uint64_t
raft_parse_uint64__(struct ovsdb_parser *p, const char *name, bool optional)
{
enum ovsdb_parser_types types = OP_INTEGER | (optional ? OP_OPTIONAL : 0);
const struct json *json = ovsdb_parser_member(p, name, types);
return json ? json_integer(json) : 0;
}
uint64_t
raft_parse_optional_uint64(struct ovsdb_parser *p, const char *name)
{
return raft_parse_uint64__(p, name, true);
}
uint64_t
raft_parse_required_uint64(struct ovsdb_parser *p, const char *name)
{
return raft_parse_uint64__(p, name, false);
}
static int
raft_parse_boolean__(struct ovsdb_parser *p, const char *name, bool optional)
{
enum ovsdb_parser_types types = OP_BOOLEAN | (optional ? OP_OPTIONAL : 0);
const struct json *json = ovsdb_parser_member(p, name, types);
return json ? json_boolean(json) : -1;
}
bool
raft_parse_required_boolean(struct ovsdb_parser *p, const char *name)
{
return raft_parse_boolean__(p, name, false);
}
/* Returns true or false if present, -1 if absent. */
int
raft_parse_optional_boolean(struct ovsdb_parser *p, const char *name)
{
return raft_parse_boolean__(p, name, true);
}
static const char *
raft_parse_string__(struct ovsdb_parser *p, const char *name, bool optional)
{
enum ovsdb_parser_types types = OP_STRING | (optional ? OP_OPTIONAL : 0);
const struct json *json = ovsdb_parser_member(p, name, types);
return json ? json_string(json) : NULL;
}
const char *
raft_parse_required_string(struct ovsdb_parser *p, const char *name)
{
return raft_parse_string__(p, name, false);
}
const char *
raft_parse_optional_string(struct ovsdb_parser *p, const char *name)
{
return raft_parse_string__(p, name, true);
}
bool
raft_parse_uuid(struct ovsdb_parser *p, const char *name, bool optional,
struct uuid *uuid)
{
const char *s = raft_parse_string__(p, name, optional);
if (s) {
if (uuid_from_string(uuid, s)) {
return true;
}
ovsdb_parser_raise_error(p, "%s is not a valid UUID", name);
}
*uuid = UUID_ZERO;
return false;
}
struct uuid
raft_parse_required_uuid(struct ovsdb_parser *p, const char *name)
{
struct uuid uuid;
raft_parse_uuid(p, name, false, &uuid);
return uuid;
}
bool
raft_parse_optional_uuid(struct ovsdb_parser *p, const char *name,
struct uuid *uuid)
{
return raft_parse_uuid(p, name, true, uuid);
}