]>
Commit | Line | Data |
---|---|---|
d0632593 BP |
1 | /* |
2 | * Copyright (c) 2009 Nicira Networks. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | ||
19 | #include <errno.h> | |
20 | #include <getopt.h> | |
21 | #include <limits.h> | |
22 | #include <signal.h> | |
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | #include <unistd.h> | |
26 | ||
27 | #include "command-line.h" | |
28 | #include "column.h" | |
29 | #include "compiler.h" | |
3f262d7d | 30 | #include "daemon.h" |
d0632593 BP |
31 | #include "dynamic-string.h" |
32 | #include "json.h" | |
33 | #include "jsonrpc.h" | |
34 | #include "ovsdb.h" | |
35 | #include "ovsdb-error.h" | |
36 | #include "stream.h" | |
37 | #include "table.h" | |
38 | #include "timeval.h" | |
39 | #include "util.h" | |
40 | ||
41 | #include "vlog.h" | |
42 | #define THIS_MODULE VLM_ovsdb_client | |
43 | ||
44 | /* --format: Output formatting. */ | |
45 | static enum { | |
46 | FMT_TABLE, /* Textual table. */ | |
47 | FMT_HTML, /* HTML table. */ | |
48 | FMT_CSV /* Comma-separated lines. */ | |
49 | } output_format; | |
50 | ||
51 | /* --wide: For --format=table, the maximum output width. */ | |
52 | static int output_width; | |
53 | ||
54 | /* --no-headings: Whether table output should include headings. */ | |
55 | static int output_headings = true; | |
56 | ||
b87fde85 BP |
57 | /* --pretty: Flags to pass to json_to_string(). */ |
58 | static int json_flags = JSSF_SORT; | |
59 | ||
d0632593 BP |
60 | static const struct command all_commands[]; |
61 | ||
62 | static void usage(void) NO_RETURN; | |
63 | static void parse_options(int argc, char *argv[]); | |
64 | ||
65 | int | |
66 | main(int argc, char *argv[]) | |
67 | { | |
68 | set_program_name(argv[0]); | |
69 | time_init(); | |
70 | vlog_init(); | |
71 | parse_options(argc, argv); | |
72 | signal(SIGPIPE, SIG_IGN); | |
73 | run_command(argc - optind, argv + optind, all_commands); | |
74 | return 0; | |
75 | } | |
76 | ||
77 | static void | |
78 | parse_options(int argc, char *argv[]) | |
79 | { | |
80 | static struct option long_options[] = { | |
81 | {"wide", no_argument, &output_width, INT_MAX}, | |
82 | {"format", required_argument, 0, 'f'}, | |
83 | {"no-headings", no_argument, &output_headings, 0}, | |
b87fde85 | 84 | {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT}, |
d0632593 BP |
85 | {"verbose", optional_argument, 0, 'v'}, |
86 | {"help", no_argument, 0, 'h'}, | |
87 | {"version", no_argument, 0, 'V'}, | |
3f262d7d | 88 | DAEMON_LONG_OPTIONS, |
d0632593 BP |
89 | {0, 0, 0, 0}, |
90 | }; | |
91 | char *short_options = long_options_to_short_options(long_options); | |
92 | ||
93 | output_width = isatty(fileno(stdout)) ? 79 : INT_MAX; | |
94 | for (;;) { | |
95 | int c; | |
96 | ||
97 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
98 | if (c == -1) { | |
99 | break; | |
100 | } | |
101 | ||
102 | switch (c) { | |
103 | case 'f': | |
104 | if (!strcmp(optarg, "table")) { | |
105 | output_format = FMT_TABLE; | |
106 | } else if (!strcmp(optarg, "html")) { | |
107 | output_format = FMT_HTML; | |
108 | } else if (!strcmp(optarg, "csv")) { | |
109 | output_format = FMT_CSV; | |
110 | } else { | |
111 | ovs_fatal(0, "unknown output format \"%s\"", optarg); | |
112 | } | |
113 | break; | |
114 | ||
115 | case 'w': | |
116 | output_width = INT_MAX; | |
117 | break; | |
118 | ||
119 | case 'h': | |
120 | usage(); | |
121 | ||
122 | case 'V': | |
123 | OVS_PRINT_VERSION(0, 0); | |
124 | exit(EXIT_SUCCESS); | |
125 | ||
126 | case 'v': | |
127 | vlog_set_verbosity(optarg); | |
128 | break; | |
129 | ||
3f262d7d BP |
130 | DAEMON_OPTION_HANDLERS |
131 | ||
d0632593 BP |
132 | case '?': |
133 | exit(EXIT_FAILURE); | |
134 | ||
135 | case 0: | |
136 | /* getopt_long() already set the value for us. */ | |
137 | break; | |
138 | ||
139 | default: | |
140 | abort(); | |
141 | } | |
142 | } | |
143 | free(short_options); | |
144 | } | |
145 | ||
146 | static void | |
147 | usage(void) | |
148 | { | |
149 | printf("%s: Open vSwitch database JSON-RPC client\n" | |
150 | "usage: %s [OPTIONS] COMMAND [ARG...]\n" | |
151 | "\nValid commands are:\n" | |
152 | "\n get-schema SERVER\n" | |
153 | " retrieve schema from SERVER\n" | |
154 | "\n list-tables SERVER\n" | |
155 | " list SERVER's tables\n" | |
156 | "\n list-columns SERVER [TABLE]\n" | |
6d65eee8 BP |
157 | " list columns in TABLE (or all tables) on SERVER\n" |
158 | "\n transact SERVER TRANSACTION\n" | |
159 | " run TRANSACTION (a JSON array of operations) on SERVER\n" | |
a8425c53 BP |
160 | " and print the results as JSON on stdout\n" |
161 | "\n monitor SERVER TABLE [COLUMN,...] [SELECT,...]\n" | |
162 | " monitor contents of (COLUMNs in) TABLE on SERVER\n" | |
163 | " Valid SELECTs are: initial, insert, delete, modify\n", | |
d0632593 | 164 | program_name, program_name); |
1b0f0f17 | 165 | stream_usage("SERVER", true, true); |
d0632593 BP |
166 | printf("\nOutput formatting options:\n" |
167 | " -f, --format=FORMAT set output formatting to FORMAT\n" | |
168 | " (\"table\", \"html\", or \"csv\"\n" | |
169 | " --wide don't limit TTY lines to 79 bytes\n" | |
b87fde85 BP |
170 | " --no-headings omit table heading row\n" |
171 | " --pretty pretty-print JSON in output"); | |
3f262d7d | 172 | daemon_usage(); |
d0632593 BP |
173 | vlog_usage(); |
174 | printf("\nOther options:\n" | |
175 | " -h, --help display this help message\n" | |
176 | " -V, --version display version information\n"); | |
177 | exit(EXIT_SUCCESS); | |
178 | } | |
179 | \f | |
6d65eee8 BP |
180 | static struct json * |
181 | parse_json(const char *s) | |
182 | { | |
183 | struct json *json = json_from_string(s); | |
184 | if (json->type == JSON_STRING) { | |
185 | ovs_fatal(0, "\"%s\": %s", s, json->u.string); | |
186 | } | |
187 | return json; | |
188 | } | |
189 | ||
d0632593 BP |
190 | static struct jsonrpc * |
191 | open_jsonrpc(const char *server) | |
192 | { | |
193 | struct stream *stream; | |
194 | int error; | |
195 | ||
196 | error = stream_open_block(server, &stream); | |
1b0f0f17 BP |
197 | if (error == EAFNOSUPPORT) { |
198 | struct pstream *pstream; | |
199 | ||
200 | error = pstream_open(server, &pstream); | |
201 | if (error) { | |
202 | ovs_fatal(error, "failed to connect or listen to \"%s\"", server); | |
203 | } | |
204 | ||
205 | VLOG_INFO("%s: waiting for connection...", server); | |
206 | error = pstream_accept_block(pstream, &stream); | |
207 | if (error) { | |
208 | ovs_fatal(error, "failed to accept connection on \"%s\"", server); | |
209 | } | |
210 | ||
211 | pstream_close(pstream); | |
212 | } else if (error) { | |
d0632593 BP |
213 | ovs_fatal(error, "failed to connect to \"%s\"", server); |
214 | } | |
215 | ||
216 | return jsonrpc_open(stream); | |
217 | } | |
218 | ||
219 | static void | |
220 | print_json(struct json *json) | |
221 | { | |
b87fde85 | 222 | char *string = json_to_string(json, json_flags); |
d0632593 BP |
223 | fputs(string, stdout); |
224 | free(string); | |
225 | } | |
226 | ||
227 | static void | |
228 | print_and_free_json(struct json *json) | |
229 | { | |
230 | print_json(json); | |
231 | json_destroy(json); | |
232 | } | |
233 | ||
234 | static void | |
235 | check_ovsdb_error(struct ovsdb_error *error) | |
236 | { | |
237 | if (error) { | |
238 | ovs_fatal(0, "%s", ovsdb_error_to_string(error)); | |
239 | } | |
240 | } | |
241 | ||
242 | static struct ovsdb_schema * | |
a8425c53 | 243 | fetch_schema_from_rpc(struct jsonrpc *rpc) |
d0632593 BP |
244 | { |
245 | struct jsonrpc_msg *request, *reply; | |
246 | struct ovsdb_schema *schema; | |
d0632593 BP |
247 | int error; |
248 | ||
20bed8be BP |
249 | request = jsonrpc_create_request("get_schema", json_array_create_empty(), |
250 | NULL); | |
d0632593 BP |
251 | error = jsonrpc_transact_block(rpc, request, &reply); |
252 | if (error) { | |
253 | ovs_fatal(error, "transaction failed"); | |
254 | } | |
255 | check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema)); | |
256 | jsonrpc_msg_destroy(reply); | |
a8425c53 BP |
257 | |
258 | return schema; | |
259 | } | |
260 | ||
261 | static struct ovsdb_schema * | |
262 | fetch_schema(const char *server) | |
263 | { | |
264 | struct ovsdb_schema *schema; | |
265 | struct jsonrpc *rpc; | |
266 | ||
267 | rpc = open_jsonrpc(server); | |
268 | schema = fetch_schema_from_rpc(rpc); | |
d0632593 BP |
269 | jsonrpc_close(rpc); |
270 | ||
271 | return schema; | |
272 | } | |
273 | \f | |
274 | struct column { | |
275 | char *heading; | |
276 | int width; | |
277 | }; | |
278 | ||
279 | struct table { | |
280 | char **cells; | |
281 | struct column *columns; | |
282 | size_t n_columns, allocated_columns; | |
283 | size_t n_rows, allocated_rows; | |
284 | size_t current_column; | |
285 | }; | |
286 | ||
287 | static void | |
288 | table_init(struct table *table) | |
289 | { | |
290 | memset(table, 0, sizeof *table); | |
291 | } | |
292 | ||
a8425c53 BP |
293 | static void |
294 | table_destroy(struct table *table) | |
295 | { | |
296 | size_t i; | |
297 | ||
298 | for (i = 0; i < table->n_columns; i++) { | |
299 | free(table->columns[i].heading); | |
300 | } | |
301 | free(table->columns); | |
302 | ||
303 | for (i = 0; i < table->n_columns * table->n_rows; i++) { | |
304 | free(table->cells[i]); | |
305 | } | |
306 | free(table->cells); | |
307 | } | |
308 | ||
d0632593 BP |
309 | static void |
310 | table_add_column(struct table *table, const char *heading, ...) | |
311 | PRINTF_FORMAT(2, 3); | |
312 | ||
313 | static void | |
314 | table_add_column(struct table *table, const char *heading, ...) | |
315 | { | |
316 | struct column *column; | |
317 | va_list args; | |
318 | ||
319 | assert(!table->n_rows); | |
320 | if (table->n_columns >= table->allocated_columns) { | |
321 | table->columns = x2nrealloc(table->columns, &table->allocated_columns, | |
322 | sizeof *table->columns); | |
323 | } | |
324 | column = &table->columns[table->n_columns++]; | |
325 | ||
326 | va_start(args, heading); | |
327 | column->heading = xvasprintf(heading, args); | |
328 | column->width = strlen(column->heading); | |
329 | va_end(args); | |
330 | } | |
331 | ||
332 | static char ** | |
333 | table_cell__(const struct table *table, size_t row, size_t column) | |
334 | { | |
335 | return &table->cells[column + row * table->n_columns]; | |
336 | } | |
337 | ||
338 | static void | |
339 | table_add_row(struct table *table) | |
340 | { | |
341 | size_t x, y; | |
342 | ||
343 | if (table->n_rows >= table->allocated_rows) { | |
344 | table->cells = x2nrealloc(table->cells, &table->allocated_rows, | |
345 | table->n_columns * sizeof *table->cells); | |
346 | } | |
347 | ||
348 | y = table->n_rows++; | |
349 | table->current_column = 0; | |
350 | for (x = 0; x < table->n_columns; x++) { | |
351 | *table_cell__(table, y, x) = NULL; | |
352 | } | |
353 | } | |
354 | ||
355 | static void | |
356 | table_add_cell_nocopy(struct table *table, char *s) | |
357 | { | |
358 | size_t x, y; | |
359 | int length; | |
360 | ||
361 | assert(table->n_rows > 0); | |
362 | assert(table->current_column < table->n_columns); | |
363 | ||
364 | x = table->current_column++; | |
365 | y = table->n_rows - 1; | |
366 | *table_cell__(table, y, x) = s; | |
367 | ||
368 | length = strlen(s); | |
369 | if (length > table->columns[x].width) { | |
370 | table->columns[x].width = length; | |
371 | } | |
372 | } | |
373 | ||
374 | static void | |
375 | table_add_cell(struct table *table, const char *format, ...) | |
376 | { | |
377 | va_list args; | |
378 | ||
379 | va_start(args, format); | |
380 | table_add_cell_nocopy(table, xvasprintf(format, args)); | |
381 | va_end(args); | |
382 | } | |
383 | ||
384 | static void | |
385 | table_print_table_line__(struct ds *line, size_t max_width) | |
386 | { | |
387 | ds_truncate(line, max_width); | |
388 | puts(ds_cstr(line)); | |
389 | ds_clear(line); | |
390 | } | |
391 | ||
392 | static void | |
393 | table_print_table__(const struct table *table) | |
394 | { | |
395 | struct ds line = DS_EMPTY_INITIALIZER; | |
396 | size_t x, y; | |
397 | ||
398 | if (output_headings) { | |
399 | for (x = 0; x < table->n_columns; x++) { | |
400 | const struct column *column = &table->columns[x]; | |
401 | if (x) { | |
402 | ds_put_char(&line, ' '); | |
403 | } | |
404 | ds_put_format(&line, "%-*s", column->width, column->heading); | |
405 | } | |
406 | table_print_table_line__(&line, output_width); | |
407 | ||
408 | for (x = 0; x < table->n_columns; x++) { | |
409 | const struct column *column = &table->columns[x]; | |
410 | int i; | |
411 | ||
412 | if (x) { | |
413 | ds_put_char(&line, ' '); | |
414 | } | |
415 | for (i = 0; i < column->width; i++) { | |
416 | ds_put_char(&line, '-'); | |
417 | } | |
418 | } | |
419 | table_print_table_line__(&line, output_width); | |
420 | } | |
421 | ||
422 | for (y = 0; y < table->n_rows; y++) { | |
423 | for (x = 0; x < table->n_columns; x++) { | |
424 | const char *cell = *table_cell__(table, y, x); | |
425 | if (x) { | |
426 | ds_put_char(&line, ' '); | |
427 | } | |
428 | ds_put_format(&line, "%-*s", table->columns[x].width, cell); | |
429 | } | |
430 | table_print_table_line__(&line, output_width); | |
431 | } | |
432 | ||
433 | ds_destroy(&line); | |
434 | } | |
435 | ||
436 | static void | |
437 | table_print_html_cell__(const char *element, const char *content) | |
438 | { | |
439 | const char *p; | |
440 | ||
441 | printf(" <%s>", element); | |
442 | for (p = content; *p != '\0'; p++) { | |
443 | switch (*p) { | |
444 | case '&': | |
445 | fputs("&", stdout); | |
446 | break; | |
447 | case '<': | |
448 | fputs("<", stdout); | |
449 | break; | |
450 | case '>': | |
451 | fputs(">", stdout); | |
452 | break; | |
453 | default: | |
454 | putchar(*p); | |
455 | break; | |
456 | } | |
457 | } | |
458 | printf("</%s>\n", element); | |
459 | } | |
460 | ||
461 | static void | |
462 | table_print_html__(const struct table *table) | |
463 | { | |
464 | size_t x, y; | |
465 | ||
466 | fputs("<table>\n", stdout); | |
467 | ||
468 | if (output_headings) { | |
469 | fputs(" <tr>\n", stdout); | |
470 | for (x = 0; x < table->n_columns; x++) { | |
471 | const struct column *column = &table->columns[x]; | |
472 | table_print_html_cell__("th", column->heading); | |
473 | } | |
474 | fputs(" </tr>\n", stdout); | |
475 | } | |
476 | ||
477 | for (y = 0; y < table->n_rows; y++) { | |
478 | fputs(" <tr>\n", stdout); | |
479 | for (x = 0; x < table->n_columns; x++) { | |
480 | table_print_html_cell__("td", *table_cell__(table, y, x)); | |
481 | } | |
482 | fputs(" </tr>\n", stdout); | |
483 | } | |
484 | ||
485 | fputs("</table>\n", stdout); | |
486 | } | |
487 | ||
488 | static void | |
489 | table_print_csv_cell__(const char *content) | |
490 | { | |
491 | const char *p; | |
492 | ||
493 | if (!strpbrk(content, "\n\",")) { | |
494 | fputs(content, stdout); | |
495 | } else { | |
496 | putchar('"'); | |
497 | for (p = content; *p != '\0'; p++) { | |
498 | switch (*p) { | |
499 | case '"': | |
500 | fputs("\"\"", stdout); | |
501 | break; | |
502 | default: | |
503 | putchar(*p); | |
504 | break; | |
505 | } | |
506 | } | |
507 | putchar('"'); | |
508 | } | |
509 | } | |
510 | ||
511 | static void | |
512 | table_print_csv__(const struct table *table) | |
513 | { | |
514 | size_t x, y; | |
515 | ||
516 | if (output_headings) { | |
517 | for (x = 0; x < table->n_columns; x++) { | |
518 | const struct column *column = &table->columns[x]; | |
519 | if (x) { | |
520 | putchar(','); | |
521 | } | |
522 | table_print_csv_cell__(column->heading); | |
523 | } | |
524 | putchar('\n'); | |
525 | } | |
526 | ||
527 | for (y = 0; y < table->n_rows; y++) { | |
528 | for (x = 0; x < table->n_columns; x++) { | |
529 | if (x) { | |
530 | putchar(','); | |
531 | } | |
532 | table_print_csv_cell__(*table_cell__(table, y, x)); | |
533 | } | |
534 | putchar('\n'); | |
535 | } | |
536 | } | |
537 | ||
538 | static void | |
539 | table_print(const struct table *table) | |
540 | { | |
541 | switch (output_format) { | |
542 | case FMT_TABLE: | |
543 | table_print_table__(table); | |
544 | break; | |
545 | ||
546 | case FMT_HTML: | |
547 | table_print_html__(table); | |
548 | break; | |
549 | ||
550 | case FMT_CSV: | |
551 | table_print_csv__(table); | |
552 | break; | |
553 | } | |
554 | } | |
555 | \f | |
556 | static void | |
557 | do_get_schema(int argc UNUSED, char *argv[]) | |
558 | { | |
559 | struct ovsdb_schema *schema = fetch_schema(argv[1]); | |
560 | print_and_free_json(ovsdb_schema_to_json(schema)); | |
561 | ovsdb_schema_destroy(schema); | |
562 | } | |
563 | ||
564 | static void | |
565 | do_list_tables(int argc UNUSED, char *argv[]) | |
566 | { | |
567 | struct ovsdb_schema *schema; | |
568 | struct shash_node *node; | |
569 | struct table t; | |
570 | ||
571 | schema = fetch_schema(argv[1]); | |
572 | table_init(&t); | |
573 | table_add_column(&t, "Table"); | |
574 | table_add_column(&t, "Comment"); | |
575 | SHASH_FOR_EACH (node, &schema->tables) { | |
576 | struct ovsdb_table_schema *ts = node->data; | |
577 | ||
578 | table_add_row(&t); | |
579 | table_add_cell(&t, ts->name); | |
580 | if (ts->comment) { | |
581 | table_add_cell(&t, ts->comment); | |
582 | } | |
583 | } | |
584 | ovsdb_schema_destroy(schema); | |
585 | table_print(&t); | |
586 | } | |
587 | ||
588 | static void | |
589 | do_list_columns(int argc UNUSED, char *argv[]) | |
590 | { | |
591 | const char *table_name = argv[2]; | |
592 | struct ovsdb_schema *schema; | |
593 | struct shash_node *table_node; | |
594 | struct table t; | |
595 | ||
596 | schema = fetch_schema(argv[1]); | |
597 | table_init(&t); | |
598 | if (!table_name) { | |
599 | table_add_column(&t, "Table"); | |
600 | } | |
601 | table_add_column(&t, "Column"); | |
602 | table_add_column(&t, "Type"); | |
603 | table_add_column(&t, "Comment"); | |
604 | SHASH_FOR_EACH (table_node, &schema->tables) { | |
605 | struct ovsdb_table_schema *ts = table_node->data; | |
606 | ||
607 | if (!table_name || !strcmp(table_name, ts->name)) { | |
608 | struct shash_node *column_node; | |
609 | ||
610 | SHASH_FOR_EACH (column_node, &ts->columns) { | |
611 | struct ovsdb_column *column = column_node->data; | |
612 | struct json *type = ovsdb_type_to_json(&column->type); | |
613 | ||
614 | table_add_row(&t); | |
615 | if (!table_name) { | |
616 | table_add_cell(&t, ts->name); | |
617 | } | |
618 | table_add_cell(&t, column->name); | |
619 | table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT)); | |
620 | if (column->comment) { | |
621 | table_add_cell(&t, column->comment); | |
622 | } | |
623 | ||
624 | json_destroy(type); | |
625 | } | |
626 | } | |
627 | } | |
628 | ovsdb_schema_destroy(schema); | |
629 | table_print(&t); | |
630 | } | |
631 | ||
6d65eee8 | 632 | static void |
a8425c53 | 633 | do_transact(int argc UNUSED, char *argv[]) |
6d65eee8 BP |
634 | { |
635 | struct jsonrpc_msg *request, *reply; | |
636 | struct json *transaction; | |
637 | struct jsonrpc *rpc; | |
638 | int error; | |
639 | ||
640 | transaction = parse_json(argv[2]); | |
641 | ||
642 | rpc = open_jsonrpc(argv[1]); | |
20bed8be | 643 | request = jsonrpc_create_request("transact", transaction, NULL); |
6d65eee8 BP |
644 | error = jsonrpc_transact_block(rpc, request, &reply); |
645 | if (error) { | |
646 | ovs_fatal(error, "transaction failed"); | |
647 | } | |
648 | if (reply->error) { | |
649 | ovs_fatal(error, "transaction returned error: %s", | |
b87fde85 | 650 | json_to_string(reply->error, json_flags)); |
6d65eee8 BP |
651 | } |
652 | print_json(reply->result); | |
653 | putchar('\n'); | |
654 | jsonrpc_msg_destroy(reply); | |
655 | jsonrpc_close(rpc); | |
656 | } | |
657 | ||
a8425c53 BP |
658 | static void |
659 | monitor_print_row(struct json *row, const char *type, const char *uuid, | |
660 | const struct ovsdb_column_set *columns, struct table *t) | |
661 | { | |
662 | size_t i; | |
663 | ||
664 | if (!row) { | |
665 | ovs_error(0, "missing %s row", type); | |
666 | return; | |
667 | } else if (row->type != JSON_OBJECT) { | |
668 | ovs_error(0, "<row> is not object"); | |
669 | return; | |
670 | } | |
671 | ||
672 | table_add_row(t); | |
673 | table_add_cell(t, uuid); | |
674 | table_add_cell(t, type); | |
675 | for (i = 0; i < columns->n_columns; i++) { | |
676 | const struct ovsdb_column *column = columns->columns[i]; | |
677 | struct json *value = shash_find_data(json_object(row), column->name); | |
678 | if (value) { | |
679 | table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT)); | |
680 | } else { | |
681 | table_add_cell(t, ""); | |
682 | } | |
683 | } | |
684 | } | |
685 | ||
686 | static void | |
687 | monitor_print(struct json *table_updates, | |
688 | const struct ovsdb_table_schema *table, | |
689 | const struct ovsdb_column_set *columns, bool initial) | |
690 | { | |
691 | struct json *table_update; | |
692 | struct shash_node *node; | |
693 | struct table t; | |
694 | size_t i; | |
695 | ||
696 | table_init(&t); | |
697 | ||
698 | if (table_updates->type != JSON_OBJECT) { | |
699 | ovs_error(0, "<table-updates> is not object"); | |
700 | return; | |
701 | } | |
702 | table_update = shash_find_data(json_object(table_updates), table->name); | |
703 | if (!table_update) { | |
704 | return; | |
705 | } | |
706 | if (table_update->type != JSON_OBJECT) { | |
707 | ovs_error(0, "<table-update> is not object"); | |
708 | return; | |
709 | } | |
710 | ||
711 | table_add_column(&t, "row"); | |
712 | table_add_column(&t, "action"); | |
713 | for (i = 0; i < columns->n_columns; i++) { | |
714 | table_add_column(&t, "%s", columns->columns[i]->name); | |
715 | } | |
716 | SHASH_FOR_EACH (node, json_object(table_update)) { | |
717 | struct json *row_update = node->data; | |
718 | struct json *old, *new; | |
719 | ||
720 | if (row_update->type != JSON_OBJECT) { | |
721 | ovs_error(0, "<row-update> is not object"); | |
722 | continue; | |
723 | } | |
724 | old = shash_find_data(json_object(row_update), "old"); | |
725 | new = shash_find_data(json_object(row_update), "new"); | |
726 | if (initial) { | |
727 | monitor_print_row(new, "initial", node->name, columns, &t); | |
728 | } else if (!old) { | |
729 | monitor_print_row(new, "insert", node->name, columns, &t); | |
730 | } else if (!new) { | |
731 | monitor_print_row(old, "delete", node->name, columns, &t); | |
732 | } else { | |
733 | monitor_print_row(old, "old", node->name, columns, &t); | |
734 | monitor_print_row(new, "new", "", columns, &t); | |
735 | } | |
736 | } | |
737 | table_print(&t); | |
738 | table_destroy(&t); | |
739 | } | |
740 | ||
741 | static void | |
742 | do_monitor(int argc, char *argv[]) | |
743 | { | |
744 | struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; | |
745 | struct ovsdb_table_schema *table; | |
746 | struct ovsdb_schema *schema; | |
747 | struct jsonrpc_msg *request; | |
748 | struct jsonrpc *rpc; | |
749 | struct json *select, *monitor, *monitor_request, *monitor_requests, | |
750 | *request_id; | |
751 | ||
752 | rpc = open_jsonrpc(argv[1]); | |
753 | ||
754 | schema = fetch_schema_from_rpc(rpc); | |
755 | table = shash_find_data(&schema->tables, argv[2]); | |
756 | if (!table) { | |
757 | ovs_fatal(0, "%s: no table named \"%s\"", argv[1], argv[2]); | |
758 | } | |
759 | ||
760 | if (argc >= 4 && *argv[3] != '\0') { | |
761 | char *save_ptr = NULL; | |
762 | char *token; | |
763 | ||
764 | for (token = strtok_r(argv[3], ",", &save_ptr); token != NULL; | |
765 | token = strtok_r(NULL, ",", &save_ptr)) { | |
766 | const struct ovsdb_column *column; | |
767 | column = ovsdb_table_schema_get_column(table, token); | |
768 | if (!column) { | |
769 | ovs_fatal(0, "%s: table \"%s\" does not have a " | |
770 | "column named \"%s\"", argv[1], argv[2], token); | |
771 | } | |
772 | ovsdb_column_set_add(&columns, column); | |
773 | } | |
774 | } else { | |
775 | struct shash_node *node; | |
776 | ||
777 | SHASH_FOR_EACH (node, &table->columns) { | |
778 | const struct ovsdb_column *column = node->data; | |
779 | if (column->index != OVSDB_COL_UUID) { | |
780 | ovsdb_column_set_add(&columns, column); | |
781 | } | |
782 | } | |
783 | } | |
784 | ||
785 | if (argc >= 5 && *argv[4] != '\0') { | |
786 | char *save_ptr = NULL; | |
787 | char *token; | |
788 | ||
789 | select = json_object_create(); | |
790 | for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL; | |
791 | token = strtok_r(NULL, ",", &save_ptr)) { | |
792 | json_object_put(select, token, json_boolean_create(true)); | |
793 | } | |
794 | } else { | |
795 | select = NULL; | |
796 | } | |
797 | ||
798 | monitor_request = json_object_create(); | |
799 | json_object_put(monitor_request, | |
800 | "columns", ovsdb_column_set_to_json(&columns)); | |
801 | if (select) { | |
802 | json_object_put(monitor_request, "select", select); | |
803 | } | |
804 | ||
805 | monitor_requests = json_object_create(); | |
806 | json_object_put(monitor_requests, argv[2], monitor_request); | |
807 | ||
808 | monitor = json_array_create_2(json_null_create(), monitor_requests); | |
20bed8be | 809 | request = jsonrpc_create_request("monitor", monitor, NULL); |
a8425c53 BP |
810 | request_id = json_clone(request->id); |
811 | jsonrpc_send(rpc, request); | |
812 | for (;;) { | |
813 | struct jsonrpc_msg *msg; | |
814 | int error; | |
815 | ||
816 | error = jsonrpc_recv_block(rpc, &msg); | |
817 | if (error) { | |
818 | ovs_fatal(error, "%s: receive failed", argv[1]); | |
819 | } | |
820 | ||
821 | if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) { | |
822 | jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params), | |
823 | msg->id)); | |
824 | } else if (msg->type == JSONRPC_REPLY | |
825 | && json_equal(msg->id, request_id)) { | |
826 | monitor_print(msg->result, table, &columns, true); | |
3f262d7d BP |
827 | fflush(stdout); |
828 | daemonize(); | |
a8425c53 BP |
829 | } else if (msg->type == JSONRPC_NOTIFY |
830 | && !strcmp(msg->method, "update")) { | |
831 | struct json *params = msg->params; | |
832 | if (params->type == JSON_ARRAY | |
833 | && params->u.array.n == 2 | |
834 | && params->u.array.elems[0]->type == JSON_NULL) { | |
835 | monitor_print(params->u.array.elems[1], | |
836 | table, &columns, false); | |
3f262d7d | 837 | fflush(stdout); |
a8425c53 BP |
838 | } |
839 | } | |
840 | } | |
841 | } | |
842 | ||
d0632593 BP |
843 | static void |
844 | do_help(int argc UNUSED, char *argv[] UNUSED) | |
845 | { | |
846 | usage(); | |
847 | } | |
848 | ||
849 | static const struct command all_commands[] = { | |
850 | { "get-schema", 1, 1, do_get_schema }, | |
851 | { "list-tables", 1, 1, do_list_tables }, | |
852 | { "list-columns", 1, 2, do_list_columns }, | |
6d65eee8 | 853 | { "transact", 2, 2, do_transact }, |
a8425c53 | 854 | { "monitor", 2, 4, do_monitor }, |
d0632593 BP |
855 | { "help", 0, INT_MAX, do_help }, |
856 | { NULL, 0, 0, NULL }, | |
857 | }; |