]>
Commit | Line | Data |
---|---|---|
c2e3cbaf | 1 | /* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc. |
f85f8ebb BP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
18 | #include "jsonrpc-server.h" | |
19 | ||
20 | #include <errno.h> | |
21 | ||
17d18afb | 22 | #include "bitmap.h" |
b93d3b6c | 23 | #include "column.h" |
3e8a2ad1 | 24 | #include "openvswitch/dynamic-string.h" |
52553aea | 25 | #include "monitor.h" |
f85f8ebb BP |
26 | #include "json.h" |
27 | #include "jsonrpc.h" | |
a8425c53 BP |
28 | #include "ovsdb-error.h" |
29 | #include "ovsdb-parser.h" | |
f85f8ebb | 30 | #include "ovsdb.h" |
48f6e410 | 31 | #include "poll-loop.h" |
6c2882f9 | 32 | #include "reconnect.h" |
a8425c53 | 33 | #include "row.h" |
e317253b | 34 | #include "server.h" |
0d085684 | 35 | #include "simap.h" |
f85f8ebb | 36 | #include "stream.h" |
a8425c53 | 37 | #include "table.h" |
f85f8ebb | 38 | #include "timeval.h" |
a8425c53 | 39 | #include "transaction.h" |
f85f8ebb | 40 | #include "trigger.h" |
e6211adc | 41 | #include "openvswitch/vlog.h" |
f85f8ebb | 42 | |
d98e6007 | 43 | VLOG_DEFINE_THIS_MODULE(ovsdb_jsonrpc_server); |
5136ce49 | 44 | |
0b1fae1b | 45 | struct ovsdb_jsonrpc_remote; |
b93d3b6c | 46 | struct ovsdb_jsonrpc_session; |
f85f8ebb | 47 | |
e47cb14c AZ |
48 | /* Set false to defeature monitor2, causing jsonrpc to respond to monitor2 |
49 | * method with an error. */ | |
50 | static bool monitor2_enable__ = true; | |
51 | ||
0b1fae1b | 52 | /* Message rate-limiting. */ |
d3d8f1f7 | 53 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
0b1fae1b | 54 | |
a8425c53 | 55 | /* Sessions. */ |
0b1fae1b BP |
56 | static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create( |
57 | struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *); | |
58 | static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *); | |
59 | static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *); | |
0d085684 BP |
60 | static void ovsdb_jsonrpc_session_get_memory_usage_all( |
61 | const struct ovsdb_jsonrpc_remote *, struct simap *usage); | |
0b1fae1b | 62 | static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *); |
31d0b6c9 | 63 | static void ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *); |
94db5407 BP |
64 | static void ovsdb_jsonrpc_session_set_all_options( |
65 | struct ovsdb_jsonrpc_remote *, const struct ovsdb_jsonrpc_options *); | |
600766e8 | 66 | static bool ovsdb_jsonrpc_active_session_get_status( |
0b3e7a8b | 67 | const struct ovsdb_jsonrpc_remote *, |
87fcbc60 | 68 | struct ovsdb_jsonrpc_remote_status *); |
600766e8 AZ |
69 | static void ovsdb_jsonrpc_session_get_status( |
70 | const struct ovsdb_jsonrpc_session *, | |
71 | struct ovsdb_jsonrpc_remote_status *); | |
da897f41 BP |
72 | static void ovsdb_jsonrpc_session_unlock_all(struct ovsdb_jsonrpc_session *); |
73 | static void ovsdb_jsonrpc_session_unlock__(struct ovsdb_lock_waiter *); | |
48f6e410 BP |
74 | static void ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *, |
75 | struct jsonrpc_msg *); | |
b93d3b6c | 76 | |
a8425c53 | 77 | /* Triggers. */ |
b93d3b6c | 78 | static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *, |
b4e8d170 | 79 | struct ovsdb *, |
b93d3b6c BP |
80 | struct json *id, struct json *params); |
81 | static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find( | |
82 | struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash); | |
83 | static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *); | |
84 | static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *); | |
85 | static void ovsdb_jsonrpc_trigger_complete_done( | |
86 | struct ovsdb_jsonrpc_session *); | |
a8425c53 BP |
87 | |
88 | /* Monitors. */ | |
508624b6 BP |
89 | static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_create( |
90 | struct ovsdb_jsonrpc_session *, struct ovsdb *, struct json *params, | |
92f8d65b | 91 | enum ovsdb_monitor_version, const struct json *request_id); |
a8425c53 BP |
92 | static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel( |
93 | struct ovsdb_jsonrpc_session *, | |
94 | struct json_array *params, | |
95 | const struct json *request_id); | |
96 | static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *); | |
48f6e410 BP |
97 | static void ovsdb_jsonrpc_monitor_flush_all(struct ovsdb_jsonrpc_session *); |
98 | static bool ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *); | |
f1de87bb | 99 | static struct json *ovsdb_jsonrpc_monitor_compose_update( |
59c35e11 | 100 | struct ovsdb_jsonrpc_monitor *monitor, bool initial); |
61b63013 | 101 | |
b93d3b6c BP |
102 | \f |
103 | /* JSON-RPC database server. */ | |
f85f8ebb BP |
104 | |
105 | struct ovsdb_jsonrpc_server { | |
e317253b | 106 | struct ovsdb_server up; |
acdd0764 | 107 | unsigned int n_sessions; |
0b1fae1b BP |
108 | struct shash remotes; /* Contains "struct ovsdb_jsonrpc_remote *"s. */ |
109 | }; | |
f85f8ebb | 110 | |
0b1fae1b BP |
111 | /* A configured remote. This is either a passive stream listener plus a list |
112 | * of the currently connected sessions, or a list of exactly one active | |
113 | * session. */ | |
114 | struct ovsdb_jsonrpc_remote { | |
115 | struct ovsdb_jsonrpc_server *server; | |
116 | struct pstream *listener; /* Listener, if passive. */ | |
ca6ba700 | 117 | struct ovs_list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */ |
e879d33e | 118 | uint8_t dscp; |
f85f8ebb BP |
119 | }; |
120 | ||
94db5407 | 121 | static struct ovsdb_jsonrpc_remote *ovsdb_jsonrpc_server_add_remote( |
f125905c MM |
122 | struct ovsdb_jsonrpc_server *, const char *name, |
123 | const struct ovsdb_jsonrpc_options *options | |
124 | ); | |
0b1fae1b BP |
125 | static void ovsdb_jsonrpc_server_del_remote(struct shash_node *); |
126 | ||
b4e8d170 BP |
127 | /* Creates and returns a new server to provide JSON-RPC access to an OVSDB. |
128 | * | |
129 | * The caller must call ovsdb_jsonrpc_server_add_db() for each database to | |
130 | * which 'server' should provide access. */ | |
b93d3b6c | 131 | struct ovsdb_jsonrpc_server * |
b4e8d170 | 132 | ovsdb_jsonrpc_server_create(void) |
f85f8ebb | 133 | { |
b93d3b6c | 134 | struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server); |
b4e8d170 | 135 | ovsdb_server_init(&server->up); |
0b1fae1b | 136 | shash_init(&server->remotes); |
b93d3b6c BP |
137 | return server; |
138 | } | |
f85f8ebb | 139 | |
b4e8d170 BP |
140 | /* Adds 'db' to the set of databases served out by 'svr'. Returns true if |
141 | * successful, false if 'db''s name is the same as some database already in | |
142 | * 'server'. */ | |
143 | bool | |
144 | ovsdb_jsonrpc_server_add_db(struct ovsdb_jsonrpc_server *svr, struct ovsdb *db) | |
145 | { | |
0a3b723b BP |
146 | /* The OVSDB protocol doesn't have a way to notify a client that a |
147 | * database has been added. If some client tried to use the database | |
148 | * that we're adding and failed, then forcing it to reconnect seems like | |
149 | * a reasonable way to make it try again. | |
150 | * | |
151 | * If this is too big of a hammer in practice, we could be more selective, | |
152 | * e.g. disconnect only connections that actually tried to use a database | |
153 | * with 'db''s name. */ | |
154 | ovsdb_jsonrpc_server_reconnect(svr); | |
155 | ||
b4e8d170 BP |
156 | return ovsdb_server_add_db(&svr->up, db); |
157 | } | |
158 | ||
0a3b723b BP |
159 | /* Removes 'db' from the set of databases served out by 'svr'. Returns |
160 | * true if successful, false if there is no database associated with 'db'. */ | |
161 | bool | |
162 | ovsdb_jsonrpc_server_remove_db(struct ovsdb_jsonrpc_server *svr, | |
163 | struct ovsdb *db) | |
164 | { | |
165 | /* There might be pointers to 'db' from 'svr', such as monitors or | |
166 | * outstanding transactions. Disconnect all JSON-RPC connections to avoid | |
167 | * accesses to freed memory. | |
168 | * | |
169 | * If this is too big of a hammer in practice, we could be more selective, | |
170 | * e.g. disconnect only connections that actually reference 'db'. */ | |
171 | ovsdb_jsonrpc_server_reconnect(svr); | |
172 | ||
173 | return ovsdb_server_remove_db(&svr->up, db); | |
174 | } | |
175 | ||
23935e8b BP |
176 | void |
177 | ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *svr) | |
178 | { | |
179 | struct shash_node *node, *next; | |
180 | ||
181 | SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) { | |
182 | ovsdb_jsonrpc_server_del_remote(node); | |
183 | } | |
184 | shash_destroy(&svr->remotes); | |
e317253b | 185 | ovsdb_server_destroy(&svr->up); |
23935e8b BP |
186 | free(svr); |
187 | } | |
188 | ||
94db5407 | 189 | struct ovsdb_jsonrpc_options * |
f1936eb6 | 190 | ovsdb_jsonrpc_default_options(const char *target) |
94db5407 BP |
191 | { |
192 | struct ovsdb_jsonrpc_options *options = xzalloc(sizeof *options); | |
94db5407 | 193 | options->max_backoff = RECONNECT_DEFAULT_MAX_BACKOFF; |
f1936eb6 EJ |
194 | options->probe_interval = (stream_or_pstream_needs_probes(target) |
195 | ? RECONNECT_DEFAULT_PROBE_INTERVAL | |
196 | : 0); | |
94db5407 BP |
197 | return options; |
198 | } | |
199 | ||
200 | /* Sets 'svr''s current set of remotes to the names in 'new_remotes', with | |
201 | * options in the struct ovsdb_jsonrpc_options supplied as the data values. | |
0b1fae1b BP |
202 | * |
203 | * A remote is an active or passive stream connection method, e.g. "pssl:" or | |
204 | * "tcp:1.2.3.4". */ | |
6dea5eaf | 205 | void |
0b1fae1b BP |
206 | ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *svr, |
207 | const struct shash *new_remotes) | |
b93d3b6c | 208 | { |
0b1fae1b BP |
209 | struct shash_node *node, *next; |
210 | ||
211 | SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) { | |
c2e3cbaf BP |
212 | struct ovsdb_jsonrpc_remote *remote = node->data; |
213 | struct ovsdb_jsonrpc_options *options | |
214 | = shash_find_data(new_remotes, node->name); | |
215 | ||
216 | if (!options) { | |
f97ffebf | 217 | VLOG_INFO("%s: remote deconfigured", node->name); |
0b1fae1b | 218 | ovsdb_jsonrpc_server_del_remote(node); |
c2e3cbaf BP |
219 | } else if (options->dscp != remote->dscp) { |
220 | ovsdb_jsonrpc_server_del_remote(node); | |
221 | } | |
0b1fae1b BP |
222 | } |
223 | SHASH_FOR_EACH (node, new_remotes) { | |
94db5407 BP |
224 | const struct ovsdb_jsonrpc_options *options = node->data; |
225 | struct ovsdb_jsonrpc_remote *remote; | |
226 | ||
227 | remote = shash_find_data(&svr->remotes, node->name); | |
228 | if (!remote) { | |
f125905c | 229 | remote = ovsdb_jsonrpc_server_add_remote(svr, node->name, options); |
94db5407 BP |
230 | if (!remote) { |
231 | continue; | |
232 | } | |
0b1fae1b | 233 | } |
94db5407 BP |
234 | |
235 | ovsdb_jsonrpc_session_set_all_options(remote, options); | |
f85f8ebb | 236 | } |
b93d3b6c | 237 | } |
f85f8ebb | 238 | |
94db5407 | 239 | static struct ovsdb_jsonrpc_remote * |
0b1fae1b | 240 | ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr, |
f125905c MM |
241 | const char *name, |
242 | const struct ovsdb_jsonrpc_options *options) | |
b93d3b6c | 243 | { |
0b1fae1b BP |
244 | struct ovsdb_jsonrpc_remote *remote; |
245 | struct pstream *listener; | |
246 | int error; | |
247 | ||
f125905c | 248 | error = jsonrpc_pstream_open(name, &listener, options->dscp); |
0b1fae1b | 249 | if (error && error != EAFNOSUPPORT) { |
10a89ef0 | 250 | VLOG_ERR_RL(&rl, "%s: listen failed: %s", name, ovs_strerror(error)); |
94db5407 | 251 | return NULL; |
0b1fae1b BP |
252 | } |
253 | ||
254 | remote = xmalloc(sizeof *remote); | |
255 | remote->server = svr; | |
256 | remote->listener = listener; | |
417e7e66 | 257 | ovs_list_init(&remote->sessions); |
e879d33e | 258 | remote->dscp = options->dscp; |
0b1fae1b BP |
259 | shash_add(&svr->remotes, name, remote); |
260 | ||
261 | if (!listener) { | |
fba6bd1d | 262 | ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name, true)); |
0b1fae1b | 263 | } |
94db5407 | 264 | return remote; |
0b1fae1b BP |
265 | } |
266 | ||
267 | static void | |
268 | ovsdb_jsonrpc_server_del_remote(struct shash_node *node) | |
269 | { | |
270 | struct ovsdb_jsonrpc_remote *remote = node->data; | |
271 | ||
272 | ovsdb_jsonrpc_session_close_all(remote); | |
273 | pstream_close(remote->listener); | |
274 | shash_delete(&remote->server->remotes, node); | |
275 | free(remote); | |
f85f8ebb BP |
276 | } |
277 | ||
87fcbc60 BP |
278 | /* Stores status information for the remote named 'target', which should have |
279 | * been configured on 'svr' with a call to ovsdb_jsonrpc_server_set_remotes(), | |
280 | * into '*status'. On success returns true, on failure (if 'svr' doesn't have | |
600766e8 | 281 | * a remote named 'target' or if that remote is an outbound remote that has no |
87fcbc60 BP |
282 | * active connections) returns false. On failure, 'status' will be zeroed. |
283 | */ | |
284 | bool | |
285 | ovsdb_jsonrpc_server_get_remote_status( | |
286 | const struct ovsdb_jsonrpc_server *svr, const char *target, | |
287 | struct ovsdb_jsonrpc_remote_status *status) | |
0b3e7a8b | 288 | { |
87fcbc60 | 289 | const struct ovsdb_jsonrpc_remote *remote; |
0b3e7a8b | 290 | |
87fcbc60 | 291 | memset(status, 0, sizeof *status); |
0b3e7a8b | 292 | |
87fcbc60 | 293 | remote = shash_find_data(&svr->remotes, target); |
600766e8 AZ |
294 | |
295 | if (!remote) { | |
296 | return false; | |
297 | } | |
298 | ||
299 | if (remote->listener) { | |
300 | status->bound_port = pstream_get_bound_port(remote->listener); | |
417e7e66 BW |
301 | status->is_connected = !ovs_list_is_empty(&remote->sessions); |
302 | status->n_connections = ovs_list_size(&remote->sessions); | |
600766e8 AZ |
303 | return true; |
304 | } | |
305 | ||
306 | return ovsdb_jsonrpc_active_session_get_status(remote, status); | |
0b3e7a8b AE |
307 | } |
308 | ||
da897f41 BP |
309 | void |
310 | ovsdb_jsonrpc_server_free_remote_status( | |
311 | struct ovsdb_jsonrpc_remote_status *status) | |
312 | { | |
313 | free(status->locks_held); | |
314 | free(status->locks_waiting); | |
315 | free(status->locks_lost); | |
316 | } | |
317 | ||
31d0b6c9 BP |
318 | /* Forces all of the JSON-RPC sessions managed by 'svr' to disconnect and |
319 | * reconnect. */ | |
320 | void | |
321 | ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *svr) | |
322 | { | |
323 | struct shash_node *node; | |
324 | ||
325 | SHASH_FOR_EACH (node, &svr->remotes) { | |
326 | struct ovsdb_jsonrpc_remote *remote = node->data; | |
327 | ||
328 | ovsdb_jsonrpc_session_reconnect_all(remote); | |
329 | } | |
330 | } | |
331 | ||
f85f8ebb BP |
332 | void |
333 | ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr) | |
334 | { | |
0b1fae1b BP |
335 | struct shash_node *node; |
336 | ||
337 | SHASH_FOR_EACH (node, &svr->remotes) { | |
338 | struct ovsdb_jsonrpc_remote *remote = node->data; | |
339 | ||
04abfbe7 | 340 | if (remote->listener) { |
acdd0764 AZ |
341 | struct stream *stream; |
342 | int error; | |
343 | ||
344 | error = pstream_accept(remote->listener, &stream); | |
345 | if (!error) { | |
346 | struct jsonrpc_session *js; | |
347 | js = jsonrpc_session_open_unreliably(jsonrpc_open(stream), | |
348 | remote->dscp); | |
349 | ovsdb_jsonrpc_session_create(remote, js); | |
350 | } else if (error != EAGAIN) { | |
351 | VLOG_WARN_RL(&rl, "%s: accept failed: %s", | |
0b1fae1b | 352 | pstream_get_name(remote->listener), |
acdd0764 | 353 | ovs_strerror(error)); |
0b1fae1b | 354 | } |
f85f8ebb | 355 | } |
f85f8ebb | 356 | |
0b1fae1b BP |
357 | ovsdb_jsonrpc_session_run_all(remote); |
358 | } | |
f85f8ebb BP |
359 | } |
360 | ||
361 | void | |
362 | ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr) | |
363 | { | |
0b1fae1b BP |
364 | struct shash_node *node; |
365 | ||
366 | SHASH_FOR_EACH (node, &svr->remotes) { | |
367 | struct ovsdb_jsonrpc_remote *remote = node->data; | |
f85f8ebb | 368 | |
acdd0764 | 369 | if (remote->listener) { |
0b1fae1b | 370 | pstream_wait(remote->listener); |
f85f8ebb | 371 | } |
f85f8ebb | 372 | |
0b1fae1b BP |
373 | ovsdb_jsonrpc_session_wait_all(remote); |
374 | } | |
f85f8ebb | 375 | } |
0d085684 BP |
376 | |
377 | /* Adds some memory usage statistics for 'svr' into 'usage', for use with | |
378 | * memory_report(). */ | |
379 | void | |
380 | ovsdb_jsonrpc_server_get_memory_usage(const struct ovsdb_jsonrpc_server *svr, | |
381 | struct simap *usage) | |
382 | { | |
383 | struct shash_node *node; | |
384 | ||
385 | simap_increase(usage, "sessions", svr->n_sessions); | |
386 | SHASH_FOR_EACH (node, &svr->remotes) { | |
387 | struct ovsdb_jsonrpc_remote *remote = node->data; | |
388 | ||
389 | ovsdb_jsonrpc_session_get_memory_usage_all(remote, usage); | |
390 | } | |
391 | } | |
b93d3b6c BP |
392 | \f |
393 | /* JSON-RPC database server session. */ | |
f85f8ebb | 394 | |
b93d3b6c | 395 | struct ovsdb_jsonrpc_session { |
ca6ba700 | 396 | struct ovs_list node; /* Element in remote's sessions list. */ |
e317253b | 397 | struct ovsdb_session up; |
0b1fae1b | 398 | struct ovsdb_jsonrpc_remote *remote; |
f85f8ebb | 399 | |
b93d3b6c BP |
400 | /* Triggers. */ |
401 | struct hmap triggers; /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */ | |
f85f8ebb | 402 | |
a8425c53 BP |
403 | /* Monitors. */ |
404 | struct hmap monitors; /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */ | |
405 | ||
4931f33a BP |
406 | /* Network connectivity. */ |
407 | struct jsonrpc_session *js; /* JSON-RPC session. */ | |
408 | unsigned int js_seqno; /* Last jsonrpc_session_get_seqno() value. */ | |
b93d3b6c | 409 | }; |
f85f8ebb | 410 | |
b93d3b6c | 411 | static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *); |
b93d3b6c BP |
412 | static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *); |
413 | static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *); | |
0d085684 BP |
414 | static void ovsdb_jsonrpc_session_get_memory_usage( |
415 | const struct ovsdb_jsonrpc_session *, struct simap *usage); | |
b93d3b6c BP |
416 | static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *, |
417 | struct jsonrpc_msg *); | |
418 | static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *, | |
419 | struct jsonrpc_msg *); | |
f85f8ebb | 420 | |
6c2882f9 | 421 | static struct ovsdb_jsonrpc_session * |
0b1fae1b | 422 | ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote, |
4931f33a | 423 | struct jsonrpc_session *js) |
f85f8ebb BP |
424 | { |
425 | struct ovsdb_jsonrpc_session *s; | |
426 | ||
427 | s = xzalloc(sizeof *s); | |
b4e8d170 | 428 | ovsdb_session_init(&s->up, &remote->server->up); |
0b1fae1b | 429 | s->remote = remote; |
417e7e66 | 430 | ovs_list_push_back(&remote->sessions, &s->node); |
f85f8ebb | 431 | hmap_init(&s->triggers); |
a8425c53 | 432 | hmap_init(&s->monitors); |
4931f33a BP |
433 | s->js = js; |
434 | s->js_seqno = jsonrpc_session_get_seqno(js); | |
6c2882f9 | 435 | |
0b1fae1b | 436 | remote->server->n_sessions++; |
6c2882f9 BP |
437 | |
438 | return s; | |
f85f8ebb BP |
439 | } |
440 | ||
6c2882f9 BP |
441 | static void |
442 | ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s) | |
443 | { | |
e084f690 | 444 | ovsdb_jsonrpc_monitor_remove_all(s); |
da897f41 | 445 | ovsdb_jsonrpc_session_unlock_all(s); |
aca16ce6 BP |
446 | ovsdb_jsonrpc_trigger_complete_all(s); |
447 | ||
448 | hmap_destroy(&s->monitors); | |
449 | hmap_destroy(&s->triggers); | |
450 | ||
4931f33a | 451 | jsonrpc_session_close(s->js); |
417e7e66 | 452 | ovs_list_remove(&s->node); |
0b1fae1b | 453 | s->remote->server->n_sessions--; |
e317253b | 454 | ovsdb_session_destroy(&s->up); |
e084f690 | 455 | free(s); |
f85f8ebb BP |
456 | } |
457 | ||
4931f33a BP |
458 | static int |
459 | ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s) | |
6c2882f9 | 460 | { |
4931f33a BP |
461 | jsonrpc_session_run(s->js); |
462 | if (s->js_seqno != jsonrpc_session_get_seqno(s->js)) { | |
463 | s->js_seqno = jsonrpc_session_get_seqno(s->js); | |
b93d3b6c | 464 | ovsdb_jsonrpc_trigger_complete_all(s); |
a8425c53 | 465 | ovsdb_jsonrpc_monitor_remove_all(s); |
da897f41 | 466 | ovsdb_jsonrpc_session_unlock_all(s); |
6c2882f9 | 467 | } |
6c2882f9 | 468 | |
4931f33a | 469 | ovsdb_jsonrpc_trigger_complete_done(s); |
6c2882f9 | 470 | |
633f7247 | 471 | if (!jsonrpc_session_get_backlog(s->js)) { |
48f6e410 BP |
472 | struct jsonrpc_msg *msg; |
473 | ||
474 | ovsdb_jsonrpc_monitor_flush_all(s); | |
475 | ||
476 | msg = jsonrpc_session_recv(s->js); | |
4931f33a | 477 | if (msg) { |
6c2882f9 BP |
478 | if (msg->type == JSONRPC_REQUEST) { |
479 | ovsdb_jsonrpc_session_got_request(s, msg); | |
480 | } else if (msg->type == JSONRPC_NOTIFY) { | |
481 | ovsdb_jsonrpc_session_got_notify(s, msg); | |
482 | } else { | |
483 | VLOG_WARN("%s: received unexpected %s message", | |
4931f33a | 484 | jsonrpc_session_get_name(s->js), |
6c2882f9 | 485 | jsonrpc_msg_type_to_string(msg->type)); |
4931f33a | 486 | jsonrpc_session_force_reconnect(s->js); |
6c2882f9 BP |
487 | jsonrpc_msg_destroy(msg); |
488 | } | |
489 | } | |
6c2882f9 | 490 | } |
4931f33a | 491 | return jsonrpc_session_is_alive(s->js) ? 0 : ETIMEDOUT; |
b93d3b6c | 492 | } |
6c2882f9 | 493 | |
94db5407 BP |
494 | static void |
495 | ovsdb_jsonrpc_session_set_options(struct ovsdb_jsonrpc_session *session, | |
496 | const struct ovsdb_jsonrpc_options *options) | |
497 | { | |
498 | jsonrpc_session_set_max_backoff(session->js, options->max_backoff); | |
499 | jsonrpc_session_set_probe_interval(session->js, options->probe_interval); | |
f125905c | 500 | jsonrpc_session_set_dscp(session->js, options->dscp); |
94db5407 BP |
501 | } |
502 | ||
b93d3b6c | 503 | static void |
0b1fae1b | 504 | ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *remote) |
b93d3b6c BP |
505 | { |
506 | struct ovsdb_jsonrpc_session *s, *next; | |
507 | ||
4e8e4213 | 508 | LIST_FOR_EACH_SAFE (s, next, node, &remote->sessions) { |
b93d3b6c BP |
509 | int error = ovsdb_jsonrpc_session_run(s); |
510 | if (error) { | |
511 | ovsdb_jsonrpc_session_close(s); | |
512 | } | |
513 | } | |
6c2882f9 BP |
514 | } |
515 | ||
516 | static void | |
517 | ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s) | |
518 | { | |
4931f33a BP |
519 | jsonrpc_session_wait(s->js); |
520 | if (!jsonrpc_session_get_backlog(s->js)) { | |
48f6e410 BP |
521 | if (ovsdb_jsonrpc_monitor_needs_flush(s)) { |
522 | poll_immediate_wake(); | |
523 | } else { | |
524 | jsonrpc_session_recv_wait(s->js); | |
525 | } | |
6c2882f9 | 526 | } |
6c2882f9 BP |
527 | } |
528 | ||
b93d3b6c | 529 | static void |
0b1fae1b | 530 | ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *remote) |
f85f8ebb | 531 | { |
b93d3b6c | 532 | struct ovsdb_jsonrpc_session *s; |
f85f8ebb | 533 | |
4e8e4213 | 534 | LIST_FOR_EACH (s, node, &remote->sessions) { |
b93d3b6c | 535 | ovsdb_jsonrpc_session_wait(s); |
f85f8ebb | 536 | } |
b93d3b6c | 537 | } |
f85f8ebb | 538 | |
0d085684 BP |
539 | static void |
540 | ovsdb_jsonrpc_session_get_memory_usage(const struct ovsdb_jsonrpc_session *s, | |
541 | struct simap *usage) | |
542 | { | |
543 | simap_increase(usage, "triggers", hmap_count(&s->triggers)); | |
0d085684 BP |
544 | simap_increase(usage, "backlog", jsonrpc_session_get_backlog(s->js)); |
545 | } | |
546 | ||
547 | static void | |
548 | ovsdb_jsonrpc_session_get_memory_usage_all( | |
549 | const struct ovsdb_jsonrpc_remote *remote, | |
550 | struct simap *usage) | |
551 | { | |
552 | struct ovsdb_jsonrpc_session *s; | |
553 | ||
554 | LIST_FOR_EACH (s, node, &remote->sessions) { | |
555 | ovsdb_jsonrpc_session_get_memory_usage(s, usage); | |
556 | } | |
557 | } | |
558 | ||
0b1fae1b BP |
559 | static void |
560 | ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote) | |
561 | { | |
562 | struct ovsdb_jsonrpc_session *s, *next; | |
563 | ||
4e8e4213 | 564 | LIST_FOR_EACH_SAFE (s, next, node, &remote->sessions) { |
0b1fae1b BP |
565 | ovsdb_jsonrpc_session_close(s); |
566 | } | |
567 | } | |
568 | ||
31d0b6c9 BP |
569 | /* Forces all of the JSON-RPC sessions managed by 'remote' to disconnect and |
570 | * reconnect. */ | |
571 | static void | |
572 | ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *remote) | |
573 | { | |
574 | struct ovsdb_jsonrpc_session *s, *next; | |
575 | ||
4e8e4213 | 576 | LIST_FOR_EACH_SAFE (s, next, node, &remote->sessions) { |
31d0b6c9 BP |
577 | jsonrpc_session_force_reconnect(s->js); |
578 | if (!jsonrpc_session_is_alive(s->js)) { | |
579 | ovsdb_jsonrpc_session_close(s); | |
580 | } | |
581 | } | |
582 | } | |
583 | ||
94db5407 | 584 | /* Sets the options for all of the JSON-RPC sessions managed by 'remote' to |
c2e3cbaf BP |
585 | * 'options'. |
586 | * | |
587 | * (The dscp value can't be changed directly; the caller must instead close and | |
588 | * re-open the session.) */ | |
94db5407 BP |
589 | static void |
590 | ovsdb_jsonrpc_session_set_all_options( | |
591 | struct ovsdb_jsonrpc_remote *remote, | |
592 | const struct ovsdb_jsonrpc_options *options) | |
593 | { | |
594 | struct ovsdb_jsonrpc_session *s; | |
595 | ||
596 | LIST_FOR_EACH (s, node, &remote->sessions) { | |
597 | ovsdb_jsonrpc_session_set_options(s, options); | |
598 | } | |
599 | } | |
600 | ||
600766e8 | 601 | /* Sets the 'status' of for the 'remote' with an outgoing connection. */ |
87fcbc60 | 602 | static bool |
600766e8 AZ |
603 | ovsdb_jsonrpc_active_session_get_status( |
604 | const struct ovsdb_jsonrpc_remote *remote, | |
605 | struct ovsdb_jsonrpc_remote_status *status) | |
0b3e7a8b | 606 | { |
600766e8 | 607 | const struct ovs_list *sessions = &remote->sessions; |
0b3e7a8b | 608 | const struct ovsdb_jsonrpc_session *s; |
600766e8 | 609 | |
417e7e66 | 610 | if (ovs_list_is_empty(sessions)) { |
600766e8 AZ |
611 | return false; |
612 | } | |
613 | ||
417e7e66 BW |
614 | ovs_assert(ovs_list_is_singleton(sessions)); |
615 | s = CONTAINER_OF(ovs_list_front(sessions), struct ovsdb_jsonrpc_session, node); | |
600766e8 AZ |
616 | ovsdb_jsonrpc_session_get_status(s, status); |
617 | status->n_connections = 1; | |
618 | ||
619 | return true; | |
620 | } | |
621 | ||
622 | static void | |
623 | ovsdb_jsonrpc_session_get_status(const struct ovsdb_jsonrpc_session *session, | |
624 | struct ovsdb_jsonrpc_remote_status *status) | |
625 | { | |
626 | const struct ovsdb_jsonrpc_session *s = session; | |
0b3e7a8b | 627 | const struct jsonrpc_session *js; |
da897f41 | 628 | struct ovsdb_lock_waiter *waiter; |
0b3e7a8b | 629 | struct reconnect_stats rstats; |
da897f41 | 630 | struct ds locks_held, locks_waiting, locks_lost; |
0b3e7a8b | 631 | |
0b3e7a8b | 632 | js = s->js; |
0b3e7a8b AE |
633 | |
634 | status->is_connected = jsonrpc_session_is_connected(js); | |
635 | status->last_error = jsonrpc_session_get_status(js); | |
636 | ||
637 | jsonrpc_session_get_reconnect_stats(js, &rstats); | |
638 | status->state = rstats.state; | |
5eda645e AE |
639 | status->sec_since_connect = rstats.msec_since_connect == UINT_MAX |
640 | ? UINT_MAX : rstats.msec_since_connect / 1000; | |
641 | status->sec_since_disconnect = rstats.msec_since_disconnect == UINT_MAX | |
642 | ? UINT_MAX : rstats.msec_since_disconnect / 1000; | |
87fcbc60 | 643 | |
da897f41 BP |
644 | ds_init(&locks_held); |
645 | ds_init(&locks_waiting); | |
646 | ds_init(&locks_lost); | |
647 | HMAP_FOR_EACH (waiter, session_node, &s->up.waiters) { | |
648 | struct ds *string; | |
649 | ||
650 | string = (ovsdb_lock_waiter_is_owner(waiter) ? &locks_held | |
651 | : waiter->mode == OVSDB_LOCK_WAIT ? &locks_waiting | |
652 | : &locks_lost); | |
653 | if (string->length) { | |
654 | ds_put_char(string, ' '); | |
655 | } | |
656 | ds_put_cstr(string, waiter->lock_name); | |
657 | } | |
658 | status->locks_held = ds_steal_cstr(&locks_held); | |
659 | status->locks_waiting = ds_steal_cstr(&locks_waiting); | |
660 | status->locks_lost = ds_steal_cstr(&locks_lost); | |
0b3e7a8b AE |
661 | } |
662 | ||
b4e8d170 BP |
663 | /* Examines 'request' to determine the database to which it relates, and then |
664 | * searches 's' to find that database: | |
665 | * | |
666 | * - If successful, returns the database and sets '*replyp' to NULL. | |
667 | * | |
668 | * - If no such database exists, returns NULL and sets '*replyp' to an | |
669 | * appropriate JSON-RPC error reply, owned by the caller. */ | |
670 | static struct ovsdb * | |
671 | ovsdb_jsonrpc_lookup_db(const struct ovsdb_jsonrpc_session *s, | |
672 | const struct jsonrpc_msg *request, | |
673 | struct jsonrpc_msg **replyp) | |
9cb53f26 BP |
674 | { |
675 | struct json_array *params; | |
9cb53f26 | 676 | struct ovsdb_error *error; |
b4e8d170 BP |
677 | const char *db_name; |
678 | struct ovsdb *db; | |
9cb53f26 BP |
679 | |
680 | params = json_array(request->params); | |
681 | if (!params->n || params->elems[0]->type != JSON_STRING) { | |
682 | error = ovsdb_syntax_error( | |
683 | request->params, NULL, | |
684 | "%s request params must begin with <db-name>", request->method); | |
685 | goto error; | |
686 | } | |
687 | ||
b4e8d170 BP |
688 | db_name = params->elems[0]->u.string; |
689 | db = shash_find_data(&s->up.server->dbs, db_name); | |
690 | if (!db) { | |
9cb53f26 BP |
691 | error = ovsdb_syntax_error( |
692 | request->params, "unknown database", | |
693 | "%s request specifies unknown database %s", | |
b4e8d170 | 694 | request->method, db_name); |
9cb53f26 BP |
695 | goto error; |
696 | } | |
697 | ||
b4e8d170 BP |
698 | *replyp = NULL; |
699 | return db; | |
9cb53f26 BP |
700 | |
701 | error: | |
508624b6 | 702 | *replyp = jsonrpc_create_error(ovsdb_error_to_json(error), request->id); |
9cb53f26 | 703 | ovsdb_error_destroy(error); |
b4e8d170 | 704 | return NULL; |
9cb53f26 BP |
705 | } |
706 | ||
da897f41 BP |
707 | static struct ovsdb_error * |
708 | ovsdb_jsonrpc_session_parse_lock_name(const struct jsonrpc_msg *request, | |
709 | const char **lock_namep) | |
710 | { | |
711 | const struct json_array *params; | |
712 | ||
713 | params = json_array(request->params); | |
714 | if (params->n != 1 || params->elems[0]->type != JSON_STRING || | |
715 | !ovsdb_parser_is_id(json_string(params->elems[0]))) { | |
716 | *lock_namep = NULL; | |
717 | return ovsdb_syntax_error(request->params, NULL, | |
718 | "%s request params must be <id>", | |
719 | request->method); | |
720 | } | |
721 | ||
722 | *lock_namep = json_string(params->elems[0]); | |
723 | return NULL; | |
724 | } | |
725 | ||
726 | static void | |
727 | ovsdb_jsonrpc_session_notify(struct ovsdb_session *session, | |
728 | const char *lock_name, | |
729 | const char *method) | |
730 | { | |
731 | struct ovsdb_jsonrpc_session *s; | |
732 | struct json *params; | |
733 | ||
734 | s = CONTAINER_OF(session, struct ovsdb_jsonrpc_session, up); | |
735 | params = json_array_create_1(json_string_create(lock_name)); | |
48f6e410 | 736 | ovsdb_jsonrpc_session_send(s, jsonrpc_create_notify(method, params)); |
da897f41 BP |
737 | } |
738 | ||
739 | static struct jsonrpc_msg * | |
740 | ovsdb_jsonrpc_session_lock(struct ovsdb_jsonrpc_session *s, | |
741 | struct jsonrpc_msg *request, | |
742 | enum ovsdb_lock_mode mode) | |
743 | { | |
744 | struct ovsdb_lock_waiter *waiter; | |
745 | struct jsonrpc_msg *reply; | |
746 | struct ovsdb_error *error; | |
747 | struct ovsdb_session *victim; | |
748 | const char *lock_name; | |
749 | struct json *result; | |
750 | ||
751 | error = ovsdb_jsonrpc_session_parse_lock_name(request, &lock_name); | |
752 | if (error) { | |
753 | goto error; | |
754 | } | |
755 | ||
756 | /* Report error if this session has issued a "lock" or "steal" without a | |
757 | * matching "unlock" for this lock. */ | |
758 | waiter = ovsdb_session_get_lock_waiter(&s->up, lock_name); | |
759 | if (waiter) { | |
760 | error = ovsdb_syntax_error( | |
761 | request->params, NULL, | |
762 | "must issue \"unlock\" before new \"%s\"", request->method); | |
763 | goto error; | |
764 | } | |
765 | ||
766 | /* Get the lock, add us as a waiter. */ | |
767 | waiter = ovsdb_server_lock(&s->remote->server->up, &s->up, lock_name, mode, | |
768 | &victim); | |
769 | if (victim) { | |
770 | ovsdb_jsonrpc_session_notify(victim, lock_name, "stolen"); | |
771 | } | |
772 | ||
773 | result = json_object_create(); | |
774 | json_object_put(result, "locked", | |
775 | json_boolean_create(ovsdb_lock_waiter_is_owner(waiter))); | |
776 | ||
777 | return jsonrpc_create_reply(result, request->id); | |
778 | ||
779 | error: | |
508624b6 | 780 | reply = jsonrpc_create_error(ovsdb_error_to_json(error), request->id); |
da897f41 BP |
781 | ovsdb_error_destroy(error); |
782 | return reply; | |
783 | } | |
784 | ||
785 | static void | |
786 | ovsdb_jsonrpc_session_unlock_all(struct ovsdb_jsonrpc_session *s) | |
787 | { | |
788 | struct ovsdb_lock_waiter *waiter, *next; | |
789 | ||
790 | HMAP_FOR_EACH_SAFE (waiter, next, session_node, &s->up.waiters) { | |
791 | ovsdb_jsonrpc_session_unlock__(waiter); | |
792 | } | |
793 | } | |
794 | ||
795 | static void | |
796 | ovsdb_jsonrpc_session_unlock__(struct ovsdb_lock_waiter *waiter) | |
797 | { | |
798 | struct ovsdb_lock *lock = waiter->lock; | |
799 | ||
800 | if (lock) { | |
801 | struct ovsdb_session *new_owner = ovsdb_lock_waiter_remove(waiter); | |
802 | if (new_owner) { | |
803 | ovsdb_jsonrpc_session_notify(new_owner, lock->name, "locked"); | |
804 | } else { | |
805 | /* ovsdb_server_lock() might have freed 'lock'. */ | |
806 | } | |
807 | } | |
808 | ||
809 | ovsdb_lock_waiter_destroy(waiter); | |
810 | } | |
811 | ||
812 | static struct jsonrpc_msg * | |
813 | ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s, | |
814 | struct jsonrpc_msg *request) | |
815 | { | |
816 | struct ovsdb_lock_waiter *waiter; | |
817 | struct jsonrpc_msg *reply; | |
818 | struct ovsdb_error *error; | |
819 | const char *lock_name; | |
820 | ||
821 | error = ovsdb_jsonrpc_session_parse_lock_name(request, &lock_name); | |
822 | if (error) { | |
823 | goto error; | |
824 | } | |
825 | ||
826 | /* Report error if this session has not issued a "lock" or "steal" for this | |
827 | * lock. */ | |
828 | waiter = ovsdb_session_get_lock_waiter(&s->up, lock_name); | |
829 | if (!waiter) { | |
830 | error = ovsdb_syntax_error( | |
831 | request->params, NULL, "\"unlock\" without \"lock\" or \"steal\""); | |
832 | goto error; | |
833 | } | |
834 | ||
835 | ovsdb_jsonrpc_session_unlock__(waiter); | |
836 | ||
837 | return jsonrpc_create_reply(json_object_create(), request->id); | |
838 | ||
839 | error: | |
508624b6 | 840 | reply = jsonrpc_create_error(ovsdb_error_to_json(error), request->id); |
da897f41 BP |
841 | ovsdb_error_destroy(error); |
842 | return reply; | |
843 | } | |
844 | ||
b93d3b6c | 845 | static struct jsonrpc_msg * |
b4e8d170 | 846 | execute_transaction(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, |
b93d3b6c BP |
847 | struct jsonrpc_msg *request) |
848 | { | |
b4e8d170 | 849 | ovsdb_jsonrpc_trigger_create(s, db, request->id, request->params); |
f85f8ebb BP |
850 | request->id = NULL; |
851 | request->params = NULL; | |
e084f690 | 852 | jsonrpc_msg_destroy(request); |
f85f8ebb BP |
853 | return NULL; |
854 | } | |
855 | ||
856 | static void | |
857 | ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s, | |
858 | struct jsonrpc_msg *request) | |
859 | { | |
860 | struct jsonrpc_msg *reply; | |
861 | ||
862 | if (!strcmp(request->method, "transact")) { | |
b4e8d170 | 863 | struct ovsdb *db = ovsdb_jsonrpc_lookup_db(s, request, &reply); |
9cb53f26 | 864 | if (!reply) { |
b4e8d170 | 865 | reply = execute_transaction(s, db, request); |
9cb53f26 | 866 | } |
92f8d65b | 867 | } else if (!strcmp(request->method, "monitor") || |
e47cb14c | 868 | (monitor2_enable__ && !strcmp(request->method, "monitor2"))) { |
b4e8d170 | 869 | struct ovsdb *db = ovsdb_jsonrpc_lookup_db(s, request, &reply); |
9cb53f26 | 870 | if (!reply) { |
92f8d65b AZ |
871 | int l = strlen(request->method) - strlen("monitor"); |
872 | enum ovsdb_monitor_version version = l ? OVSDB_MONITOR_V2 | |
873 | : OVSDB_MONITOR_V1; | |
508624b6 | 874 | reply = ovsdb_jsonrpc_monitor_create(s, db, request->params, |
92f8d65b | 875 | version, request->id); |
9cb53f26 | 876 | } |
a8425c53 BP |
877 | } else if (!strcmp(request->method, "monitor_cancel")) { |
878 | reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params), | |
879 | request->id); | |
f85f8ebb | 880 | } else if (!strcmp(request->method, "get_schema")) { |
b4e8d170 | 881 | struct ovsdb *db = ovsdb_jsonrpc_lookup_db(s, request, &reply); |
9cb53f26 | 882 | if (!reply) { |
b4e8d170 BP |
883 | reply = jsonrpc_create_reply(ovsdb_schema_to_json(db->schema), |
884 | request->id); | |
9cb53f26 BP |
885 | } |
886 | } else if (!strcmp(request->method, "list_dbs")) { | |
b4e8d170 BP |
887 | size_t n_dbs = shash_count(&s->up.server->dbs); |
888 | struct shash_node *node; | |
889 | struct json **dbs; | |
890 | size_t i; | |
891 | ||
892 | dbs = xmalloc(n_dbs * sizeof *dbs); | |
893 | i = 0; | |
894 | SHASH_FOR_EACH (node, &s->up.server->dbs) { | |
895 | dbs[i++] = json_string_create(node->name); | |
896 | } | |
897 | reply = jsonrpc_create_reply(json_array_create(dbs, n_dbs), | |
898 | request->id); | |
da897f41 BP |
899 | } else if (!strcmp(request->method, "lock")) { |
900 | reply = ovsdb_jsonrpc_session_lock(s, request, OVSDB_LOCK_WAIT); | |
901 | } else if (!strcmp(request->method, "steal")) { | |
902 | reply = ovsdb_jsonrpc_session_lock(s, request, OVSDB_LOCK_STEAL); | |
903 | } else if (!strcmp(request->method, "unlock")) { | |
904 | reply = ovsdb_jsonrpc_session_unlock(s, request); | |
6c2882f9 BP |
905 | } else if (!strcmp(request->method, "echo")) { |
906 | reply = jsonrpc_create_reply(json_clone(request->params), request->id); | |
f85f8ebb BP |
907 | } else { |
908 | reply = jsonrpc_create_error(json_string_create("unknown method"), | |
909 | request->id); | |
910 | } | |
911 | ||
912 | if (reply) { | |
913 | jsonrpc_msg_destroy(request); | |
48f6e410 | 914 | ovsdb_jsonrpc_session_send(s, reply); |
f85f8ebb BP |
915 | } |
916 | } | |
917 | ||
918 | static void | |
919 | execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request) | |
920 | { | |
6e79e210 BP |
921 | if (json_array(request->params)->n == 1) { |
922 | struct ovsdb_jsonrpc_trigger *t; | |
923 | struct json *id; | |
f85f8ebb | 924 | |
6e79e210 BP |
925 | id = request->params->u.array.elems[0]; |
926 | t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0)); | |
927 | if (t) { | |
928 | ovsdb_jsonrpc_trigger_complete(t); | |
929 | } | |
f85f8ebb BP |
930 | } |
931 | } | |
932 | ||
933 | static void | |
934 | ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s, | |
935 | struct jsonrpc_msg *request) | |
936 | { | |
937 | if (!strcmp(request->method, "cancel")) { | |
938 | execute_cancel(s, request); | |
939 | } | |
940 | jsonrpc_msg_destroy(request); | |
941 | } | |
48f6e410 BP |
942 | |
943 | static void | |
944 | ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *s, | |
945 | struct jsonrpc_msg *msg) | |
946 | { | |
947 | ovsdb_jsonrpc_monitor_flush_all(s); | |
948 | jsonrpc_session_send(s->js, msg); | |
949 | } | |
b93d3b6c BP |
950 | \f |
951 | /* JSON-RPC database server triggers. | |
952 | * | |
953 | * (Every transaction is treated as a trigger even if it doesn't actually have | |
954 | * any "wait" operations.) */ | |
955 | ||
956 | struct ovsdb_jsonrpc_trigger { | |
957 | struct ovsdb_trigger trigger; | |
b93d3b6c BP |
958 | struct hmap_node hmap_node; /* In session's "triggers" hmap. */ |
959 | struct json *id; | |
960 | }; | |
961 | ||
962 | static void | |
b4e8d170 | 963 | ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, |
b93d3b6c BP |
964 | struct json *id, struct json *params) |
965 | { | |
966 | struct ovsdb_jsonrpc_trigger *t; | |
967 | size_t hash; | |
968 | ||
969 | /* Check for duplicate ID. */ | |
970 | hash = json_hash(id, 0); | |
971 | t = ovsdb_jsonrpc_trigger_find(s, id, hash); | |
972 | if (t) { | |
4931f33a BP |
973 | struct jsonrpc_msg *msg; |
974 | ||
975 | msg = jsonrpc_create_error(json_string_create("duplicate request ID"), | |
976 | id); | |
48f6e410 | 977 | ovsdb_jsonrpc_session_send(s, msg); |
b93d3b6c BP |
978 | json_destroy(id); |
979 | json_destroy(params); | |
980 | return; | |
981 | } | |
982 | ||
983 | /* Insert into trigger table. */ | |
984 | t = xmalloc(sizeof *t); | |
b4e8d170 | 985 | ovsdb_trigger_init(&s->up, db, &t->trigger, params, time_msec()); |
b93d3b6c BP |
986 | t->id = id; |
987 | hmap_insert(&s->triggers, &t->hmap_node, hash); | |
988 | ||
989 | /* Complete early if possible. */ | |
990 | if (ovsdb_trigger_is_complete(&t->trigger)) { | |
991 | ovsdb_jsonrpc_trigger_complete(t); | |
992 | } | |
993 | } | |
994 | ||
995 | static struct ovsdb_jsonrpc_trigger * | |
996 | ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s, | |
997 | const struct json *id, size_t hash) | |
998 | { | |
999 | struct ovsdb_jsonrpc_trigger *t; | |
1000 | ||
4e8e4213 | 1001 | HMAP_FOR_EACH_WITH_HASH (t, hmap_node, hash, &s->triggers) { |
b93d3b6c BP |
1002 | if (json_equal(t->id, id)) { |
1003 | return t; | |
1004 | } | |
1005 | } | |
1006 | ||
1007 | return NULL; | |
1008 | } | |
1009 | ||
1010 | static void | |
1011 | ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t) | |
1012 | { | |
e317253b BP |
1013 | struct ovsdb_jsonrpc_session *s; |
1014 | ||
1015 | s = CONTAINER_OF(t->trigger.session, struct ovsdb_jsonrpc_session, up); | |
b93d3b6c | 1016 | |
4931f33a | 1017 | if (jsonrpc_session_is_connected(s->js)) { |
b93d3b6c BP |
1018 | struct jsonrpc_msg *reply; |
1019 | struct json *result; | |
1020 | ||
1021 | result = ovsdb_trigger_steal_result(&t->trigger); | |
1022 | if (result) { | |
1023 | reply = jsonrpc_create_reply(result, t->id); | |
1024 | } else { | |
1025 | reply = jsonrpc_create_error(json_string_create("canceled"), | |
1026 | t->id); | |
1027 | } | |
48f6e410 | 1028 | ovsdb_jsonrpc_session_send(s, reply); |
b93d3b6c BP |
1029 | } |
1030 | ||
1031 | json_destroy(t->id); | |
1032 | ovsdb_trigger_destroy(&t->trigger); | |
1033 | hmap_remove(&s->triggers, &t->hmap_node); | |
1034 | free(t); | |
1035 | } | |
1036 | ||
1037 | static void | |
1038 | ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s) | |
1039 | { | |
1040 | struct ovsdb_jsonrpc_trigger *t, *next; | |
4e8e4213 | 1041 | HMAP_FOR_EACH_SAFE (t, next, hmap_node, &s->triggers) { |
b93d3b6c BP |
1042 | ovsdb_jsonrpc_trigger_complete(t); |
1043 | } | |
1044 | } | |
1045 | ||
1046 | static void | |
1047 | ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s) | |
1048 | { | |
417e7e66 | 1049 | while (!ovs_list_is_empty(&s->up.completions)) { |
b93d3b6c | 1050 | struct ovsdb_jsonrpc_trigger *t |
e317253b | 1051 | = CONTAINER_OF(s->up.completions.next, |
b93d3b6c BP |
1052 | struct ovsdb_jsonrpc_trigger, trigger.node); |
1053 | ovsdb_jsonrpc_trigger_complete(t); | |
1054 | } | |
1055 | } | |
a8425c53 | 1056 | \f |
897af587 AZ |
1057 | /* Jsonrpc front end monitor. */ |
1058 | struct ovsdb_jsonrpc_monitor { | |
a8425c53 | 1059 | struct ovsdb_jsonrpc_session *session; |
b4e8d170 | 1060 | struct ovsdb *db; |
a8425c53 | 1061 | struct hmap_node node; /* In ovsdb_jsonrpc_session's "monitors". */ |
a8425c53 | 1062 | struct json *monitor_id; |
897af587 | 1063 | struct ovsdb_monitor *dbmon; |
59c35e11 AZ |
1064 | uint64_t unflushed; /* The first transaction that has not been |
1065 | flushed to the jsonrpc remote client. */ | |
92f8d65b | 1066 | enum ovsdb_monitor_version version; |
a8425c53 BP |
1067 | }; |
1068 | ||
2fa1df7b | 1069 | static struct ovsdb_jsonrpc_monitor * |
a8425c53 BP |
1070 | ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s, |
1071 | const struct json *monitor_id) | |
1072 | { | |
1073 | struct ovsdb_jsonrpc_monitor *m; | |
1074 | ||
4e8e4213 | 1075 | HMAP_FOR_EACH_WITH_HASH (m, node, json_hash(monitor_id, 0), &s->monitors) { |
a8425c53 BP |
1076 | if (json_equal(m->monitor_id, monitor_id)) { |
1077 | return m; | |
1078 | } | |
1079 | } | |
1080 | ||
1081 | return NULL; | |
1082 | } | |
1083 | ||
2fa1df7b AZ |
1084 | static bool |
1085 | parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value) | |
20aa445d | 1086 | { |
2fa1df7b | 1087 | const struct json *json; |
20aa445d | 1088 | |
2fa1df7b AZ |
1089 | json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL); |
1090 | return json ? json_boolean(json) : default_value; | |
f3395ab3 AZ |
1091 | } |
1092 | ||
cab50449 | 1093 | static struct ovsdb_error * OVS_WARN_UNUSED_RESULT |
83d300f6 AZ |
1094 | ovsdb_jsonrpc_parse_monitor_request(struct ovsdb_monitor *dbmon, |
1095 | const struct ovsdb_table *table, | |
20aa445d BP |
1096 | const struct json *monitor_request, |
1097 | size_t *allocated_columns) | |
1098 | { | |
83d300f6 | 1099 | const struct ovsdb_table_schema *ts = table->schema; |
897af587 | 1100 | enum ovsdb_monitor_selection select; |
20aa445d BP |
1101 | const struct json *columns, *select_json; |
1102 | struct ovsdb_parser parser; | |
1103 | struct ovsdb_error *error; | |
1104 | ||
1105 | ovsdb_parser_init(&parser, monitor_request, "table %s", ts->name); | |
1106 | columns = ovsdb_parser_member(&parser, "columns", OP_ARRAY | OP_OPTIONAL); | |
1107 | select_json = ovsdb_parser_member(&parser, "select", | |
1108 | OP_OBJECT | OP_OPTIONAL); | |
1109 | error = ovsdb_parser_finish(&parser); | |
1110 | if (error) { | |
1111 | return error; | |
1112 | } | |
1113 | ||
1114 | if (select_json) { | |
1115 | select = 0; | |
1116 | ovsdb_parser_init(&parser, select_json, "table %s select", ts->name); | |
1117 | if (parse_bool(&parser, "initial", true)) { | |
1118 | select |= OJMS_INITIAL; | |
1119 | } | |
1120 | if (parse_bool(&parser, "insert", true)) { | |
1121 | select |= OJMS_INSERT; | |
1122 | } | |
1123 | if (parse_bool(&parser, "delete", true)) { | |
1124 | select |= OJMS_DELETE; | |
1125 | } | |
1126 | if (parse_bool(&parser, "modify", true)) { | |
1127 | select |= OJMS_MODIFY; | |
1128 | } | |
1129 | error = ovsdb_parser_finish(&parser); | |
1130 | if (error) { | |
1131 | return error; | |
1132 | } | |
1133 | } else { | |
1134 | select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY; | |
1135 | } | |
20aa445d | 1136 | |
ea585a0e | 1137 | ovsdb_monitor_table_add_select(dbmon, table, select); |
20aa445d BP |
1138 | if (columns) { |
1139 | size_t i; | |
1140 | ||
1141 | if (columns->type != JSON_ARRAY) { | |
1142 | return ovsdb_syntax_error(columns, NULL, | |
1143 | "array of column names expected"); | |
1144 | } | |
1145 | ||
1146 | for (i = 0; i < columns->u.array.n; i++) { | |
1147 | const struct ovsdb_column *column; | |
1148 | const char *s; | |
1149 | ||
1150 | if (columns->u.array.elems[i]->type != JSON_STRING) { | |
1151 | return ovsdb_syntax_error(columns, NULL, | |
1152 | "array of column names expected"); | |
1153 | } | |
1154 | ||
1155 | s = columns->u.array.elems[i]->u.string; | |
ea585a0e | 1156 | column = shash_find_data(&table->schema->columns, s); |
20aa445d BP |
1157 | if (!column) { |
1158 | return ovsdb_syntax_error(columns, NULL, "%s is not a valid " | |
1159 | "column name", s); | |
1160 | } | |
ea585a0e AZ |
1161 | ovsdb_monitor_add_column(dbmon, table, column, select, |
1162 | allocated_columns); | |
20aa445d BP |
1163 | } |
1164 | } else { | |
1165 | struct shash_node *node; | |
1166 | ||
1167 | SHASH_FOR_EACH (node, &ts->columns) { | |
1168 | const struct ovsdb_column *column = node->data; | |
1169 | if (column->index != OVSDB_COL_UUID) { | |
ea585a0e | 1170 | ovsdb_monitor_add_column(dbmon, table, column, select, |
897af587 | 1171 | allocated_columns); |
20aa445d BP |
1172 | } |
1173 | } | |
1174 | } | |
1175 | ||
1176 | return NULL; | |
1177 | } | |
1178 | ||
508624b6 | 1179 | static struct jsonrpc_msg * |
b4e8d170 | 1180 | ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, |
508624b6 | 1181 | struct json *params, |
92f8d65b | 1182 | enum ovsdb_monitor_version version, |
508624b6 | 1183 | const struct json *request_id) |
a8425c53 BP |
1184 | { |
1185 | struct ovsdb_jsonrpc_monitor *m = NULL; | |
6e5a9216 | 1186 | struct ovsdb_monitor *dbmon = NULL; |
a8425c53 BP |
1187 | struct json *monitor_id, *monitor_requests; |
1188 | struct ovsdb_error *error = NULL; | |
1189 | struct shash_node *node; | |
1190 | struct json *json; | |
1191 | ||
9cb53f26 | 1192 | if (json_array(params)->n != 3) { |
a8425c53 BP |
1193 | error = ovsdb_syntax_error(params, NULL, "invalid parameters"); |
1194 | goto error; | |
1195 | } | |
9cb53f26 BP |
1196 | monitor_id = params->u.array.elems[1]; |
1197 | monitor_requests = params->u.array.elems[2]; | |
a8425c53 BP |
1198 | if (monitor_requests->type != JSON_OBJECT) { |
1199 | error = ovsdb_syntax_error(monitor_requests, NULL, | |
1200 | "monitor-requests must be object"); | |
1201 | goto error; | |
1202 | } | |
1203 | ||
1204 | if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) { | |
1205 | error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID"); | |
1206 | goto error; | |
1207 | } | |
1208 | ||
1209 | m = xzalloc(sizeof *m); | |
a8425c53 | 1210 | m->session = s; |
b4e8d170 | 1211 | m->db = db; |
2fa1df7b | 1212 | m->dbmon = ovsdb_monitor_create(db, m); |
59c35e11 | 1213 | m->unflushed = 0; |
92f8d65b | 1214 | m->version = version; |
a8425c53 BP |
1215 | hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0)); |
1216 | m->monitor_id = json_clone(monitor_id); | |
a8425c53 BP |
1217 | |
1218 | SHASH_FOR_EACH (node, json_object(monitor_requests)) { | |
1219 | const struct ovsdb_table *table; | |
83d300f6 | 1220 | const char *column_name; |
20aa445d BP |
1221 | size_t allocated_columns; |
1222 | const struct json *mr_value; | |
1223 | size_t i; | |
a8425c53 | 1224 | |
b4e8d170 | 1225 | table = ovsdb_get_table(m->db, node->name); |
a8425c53 BP |
1226 | if (!table) { |
1227 | error = ovsdb_syntax_error(NULL, NULL, | |
1228 | "no table named %s", node->name); | |
1229 | goto error; | |
1230 | } | |
1231 | ||
83d300f6 | 1232 | ovsdb_monitor_add_table(m->dbmon, table); |
a8425c53 | 1233 | |
20aa445d BP |
1234 | /* Parse columns. */ |
1235 | mr_value = node->data; | |
1236 | allocated_columns = 0; | |
1237 | if (mr_value->type == JSON_ARRAY) { | |
1238 | const struct json_array *array = &mr_value->u.array; | |
1239 | ||
1240 | for (i = 0; i < array->n; i++) { | |
1241 | error = ovsdb_jsonrpc_parse_monitor_request( | |
83d300f6 | 1242 | m->dbmon, table, array->elems[i], &allocated_columns); |
20aa445d BP |
1243 | if (error) { |
1244 | goto error; | |
1245 | } | |
a8425c53 BP |
1246 | } |
1247 | } else { | |
20aa445d | 1248 | error = ovsdb_jsonrpc_parse_monitor_request( |
83d300f6 | 1249 | m->dbmon, table, mr_value, &allocated_columns); |
20aa445d BP |
1250 | if (error) { |
1251 | goto error; | |
a8425c53 BP |
1252 | } |
1253 | } | |
1254 | ||
83d300f6 AZ |
1255 | column_name = ovsdb_monitor_table_check_duplicates(m->dbmon, table); |
1256 | ||
1257 | if (column_name) { | |
1258 | error = ovsdb_syntax_error(mr_value, NULL, "column %s " | |
1259 | "mentioned more than once", | |
1260 | column_name); | |
1261 | goto error; | |
a8425c53 BP |
1262 | } |
1263 | } | |
1264 | ||
6e5a9216 AZ |
1265 | dbmon = ovsdb_monitor_add(m->dbmon); |
1266 | if (dbmon != m->dbmon) { | |
1267 | /* Found an exisiting dbmon, reuse the current one. */ | |
f76def25 | 1268 | ovsdb_monitor_remove_jsonrpc_monitor(m->dbmon, m, m->unflushed); |
6e5a9216 AZ |
1269 | ovsdb_monitor_add_jsonrpc_monitor(dbmon, m); |
1270 | m->dbmon = dbmon; | |
1271 | } | |
1272 | ||
61b63013 | 1273 | ovsdb_monitor_get_initial(m->dbmon); |
f1de87bb | 1274 | json = ovsdb_jsonrpc_monitor_compose_update(m, true); |
61b63013 AZ |
1275 | json = json ? json : json_object_create(); |
1276 | return jsonrpc_create_reply(json, request_id); | |
a8425c53 BP |
1277 | |
1278 | error: | |
23f37a97 | 1279 | if (m) { |
88b63308 | 1280 | ovsdb_jsonrpc_monitor_destroy(m); |
23f37a97 | 1281 | } |
a8425c53 BP |
1282 | |
1283 | json = ovsdb_error_to_json(error); | |
1284 | ovsdb_error_destroy(error); | |
508624b6 | 1285 | return jsonrpc_create_error(json, request_id); |
a8425c53 BP |
1286 | } |
1287 | ||
1288 | static struct jsonrpc_msg * | |
1289 | ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s, | |
1290 | struct json_array *params, | |
1291 | const struct json *request_id) | |
1292 | { | |
1293 | if (params->n != 1) { | |
1294 | return jsonrpc_create_error(json_string_create("invalid parameters"), | |
1295 | request_id); | |
1296 | } else { | |
1297 | struct ovsdb_jsonrpc_monitor *m; | |
1298 | ||
1299 | m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]); | |
1300 | if (!m) { | |
1301 | return jsonrpc_create_error(json_string_create("unknown monitor"), | |
1302 | request_id); | |
1303 | } else { | |
88b63308 | 1304 | ovsdb_jsonrpc_monitor_destroy(m); |
a8425c53 BP |
1305 | return jsonrpc_create_reply(json_object_create(), request_id); |
1306 | } | |
1307 | } | |
1308 | } | |
1309 | ||
1310 | static void | |
1311 | ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s) | |
1312 | { | |
1313 | struct ovsdb_jsonrpc_monitor *m, *next; | |
1314 | ||
4e8e4213 | 1315 | HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) { |
88b63308 | 1316 | ovsdb_jsonrpc_monitor_destroy(m); |
a8425c53 BP |
1317 | } |
1318 | } | |
1319 | ||
92d5d643 | 1320 | static struct json * |
1158f320 AZ |
1321 | ovsdb_jsonrpc_monitor_compose_update(struct ovsdb_jsonrpc_monitor *m, |
1322 | bool initial) | |
92d5d643 | 1323 | { |
8cffdcd0 AZ |
1324 | if (!ovsdb_monitor_needs_flush(m->dbmon, m->unflushed)) { |
1325 | return NULL; | |
1326 | } | |
1327 | ||
52553aea | 1328 | return ovsdb_monitor_get_update(m->dbmon, initial, &m->unflushed, |
92f8d65b | 1329 | m->version); |
92d5d643 AZ |
1330 | } |
1331 | ||
48f6e410 BP |
1332 | static bool |
1333 | ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *s) | |
1334 | { | |
1335 | struct ovsdb_jsonrpc_monitor *m; | |
1336 | ||
1337 | HMAP_FOR_EACH (m, node, &s->monitors) { | |
59c35e11 | 1338 | if (ovsdb_monitor_needs_flush(m->dbmon, m->unflushed)) { |
dbc1cfbb | 1339 | return true; |
48f6e410 BP |
1340 | } |
1341 | } | |
1342 | ||
1343 | return false; | |
1344 | } | |
1345 | ||
2fa1df7b AZ |
1346 | void |
1347 | ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m) | |
1348 | { | |
1349 | json_destroy(m->monitor_id); | |
1350 | hmap_remove(&m->session->monitors, &m->node); | |
f76def25 | 1351 | ovsdb_monitor_remove_jsonrpc_monitor(m->dbmon, m, m->unflushed); |
2fa1df7b AZ |
1352 | free(m); |
1353 | } | |
1354 | ||
92f8d65b AZ |
1355 | static struct jsonrpc_msg * |
1356 | ovsdb_jsonrpc_create_notify(const struct ovsdb_jsonrpc_monitor *m, | |
1357 | struct json *params) | |
1358 | { | |
1359 | const char *method; | |
1360 | ||
1361 | switch(m->version) { | |
1362 | case OVSDB_MONITOR_V1: | |
1363 | method = "update"; | |
1364 | break; | |
1365 | case OVSDB_MONITOR_V2: | |
1366 | method = "update2"; | |
1367 | break; | |
1368 | case OVSDB_MONITOR_VERSION_MAX: | |
1369 | default: | |
1370 | OVS_NOT_REACHED(); | |
1371 | } | |
1372 | ||
1373 | return jsonrpc_create_notify(method, params); | |
1374 | } | |
1375 | ||
48f6e410 BP |
1376 | static void |
1377 | ovsdb_jsonrpc_monitor_flush_all(struct ovsdb_jsonrpc_session *s) | |
1378 | { | |
1379 | struct ovsdb_jsonrpc_monitor *m; | |
1380 | ||
1381 | HMAP_FOR_EACH (m, node, &s->monitors) { | |
1382 | struct json *json; | |
1383 | ||
f1de87bb | 1384 | json = ovsdb_jsonrpc_monitor_compose_update(m, false); |
48f6e410 BP |
1385 | if (json) { |
1386 | struct jsonrpc_msg *msg; | |
1387 | struct json *params; | |
1388 | ||
1389 | params = json_array_create_2(json_clone(m->monitor_id), json); | |
92f8d65b | 1390 | msg = ovsdb_jsonrpc_create_notify(m, params); |
48f6e410 BP |
1391 | jsonrpc_session_send(s->js, msg); |
1392 | } | |
1393 | } | |
1394 | } | |
e47cb14c AZ |
1395 | |
1396 | void | |
5d8b248c | 1397 | ovsdb_jsonrpc_disable_monitor2(void) |
e47cb14c AZ |
1398 | { |
1399 | /* Once disabled, it is not possible to re-enable it. */ | |
1400 | monitor2_enable__ = false; | |
1401 | } |