/src/openvswitch/lib/ovsdb-cs.c
Line | Count | Source |
1 | | /* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Nicira, Inc. |
2 | | * Copyright (C) 2016 Hewlett Packard Enterprise Development LP |
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 CONDITION 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 "ovsdb-cs.h" |
20 | | |
21 | | #include <errno.h> |
22 | | |
23 | | #include "hash.h" |
24 | | #include "jsonrpc.h" |
25 | | #include "openvswitch/dynamic-string.h" |
26 | | #include "openvswitch/hmap.h" |
27 | | #include "openvswitch/json.h" |
28 | | #include "openvswitch/poll-loop.h" |
29 | | #include "openvswitch/shash.h" |
30 | | #include "openvswitch/vlog.h" |
31 | | #include "ovsdb-data.h" |
32 | | #include "ovsdb-error.h" |
33 | | #include "ovsdb-parser.h" |
34 | | #include "ovsdb-session.h" |
35 | | #include "ovsdb-types.h" |
36 | | #include "svec.h" |
37 | | #include "util.h" |
38 | | #include "uuid.h" |
39 | | |
40 | | VLOG_DEFINE_THIS_MODULE(ovsdb_cs); |
41 | | |
42 | | /* Connection state machine. |
43 | | * |
44 | | * When a JSON-RPC session connects, the CS layer sends a "monitor_cond" |
45 | | * request for the Database table in the _Server database and transitions to |
46 | | * the CS_S_SERVER_MONITOR_REQUESTED state. If the session drops and |
47 | | * reconnects, or if the CS receives a "monitor_canceled" notification for a |
48 | | * table it is monitoring, the CS starts over again in the same way. */ |
49 | | #define OVSDB_CS_STATES \ |
50 | | /* Waits for "get_schema" reply, then sends "monitor_cond" \ |
51 | | * request for the Database table in the _Server database, whose \ |
52 | | * details are informed by the schema, and transitions to \ |
53 | | * CS_S_SERVER_MONITOR_REQUESTED. */ \ |
54 | 0 | OVSDB_CS_STATE(SERVER_SCHEMA_REQUESTED) \ |
55 | 0 | \ |
56 | 0 | /* Waits for "monitor_cond" reply for the Database table: \ |
57 | 0 | * \ |
58 | 0 | * - If the reply indicates success, and the Database table has a \ |
59 | 0 | * row for the CS database: \ |
60 | 0 | * \ |
61 | 0 | * * If the row indicates that this is a clustered database \ |
62 | 0 | * that is not connected to the cluster, closes the \ |
63 | 0 | * connection. The next connection attempt has a chance at \ |
64 | 0 | * picking a connected server. \ |
65 | 0 | * \ |
66 | 0 | * * Otherwise, sends a monitoring request for the CS \ |
67 | 0 | * database whose details are informed by the schema \ |
68 | 0 | * (obtained from the row), and transitions to \ |
69 | 0 | * CS_S_DATA_MONITOR_(COND_(SINCE_))REQUESTED. \ |
70 | 0 | * \ |
71 | 0 | * - If the reply indicates success, but the Database table does \ |
72 | 0 | * not have a row for the CS database, transitions to \ |
73 | 0 | * CS_S_ERROR. \ |
74 | 0 | * \ |
75 | 0 | * - If the reply indicates failure, sends a "get_schema" request \ |
76 | 0 | * for the CS database and transitions to \ |
77 | 0 | * CS_S_DATA_SCHEMA_REQUESTED. */ \ |
78 | 0 | OVSDB_CS_STATE(SERVER_MONITOR_REQUESTED) \ |
79 | 0 | \ |
80 | 0 | /* Waits for "get_schema" reply, then sends "monitor_cond" \ |
81 | 0 | * request whose details are informed by the schema, and \ |
82 | 0 | * transitions to CS_S_DATA_MONITOR_COND_REQUESTED. */ \ |
83 | 0 | OVSDB_CS_STATE(DATA_SCHEMA_REQUESTED) \ |
84 | 0 | \ |
85 | 0 | /* Waits for "monitor_cond_since" reply. If successful, replaces \ |
86 | 0 | * the CS contents by the data carried in the reply and \ |
87 | 0 | * transitions to CS_S_MONITORING. On failure, sends a \ |
88 | 0 | * "monitor_cond" request and transitions to \ |
89 | 0 | * CS_S_DATA_MONITOR_COND_REQUESTED. */ \ |
90 | 0 | OVSDB_CS_STATE(DATA_MONITOR_COND_SINCE_REQUESTED) \ |
91 | 0 | \ |
92 | 0 | /* Waits for "monitor_cond" reply. If successful, replaces the \ |
93 | 0 | * CS contents by the data carried in the reply and transitions \ |
94 | 0 | * to CS_S_MONITORING. On failure, sends a "monitor" request \ |
95 | 0 | * and transitions to CS_S_DATA_MONITOR_REQUESTED. */ \ |
96 | 0 | OVSDB_CS_STATE(DATA_MONITOR_COND_REQUESTED) \ |
97 | 0 | \ |
98 | 0 | /* Waits for "monitor" reply. If successful, replaces the CS \ |
99 | 0 | * contents by the data carried in the reply and transitions to \ |
100 | 0 | * CS_S_MONITORING. On failure, transitions to CS_S_ERROR. */ \ |
101 | 0 | OVSDB_CS_STATE(DATA_MONITOR_REQUESTED) \ |
102 | 0 | \ |
103 | 0 | /* State that processes "update", "update2" or "update3" \ |
104 | 0 | * notifications for the main database (and the Database table \ |
105 | 0 | * in _Server if available). \ |
106 | 0 | * \ |
107 | 0 | * If we're monitoring the Database table and we get notified \ |
108 | 0 | * that the CS database has been deleted, we close the \ |
109 | 0 | * connection (which will restart the state machine). */ \ |
110 | 0 | OVSDB_CS_STATE(MONITORING) \ |
111 | 0 | \ |
112 | 0 | /* Terminal error state that indicates that nothing useful can be \ |
113 | 0 | * done, for example because the database server doesn't actually \ |
114 | 0 | * have the desired database. We maintain the session with the \ |
115 | 0 | * database server anyway. If it starts serving the database \ |
116 | 0 | * that we want, or if someone fixes and restarts the database, \ |
117 | 0 | * then it will kill the session and we will automatically \ |
118 | 0 | * reconnect and try again. */ \ |
119 | 0 | OVSDB_CS_STATE(ERROR) \ |
120 | 0 | \ |
121 | 0 | /* Terminal state that indicates we connected to a useless server \ |
122 | 0 | * in a cluster, e.g. one that is partitioned from the rest of \ |
123 | 0 | * the cluster. We're waiting to retry. */ \ |
124 | 0 | OVSDB_CS_STATE(RETRY) |
125 | | |
126 | | enum ovsdb_cs_state { |
127 | | #define OVSDB_CS_STATE(NAME) CS_S_##NAME, |
128 | | OVSDB_CS_STATES |
129 | | #undef OVSDB_CS_STATE |
130 | | }; |
131 | | |
132 | | static const char * |
133 | | ovsdb_cs_state_to_string(enum ovsdb_cs_state state) |
134 | 0 | { |
135 | 0 | switch (state) { |
136 | 0 | #define OVSDB_CS_STATE(NAME) case CS_S_##NAME: return #NAME; |
137 | 0 | OVSDB_CS_STATES |
138 | 0 | #undef OVSDB_CS_STATE |
139 | 0 | default: return "<unknown>"; |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | /* A database being monitored. |
144 | | * |
145 | | * There are two instances of this data structure for each CS instance, one for |
146 | | * the _Server database used for working with clusters, and the other one for |
147 | | * the actual database that the client is interested in. */ |
148 | | struct ovsdb_cs_db { |
149 | | struct ovsdb_cs *cs; |
150 | | |
151 | | /* Data. */ |
152 | | const char *db_name; /* Database's name. */ |
153 | | struct hmap tables; /* Contains "struct ovsdb_cs_db_table *"s.*/ |
154 | | struct json *monitor_id; |
155 | | struct json *schema; |
156 | | |
157 | | /* Monitor version. */ |
158 | | int max_version; /* Maximum version of monitor request to use. */ |
159 | | int monitor_version; /* 0 if not monitoring, 1=monitor, |
160 | | * 2=monitor_cond, 3=monitor_cond_since. */ |
161 | | |
162 | | /* Condition changes. */ |
163 | | bool cond_changed; /* Change not yet sent to server? */ |
164 | | unsigned int cond_seqno; /* Increments when condition changes. */ |
165 | | |
166 | | /* Database locking. */ |
167 | | char *lock_name; /* Name of lock we need, NULL if none. */ |
168 | | bool has_lock; /* Has db server told us we have the lock? */ |
169 | | bool is_lock_contended; /* Has db server told us we can't get lock? */ |
170 | | struct json *lock_request_id; /* JSON-RPC ID of in-flight lock request. */ |
171 | | |
172 | | /* Last db txn id, used for fast resync through monitor_cond_since */ |
173 | | struct uuid last_id; |
174 | | |
175 | | /* Client interface. */ |
176 | | struct ovs_list events; |
177 | | const struct ovsdb_cs_ops *ops; |
178 | | void *ops_aux; |
179 | | }; |
180 | | |
181 | | static const struct ovsdb_cs_ops ovsdb_cs_server_ops; |
182 | | |
183 | | static void ovsdb_cs_db_destroy_tables(struct ovsdb_cs_db *); |
184 | | static unsigned int ovsdb_cs_db_set_condition( |
185 | | struct ovsdb_cs_db *, const char *db_name, const struct json *condition); |
186 | | |
187 | | static void ovsdb_cs_send_schema_request(struct ovsdb_cs *, |
188 | | struct ovsdb_cs_db *); |
189 | | static void ovsdb_cs_send_db_change_aware(struct ovsdb_cs *); |
190 | | static bool ovsdb_cs_check_server_db(struct ovsdb_cs *); |
191 | | static void ovsdb_cs_clear_server_rows(struct ovsdb_cs *); |
192 | | static void ovsdb_cs_send_monitor_request(struct ovsdb_cs *, |
193 | | struct ovsdb_cs_db *, int version); |
194 | | static void ovsdb_cs_db_ack_condition(struct ovsdb_cs_db *db); |
195 | | static void ovsdb_cs_db_sync_condition(struct ovsdb_cs_db *db); |
196 | | |
197 | | struct ovsdb_cs { |
198 | | struct ovsdb_cs_db server; |
199 | | struct ovsdb_cs_db data; |
200 | | |
201 | | /* Session state. |
202 | | * |
203 | | * 'state_seqno' is a snapshot of the session's sequence number as returned |
204 | | * jsonrpc_session_get_seqno(session), so if it differs from the value that |
205 | | * function currently returns then the session has reconnected and the |
206 | | * state machine must restart. */ |
207 | | struct jsonrpc_session *session; /* Connection to the server. */ |
208 | | char *remote; /* 'session' remote name. */ |
209 | | enum ovsdb_cs_state state; /* Current session state. */ |
210 | | unsigned int state_seqno; /* See above. */ |
211 | | struct json *request_id; /* JSON ID for request awaiting reply. */ |
212 | | |
213 | | /* IDs of outstanding transactions. */ |
214 | | struct json **txns; |
215 | | size_t n_txns, allocated_txns; |
216 | | |
217 | | /* Info for the _Server database. */ |
218 | | struct uuid cid; |
219 | | struct hmap server_rows; |
220 | | |
221 | | /* Whether to send 'set_db_change_aware'. */ |
222 | | bool set_db_change_aware; |
223 | | |
224 | | /* Clustered servers. */ |
225 | | uint64_t min_index; /* Minimum allowed index, to avoid regression. */ |
226 | | bool leader_only; /* If true, do not connect to Raft followers. */ |
227 | | bool shuffle_remotes; /* If true, connect to servers in random order. */ |
228 | | }; |
229 | | |
230 | | static void ovsdb_cs_transition_at(struct ovsdb_cs *, enum ovsdb_cs_state, |
231 | | const char *where); |
232 | | #define ovsdb_cs_transition(CS, STATE) \ |
233 | 0 | ovsdb_cs_transition_at(CS, STATE, OVS_SOURCE_LOCATOR) |
234 | | |
235 | | static void ovsdb_cs_retry_at(struct ovsdb_cs *, const char *where); |
236 | 0 | #define ovsdb_cs_retry(CS) ovsdb_cs_retry_at(CS, OVS_SOURCE_LOCATOR) |
237 | | |
238 | | static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5); |
239 | | |
240 | | static void ovsdb_cs_db_parse_monitor_reply(struct ovsdb_cs_db *, |
241 | | const struct json *result, |
242 | | int version); |
243 | | static bool ovsdb_cs_db_parse_update_rpc(struct ovsdb_cs_db *, |
244 | | const struct jsonrpc_msg *); |
245 | | static bool ovsdb_cs_handle_monitor_canceled(struct ovsdb_cs *, |
246 | | struct ovsdb_cs_db *, |
247 | | const struct jsonrpc_msg *); |
248 | | |
249 | | static bool ovsdb_cs_db_process_lock_replies(struct ovsdb_cs_db *, |
250 | | const struct jsonrpc_msg *); |
251 | | static struct jsonrpc_msg *ovsdb_cs_db_compose_lock_request( |
252 | | struct ovsdb_cs_db *); |
253 | | static struct jsonrpc_msg *ovsdb_cs_db_compose_unlock_request( |
254 | | struct ovsdb_cs_db *); |
255 | | static void ovsdb_cs_db_parse_lock_reply(struct ovsdb_cs_db *, |
256 | | const struct json *); |
257 | | static bool ovsdb_cs_db_parse_lock_notify(struct ovsdb_cs_db *, |
258 | | const struct json *params, |
259 | | bool new_has_lock); |
260 | | static void ovsdb_cs_send_cond_change(struct ovsdb_cs *); |
261 | | |
262 | | static bool ovsdb_cs_db_txn_process_reply(struct ovsdb_cs *, |
263 | | const struct jsonrpc_msg *reply); |
264 | | |
265 | | /* Events. */ |
266 | | |
267 | | void |
268 | | ovsdb_cs_event_destroy(struct ovsdb_cs_event *event) |
269 | 0 | { |
270 | 0 | if (event) { |
271 | 0 | switch (event->type) { |
272 | 0 | case OVSDB_CS_EVENT_TYPE_RECONNECT: |
273 | 0 | case OVSDB_CS_EVENT_TYPE_LOCKED: |
274 | 0 | break; |
275 | | |
276 | 0 | case OVSDB_CS_EVENT_TYPE_UPDATE: |
277 | 0 | json_destroy(event->update.table_updates); |
278 | 0 | break; |
279 | | |
280 | 0 | case OVSDB_CS_EVENT_TYPE_TXN_REPLY: |
281 | 0 | jsonrpc_msg_destroy(event->txn_reply); |
282 | 0 | break; |
283 | 0 | } |
284 | 0 | free(event); |
285 | 0 | } |
286 | 0 | } |
287 | | |
288 | | /* Lifecycle. */ |
289 | | |
290 | | static void |
291 | | ovsdb_cs_db_init(struct ovsdb_cs_db *db, const char *db_name, |
292 | | struct ovsdb_cs *parent, int max_version, |
293 | | const struct ovsdb_cs_ops *ops, void *ops_aux) |
294 | 0 | { |
295 | 0 | *db = (struct ovsdb_cs_db) { |
296 | 0 | .cs = parent, |
297 | 0 | .db_name = db_name, |
298 | 0 | .tables = HMAP_INITIALIZER(&db->tables), |
299 | 0 | .max_version = max_version, |
300 | 0 | .monitor_id = json_array_create_2(json_string_create("monid"), |
301 | 0 | json_string_create(db_name)), |
302 | 0 | .events = OVS_LIST_INITIALIZER(&db->events), |
303 | 0 | .ops = ops, |
304 | 0 | .ops_aux = ops_aux, |
305 | 0 | }; |
306 | 0 | } |
307 | | |
308 | | /* Creates and returns a new client synchronization object. The connection |
309 | | * will monitor remote database 'db_name'. If 'retry' is true, then also |
310 | | * reconnect if the connection fails. |
311 | | * |
312 | | * XXX 'max_version' should ordinarily be 3, to allow use of the most efficient |
313 | | * "monitor_cond_since" method with the database. Currently there's some kind |
314 | | * of bug in the DDlog Rust code that interfaces to that, so instead |
315 | | * ovn-northd-ddlog passes 1 to use plain 'monitor' instead. Once the DDlog |
316 | | * Rust code gets fixed, we might as well just delete 'max_version' |
317 | | * entirely. |
318 | | * |
319 | | * 'ops' is a struct for northd_cs_run() to use, and 'ops_aux' is a pointer |
320 | | * that gets passed into each call. |
321 | | * |
322 | | * Use ovsdb_cs_set_remote() to configure the database to which to connect. |
323 | | * Until a remote is configured, no data can be retrieved. |
324 | | */ |
325 | | struct ovsdb_cs * |
326 | | ovsdb_cs_create(const char *db_name, int max_version, |
327 | | const struct ovsdb_cs_ops *ops, void *ops_aux) |
328 | 0 | { |
329 | 0 | struct ovsdb_cs *cs = xzalloc(sizeof *cs); |
330 | 0 | ovsdb_cs_db_init(&cs->server, "_Server", cs, 2, &ovsdb_cs_server_ops, cs); |
331 | 0 | ovsdb_cs_db_init(&cs->data, db_name, cs, max_version, ops, ops_aux); |
332 | 0 | cs->state_seqno = UINT_MAX; |
333 | 0 | cs->request_id = NULL; |
334 | 0 | cs->leader_only = true; |
335 | 0 | cs->shuffle_remotes = true; |
336 | 0 | cs->set_db_change_aware = true; |
337 | 0 | hmap_init(&cs->server_rows); |
338 | |
|
339 | 0 | return cs; |
340 | 0 | } |
341 | | |
342 | | static void |
343 | | ovsdb_cs_db_destroy(struct ovsdb_cs_db *db) |
344 | 0 | { |
345 | 0 | ovsdb_cs_db_destroy_tables(db); |
346 | |
|
347 | 0 | json_destroy(db->monitor_id); |
348 | 0 | json_destroy(db->schema); |
349 | |
|
350 | 0 | free(db->lock_name); |
351 | |
|
352 | 0 | json_destroy(db->lock_request_id); |
353 | | |
354 | | /* This list always gets flushed out at the end of ovsdb_cs_run(). */ |
355 | 0 | ovs_assert(ovs_list_is_empty(&db->events)); |
356 | 0 | } |
357 | | |
358 | | /* Destroys 'cs' and all of the data structures that it manages. */ |
359 | | void |
360 | | ovsdb_cs_destroy(struct ovsdb_cs *cs) |
361 | 0 | { |
362 | 0 | if (cs) { |
363 | 0 | ovsdb_cs_db_destroy(&cs->server); |
364 | 0 | ovsdb_cs_db_destroy(&cs->data); |
365 | 0 | jsonrpc_session_close(cs->session); |
366 | 0 | free(cs->remote); |
367 | 0 | json_destroy(cs->request_id); |
368 | |
|
369 | 0 | for (size_t i = 0; i < cs->n_txns; i++) { |
370 | 0 | json_destroy(cs->txns[i]); |
371 | 0 | } |
372 | 0 | free(cs->txns); |
373 | |
|
374 | 0 | ovsdb_cs_clear_server_rows(cs); |
375 | 0 | hmap_destroy(&cs->server_rows); |
376 | |
|
377 | 0 | free(cs); |
378 | 0 | } |
379 | 0 | } |
380 | | |
381 | | static void |
382 | | ovsdb_cs_transition_at(struct ovsdb_cs *cs, enum ovsdb_cs_state new_state, |
383 | | const char *where) |
384 | 0 | { |
385 | 0 | VLOG_DBG("%s: %s -> %s at %s", |
386 | 0 | cs->session ? jsonrpc_session_get_name(cs->session) : "void", |
387 | 0 | ovsdb_cs_state_to_string(cs->state), |
388 | 0 | ovsdb_cs_state_to_string(new_state), |
389 | 0 | where); |
390 | 0 | cs->state = new_state; |
391 | 0 | } |
392 | | |
393 | | static void |
394 | | ovsdb_cs_send_request(struct ovsdb_cs *cs, struct jsonrpc_msg *request) |
395 | 0 | { |
396 | 0 | json_destroy(cs->request_id); |
397 | 0 | cs->request_id = json_clone(request->id); |
398 | 0 | if (cs->session) { |
399 | 0 | jsonrpc_session_send(cs->session, request); |
400 | 0 | } else { |
401 | 0 | jsonrpc_msg_destroy(request); |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | static void |
406 | | ovsdb_cs_retry_at(struct ovsdb_cs *cs, const char *where) |
407 | 0 | { |
408 | 0 | ovsdb_cs_force_reconnect(cs); |
409 | 0 | ovsdb_cs_transition_at(cs, CS_S_RETRY, where); |
410 | 0 | } |
411 | | |
412 | | static void |
413 | | ovsdb_cs_restart_fsm(struct ovsdb_cs *cs) |
414 | 0 | { |
415 | 0 | ovsdb_cs_send_schema_request(cs, &cs->server); |
416 | 0 | ovsdb_cs_transition(cs, CS_S_SERVER_SCHEMA_REQUESTED); |
417 | 0 | cs->data.monitor_version = 0; |
418 | 0 | cs->server.monitor_version = 0; |
419 | 0 | } |
420 | | |
421 | | static void |
422 | | ovsdb_cs_process_response(struct ovsdb_cs *cs, struct jsonrpc_msg *msg) |
423 | 0 | { |
424 | 0 | bool ok = msg->type == JSONRPC_REPLY; |
425 | 0 | if (!ok |
426 | 0 | && cs->state != CS_S_SERVER_SCHEMA_REQUESTED |
427 | 0 | && cs->state != CS_S_SERVER_MONITOR_REQUESTED |
428 | 0 | && cs->state != CS_S_DATA_MONITOR_COND_REQUESTED |
429 | 0 | && cs->state != CS_S_DATA_MONITOR_COND_SINCE_REQUESTED) { |
430 | 0 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); |
431 | 0 | char *s = jsonrpc_msg_to_string(msg); |
432 | 0 | VLOG_INFO_RL(&rl, "%s: received unexpected %s response in " |
433 | 0 | "%s state: %s", jsonrpc_session_get_name(cs->session), |
434 | 0 | jsonrpc_msg_type_to_string(msg->type), |
435 | 0 | ovsdb_cs_state_to_string(cs->state), |
436 | 0 | s); |
437 | 0 | free(s); |
438 | 0 | ovsdb_cs_retry(cs); |
439 | 0 | return; |
440 | 0 | } |
441 | | |
442 | 0 | switch (cs->state) { |
443 | 0 | case CS_S_SERVER_SCHEMA_REQUESTED: |
444 | 0 | if (ok) { |
445 | 0 | json_destroy(cs->server.schema); |
446 | 0 | cs->server.schema = json_clone(msg->result); |
447 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->server, |
448 | 0 | cs->server.max_version); |
449 | 0 | ovsdb_cs_transition(cs, CS_S_SERVER_MONITOR_REQUESTED); |
450 | 0 | } else { |
451 | 0 | ovsdb_cs_send_schema_request(cs, &cs->data); |
452 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_SCHEMA_REQUESTED); |
453 | 0 | } |
454 | 0 | break; |
455 | | |
456 | 0 | case CS_S_SERVER_MONITOR_REQUESTED: |
457 | 0 | if (ok) { |
458 | 0 | cs->server.monitor_version = cs->server.max_version; |
459 | 0 | ovsdb_cs_db_parse_monitor_reply(&cs->server, msg->result, |
460 | 0 | cs->server.monitor_version); |
461 | 0 | if (ovsdb_cs_check_server_db(cs) && cs->set_db_change_aware) { |
462 | 0 | ovsdb_cs_send_db_change_aware(cs); |
463 | 0 | } |
464 | 0 | } else { |
465 | 0 | ovsdb_cs_send_schema_request(cs, &cs->data); |
466 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_SCHEMA_REQUESTED); |
467 | 0 | } |
468 | 0 | break; |
469 | | |
470 | 0 | case CS_S_DATA_SCHEMA_REQUESTED: |
471 | 0 | json_destroy(cs->data.schema); |
472 | 0 | cs->data.schema = json_clone(msg->result); |
473 | 0 | if (cs->data.max_version >= 2) { |
474 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 2); |
475 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_COND_REQUESTED); |
476 | 0 | } else { |
477 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 1); |
478 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_REQUESTED); |
479 | 0 | } |
480 | 0 | break; |
481 | | |
482 | 0 | case CS_S_DATA_MONITOR_COND_SINCE_REQUESTED: |
483 | 0 | if (!ok) { |
484 | | /* "monitor_cond_since" not supported. Try "monitor_cond". */ |
485 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 2); |
486 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_COND_REQUESTED); |
487 | 0 | } else { |
488 | 0 | cs->data.monitor_version = 3; |
489 | 0 | ovsdb_cs_transition(cs, CS_S_MONITORING); |
490 | 0 | ovsdb_cs_db_parse_monitor_reply(&cs->data, msg->result, 3); |
491 | 0 | } |
492 | 0 | break; |
493 | | |
494 | 0 | case CS_S_DATA_MONITOR_COND_REQUESTED: |
495 | 0 | if (!ok) { |
496 | | /* "monitor_cond" not supported. Try "monitor". */ |
497 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 1); |
498 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_REQUESTED); |
499 | 0 | } else { |
500 | 0 | cs->data.monitor_version = 2; |
501 | 0 | ovsdb_cs_transition(cs, CS_S_MONITORING); |
502 | 0 | ovsdb_cs_db_parse_monitor_reply(&cs->data, msg->result, 2); |
503 | 0 | } |
504 | 0 | break; |
505 | | |
506 | 0 | case CS_S_DATA_MONITOR_REQUESTED: |
507 | 0 | cs->data.monitor_version = 1; |
508 | 0 | ovsdb_cs_transition(cs, CS_S_MONITORING); |
509 | 0 | ovsdb_cs_db_parse_monitor_reply(&cs->data, msg->result, 1); |
510 | 0 | break; |
511 | | |
512 | 0 | case CS_S_MONITORING: |
513 | | /* We don't normally have a request outstanding in this state. If we |
514 | | * do, it's a "monitor_cond_change", which means that the conditional |
515 | | * monitor clauses were updated. |
516 | | * |
517 | | * Mark the last requested conditions as acked and if further |
518 | | * condition changes were pending, send them now. */ |
519 | 0 | ovsdb_cs_db_ack_condition(&cs->data); |
520 | 0 | ovsdb_cs_send_cond_change(cs); |
521 | 0 | cs->data.cond_seqno++; |
522 | 0 | break; |
523 | | |
524 | 0 | case CS_S_ERROR: |
525 | 0 | case CS_S_RETRY: |
526 | | /* Nothing to do in this state. */ |
527 | 0 | break; |
528 | | |
529 | 0 | default: |
530 | 0 | OVS_NOT_REACHED(); |
531 | 0 | } |
532 | 0 | } |
533 | | |
534 | | static void |
535 | | ovsdb_cs_process_msg(struct ovsdb_cs *cs, struct jsonrpc_msg *msg) |
536 | 0 | { |
537 | 0 | bool is_response = (msg->type == JSONRPC_REPLY || |
538 | 0 | msg->type == JSONRPC_ERROR); |
539 | | |
540 | | /* Process a reply to an outstanding request. */ |
541 | 0 | if (is_response |
542 | 0 | && cs->request_id && json_equal(cs->request_id, msg->id)) { |
543 | 0 | json_destroy(cs->request_id); |
544 | 0 | cs->request_id = NULL; |
545 | 0 | ovsdb_cs_process_response(cs, msg); |
546 | 0 | return; |
547 | 0 | } |
548 | | |
549 | | /* Process database contents updates. */ |
550 | 0 | if (ovsdb_cs_db_parse_update_rpc(&cs->data, msg)) { |
551 | 0 | return; |
552 | 0 | } |
553 | 0 | if (cs->server.monitor_version |
554 | 0 | && ovsdb_cs_db_parse_update_rpc(&cs->server, msg)) { |
555 | 0 | ovsdb_cs_check_server_db(cs); |
556 | 0 | return; |
557 | 0 | } |
558 | | |
559 | 0 | if (ovsdb_cs_handle_monitor_canceled(cs, &cs->data, msg) |
560 | 0 | || (cs->server.monitor_version |
561 | 0 | && ovsdb_cs_handle_monitor_canceled(cs, &cs->server, msg))) { |
562 | 0 | return; |
563 | 0 | } |
564 | | |
565 | | /* Process "lock" replies and related notifications. */ |
566 | 0 | if (ovsdb_cs_db_process_lock_replies(&cs->data, msg)) { |
567 | 0 | return; |
568 | 0 | } |
569 | | |
570 | | /* Process response to a database transaction we submitted. */ |
571 | 0 | if (is_response && ovsdb_cs_db_txn_process_reply(cs, msg)) { |
572 | 0 | return; |
573 | 0 | } |
574 | | |
575 | | /* Unknown message. Log at a low level because this can happen if |
576 | | * ovsdb_cs_txn_destroy() is called to destroy a transaction |
577 | | * before we receive the reply. |
578 | | * |
579 | | * (We could sort those out from other kinds of unknown messages by |
580 | | * using distinctive IDs for transactions, if it seems valuable to |
581 | | * do so, and then it would be possible to use different log |
582 | | * levels. XXX?) */ |
583 | 0 | char *s = jsonrpc_msg_to_string(msg); |
584 | 0 | VLOG_DBG("%s: received unexpected %s message: %s", |
585 | 0 | jsonrpc_session_get_name(cs->session), |
586 | 0 | jsonrpc_msg_type_to_string(msg->type), s); |
587 | 0 | free(s); |
588 | 0 | } |
589 | | |
590 | | static struct ovsdb_cs_event * |
591 | | ovsdb_cs_db_add_event(struct ovsdb_cs_db *db, enum ovsdb_cs_event_type type) |
592 | 0 | { |
593 | 0 | struct ovsdb_cs_event *event = xmalloc(sizeof *event); |
594 | 0 | event->type = type; |
595 | 0 | ovs_list_push_back(&db->events, &event->list_node); |
596 | 0 | return event; |
597 | 0 | } |
598 | | |
599 | | /* Processes a batch of messages from the database server on 'cs'. This may |
600 | | * cause the CS's contents to change. |
601 | | * |
602 | | * Initializes 'events' with a list of events that occurred on 'cs'. The |
603 | | * caller must process and destroy all of the events. */ |
604 | | void |
605 | | ovsdb_cs_run(struct ovsdb_cs *cs, struct ovs_list *events) |
606 | 0 | { |
607 | 0 | ovs_list_init(events); |
608 | 0 | if (!cs->session) { |
609 | 0 | return; |
610 | 0 | } |
611 | | |
612 | 0 | ovsdb_cs_send_cond_change(cs); |
613 | |
|
614 | 0 | jsonrpc_session_run(cs->session); |
615 | |
|
616 | 0 | unsigned int seqno = jsonrpc_session_get_seqno(cs->session); |
617 | 0 | if (cs->state_seqno != seqno) { |
618 | 0 | cs->state_seqno = seqno; |
619 | 0 | ovsdb_cs_restart_fsm(cs); |
620 | |
|
621 | 0 | for (size_t i = 0; i < cs->n_txns; i++) { |
622 | 0 | json_destroy(cs->txns[i]); |
623 | 0 | } |
624 | 0 | cs->n_txns = 0; |
625 | |
|
626 | 0 | ovsdb_cs_db_add_event(&cs->data, OVSDB_CS_EVENT_TYPE_RECONNECT); |
627 | |
|
628 | 0 | if (cs->data.lock_name) { |
629 | 0 | jsonrpc_session_send( |
630 | 0 | cs->session, |
631 | 0 | ovsdb_cs_db_compose_lock_request(&cs->data)); |
632 | 0 | } |
633 | 0 | } |
634 | |
|
635 | 0 | for (int i = 0; i < 50; i++) { |
636 | 0 | struct jsonrpc_msg *msg = jsonrpc_session_recv(cs->session); |
637 | 0 | if (!msg) { |
638 | 0 | break; |
639 | 0 | } |
640 | 0 | ovsdb_cs_process_msg(cs, msg); |
641 | 0 | jsonrpc_msg_destroy(msg); |
642 | 0 | } |
643 | 0 | ovs_list_push_back_all(events, &cs->data.events); |
644 | 0 | } |
645 | | |
646 | | /* Arranges for poll_block() to wake up when ovsdb_cs_run() has something to |
647 | | * do or when activity occurs on a transaction on 'cs'. */ |
648 | | void |
649 | | ovsdb_cs_wait(struct ovsdb_cs *cs) |
650 | 0 | { |
651 | 0 | if (!cs->session) { |
652 | 0 | return; |
653 | 0 | } |
654 | 0 | jsonrpc_session_wait(cs->session); |
655 | 0 | jsonrpc_session_recv_wait(cs->session); |
656 | 0 | } |
657 | | |
658 | | /* Network connection. */ |
659 | | |
660 | | /* Changes the remote and creates a new session. Keeps existing connection |
661 | | * if current remote is still valid. |
662 | | * |
663 | | * If 'retry' is true, the connection to the remote will automatically retry |
664 | | * when it fails. If 'retry' is false, the connection is one-time. */ |
665 | | void |
666 | | ovsdb_cs_set_remote(struct ovsdb_cs *cs, const char *remote, bool retry) |
667 | 0 | { |
668 | 0 | if (cs |
669 | 0 | && ((remote != NULL) != (cs->remote != NULL) |
670 | 0 | || (remote && cs->remote && strcmp(remote, cs->remote)))) { |
671 | 0 | struct jsonrpc *rpc = NULL; |
672 | | |
673 | | /* Close the old session, if any. */ |
674 | 0 | if (cs->session) { |
675 | | /* Save the current open connection and close the session. */ |
676 | 0 | rpc = jsonrpc_session_steal(cs->session); |
677 | 0 | cs->session = NULL; |
678 | |
|
679 | 0 | free(cs->remote); |
680 | 0 | cs->remote = NULL; |
681 | 0 | } |
682 | | |
683 | | /* Open new session, if any. */ |
684 | 0 | if (remote) { |
685 | 0 | struct svec remotes = SVEC_EMPTY_INITIALIZER; |
686 | 0 | struct uuid old_cid = cs->cid; |
687 | |
|
688 | 0 | ovsdb_session_parse_remote(remote, &remotes, &cs->cid); |
689 | 0 | if (cs->shuffle_remotes) { |
690 | 0 | svec_shuffle(&remotes); |
691 | 0 | } |
692 | 0 | cs->session = jsonrpc_session_open_multiple(&remotes, retry); |
693 | | |
694 | | /* Use old connection, if cluster id didn't change and the remote |
695 | | * is still on the list, to avoid unnecessary re-connection. */ |
696 | 0 | if (rpc && uuid_equals(&old_cid, &cs->cid) |
697 | 0 | && svec_contains_unsorted(&remotes, jsonrpc_get_name(rpc))) { |
698 | 0 | jsonrpc_session_replace(cs->session, rpc); |
699 | 0 | cs->state_seqno = jsonrpc_session_get_seqno(cs->session); |
700 | 0 | rpc = NULL; |
701 | 0 | } else { |
702 | 0 | cs->state_seqno = UINT_MAX; |
703 | 0 | } |
704 | |
|
705 | 0 | svec_destroy(&remotes); |
706 | 0 | cs->remote = xstrdup(remote); |
707 | 0 | } |
708 | |
|
709 | 0 | jsonrpc_close(rpc); |
710 | 0 | } |
711 | 0 | } |
712 | | |
713 | | /* Reconfigures 'cs' so that it would reconnect to the database, if |
714 | | * connection was dropped. */ |
715 | | void |
716 | | ovsdb_cs_enable_reconnect(struct ovsdb_cs *cs) |
717 | 0 | { |
718 | 0 | if (cs->session) { |
719 | 0 | jsonrpc_session_enable_reconnect(cs->session); |
720 | 0 | } |
721 | 0 | } |
722 | | |
723 | | /* Forces 'cs' to drop its connection to the database and reconnect. In the |
724 | | * meantime, the contents of 'cs' will not change. */ |
725 | | void |
726 | | ovsdb_cs_force_reconnect(struct ovsdb_cs *cs) |
727 | 0 | { |
728 | 0 | if (cs->session) { |
729 | 0 | if (cs->state == CS_S_MONITORING) { |
730 | | /* The ovsdb-cs was in MONITORING state, so we either had data |
731 | | * inconsistency on this server, or it stopped being the cluster |
732 | | * leader, or the user requested to re-connect. Avoiding backoff |
733 | | * in these cases, as we need to re-connect as soon as possible. |
734 | | * Connections that are not in MONITORING state should have their |
735 | | * backoff to avoid constant flood of re-connection attempts in |
736 | | * case there is no suitable database server. */ |
737 | 0 | jsonrpc_session_reset_backoff(cs->session); |
738 | 0 | } |
739 | 0 | jsonrpc_session_force_reconnect(cs->session); |
740 | 0 | } |
741 | 0 | } |
742 | | |
743 | | /* Drops 'cs''s current connection and the cached session. This is useful if |
744 | | * the client notices some kind of inconsistency. */ |
745 | | void |
746 | | ovsdb_cs_flag_inconsistency(struct ovsdb_cs *cs) |
747 | 0 | { |
748 | 0 | cs->data.last_id = UUID_ZERO; |
749 | 0 | ovsdb_cs_retry(cs); |
750 | 0 | } |
751 | | |
752 | | /* Returns true if 'cs' is currently connected or will eventually try to |
753 | | * reconnect. */ |
754 | | bool |
755 | | ovsdb_cs_is_alive(const struct ovsdb_cs *cs) |
756 | 0 | { |
757 | 0 | return (cs->session |
758 | 0 | && jsonrpc_session_is_alive(cs->session) |
759 | 0 | && cs->state != CS_S_ERROR); |
760 | 0 | } |
761 | | |
762 | | /* Returns true if 'cs' is currently connected to a server. */ |
763 | | bool |
764 | | ovsdb_cs_is_connected(const struct ovsdb_cs *cs) |
765 | 0 | { |
766 | 0 | return cs->session && jsonrpc_session_is_connected(cs->session); |
767 | 0 | } |
768 | | |
769 | | /* Returns the last error reported on a connection by 'cs'. The return value |
770 | | * is 0 only if no connection made by 'cs' has ever encountered an error and |
771 | | * a negative response to a schema request has never been received. See |
772 | | * jsonrpc_get_status() for jsonrpc_session_get_last_error() return value |
773 | | * interpretation. */ |
774 | | int |
775 | | ovsdb_cs_get_last_error(const struct ovsdb_cs *cs) |
776 | 0 | { |
777 | 0 | int err = cs->session ? jsonrpc_session_get_last_error(cs->session) : 0; |
778 | 0 | if (err) { |
779 | 0 | return err; |
780 | 0 | } else if (cs->state == CS_S_ERROR) { |
781 | 0 | return ENOENT; |
782 | 0 | } else { |
783 | 0 | return 0; |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | | /* Sets all the JSON-RPC session 'options' for 'cs''s current session. */ |
788 | | void |
789 | | ovsdb_cs_set_jsonrpc_options(const struct ovsdb_cs *cs, |
790 | | const struct jsonrpc_session_options *options) |
791 | 0 | { |
792 | 0 | if (cs->session) { |
793 | 0 | jsonrpc_session_set_options(cs->session, options); |
794 | 0 | } |
795 | 0 | } |
796 | | |
797 | | /* Sets the "probe interval" for 'cs''s current session to 'probe_interval', in |
798 | | * milliseconds. */ |
799 | | void |
800 | | ovsdb_cs_set_probe_interval(const struct ovsdb_cs *cs, int probe_interval) |
801 | 0 | { |
802 | 0 | if (cs->session) { |
803 | 0 | jsonrpc_session_set_probe_interval(cs->session, probe_interval); |
804 | 0 | } |
805 | 0 | } |
806 | | |
807 | | /* Conditional monitoring. */ |
808 | | |
809 | | /* A table being monitored. |
810 | | * |
811 | | * At the CS layer, the only thing we care about, table-wise, is the conditions |
812 | | * we're using for monitoring them, so there's little here. We only create |
813 | | * these table structures at all for tables that have conditions. */ |
814 | | struct ovsdb_cs_db_table { |
815 | | struct hmap_node hmap_node; /* Indexed by 'name'. */ |
816 | | char *name; /* Table name. */ |
817 | | |
818 | | /* Each of these is a null pointer if it is empty, or JSON [<condition>*] |
819 | | * or [true] or [false] otherwise. [true] could be represented as a null |
820 | | * pointer, but we want to distinguish "empty slot" from "a condition that |
821 | | * is always true" in a slot. */ |
822 | | struct json *ack_cond; /* Last condition acked by the server. */ |
823 | | struct json *req_cond; /* Last condition requested to the server. */ |
824 | | struct json *new_cond; /* Latest condition set by the IDL client. */ |
825 | | }; |
826 | | |
827 | | /* A kind of condition, so that we can treat equivalent JSON as equivalent. */ |
828 | | enum condition_type { |
829 | | COND_FALSE, /* [] or [false] */ |
830 | | COND_TRUE, /* Null pointer or [true] */ |
831 | | COND_OTHER /* Anything else. */ |
832 | | }; |
833 | | |
834 | | /* Returns the condition_type for 'condition'. */ |
835 | | static enum condition_type |
836 | | condition_classify(const struct json *condition) |
837 | 0 | { |
838 | 0 | if (condition) { |
839 | 0 | switch (json_array_size(condition)) { |
840 | 0 | case 0: |
841 | 0 | return COND_FALSE; |
842 | | |
843 | 0 | case 1: { |
844 | 0 | const struct json *cond = json_array_at(condition, 0); |
845 | |
|
846 | 0 | return (cond->type == JSON_FALSE ? COND_FALSE |
847 | 0 | : cond->type == JSON_TRUE ? COND_TRUE |
848 | 0 | : COND_OTHER); |
849 | 0 | } |
850 | | |
851 | 0 | default: |
852 | 0 | return COND_OTHER; |
853 | 0 | } |
854 | 0 | } else { |
855 | 0 | return COND_TRUE; |
856 | 0 | } |
857 | 0 | } |
858 | | |
859 | | /* Returns true if 'a' and 'b' are the same condition (in an obvious way; we're |
860 | | * not going to compare for boolean equivalence or anything). */ |
861 | | static bool |
862 | | condition_equal(const struct json *a, const struct json *b) |
863 | 0 | { |
864 | 0 | enum condition_type type = condition_classify(a); |
865 | 0 | return (type == condition_classify(b) |
866 | 0 | && (type != COND_OTHER || json_equal(a, b))); |
867 | 0 | } |
868 | | |
869 | | /* Returns a clone of 'condition', translating always-true and always-false to |
870 | | * [true] and [false], respectively. */ |
871 | | static struct json * |
872 | | condition_clone(const struct json *condition) |
873 | 0 | { |
874 | 0 | switch (condition_classify(condition)) { |
875 | 0 | case COND_TRUE: |
876 | 0 | return json_array_create_1(json_boolean_create(true)); |
877 | | |
878 | 0 | case COND_FALSE: |
879 | 0 | return json_array_create_1(json_boolean_create(false)); |
880 | | |
881 | 0 | case COND_OTHER: |
882 | 0 | return json_clone(condition); |
883 | 0 | } |
884 | | |
885 | 0 | OVS_NOT_REACHED(); |
886 | 0 | } |
887 | | |
888 | | /* Returns the ovsdb_cs_db_table associated with 'table' in 'db', creating an |
889 | | * empty one if necessary. */ |
890 | | static struct ovsdb_cs_db_table * |
891 | | ovsdb_cs_db_get_table(struct ovsdb_cs_db *db, const char *table) |
892 | 0 | { |
893 | 0 | uint32_t hash = hash_string(table, 0); |
894 | 0 | struct ovsdb_cs_db_table *t; |
895 | |
|
896 | 0 | HMAP_FOR_EACH_WITH_HASH (t, hmap_node, hash, &db->tables) { |
897 | 0 | if (!strcmp(t->name, table)) { |
898 | 0 | return t; |
899 | 0 | } |
900 | 0 | } |
901 | | |
902 | 0 | t = xzalloc(sizeof *t); |
903 | 0 | t->name = xstrdup(table); |
904 | 0 | t->ack_cond = json_array_create_1(json_boolean_create(true)); |
905 | 0 | hmap_insert(&db->tables, &t->hmap_node, hash); |
906 | 0 | return t; |
907 | 0 | } |
908 | | |
909 | | static void |
910 | | ovsdb_cs_db_destroy_table(struct ovsdb_cs_db_table *table, |
911 | | struct ovsdb_cs_db *db) |
912 | 0 | { |
913 | 0 | json_destroy(table->ack_cond); |
914 | 0 | json_destroy(table->req_cond); |
915 | 0 | json_destroy(table->new_cond); |
916 | 0 | hmap_remove(&db->tables, &table->hmap_node); |
917 | 0 | free(table->name); |
918 | 0 | free(table); |
919 | 0 | } |
920 | | |
921 | | /* Destroy a given ovsdb_cs_db_table according to the table name. */ |
922 | | void |
923 | | ovsdb_cs_clear_condition(struct ovsdb_cs *cs, const char *table) |
924 | 0 | { |
925 | 0 | uint32_t hash = hash_string(table, 0); |
926 | 0 | struct ovsdb_cs_db *db = &cs->data; |
927 | |
|
928 | 0 | struct ovsdb_cs_db_table *t; |
929 | 0 | HMAP_FOR_EACH_WITH_HASH (t, hmap_node, hash, &db->tables) { |
930 | 0 | if (!strcmp(t->name, table)) { |
931 | 0 | ovsdb_cs_db_destroy_table(t, db); |
932 | 0 | db->last_id = UUID_ZERO; |
933 | 0 | return; |
934 | 0 | } |
935 | 0 | } |
936 | 0 | } |
937 | | |
938 | | void |
939 | | ovsdb_cs_reset_last_id(struct ovsdb_cs *cs) |
940 | 0 | { |
941 | 0 | cs->data.last_id = UUID_ZERO; |
942 | 0 | } |
943 | | |
944 | | static void |
945 | | ovsdb_cs_db_destroy_tables(struct ovsdb_cs_db *db) |
946 | 0 | { |
947 | 0 | struct ovsdb_cs_db_table *table; |
948 | 0 | HMAP_FOR_EACH_SAFE (table, hmap_node, &db->tables) { |
949 | 0 | ovsdb_cs_db_destroy_table(table, db); |
950 | 0 | } |
951 | 0 | hmap_destroy(&db->tables); |
952 | 0 | } |
953 | | |
954 | | static unsigned int |
955 | | ovsdb_cs_db_set_condition(struct ovsdb_cs_db *db, const char *table, |
956 | | const struct json *condition) |
957 | 0 | { |
958 | | /* Compare the new condition to the last known condition which can be |
959 | | * either "new" (not sent yet), "requested" or "acked", in this order. */ |
960 | 0 | struct ovsdb_cs_db_table *t = ovsdb_cs_db_get_table(db, table); |
961 | 0 | const struct json *table_cond = (t->new_cond ? t->new_cond |
962 | 0 | : t->req_cond ? t->req_cond |
963 | 0 | : t->ack_cond); |
964 | 0 | if (!condition_equal(condition, table_cond)) { |
965 | 0 | json_destroy(t->new_cond); |
966 | 0 | t->new_cond = condition_clone(condition); |
967 | 0 | db->cond_changed = true; |
968 | 0 | poll_immediate_wake(); |
969 | 0 | } |
970 | | |
971 | | /* Conditions will be up to date when we receive replies for already |
972 | | * requested and new conditions, if any. This includes condition change |
973 | | * requests for other tables too. |
974 | | */ |
975 | 0 | if (t->new_cond) { |
976 | | /* New condition will be sent out after all already requested ones |
977 | | * are acked. |
978 | | */ |
979 | 0 | bool any_req_cond = false; |
980 | 0 | HMAP_FOR_EACH (t, hmap_node, &db->tables) { |
981 | 0 | if (t->req_cond) { |
982 | 0 | any_req_cond = true; |
983 | 0 | break; |
984 | 0 | } |
985 | 0 | } |
986 | 0 | return db->cond_seqno + any_req_cond + 1; |
987 | 0 | } else { |
988 | | /* Already requested conditions should be up to date at |
989 | | * db->cond_seqno + 1 while acked conditions are already up to date. |
990 | | */ |
991 | 0 | return db->cond_seqno + !!t->req_cond; |
992 | 0 | } |
993 | 0 | } |
994 | | |
995 | | /* Sets the replication condition for 'tc' in 'cs' to 'condition' and arranges |
996 | | * to send the new condition to the database server. |
997 | | * |
998 | | * Return the next conditional update sequence number. When this value and |
999 | | * ovsdb_cs_get_condition_seqno() matches, 'cs' contains rows that match the |
1000 | | * 'condition'. */ |
1001 | | unsigned int |
1002 | | ovsdb_cs_set_condition(struct ovsdb_cs *cs, const char *table, |
1003 | | const struct json *condition) |
1004 | 0 | { |
1005 | 0 | return ovsdb_cs_db_set_condition(&cs->data, table, condition); |
1006 | 0 | } |
1007 | | |
1008 | | /* Returns a "sequence number" that represents the number of conditional |
1009 | | * monitoring updates successfully received by the OVSDB server of a CS |
1010 | | * connection. |
1011 | | * |
1012 | | * ovsdb_cs_set_condition() sets a new condition that is different from the |
1013 | | * current condtion, the next expected "sequence number" is returned. |
1014 | | * |
1015 | | * Whenever ovsdb_cs_get_condition_seqno() returns a value that matches the |
1016 | | * return value of ovsdb_cs_set_condition(), the client is assured that: |
1017 | | * |
1018 | | * - The ovsdb_cs_set_condition() changes has been acknowledged by the OVSDB |
1019 | | * server. |
1020 | | * |
1021 | | * - 'cs' now contains the content matches the new conditions. */ |
1022 | | unsigned int |
1023 | | ovsdb_cs_get_condition_seqno(const struct ovsdb_cs *cs) |
1024 | 0 | { |
1025 | 0 | return cs->data.cond_seqno; |
1026 | 0 | } |
1027 | | |
1028 | | static struct json * |
1029 | | ovsdb_cs_create_cond_change_req(const struct json *cond) |
1030 | 0 | { |
1031 | 0 | struct json *monitor_cond_change_request = json_object_create(); |
1032 | 0 | json_object_put(monitor_cond_change_request, "where", json_clone(cond)); |
1033 | 0 | return monitor_cond_change_request; |
1034 | 0 | } |
1035 | | |
1036 | | static struct jsonrpc_msg * |
1037 | | ovsdb_cs_db_compose_cond_change(struct ovsdb_cs_db *db) |
1038 | 0 | { |
1039 | 0 | if (!db->cond_changed) { |
1040 | 0 | return NULL; |
1041 | 0 | } |
1042 | | |
1043 | 0 | struct json *monitor_cond_change_requests = NULL; |
1044 | 0 | struct ovsdb_cs_db_table *table; |
1045 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1046 | | /* Always use the most recent conditions set by the CS client when |
1047 | | * requesting monitor_cond_change, i.e., table->new_cond. |
1048 | | */ |
1049 | 0 | if (table->new_cond) { |
1050 | 0 | struct json *req = |
1051 | 0 | ovsdb_cs_create_cond_change_req(table->new_cond); |
1052 | 0 | if (req) { |
1053 | 0 | if (!monitor_cond_change_requests) { |
1054 | 0 | monitor_cond_change_requests = json_object_create(); |
1055 | 0 | } |
1056 | 0 | json_object_put(monitor_cond_change_requests, |
1057 | 0 | table->name, |
1058 | 0 | json_array_create_1(req)); |
1059 | 0 | } |
1060 | | /* Mark the new condition as requested by moving it to req_cond. |
1061 | | * If there's already requested condition that's a bug. |
1062 | | */ |
1063 | 0 | ovs_assert(table->req_cond == NULL); |
1064 | 0 | table->req_cond = table->new_cond; |
1065 | 0 | table->new_cond = NULL; |
1066 | 0 | } |
1067 | 0 | } |
1068 | |
|
1069 | 0 | if (!monitor_cond_change_requests) { |
1070 | 0 | return NULL; |
1071 | 0 | } |
1072 | | |
1073 | 0 | db->cond_changed = false; |
1074 | 0 | struct json *params = json_array_create_3(json_clone(db->monitor_id), |
1075 | 0 | json_clone(db->monitor_id), |
1076 | 0 | monitor_cond_change_requests); |
1077 | 0 | return jsonrpc_create_request("monitor_cond_change", params, NULL); |
1078 | 0 | } |
1079 | | |
1080 | | /* Marks all requested table conditions in 'db' as acked by the server. |
1081 | | * It should be called when the server replies to monitor_cond_change |
1082 | | * requests. |
1083 | | */ |
1084 | | static void |
1085 | | ovsdb_cs_db_ack_condition(struct ovsdb_cs_db *db) |
1086 | 0 | { |
1087 | 0 | struct ovsdb_cs_db_table *table; |
1088 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1089 | 0 | if (table->req_cond) { |
1090 | 0 | json_destroy(table->ack_cond); |
1091 | 0 | table->ack_cond = table->req_cond; |
1092 | 0 | table->req_cond = NULL; |
1093 | 0 | } |
1094 | 0 | } |
1095 | 0 | } |
1096 | | |
1097 | | /* Should be called when the CS fsm is restarted and resyncs table conditions |
1098 | | * based on the state the DB is in: |
1099 | | * - If a non-zero 'last_id' is available for the DB, then upon reconnect |
1100 | | * the CS should first request acked conditions to avoid missing updates |
1101 | | * about records that were added before the transaction with |
1102 | | * txn-id == last_id. |
1103 | | * |
1104 | | * - If there were changes in flight then there are two cases: |
1105 | | * a. either the server already processed the requested monitor condition |
1106 | | * change but the FSM was restarted before the client was notified. |
1107 | | * In this case the client should clear its local cache because it's |
1108 | | * out of sync with the monitor view on the server side. |
1109 | | * |
1110 | | * b. OR the server hasn't processed the requested monitor condition |
1111 | | * change yet. |
1112 | | * |
1113 | | * As there's no easy way to differentiate between the two, and given that |
1114 | | * this condition should be rare, reset the 'last_id', essentially flushing |
1115 | | * the local cached DB contents. |
1116 | | * |
1117 | | * - If there's no 'last_id' available for the DB or it was reset, then it's |
1118 | | * safe to use the latest conditions set by the CS client even if they |
1119 | | * weren't acked yet, since the local cache will be cleared anyway. |
1120 | | */ |
1121 | | static void |
1122 | | ovsdb_cs_db_sync_condition(struct ovsdb_cs_db *db) |
1123 | 0 | { |
1124 | 0 | struct ovsdb_cs_db_table *table; |
1125 | |
|
1126 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1127 | 0 | if (table->req_cond) { |
1128 | | /* There was an in-flight condition change - reset. */ |
1129 | 0 | db->last_id = UUID_ZERO; |
1130 | 0 | break; |
1131 | 0 | } |
1132 | 0 | } |
1133 | |
|
1134 | 0 | if (uuid_is_zero(&db->last_id)) { |
1135 | | /* No 'last_id' - use the latest conditions for the monitor request. */ |
1136 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1137 | 0 | if (table->new_cond) { |
1138 | 0 | json_destroy(table->req_cond); |
1139 | 0 | table->req_cond = table->new_cond; |
1140 | 0 | table->new_cond = NULL; |
1141 | 0 | } |
1142 | |
|
1143 | 0 | if (table->req_cond) { |
1144 | 0 | json_destroy(table->ack_cond); |
1145 | 0 | table->ack_cond = table->req_cond; |
1146 | 0 | table->req_cond = NULL; |
1147 | 0 | } |
1148 | 0 | } |
1149 | | /* Nothing to send after the initial monitor request. */ |
1150 | 0 | db->cond_changed = false; |
1151 | 0 | } else { |
1152 | | /* No in-flight changes and a non-zero 'last_id'. Send acknowledged |
1153 | | * first, then follow up with the new, if any. */ |
1154 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1155 | 0 | if (table->new_cond) { |
1156 | 0 | db->cond_changed = true; |
1157 | 0 | break; |
1158 | 0 | } |
1159 | 0 | } |
1160 | 0 | } |
1161 | 0 | } |
1162 | | |
1163 | | static void |
1164 | | ovsdb_cs_send_cond_change(struct ovsdb_cs *cs) |
1165 | 0 | { |
1166 | | /* When 'cs->request_id' is not NULL, there is an outstanding |
1167 | | * conditional monitoring update request that we have not heard |
1168 | | * from the server yet. Don't generate another request in this case. */ |
1169 | 0 | if (!jsonrpc_session_is_connected(cs->session) |
1170 | 0 | || cs->data.monitor_version == 1 |
1171 | 0 | || cs->request_id) { |
1172 | 0 | return; |
1173 | 0 | } |
1174 | | |
1175 | 0 | struct jsonrpc_msg *msg = ovsdb_cs_db_compose_cond_change(&cs->data); |
1176 | 0 | if (msg) { |
1177 | 0 | cs->request_id = json_clone(msg->id); |
1178 | 0 | jsonrpc_session_send(cs->session, msg); |
1179 | 0 | } |
1180 | 0 | } |
1181 | | |
1182 | | /* Database change awareness. */ |
1183 | | |
1184 | | /* By default, or if 'set_db_change_aware' is true, 'cs' will send |
1185 | | * 'set_db_change_aware' request to the server after receiving the _SERVER data |
1186 | | * (when the server supports it), which is useful for clients that intends to |
1187 | | * keep long connections to the server. Otherwise, 'cs' will not send the |
1188 | | * 'set_db_change_aware' request, which is more reasonable for short-lived |
1189 | | * connections to avoid unnecessary processing at the server side and possible |
1190 | | * error handling due to connections being closed by the clients before the |
1191 | | * responses are sent by the server. */ |
1192 | | void |
1193 | | ovsdb_cs_set_db_change_aware(struct ovsdb_cs *cs, bool set_db_change_aware) |
1194 | 0 | { |
1195 | 0 | cs->set_db_change_aware = set_db_change_aware; |
1196 | 0 | } |
1197 | | |
1198 | | /* Clustered servers. */ |
1199 | | |
1200 | | /* By default, or if 'leader_only' is true, when 'cs' connects to a clustered |
1201 | | * database, the CS layer will avoid servers other than the cluster |
1202 | | * leader. This ensures that any data that it reads and reports is up-to-date. |
1203 | | * If 'leader_only' is false, the CS layer will accept any server in the |
1204 | | * cluster, which means that for read-only transactions it can report and act |
1205 | | * on stale data (transactions that modify the database are always serialized |
1206 | | * even with false 'leader_only'). Refer to Understanding Cluster Consistency |
1207 | | * in ovsdb(7) for more information. */ |
1208 | | void |
1209 | | ovsdb_cs_set_leader_only(struct ovsdb_cs *cs, bool leader_only) |
1210 | 0 | { |
1211 | 0 | cs->leader_only = leader_only; |
1212 | 0 | if (leader_only && cs->server.monitor_version) { |
1213 | 0 | ovsdb_cs_check_server_db(cs); |
1214 | 0 | } |
1215 | 0 | } |
1216 | | |
1217 | | /* Set whether the order of remotes should be shuffled, when there is more than |
1218 | | * one remote. The setting doesn't take effect until the next time when |
1219 | | * ovsdb_cs_set_remote() is called. */ |
1220 | | void |
1221 | | ovsdb_cs_set_shuffle_remotes(struct ovsdb_cs *cs, bool shuffle) |
1222 | 0 | { |
1223 | 0 | cs->shuffle_remotes = shuffle; |
1224 | 0 | } |
1225 | | |
1226 | | /* Reset min_index to 0. This prevents a situation where the client |
1227 | | * thinks all databases have stale data, when they actually have all |
1228 | | * been destroyed and rebuilt from scratch. |
1229 | | */ |
1230 | | void |
1231 | | ovsdb_cs_reset_min_index(struct ovsdb_cs *cs) |
1232 | 0 | { |
1233 | 0 | cs->min_index = 0; |
1234 | 0 | } |
1235 | | |
1236 | | /* Database locks. */ |
1237 | | |
1238 | | static struct jsonrpc_msg * |
1239 | | ovsdb_cs_db_set_lock(struct ovsdb_cs_db *db, const char *lock_name) |
1240 | 0 | { |
1241 | 0 | if (db->lock_name |
1242 | 0 | && (!lock_name || strcmp(lock_name, db->lock_name))) { |
1243 | | /* Release previous lock. */ |
1244 | 0 | struct jsonrpc_msg *msg = ovsdb_cs_db_compose_unlock_request(db); |
1245 | 0 | free(db->lock_name); |
1246 | 0 | db->lock_name = NULL; |
1247 | 0 | db->is_lock_contended = false; |
1248 | 0 | return msg; |
1249 | 0 | } |
1250 | | |
1251 | 0 | if (lock_name && !db->lock_name) { |
1252 | | /* Acquire new lock. */ |
1253 | 0 | db->lock_name = xstrdup(lock_name); |
1254 | 0 | return ovsdb_cs_db_compose_lock_request(db); |
1255 | 0 | } |
1256 | | |
1257 | 0 | return NULL; |
1258 | 0 | } |
1259 | | |
1260 | | /* If 'lock_name' is nonnull, configures 'cs' to obtain the named lock from the |
1261 | | * database server and to prevent modifying the database when the lock cannot |
1262 | | * be acquired (that is, when another client has the same lock). |
1263 | | * |
1264 | | * If 'lock_name' is NULL, drops the locking requirement and releases the |
1265 | | * lock. */ |
1266 | | void |
1267 | | ovsdb_cs_set_lock(struct ovsdb_cs *cs, const char *lock_name) |
1268 | 0 | { |
1269 | 0 | for (;;) { |
1270 | 0 | struct jsonrpc_msg *msg = ovsdb_cs_db_set_lock(&cs->data, lock_name); |
1271 | 0 | if (!msg) { |
1272 | 0 | break; |
1273 | 0 | } |
1274 | 0 | if (cs->session) { |
1275 | 0 | jsonrpc_session_send(cs->session, msg); |
1276 | 0 | } else { |
1277 | 0 | jsonrpc_msg_destroy(msg); |
1278 | 0 | } |
1279 | 0 | } |
1280 | 0 | } |
1281 | | |
1282 | | /* Returns the name of the lock that 'cs' is trying to obtain, or NULL if none |
1283 | | * is configured. */ |
1284 | | const char * |
1285 | | ovsdb_cs_get_lock(const struct ovsdb_cs *cs) |
1286 | 0 | { |
1287 | 0 | return cs->data.lock_name; |
1288 | 0 | } |
1289 | | |
1290 | | /* Returns true if 'cs' is configured to obtain a lock and owns that lock, |
1291 | | * false if it doesn't own the lock or isn't configured to obtain one. |
1292 | | * |
1293 | | * Locking and unlocking happens asynchronously from the database client's |
1294 | | * point of view, so the information is only useful for optimization (e.g. if |
1295 | | * the client doesn't have the lock then there's no point in trying to write to |
1296 | | * the database). */ |
1297 | | bool |
1298 | | ovsdb_cs_has_lock(const struct ovsdb_cs *cs) |
1299 | 0 | { |
1300 | 0 | return cs->data.has_lock; |
1301 | 0 | } |
1302 | | |
1303 | | /* Returns true if 'cs' is configured to obtain a lock but the database server |
1304 | | * has indicated that some other client already owns the requested lock. */ |
1305 | | bool |
1306 | | ovsdb_cs_is_lock_contended(const struct ovsdb_cs *cs) |
1307 | 0 | { |
1308 | 0 | return cs->data.is_lock_contended; |
1309 | 0 | } |
1310 | | |
1311 | | static void |
1312 | | ovsdb_cs_db_update_has_lock(struct ovsdb_cs_db *db, bool new_has_lock) |
1313 | 0 | { |
1314 | 0 | if (new_has_lock && !db->has_lock) { |
1315 | 0 | ovsdb_cs_db_add_event(db, OVSDB_CS_EVENT_TYPE_LOCKED); |
1316 | 0 | db->is_lock_contended = false; |
1317 | 0 | } |
1318 | 0 | db->has_lock = new_has_lock; |
1319 | 0 | } |
1320 | | |
1321 | | static bool |
1322 | | ovsdb_cs_db_process_lock_replies(struct ovsdb_cs_db *db, |
1323 | | const struct jsonrpc_msg *msg) |
1324 | 0 | { |
1325 | 0 | if (msg->type == JSONRPC_REPLY |
1326 | 0 | && db->lock_request_id |
1327 | 0 | && json_equal(db->lock_request_id, msg->id)) { |
1328 | | /* Reply to our "lock" request. */ |
1329 | 0 | ovsdb_cs_db_parse_lock_reply(db, msg->result); |
1330 | 0 | return true; |
1331 | 0 | } |
1332 | | |
1333 | 0 | if (msg->type == JSONRPC_NOTIFY) { |
1334 | 0 | if (!strcmp(msg->method, "locked")) { |
1335 | | /* We got our lock. */ |
1336 | 0 | return ovsdb_cs_db_parse_lock_notify(db, msg->params, true); |
1337 | 0 | } else if (!strcmp(msg->method, "stolen")) { |
1338 | | /* Someone else stole our lock. */ |
1339 | 0 | return ovsdb_cs_db_parse_lock_notify(db, msg->params, false); |
1340 | 0 | } |
1341 | 0 | } |
1342 | | |
1343 | 0 | return false; |
1344 | 0 | } |
1345 | | |
1346 | | static struct jsonrpc_msg * |
1347 | | ovsdb_cs_db_compose_lock_request__(struct ovsdb_cs_db *db, |
1348 | | const char *method) |
1349 | 0 | { |
1350 | 0 | ovsdb_cs_db_update_has_lock(db, false); |
1351 | |
|
1352 | 0 | json_destroy(db->lock_request_id); |
1353 | 0 | db->lock_request_id = NULL; |
1354 | |
|
1355 | 0 | struct json *params = json_array_create_1(json_string_create( |
1356 | 0 | db->lock_name)); |
1357 | 0 | return jsonrpc_create_request(method, params, NULL); |
1358 | 0 | } |
1359 | | |
1360 | | static struct jsonrpc_msg * |
1361 | | ovsdb_cs_db_compose_lock_request(struct ovsdb_cs_db *db) |
1362 | 0 | { |
1363 | 0 | struct jsonrpc_msg *msg = ovsdb_cs_db_compose_lock_request__(db, "lock"); |
1364 | 0 | db->lock_request_id = json_clone(msg->id); |
1365 | 0 | return msg; |
1366 | 0 | } |
1367 | | |
1368 | | static struct jsonrpc_msg * |
1369 | | ovsdb_cs_db_compose_unlock_request(struct ovsdb_cs_db *db) |
1370 | 0 | { |
1371 | 0 | return ovsdb_cs_db_compose_lock_request__(db, "unlock"); |
1372 | 0 | } |
1373 | | |
1374 | | static void |
1375 | | ovsdb_cs_db_parse_lock_reply(struct ovsdb_cs_db *db, |
1376 | | const struct json *result) |
1377 | 0 | { |
1378 | 0 | bool got_lock; |
1379 | |
|
1380 | 0 | json_destroy(db->lock_request_id); |
1381 | 0 | db->lock_request_id = NULL; |
1382 | |
|
1383 | 0 | if (result->type == JSON_OBJECT) { |
1384 | 0 | const struct json *locked; |
1385 | |
|
1386 | 0 | locked = shash_find_data(json_object(result), "locked"); |
1387 | 0 | got_lock = locked && locked->type == JSON_TRUE; |
1388 | 0 | } else { |
1389 | 0 | got_lock = false; |
1390 | 0 | } |
1391 | |
|
1392 | 0 | ovsdb_cs_db_update_has_lock(db, got_lock); |
1393 | 0 | if (!got_lock) { |
1394 | 0 | db->is_lock_contended = true; |
1395 | 0 | } |
1396 | 0 | } |
1397 | | |
1398 | | static bool |
1399 | | ovsdb_cs_db_parse_lock_notify(struct ovsdb_cs_db *db, |
1400 | | const struct json *params, |
1401 | | bool new_has_lock) |
1402 | 0 | { |
1403 | 0 | if (db->lock_name |
1404 | 0 | && params->type == JSON_ARRAY |
1405 | 0 | && json_array_size(params) > 0 |
1406 | 0 | && json_array_at(params, 0)->type == JSON_STRING) { |
1407 | 0 | const char *lock_name = json_string(json_array_at(params, 0)); |
1408 | |
|
1409 | 0 | if (!strcmp(db->lock_name, lock_name)) { |
1410 | 0 | ovsdb_cs_db_update_has_lock(db, new_has_lock); |
1411 | 0 | if (!new_has_lock) { |
1412 | 0 | db->is_lock_contended = true; |
1413 | 0 | } |
1414 | 0 | return true; |
1415 | 0 | } |
1416 | 0 | } |
1417 | 0 | return false; |
1418 | 0 | } |
1419 | | |
1420 | | /* Transactions. */ |
1421 | | |
1422 | | static bool |
1423 | | ovsdb_cs_db_txn_process_reply(struct ovsdb_cs *cs, |
1424 | | const struct jsonrpc_msg *reply) |
1425 | 0 | { |
1426 | 0 | bool found = ovsdb_cs_forget_transaction(cs, reply->id); |
1427 | 0 | if (found) { |
1428 | 0 | struct ovsdb_cs_event *event |
1429 | 0 | = ovsdb_cs_db_add_event(&cs->data, OVSDB_CS_EVENT_TYPE_TXN_REPLY); |
1430 | 0 | event->txn_reply = jsonrpc_msg_clone(reply); |
1431 | 0 | } |
1432 | 0 | return found; |
1433 | 0 | } |
1434 | | |
1435 | | /* Returns true if 'cs' can be sent a transaction now, false otherwise. This |
1436 | | * is useful for optimization: there is no point in composing and sending a |
1437 | | * transaction if it returns false. */ |
1438 | | bool |
1439 | | ovsdb_cs_may_send_transaction(const struct ovsdb_cs *cs) |
1440 | 0 | { |
1441 | 0 | return (cs->session != NULL |
1442 | 0 | && cs->state == CS_S_MONITORING |
1443 | 0 | && (!cs->data.lock_name || ovsdb_cs_has_lock(cs))); |
1444 | 0 | } |
1445 | | |
1446 | | /* Attempts to send a transaction with the specified 'operations' to 'cs''s |
1447 | | * server. On success, returns the request ID; the caller must eventually free |
1448 | | * it. On failure, returns NULL. */ |
1449 | | struct json * OVS_WARN_UNUSED_RESULT |
1450 | | ovsdb_cs_send_transaction(struct ovsdb_cs *cs, struct json *operations) |
1451 | 0 | { |
1452 | 0 | if (!ovsdb_cs_may_send_transaction(cs)) { |
1453 | 0 | json_destroy(operations); |
1454 | 0 | return NULL; |
1455 | 0 | } |
1456 | | |
1457 | 0 | if (cs->data.lock_name) { |
1458 | 0 | struct json *assertion = json_object_create(); |
1459 | 0 | json_object_put_string(assertion, "op", "assert"); |
1460 | 0 | json_object_put_string(assertion, "lock", cs->data.lock_name); |
1461 | 0 | json_array_add(operations, assertion); |
1462 | 0 | } |
1463 | |
|
1464 | 0 | struct json *request_id; |
1465 | 0 | struct jsonrpc_msg *request = jsonrpc_create_request( |
1466 | 0 | "transact", operations, &request_id); |
1467 | 0 | int error = jsonrpc_session_send(cs->session, request); |
1468 | 0 | if (error) { |
1469 | 0 | json_destroy(request_id); |
1470 | 0 | return NULL; |
1471 | 0 | } |
1472 | | |
1473 | 0 | if (cs->n_txns >= cs->allocated_txns) { |
1474 | 0 | cs->txns = x2nrealloc(cs->txns, &cs->allocated_txns, |
1475 | 0 | sizeof *cs->txns); |
1476 | 0 | } |
1477 | 0 | cs->txns[cs->n_txns++] = request_id; |
1478 | 0 | return json_clone(request_id); |
1479 | 0 | } |
1480 | | |
1481 | | /* Makes 'cs' drop its record of transaction 'request_id'. If a reply arrives |
1482 | | * for it later (which it will, unless the connection drops in the meantime), |
1483 | | * it won't be reported through an event. |
1484 | | * |
1485 | | * Returns true if 'request_id' was known, false otherwise. */ |
1486 | | bool |
1487 | | ovsdb_cs_forget_transaction(struct ovsdb_cs *cs, const struct json *request_id) |
1488 | 0 | { |
1489 | 0 | for (size_t i = 0; i < cs->n_txns; i++) { |
1490 | 0 | if (json_equal(request_id, cs->txns[i])) { |
1491 | 0 | json_destroy(cs->txns[i]); |
1492 | 0 | cs->txns[i] = cs->txns[--cs->n_txns]; |
1493 | 0 | return true; |
1494 | 0 | } |
1495 | 0 | } |
1496 | 0 | return false; |
1497 | 0 | } |
1498 | | |
1499 | | static void |
1500 | | ovsdb_cs_send_schema_request(struct ovsdb_cs *cs, |
1501 | | struct ovsdb_cs_db *db) |
1502 | 0 | { |
1503 | 0 | ovsdb_cs_send_request(cs, jsonrpc_create_request( |
1504 | 0 | "get_schema", |
1505 | 0 | json_array_create_1(json_string_create( |
1506 | 0 | db->db_name)), |
1507 | 0 | NULL)); |
1508 | 0 | } |
1509 | | |
1510 | | static void |
1511 | | ovsdb_cs_send_db_change_aware(struct ovsdb_cs *cs) |
1512 | 0 | { |
1513 | 0 | struct jsonrpc_msg *msg = jsonrpc_create_request( |
1514 | 0 | "set_db_change_aware", json_array_create_1(json_boolean_create(true)), |
1515 | 0 | NULL); |
1516 | 0 | jsonrpc_session_send(cs->session, msg); |
1517 | 0 | } |
1518 | | |
1519 | | static void |
1520 | | ovsdb_cs_send_monitor_request(struct ovsdb_cs *cs, struct ovsdb_cs_db *db, |
1521 | | int version) |
1522 | 0 | { |
1523 | 0 | struct json *mrs = db->ops->compose_monitor_requests( |
1524 | 0 | db->schema, db->ops_aux); |
1525 | | /* XXX handle failure */ |
1526 | 0 | ovs_assert(mrs->type == JSON_OBJECT); |
1527 | |
|
1528 | 0 | ovsdb_cs_db_sync_condition(db); |
1529 | |
|
1530 | 0 | if (version > 1) { |
1531 | 0 | struct ovsdb_cs_db_table *table; |
1532 | 0 | HMAP_FOR_EACH (table, hmap_node, &db->tables) { |
1533 | 0 | if (table->ack_cond) { |
1534 | 0 | struct json *mr = shash_find_data(json_object(mrs), |
1535 | 0 | table->name); |
1536 | 0 | if (!mr) { |
1537 | 0 | mr = json_array_create_empty(); |
1538 | 0 | json_object_put(mrs, table->name, mr); |
1539 | 0 | } |
1540 | 0 | ovs_assert(mr->type == JSON_ARRAY); |
1541 | |
|
1542 | 0 | struct json *mr0; |
1543 | 0 | if (json_array_size(mr) == 0) { |
1544 | 0 | mr0 = json_object_create(); |
1545 | 0 | json_object_put(mr0, "columns", json_array_create_empty()); |
1546 | 0 | json_array_add(mr, mr0); |
1547 | 0 | } else { |
1548 | 0 | mr0 = CONST_CAST(struct json *, json_array_at(mr, 0)); |
1549 | 0 | } |
1550 | 0 | ovs_assert(mr0->type == JSON_OBJECT); |
1551 | |
|
1552 | 0 | json_object_put(mr0, "where", |
1553 | 0 | json_clone(table->ack_cond)); |
1554 | 0 | } |
1555 | 0 | } |
1556 | 0 | } |
1557 | |
|
1558 | 0 | const char *method = (version == 1 ? "monitor" |
1559 | 0 | : version == 2 ? "monitor_cond" |
1560 | 0 | : "monitor_cond_since"); |
1561 | 0 | struct json *params = json_array_create_3( |
1562 | 0 | json_string_create(db->db_name), |
1563 | 0 | json_clone(db->monitor_id), |
1564 | 0 | mrs); |
1565 | 0 | if (version == 3) { |
1566 | 0 | json_array_add(params, json_string_create_uuid(&db->last_id)); |
1567 | 0 | } |
1568 | 0 | ovsdb_cs_send_request(cs, jsonrpc_create_request(method, params, NULL)); |
1569 | 0 | } |
1570 | | |
1571 | | static void |
1572 | | log_parse_update_error(struct ovsdb_error *error) |
1573 | 0 | { |
1574 | 0 | if (!VLOG_DROP_WARN(&syntax_rl)) { |
1575 | 0 | char *s = ovsdb_error_to_string(error); |
1576 | 0 | VLOG_WARN_RL(&syntax_rl, "%s", s); |
1577 | 0 | free(s); |
1578 | 0 | } |
1579 | 0 | ovsdb_error_destroy(error); |
1580 | 0 | } |
1581 | | |
1582 | | static void |
1583 | | ovsdb_cs_db_add_update(struct ovsdb_cs_db *db, |
1584 | | const struct json *table_updates, int version, |
1585 | | bool clear, bool monitor_reply) |
1586 | 0 | { |
1587 | 0 | struct ovsdb_cs_event *event = ovsdb_cs_db_add_event( |
1588 | 0 | db, OVSDB_CS_EVENT_TYPE_UPDATE); |
1589 | 0 | event->update = (struct ovsdb_cs_update_event) { |
1590 | 0 | .table_updates = json_clone(table_updates), |
1591 | 0 | .clear = clear, |
1592 | 0 | .monitor_reply = monitor_reply, |
1593 | 0 | .version = version, |
1594 | 0 | .last_id = db->last_id, |
1595 | 0 | }; |
1596 | 0 | } |
1597 | | |
1598 | | static void |
1599 | | ovsdb_cs_db_parse_monitor_reply(struct ovsdb_cs_db *db, |
1600 | | const struct json *result, int version) |
1601 | 0 | { |
1602 | 0 | const struct json *table_updates; |
1603 | 0 | bool clear; |
1604 | 0 | if (version == 3) { |
1605 | 0 | if (result->type != JSON_ARRAY || json_array_size(result) != 3 |
1606 | 0 | || (json_array_at(result, 0)->type != JSON_TRUE && |
1607 | 0 | json_array_at(result, 0)->type != JSON_FALSE) |
1608 | 0 | || json_array_at(result, 1)->type != JSON_STRING |
1609 | 0 | || !uuid_from_string(&db->last_id, |
1610 | 0 | json_string(json_array_at(result, 1)))) { |
1611 | 0 | struct ovsdb_error *error = ovsdb_syntax_error( |
1612 | 0 | result, NULL, "bad monitor_cond_since reply format"); |
1613 | 0 | log_parse_update_error(error); |
1614 | 0 | return; |
1615 | 0 | } |
1616 | | |
1617 | 0 | bool found = json_boolean(json_array_at(result, 0)); |
1618 | 0 | clear = !found; |
1619 | 0 | table_updates = json_array_at(result, 2); |
1620 | 0 | } else { |
1621 | 0 | clear = true; |
1622 | 0 | table_updates = result; |
1623 | 0 | } |
1624 | | |
1625 | 0 | ovsdb_cs_db_add_update(db, table_updates, version, clear, true); |
1626 | 0 | } |
1627 | | |
1628 | | static bool |
1629 | | ovsdb_cs_db_parse_update_rpc(struct ovsdb_cs_db *db, |
1630 | | const struct jsonrpc_msg *msg) |
1631 | 0 | { |
1632 | 0 | if (msg->type != JSONRPC_NOTIFY) { |
1633 | 0 | return false; |
1634 | 0 | } |
1635 | | |
1636 | 0 | int version = (!strcmp(msg->method, "update") ? 1 |
1637 | 0 | : !strcmp(msg->method, "update2") ? 2 |
1638 | 0 | : !strcmp(msg->method, "update3") ? 3 |
1639 | 0 | : 0); |
1640 | 0 | if (!version) { |
1641 | 0 | return false; |
1642 | 0 | } |
1643 | | |
1644 | 0 | struct json *params = msg->params; |
1645 | 0 | int n = version == 3 ? 3 : 2; |
1646 | 0 | if (params->type != JSON_ARRAY || json_array_size(params) != n) { |
1647 | 0 | struct ovsdb_error *error = ovsdb_syntax_error( |
1648 | 0 | params, NULL, "%s must be an array with %u elements.", |
1649 | 0 | msg->method, n); |
1650 | 0 | log_parse_update_error(error); |
1651 | 0 | return false; |
1652 | 0 | } |
1653 | | |
1654 | 0 | if (!json_equal(json_array_at(params, 0), db->monitor_id)) { |
1655 | 0 | return false; |
1656 | 0 | } |
1657 | | |
1658 | 0 | if (version == 3) { |
1659 | 0 | const char *last_id = json_string(json_array_at(params, 1)); |
1660 | 0 | if (!uuid_from_string(&db->last_id, last_id)) { |
1661 | 0 | struct ovsdb_error *error = ovsdb_syntax_error( |
1662 | 0 | params, NULL, "Last-id %s is not in UUID format.", last_id); |
1663 | 0 | log_parse_update_error(error); |
1664 | 0 | return false; |
1665 | 0 | } |
1666 | 0 | } |
1667 | | |
1668 | 0 | const struct json *table_updates = json_array_at(params, |
1669 | 0 | version == 3 ? 2 : 1); |
1670 | 0 | ovsdb_cs_db_add_update(db, table_updates, version, false, false); |
1671 | 0 | return true; |
1672 | 0 | } |
1673 | | |
1674 | | static bool |
1675 | | ovsdb_cs_handle_monitor_canceled(struct ovsdb_cs *cs, |
1676 | | struct ovsdb_cs_db *db, |
1677 | | const struct jsonrpc_msg *msg) |
1678 | 0 | { |
1679 | 0 | if (msg->type != JSONRPC_NOTIFY |
1680 | 0 | || strcmp(msg->method, "monitor_canceled") |
1681 | 0 | || msg->params->type != JSON_ARRAY |
1682 | 0 | || json_array_size(msg->params) != 1 |
1683 | 0 | || !json_equal(json_array_at(msg->params, 0), db->monitor_id)) { |
1684 | 0 | return false; |
1685 | 0 | } |
1686 | | |
1687 | 0 | db->monitor_version = 0; |
1688 | | |
1689 | | /* Cancel the other monitor and restart the FSM from the top. |
1690 | | * |
1691 | | * Maybe a more sophisticated response would be better in some cases, but |
1692 | | * it doesn't seem worth optimizing yet. (Although this is already more |
1693 | | * sophisticated than just dropping the connection and reconnecting.) */ |
1694 | 0 | struct ovsdb_cs_db *other_db |
1695 | 0 | = db == &cs->data ? &cs->server : &cs->data; |
1696 | 0 | if (other_db->monitor_version) { |
1697 | 0 | jsonrpc_session_send( |
1698 | 0 | cs->session, |
1699 | 0 | jsonrpc_create_request( |
1700 | 0 | "monitor_cancel", |
1701 | 0 | json_array_create_1(json_clone(other_db->monitor_id)), NULL)); |
1702 | 0 | other_db->monitor_version = 0; |
1703 | 0 | } |
1704 | 0 | ovsdb_cs_restart_fsm(cs); |
1705 | |
|
1706 | 0 | return true; |
1707 | 0 | } |
1708 | | |
1709 | | /* The _Server database. |
1710 | | * |
1711 | | * We replicate the Database table in the _Server database because this is the |
1712 | | * only way to find out properties we need to know for clustering, such as |
1713 | | * whether a database is clustered at all and whether this server is the |
1714 | | * leader. |
1715 | | * |
1716 | | * This code implements a kind of simple IDL-like layer. */ |
1717 | | |
1718 | | struct server_column { |
1719 | | const char *name; |
1720 | | struct ovsdb_type type; |
1721 | | }; |
1722 | | enum server_column_index { |
1723 | | COL_NAME, |
1724 | | COL_MODEL, |
1725 | | COL_CONNECTED, |
1726 | | COL_LEADER, |
1727 | | COL_SCHEMA, |
1728 | | COL_CID, |
1729 | | COL_INDEX, |
1730 | | }; |
1731 | | #define OPTIONAL_COLUMN(TYPE) \ |
1732 | | { \ |
1733 | | .key = OVSDB_BASE_##TYPE##_INIT, \ |
1734 | | .value = OVSDB_BASE_VOID_INIT, \ |
1735 | | .n_min = 0, \ |
1736 | | .n_max = 1 \ |
1737 | | } |
1738 | | static const struct server_column server_columns[] = { |
1739 | | [COL_NAME] = {"name", OPTIONAL_COLUMN(STRING) }, |
1740 | | [COL_MODEL] = {"model", OPTIONAL_COLUMN(STRING) }, |
1741 | | [COL_CONNECTED] = {"connected", OPTIONAL_COLUMN(BOOLEAN) }, |
1742 | | [COL_LEADER] = {"leader", OPTIONAL_COLUMN(BOOLEAN) }, |
1743 | | [COL_SCHEMA] = {"schema", OPTIONAL_COLUMN(STRING) }, |
1744 | | [COL_CID] = {"cid", OPTIONAL_COLUMN(UUID) }, |
1745 | | [COL_INDEX] = {"index", OPTIONAL_COLUMN(INTEGER) }, |
1746 | | }; |
1747 | 0 | #define N_SERVER_COLUMNS ARRAY_SIZE(server_columns) |
1748 | | struct server_row { |
1749 | | struct hmap_node hmap_node; |
1750 | | struct uuid uuid; |
1751 | | struct ovsdb_datum data[N_SERVER_COLUMNS]; |
1752 | | }; |
1753 | | |
1754 | | static void |
1755 | | server_row_destroy(struct server_row *row) |
1756 | 0 | { |
1757 | 0 | if (row) { |
1758 | 0 | for (size_t i = 0; i < N_SERVER_COLUMNS; i++) { |
1759 | 0 | ovsdb_datum_destroy(&row->data[i], &server_columns[i].type); |
1760 | 0 | } |
1761 | 0 | free(row); |
1762 | 0 | } |
1763 | 0 | } |
1764 | | |
1765 | | static struct server_row * |
1766 | | ovsdb_cs_find_server_row(struct ovsdb_cs *cs, const struct uuid *uuid) |
1767 | 0 | { |
1768 | 0 | struct server_row *row; |
1769 | 0 | HMAP_FOR_EACH (row, hmap_node, &cs->server_rows) { |
1770 | 0 | if (uuid_equals(uuid, &row->uuid)) { |
1771 | 0 | return row; |
1772 | 0 | } |
1773 | 0 | } |
1774 | 0 | return NULL; |
1775 | 0 | } |
1776 | | |
1777 | | static void |
1778 | | ovsdb_cs_delete_server_row(struct ovsdb_cs *cs, struct server_row *row) |
1779 | 0 | { |
1780 | 0 | hmap_remove(&cs->server_rows, &row->hmap_node); |
1781 | 0 | server_row_destroy(row); |
1782 | 0 | } |
1783 | | |
1784 | | static struct server_row * |
1785 | | ovsdb_cs_insert_server_row(struct ovsdb_cs *cs, const struct uuid *uuid) |
1786 | 0 | { |
1787 | 0 | struct server_row *row = xmalloc(sizeof *row); |
1788 | 0 | hmap_insert(&cs->server_rows, &row->hmap_node, uuid_hash(uuid)); |
1789 | 0 | row->uuid = *uuid; |
1790 | 0 | for (size_t i = 0; i < N_SERVER_COLUMNS; i++) { |
1791 | 0 | ovsdb_datum_init_default(&row->data[i], &server_columns[i].type); |
1792 | 0 | } |
1793 | 0 | return row; |
1794 | 0 | } |
1795 | | |
1796 | | static void |
1797 | | ovsdb_cs_update_server_row(struct server_row *row, |
1798 | | const struct shash *update, bool xor) |
1799 | 0 | { |
1800 | 0 | for (size_t i = 0; i < N_SERVER_COLUMNS; i++) { |
1801 | 0 | const struct server_column *column = &server_columns[i]; |
1802 | 0 | struct shash_node *node = shash_find(update, column->name); |
1803 | 0 | if (!node) { |
1804 | 0 | continue; |
1805 | 0 | } |
1806 | 0 | const struct json *json = node->data; |
1807 | |
|
1808 | 0 | struct ovsdb_datum *old = &row->data[i]; |
1809 | 0 | struct ovsdb_datum new; |
1810 | 0 | if (!xor) { |
1811 | 0 | struct ovsdb_error *error = ovsdb_datum_from_json( |
1812 | 0 | &new, &column->type, json, NULL); |
1813 | 0 | if (error) { |
1814 | 0 | ovsdb_error_destroy(error); |
1815 | 0 | continue; |
1816 | 0 | } |
1817 | 0 | } else { |
1818 | 0 | struct ovsdb_datum diff; |
1819 | 0 | struct ovsdb_error *error = ovsdb_transient_datum_from_json( |
1820 | 0 | &diff, &column->type, json); |
1821 | 0 | if (error) { |
1822 | 0 | ovsdb_error_destroy(error); |
1823 | 0 | continue; |
1824 | 0 | } |
1825 | | |
1826 | 0 | error = ovsdb_datum_apply_diff(&new, old, &diff, &column->type); |
1827 | 0 | if (error) { |
1828 | 0 | ovsdb_error_destroy(error); |
1829 | 0 | ovsdb_datum_destroy(&new, &column->type); |
1830 | 0 | continue; |
1831 | 0 | } |
1832 | 0 | ovsdb_datum_destroy(&diff, &column->type); |
1833 | 0 | } |
1834 | | |
1835 | 0 | ovsdb_datum_destroy(&row->data[i], &column->type); |
1836 | 0 | row->data[i] = new; |
1837 | 0 | } |
1838 | 0 | } |
1839 | | |
1840 | | static void |
1841 | | ovsdb_cs_clear_server_rows(struct ovsdb_cs *cs) |
1842 | 0 | { |
1843 | 0 | struct server_row *row; |
1844 | 0 | HMAP_FOR_EACH_SAFE (row, hmap_node, &cs->server_rows) { |
1845 | 0 | ovsdb_cs_delete_server_row(cs, row); |
1846 | 0 | } |
1847 | 0 | } |
1848 | | |
1849 | | static void log_parse_update_error(struct ovsdb_error *); |
1850 | | |
1851 | | static void |
1852 | | ovsdb_cs_process_server_event(struct ovsdb_cs *cs, |
1853 | | const struct ovsdb_cs_event *event) |
1854 | 0 | { |
1855 | 0 | ovs_assert(event->type == OVSDB_CS_EVENT_TYPE_UPDATE); |
1856 | |
|
1857 | 0 | const struct ovsdb_cs_update_event *update = &event->update; |
1858 | 0 | struct ovsdb_cs_db_update *du; |
1859 | 0 | struct ovsdb_error *error = ovsdb_cs_parse_db_update( |
1860 | 0 | update->table_updates, update->version, &du); |
1861 | 0 | if (error) { |
1862 | 0 | log_parse_update_error(error); |
1863 | 0 | return; |
1864 | 0 | } |
1865 | | |
1866 | 0 | if (update->clear) { |
1867 | 0 | ovsdb_cs_clear_server_rows(cs); |
1868 | 0 | } |
1869 | |
|
1870 | 0 | const struct ovsdb_cs_table_update *tu = ovsdb_cs_db_update_find_table( |
1871 | 0 | du, "Database"); |
1872 | 0 | if (tu) { |
1873 | 0 | for (size_t i = 0; i < tu->n; i++) { |
1874 | 0 | const struct ovsdb_cs_row_update *ru = &tu->row_updates[i]; |
1875 | 0 | struct server_row *row |
1876 | 0 | = ovsdb_cs_find_server_row(cs, &ru->row_uuid); |
1877 | 0 | if (ru->type == OVSDB_CS_ROW_DELETE) { |
1878 | 0 | ovsdb_cs_delete_server_row(cs, row); |
1879 | 0 | } else { |
1880 | 0 | if (!row) { |
1881 | 0 | row = ovsdb_cs_insert_server_row(cs, &ru->row_uuid); |
1882 | 0 | } |
1883 | 0 | ovsdb_cs_update_server_row(row, ru->columns, |
1884 | 0 | ru->type == OVSDB_CS_ROW_XOR); |
1885 | 0 | } |
1886 | 0 | } |
1887 | 0 | } |
1888 | |
|
1889 | 0 | ovsdb_cs_db_update_destroy(du); |
1890 | 0 | } |
1891 | | |
1892 | | static const char * |
1893 | | server_column_get_string(const struct server_row *row, |
1894 | | enum server_column_index index, |
1895 | | const char *default_value) |
1896 | 0 | { |
1897 | 0 | ovs_assert(server_columns[index].type.key.type == OVSDB_TYPE_STRING); |
1898 | 0 | const struct ovsdb_datum *d = &row->data[index]; |
1899 | 0 | return d->n == 1 ? json_string(d->keys[0].s) : default_value; |
1900 | 0 | } |
1901 | | |
1902 | | static bool |
1903 | | server_column_get_bool(const struct server_row *row, |
1904 | | enum server_column_index index, |
1905 | | bool default_value) |
1906 | 0 | { |
1907 | 0 | ovs_assert(server_columns[index].type.key.type == OVSDB_TYPE_BOOLEAN); |
1908 | 0 | const struct ovsdb_datum *d = &row->data[index]; |
1909 | 0 | return d->n == 1 ? d->keys[0].boolean : default_value; |
1910 | 0 | } |
1911 | | |
1912 | | static uint64_t |
1913 | | server_column_get_int(const struct server_row *row, |
1914 | | enum server_column_index index, |
1915 | | uint64_t default_value) |
1916 | 0 | { |
1917 | 0 | ovs_assert(server_columns[index].type.key.type == OVSDB_TYPE_INTEGER); |
1918 | 0 | const struct ovsdb_datum *d = &row->data[index]; |
1919 | 0 | return d->n == 1 ? d->keys[0].integer : default_value; |
1920 | 0 | } |
1921 | | |
1922 | | static const struct uuid * |
1923 | | server_column_get_uuid(const struct server_row *row, |
1924 | | enum server_column_index index, |
1925 | | const struct uuid *default_value) |
1926 | 0 | { |
1927 | 0 | ovs_assert(server_columns[index].type.key.type == OVSDB_TYPE_UUID); |
1928 | 0 | const struct ovsdb_datum *d = &row->data[index]; |
1929 | 0 | return d->n == 1 ? &d->keys[0].uuid : default_value; |
1930 | 0 | } |
1931 | | |
1932 | | static const struct server_row * |
1933 | | ovsdb_find_server_row(struct ovsdb_cs *cs) |
1934 | 0 | { |
1935 | 0 | const struct server_row *row; |
1936 | 0 | HMAP_FOR_EACH (row, hmap_node, &cs->server_rows) { |
1937 | 0 | const struct uuid *cid = server_column_get_uuid(row, COL_CID, NULL); |
1938 | 0 | const char *name = server_column_get_string(row, COL_NAME, NULL); |
1939 | 0 | if (uuid_is_zero(&cs->cid) |
1940 | 0 | ? (name && !strcmp(cs->data.db_name, name)) |
1941 | 0 | : (cid && uuid_equals(cid, &cs->cid))) { |
1942 | 0 | return row; |
1943 | 0 | } |
1944 | 0 | } |
1945 | 0 | return NULL; |
1946 | 0 | } |
1947 | | |
1948 | | static void OVS_UNUSED |
1949 | | ovsdb_log_server_rows(const struct ovsdb_cs *cs) |
1950 | 0 | { |
1951 | 0 | int row_num = 0; |
1952 | 0 | const struct server_row *row; |
1953 | 0 | HMAP_FOR_EACH (row, hmap_node, &cs->server_rows) { |
1954 | 0 | struct ds s = DS_EMPTY_INITIALIZER; |
1955 | 0 | for (size_t i = 0; i < N_SERVER_COLUMNS; i++) { |
1956 | 0 | ds_put_format(&s, " %s=", server_columns[i].name); |
1957 | 0 | if (i == COL_SCHEMA) { |
1958 | 0 | ds_put_format(&s, "..."); |
1959 | 0 | } else { |
1960 | 0 | ovsdb_datum_to_string(&row->data[i], &server_columns[i].type, |
1961 | 0 | &s); |
1962 | 0 | } |
1963 | 0 | } |
1964 | 0 | VLOG_INFO("row %d:%s", row_num++, ds_cstr(&s)); |
1965 | 0 | ds_destroy(&s); |
1966 | 0 | } |
1967 | 0 | } |
1968 | | |
1969 | | static bool |
1970 | | ovsdb_cs_check_server_db__(struct ovsdb_cs *cs) |
1971 | 0 | { |
1972 | 0 | struct ovsdb_cs_event *event; |
1973 | 0 | LIST_FOR_EACH_POP (event, list_node, &cs->server.events) { |
1974 | 0 | ovsdb_cs_process_server_event(cs, event); |
1975 | 0 | ovsdb_cs_event_destroy(event); |
1976 | 0 | } |
1977 | |
|
1978 | 0 | const struct server_row *db_row = ovsdb_find_server_row(cs); |
1979 | 0 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); |
1980 | 0 | const char *server_name = jsonrpc_session_get_name(cs->session); |
1981 | 0 | if (!db_row) { |
1982 | 0 | VLOG_INFO_RL(&rl, "%s: server does not have %s database", |
1983 | 0 | server_name, cs->data.db_name); |
1984 | 0 | return false; |
1985 | 0 | } |
1986 | | |
1987 | 0 | bool ok = false; |
1988 | 0 | const char *model = server_column_get_string(db_row, COL_MODEL, ""); |
1989 | 0 | const char *schema = server_column_get_string(db_row, COL_SCHEMA, NULL); |
1990 | 0 | bool connected = server_column_get_bool(db_row, COL_CONNECTED, false); |
1991 | 0 | if (!strcmp(model, "clustered")) { |
1992 | 0 | bool leader = server_column_get_bool(db_row, COL_LEADER, false); |
1993 | 0 | uint64_t index = server_column_get_int(db_row, COL_INDEX, 0); |
1994 | |
|
1995 | 0 | if (!schema) { |
1996 | 0 | VLOG_INFO("%s: clustered database server has not yet joined " |
1997 | 0 | "cluster; trying another server", server_name); |
1998 | 0 | } else if (!connected) { |
1999 | 0 | VLOG_INFO("%s: clustered database server is disconnected " |
2000 | 0 | "from cluster; trying another server", server_name); |
2001 | 0 | } else if (cs->leader_only && !leader) { |
2002 | 0 | VLOG_INFO("%s: clustered database server is not cluster " |
2003 | 0 | "leader; trying another server", server_name); |
2004 | 0 | } else if (index < cs->min_index) { |
2005 | 0 | VLOG_WARN("%s: clustered database server has stale data; " |
2006 | 0 | "trying another server", server_name); |
2007 | 0 | } else { |
2008 | 0 | cs->min_index = index; |
2009 | 0 | ok = true; |
2010 | 0 | } |
2011 | 0 | } else if (!strcmp(model, "relay")) { |
2012 | 0 | if (!schema) { |
2013 | 0 | VLOG_INFO("%s: relay database server has not yet connected to the " |
2014 | 0 | "relay source; trying another server", server_name); |
2015 | 0 | } else if (!connected) { |
2016 | 0 | VLOG_INFO("%s: relay database server is disconnected from the " |
2017 | 0 | "relay source; trying another server", server_name); |
2018 | 0 | } else if (cs->leader_only) { |
2019 | 0 | VLOG_INFO("%s: relay database server cannot be a leader; " |
2020 | 0 | "trying another server", server_name); |
2021 | 0 | } else { |
2022 | 0 | ok = true; |
2023 | 0 | } |
2024 | 0 | } else { |
2025 | 0 | if (!schema) { |
2026 | 0 | VLOG_INFO("%s: missing database schema", server_name); |
2027 | 0 | } else { |
2028 | 0 | ok = true; |
2029 | 0 | } |
2030 | 0 | } |
2031 | 0 | if (!ok) { |
2032 | 0 | return false; |
2033 | 0 | } |
2034 | | |
2035 | 0 | if (cs->state == CS_S_SERVER_MONITOR_REQUESTED) { |
2036 | 0 | json_destroy(cs->data.schema); |
2037 | 0 | cs->data.schema = json_from_string(schema); |
2038 | 0 | if (cs->data.max_version >= 3) { |
2039 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 3); |
2040 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_COND_SINCE_REQUESTED); |
2041 | 0 | } else if (cs->data.max_version >= 2) { |
2042 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 2); |
2043 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_COND_REQUESTED); |
2044 | 0 | } else { |
2045 | 0 | ovsdb_cs_send_monitor_request(cs, &cs->data, 1); |
2046 | 0 | ovsdb_cs_transition(cs, CS_S_DATA_MONITOR_REQUESTED); |
2047 | 0 | } |
2048 | 0 | } |
2049 | 0 | return true; |
2050 | 0 | } |
2051 | | |
2052 | | static bool |
2053 | | ovsdb_cs_check_server_db(struct ovsdb_cs *cs) |
2054 | 0 | { |
2055 | 0 | bool ok = ovsdb_cs_check_server_db__(cs); |
2056 | 0 | if (!ok) { |
2057 | 0 | ovsdb_cs_retry(cs); |
2058 | 0 | } |
2059 | 0 | return ok; |
2060 | 0 | } |
2061 | | |
2062 | | static struct json * |
2063 | | ovsdb_cs_compose_server_monitor_request(const struct json *schema_json, |
2064 | | void *cs_) |
2065 | 0 | { |
2066 | 0 | struct ovsdb_cs *cs = cs_; |
2067 | 0 | struct shash *schema = ovsdb_cs_parse_schema(schema_json); |
2068 | 0 | struct json *monitor_requests = json_object_create(); |
2069 | |
|
2070 | 0 | const char *table_name = "Database"; |
2071 | 0 | const struct shash *table_schema |
2072 | 0 | = schema ? shash_find_data(schema, table_name) : NULL; |
2073 | 0 | if (!table_schema) { |
2074 | 0 | VLOG_WARN("%s database lacks %s table " |
2075 | 0 | "(database needs upgrade?)", |
2076 | 0 | cs->server.db_name, table_name); |
2077 | | /* XXX return failure? */ |
2078 | 0 | } else { |
2079 | 0 | struct json *columns = json_array_create_empty(); |
2080 | 0 | for (size_t j = 0; j < N_SERVER_COLUMNS; j++) { |
2081 | 0 | const struct server_column *column = &server_columns[j]; |
2082 | 0 | bool db_has_column = (table_schema && |
2083 | 0 | shash_find(table_schema, column->name)); |
2084 | 0 | if (table_schema && !db_has_column) { |
2085 | 0 | VLOG_WARN("%s table in %s database lacks %s column " |
2086 | 0 | "(database needs upgrade?)", |
2087 | 0 | table_name, cs->server.db_name, column->name); |
2088 | 0 | continue; |
2089 | 0 | } |
2090 | 0 | json_array_add(columns, json_string_create(column->name)); |
2091 | 0 | } |
2092 | |
|
2093 | 0 | struct json *monitor_request = json_object_create(); |
2094 | 0 | json_object_put(monitor_request, "columns", columns); |
2095 | 0 | json_object_put(monitor_requests, table_name, |
2096 | 0 | json_array_create_1(monitor_request)); |
2097 | 0 | } |
2098 | 0 | ovsdb_cs_free_schema(schema); |
2099 | |
|
2100 | 0 | return monitor_requests; |
2101 | 0 | } |
2102 | | |
2103 | | static const struct ovsdb_cs_ops ovsdb_cs_server_ops = { |
2104 | | ovsdb_cs_compose_server_monitor_request |
2105 | | }; |
2106 | | |
2107 | | static void |
2108 | | log_error(struct ovsdb_error *error) |
2109 | 0 | { |
2110 | 0 | char *s = ovsdb_error_to_string_free(error); |
2111 | 0 | VLOG_WARN("error parsing database schema: %s", s); |
2112 | 0 | free(s); |
2113 | 0 | } |
2114 | | |
2115 | | /* Parses 'schema_json', an OVSDB schema in JSON format as described in RFC |
2116 | | * 7047, to obtain the names of its rows and columns. If successful, returns |
2117 | | * an shash whose keys are table names and whose values are shashes, where each |
2118 | | * shash contains the names of its table's columns as keys and an ovsdb_type as |
2119 | | * data. On failure (due to a parse error), returns NULL. |
2120 | | * |
2121 | | * It would also be possible to use the general-purpose OVSDB schema parser in |
2122 | | * ovsdb-server, but that's overkill, possibly too strict for the current use |
2123 | | * case, and would require restructuring ovsdb-server to separate the schema |
2124 | | * code from the rest. */ |
2125 | | struct shash * |
2126 | | ovsdb_cs_parse_schema(const struct json *schema_json) |
2127 | 0 | { |
2128 | 0 | struct ovsdb_parser parser; |
2129 | 0 | const struct json *tables_json; |
2130 | 0 | struct shash *schema = NULL; |
2131 | 0 | struct ovsdb_error *error; |
2132 | 0 | struct shash_node *node; |
2133 | |
|
2134 | 0 | ovsdb_parser_init(&parser, schema_json, "database schema"); |
2135 | 0 | tables_json = ovsdb_parser_member(&parser, "tables", OP_OBJECT); |
2136 | 0 | error = ovsdb_parser_destroy(&parser); |
2137 | 0 | if (error) { |
2138 | 0 | log_error(error); |
2139 | 0 | return NULL; |
2140 | 0 | } |
2141 | | |
2142 | 0 | schema = xmalloc(sizeof *schema); |
2143 | 0 | shash_init(schema); |
2144 | 0 | SHASH_FOR_EACH (node, json_object(tables_json)) { |
2145 | 0 | const char *table_name = node->name; |
2146 | 0 | const struct json *json = node->data; |
2147 | 0 | const struct json *columns_json; |
2148 | |
|
2149 | 0 | ovsdb_parser_init(&parser, json, "table schema for table %s", |
2150 | 0 | table_name); |
2151 | 0 | columns_json = ovsdb_parser_member(&parser, "columns", OP_OBJECT); |
2152 | 0 | error = ovsdb_parser_destroy(&parser); |
2153 | 0 | if (error) { |
2154 | 0 | goto error; |
2155 | 0 | } |
2156 | | |
2157 | 0 | struct shash *columns = xmalloc(sizeof *columns); |
2158 | 0 | shash_init(columns); |
2159 | 0 | shash_add(schema, table_name, columns); |
2160 | |
|
2161 | 0 | struct shash_node *node2; |
2162 | 0 | SHASH_FOR_EACH (node2, json_object(columns_json)) { |
2163 | 0 | const struct json *column_json = node2->data; |
2164 | 0 | const char *column_name = node2->name; |
2165 | 0 | const struct json *type_json; |
2166 | 0 | struct ovsdb_type *type; |
2167 | |
|
2168 | 0 | ovsdb_parser_init(&parser, column_json, |
2169 | 0 | "schema for column %s", column_name); |
2170 | 0 | type_json = ovsdb_parser_member(&parser, "type", |
2171 | 0 | OP_STRING | OP_OBJECT); |
2172 | 0 | error = ovsdb_parser_destroy(&parser); |
2173 | 0 | if (error) { |
2174 | 0 | goto error; |
2175 | 0 | } |
2176 | | |
2177 | 0 | type = xzalloc(sizeof *type); |
2178 | 0 | error = ovsdb_type_from_json(type, type_json); |
2179 | 0 | if (error) { |
2180 | 0 | free(type); |
2181 | 0 | goto error; |
2182 | 0 | } |
2183 | | |
2184 | 0 | if (!shash_add_once(columns, column_name, type)) { |
2185 | | /* Should never happen, but just in case. */ |
2186 | 0 | ovsdb_type_destroy(type); |
2187 | 0 | free(type); |
2188 | 0 | } |
2189 | 0 | } |
2190 | 0 | } |
2191 | 0 | return schema; |
2192 | | |
2193 | 0 | error: |
2194 | 0 | log_error(error); |
2195 | 0 | ovsdb_cs_free_schema(schema); |
2196 | 0 | return NULL; |
2197 | 0 | } |
2198 | | |
2199 | | /* Frees 'schema', which is in the format returned by |
2200 | | * ovsdb_cs_parse_schema(). */ |
2201 | | void |
2202 | | ovsdb_cs_free_schema(struct shash *schema) |
2203 | 0 | { |
2204 | 0 | if (schema) { |
2205 | 0 | struct shash_node *node; |
2206 | |
|
2207 | 0 | SHASH_FOR_EACH_SAFE (node, schema) { |
2208 | 0 | struct shash *columns = node->data; |
2209 | 0 | struct shash_node *node2; |
2210 | |
|
2211 | 0 | SHASH_FOR_EACH (node2, columns) { |
2212 | 0 | ovsdb_type_destroy(node2->data); |
2213 | 0 | } |
2214 | 0 | shash_destroy_free_data(columns); |
2215 | 0 | free(columns); |
2216 | |
|
2217 | 0 | shash_delete(schema, node); |
2218 | 0 | } |
2219 | 0 | shash_destroy(schema); |
2220 | 0 | free(schema); |
2221 | 0 | } |
2222 | 0 | } |
2223 | | |
2224 | | static struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
2225 | | ovsdb_cs_parse_row_update1(const struct json *in, |
2226 | | struct ovsdb_cs_row_update *out) |
2227 | 0 | { |
2228 | 0 | const struct json *old_json, *new_json; |
2229 | |
|
2230 | 0 | old_json = shash_find_data(json_object(in), "old"); |
2231 | 0 | new_json = shash_find_data(json_object(in), "new"); |
2232 | 0 | if (old_json && old_json->type != JSON_OBJECT) { |
2233 | 0 | return ovsdb_syntax_error(old_json, NULL, |
2234 | 0 | "\"old\" <row> is not object"); |
2235 | 0 | } else if (new_json && new_json->type != JSON_OBJECT) { |
2236 | 0 | return ovsdb_syntax_error(new_json, NULL, |
2237 | 0 | "\"new\" <row> is not object"); |
2238 | 0 | } else if ((old_json != NULL) + (new_json != NULL) |
2239 | 0 | != shash_count(json_object(in))) { |
2240 | 0 | return ovsdb_syntax_error(in, NULL, |
2241 | 0 | "<row-update> contains " |
2242 | 0 | "unexpected member"); |
2243 | 0 | } else if (!old_json && !new_json) { |
2244 | 0 | return ovsdb_syntax_error(in, NULL, |
2245 | 0 | "<row-update> missing \"old\" " |
2246 | 0 | "and \"new\" members"); |
2247 | 0 | } |
2248 | | |
2249 | 0 | if (!new_json) { |
2250 | 0 | out->type = OVSDB_CS_ROW_DELETE; |
2251 | 0 | out->columns = json_object(old_json); |
2252 | 0 | } else if (!old_json) { |
2253 | 0 | out->type = OVSDB_CS_ROW_INSERT; |
2254 | 0 | out->columns = json_object(new_json); |
2255 | 0 | } else { |
2256 | 0 | out->type = OVSDB_CS_ROW_UPDATE; |
2257 | 0 | out->columns = json_object(new_json); |
2258 | 0 | } |
2259 | 0 | return NULL; |
2260 | 0 | } |
2261 | | |
2262 | | static struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
2263 | | ovsdb_cs_parse_row_update2(const struct json *in, |
2264 | | struct ovsdb_cs_row_update *out) |
2265 | 0 | { |
2266 | 0 | const struct shash *object = json_object(in); |
2267 | 0 | if (shash_count(object) != 1) { |
2268 | 0 | return ovsdb_syntax_error( |
2269 | 0 | in, NULL, "<row-update2> has %"PRIuSIZE" members " |
2270 | 0 | "instead of expected 1", shash_count(object)); |
2271 | 0 | } |
2272 | | |
2273 | 0 | struct shash_node *node = shash_first(object); |
2274 | 0 | const struct json *columns = node->data; |
2275 | 0 | if (!strcmp(node->name, "insert") || !strcmp(node->name, "initial")) { |
2276 | 0 | out->type = OVSDB_CS_ROW_INSERT; |
2277 | 0 | } else if (!strcmp(node->name, "modify")) { |
2278 | 0 | out->type = OVSDB_CS_ROW_XOR; |
2279 | 0 | } else if (!strcmp(node->name, "delete")) { |
2280 | 0 | out->type = OVSDB_CS_ROW_DELETE; |
2281 | 0 | if (columns->type != JSON_NULL) { |
2282 | 0 | return ovsdb_syntax_error( |
2283 | 0 | in, NULL, |
2284 | 0 | "<row-update2> delete operation has unexpected value"); |
2285 | 0 | } |
2286 | 0 | return NULL; |
2287 | 0 | } else { |
2288 | 0 | return ovsdb_syntax_error(in, NULL, |
2289 | 0 | "<row-update2> has unknown member \"%s\"", |
2290 | 0 | node->name); |
2291 | 0 | } |
2292 | | |
2293 | 0 | if (columns->type != JSON_OBJECT) { |
2294 | 0 | return ovsdb_syntax_error( |
2295 | 0 | in, NULL, |
2296 | 0 | "<row-update2> \"%s\" operation has unexpected value", |
2297 | 0 | node->name); |
2298 | 0 | } |
2299 | 0 | out->columns = json_object(columns); |
2300 | |
|
2301 | 0 | return NULL; |
2302 | 0 | } |
2303 | | |
2304 | | static struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
2305 | | ovsdb_cs_parse_row_update(const char *table_name, |
2306 | | const struct json *in, int version, |
2307 | | struct ovsdb_cs_row_update *out) |
2308 | 0 | { |
2309 | 0 | if (in->type != JSON_OBJECT) { |
2310 | 0 | const char *suffix = version > 1 ? "2" : ""; |
2311 | 0 | return ovsdb_syntax_error( |
2312 | 0 | in, NULL, |
2313 | 0 | "<table-update%s> for table \"%s\" contains <row-update%s> " |
2314 | 0 | "that is not an object", |
2315 | 0 | suffix, table_name, suffix); |
2316 | 0 | } |
2317 | | |
2318 | 0 | return (version == 1 |
2319 | 0 | ? ovsdb_cs_parse_row_update1(in, out) |
2320 | 0 | : ovsdb_cs_parse_row_update2(in, out)); |
2321 | 0 | } |
2322 | | |
2323 | | static struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
2324 | | ovsdb_cs_parse_table_update(const char *table_name, |
2325 | | const struct json *in, int version, |
2326 | | struct ovsdb_cs_table_update *out) |
2327 | 0 | { |
2328 | 0 | const char *suffix = version > 1 ? "2" : ""; |
2329 | |
|
2330 | 0 | if (in->type != JSON_OBJECT) { |
2331 | 0 | return ovsdb_syntax_error( |
2332 | 0 | in, NULL, "<table-update%s> for table \"%s\" is not an object", |
2333 | 0 | suffix, table_name); |
2334 | 0 | } |
2335 | 0 | struct shash *in_rows = json_object(in); |
2336 | |
|
2337 | 0 | out->row_updates = xmalloc(shash_count(in_rows) * sizeof *out->row_updates); |
2338 | |
|
2339 | 0 | const struct shash_node *node; |
2340 | 0 | SHASH_FOR_EACH (node, in_rows) { |
2341 | 0 | const char *row_uuid_string = node->name; |
2342 | 0 | struct uuid row_uuid; |
2343 | 0 | if (!uuid_from_string(&row_uuid, row_uuid_string)) { |
2344 | 0 | return ovsdb_syntax_error( |
2345 | 0 | in, NULL, |
2346 | 0 | "<table-update%s> for table \"%s\" contains " |
2347 | 0 | "bad UUID \"%s\" as member name", |
2348 | 0 | suffix, table_name, row_uuid_string); |
2349 | 0 | } |
2350 | | |
2351 | 0 | const struct json *in_ru = node->data; |
2352 | 0 | struct ovsdb_cs_row_update *out_ru = &out->row_updates[out->n++]; |
2353 | 0 | *out_ru = (struct ovsdb_cs_row_update) { .row_uuid = row_uuid }; |
2354 | |
|
2355 | 0 | struct ovsdb_error *error = ovsdb_cs_parse_row_update( |
2356 | 0 | table_name, in_ru, version, out_ru); |
2357 | 0 | if (error) { |
2358 | 0 | return error; |
2359 | 0 | } |
2360 | 0 | } |
2361 | | |
2362 | 0 | return NULL; |
2363 | 0 | } |
2364 | | |
2365 | | /* Parses OVSDB <table-updates> or <table-updates2> object 'in' into '*outp'. |
2366 | | * If successful, sets '*outp' to the new object and returns NULL. On failure, |
2367 | | * returns the error and sets '*outp' to NULL. |
2368 | | * |
2369 | | * On success, the caller must eventually free '*outp', with |
2370 | | * ovsdb_cs_db_update_destroy(). |
2371 | | * |
2372 | | * 'version' should be 1 if 'in' is a <table-updates>, 2 or 3 if it is a |
2373 | | * <table-updates2>. */ |
2374 | | struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
2375 | | ovsdb_cs_parse_db_update(const struct json *in, int version, |
2376 | | struct ovsdb_cs_db_update **outp) |
2377 | 0 | { |
2378 | 0 | const char *suffix = version > 1 ? "2" : ""; |
2379 | |
|
2380 | 0 | *outp = NULL; |
2381 | 0 | if (in->type != JSON_OBJECT) { |
2382 | 0 | return ovsdb_syntax_error(in, NULL, |
2383 | 0 | "<table-updates%s> is not an object", suffix); |
2384 | 0 | } |
2385 | | |
2386 | 0 | struct ovsdb_cs_db_update *out = xzalloc(sizeof *out); |
2387 | 0 | out->table_updates = xmalloc(shash_count(json_object(in)) |
2388 | 0 | * sizeof *out->table_updates); |
2389 | 0 | const struct shash_node *node; |
2390 | 0 | SHASH_FOR_EACH (node, json_object(in)) { |
2391 | 0 | const char *table_name = node->name; |
2392 | 0 | const struct json *in_tu = node->data; |
2393 | |
|
2394 | 0 | struct ovsdb_cs_table_update *out_tu = &out->table_updates[out->n++]; |
2395 | 0 | *out_tu = (struct ovsdb_cs_table_update) { .table_name = table_name }; |
2396 | |
|
2397 | 0 | struct ovsdb_error *error = ovsdb_cs_parse_table_update( |
2398 | 0 | table_name, in_tu, version, out_tu); |
2399 | 0 | if (error) { |
2400 | 0 | ovsdb_cs_db_update_destroy(out); |
2401 | 0 | return error; |
2402 | 0 | } |
2403 | 0 | } |
2404 | | |
2405 | 0 | *outp = out; |
2406 | 0 | return NULL; |
2407 | 0 | } |
2408 | | |
2409 | | /* Frees 'du', which was presumably allocated by ovsdb_cs_parse_db_update(). */ |
2410 | | void |
2411 | | ovsdb_cs_db_update_destroy(struct ovsdb_cs_db_update *du) |
2412 | 0 | { |
2413 | 0 | if (!du) { |
2414 | 0 | return; |
2415 | 0 | } |
2416 | | |
2417 | 0 | for (size_t i = 0; i < du->n; i++) { |
2418 | 0 | struct ovsdb_cs_table_update *tu = &du->table_updates[i]; |
2419 | 0 | free(tu->row_updates); |
2420 | 0 | } |
2421 | 0 | free(du->table_updates); |
2422 | 0 | free(du); |
2423 | 0 | } |
2424 | | |
2425 | | const struct ovsdb_cs_table_update * |
2426 | | ovsdb_cs_db_update_find_table(const struct ovsdb_cs_db_update *du, |
2427 | | const char *table_name) |
2428 | 0 | { |
2429 | 0 | for (size_t i = 0; i < du->n; i++) { |
2430 | 0 | const struct ovsdb_cs_table_update *tu = &du->table_updates[i]; |
2431 | 0 | if (!strcmp(tu->table_name, table_name)) { |
2432 | 0 | return tu; |
2433 | 0 | } |
2434 | 0 | } |
2435 | 0 | return NULL; |
2436 | 0 | } |
2437 | | |