ovsdb-client: Make "server" and "database" arguments optional.
authorBen Pfaff <blp@nicira.com>
Tue, 26 Jul 2011 17:16:15 +0000 (10:16 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 26 Jul 2011 23:50:08 +0000 (16:50 -0700)
In practice these arguments are almost always the defaults, so this change
makes ovsdb-client much more usable interactively.

ovsdb/ovsdb-client.1.in
ovsdb/ovsdb-client.c
tests/ovsdb-server.at

index 7d8d0b061aaa87997e4d2ca1aadf2ea3e394970e..ede3e4a36878724c973af35c5fe4106309375716 100644 (file)
 ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 .
 .SH SYNOPSIS
-\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-dbs\fI server\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-dbs \fR[\fIserver\fR]
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema\fI server database\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema \fR[\fIserver\fR] \fR[\fIdatabase\fR]
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema\-version\fI server database\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBget\-schema\-version\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-tables\fI server database\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-tables\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-columns\fI server database \fR[\fItable\fR]
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-columns\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] [\fItable\fR]
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBtransact\fI server transaction\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBtransact\fI \fR[\fIserver\fR] \fItransaction\fR
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBdump\fI server database\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBdump\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]\fR
 .br
-\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI server database table\fR
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR
 [\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
 .br
 \fBovsdb\-client help\fR
@@ -44,26 +44,29 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 .
 .SH DESCRIPTION
 The \fBovsdb\-client\fR program is a command-line client for
-interacting with a running \fBovsdb\-server\fR process.  For each
-command, the \fIserver\fR to connect to must be specified in one of
-the following forms:
+interacting with a running \fBovsdb\-server\fR process.
+Each command connects to an OVSDB server, which is
+\fBunix:@RUNDIR@/db.sock\fR by default, or may be specified as
+\fIserver\fR in one of the following forms:
 .RS
 .so ovsdb/remote-active.man
 .so ovsdb/remote-passive.man
 .RE
+.PP
+The default \fIdatabase\fR is \fBOpen_vSwitch\fR.
 .
 .SS "Commands"
 The following commands are implemented:
-.IP "\fBlist\-dbs\fI server\fR"
+.IP "\fBlist\-dbs \fR[\fIserver\fR]"
 Connects to \fIserver\fR, retrieves the list of known databases, and
 prints them one per line.  These database names are the ones that may
 be used for \fIdatabase\fR in the following commands.
 .
-.IP "\fBget\-schema\fI server database\fR"
+.IP "\fBget\-schema \fR[\fIserver\fR] \fR[\fIdatabase\fR]"
 Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
 prints it in JSON format.
 .
-.IP "\fBget\-schema\-version\fI server database\fR"
+.IP "\fBget\-schema\-version\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]"
 Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
 prints its version number on stdout.  A schema version number has the form
 \fIx\fB.\fIy\fB.\fIz\fR.  See \fBovs\-vswitchd.conf.db\fR(5) for
@@ -76,27 +79,27 @@ If \fIdatabase\fR was created before schema versioning was introduced,
 then it will not have a version number and this command will print a
 blank line.
 .
-.IP "\fBlist\-tables\fI server database\fR"
+.IP "\fBlist\-columns\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]"
 Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
 prints a table listing the name of each table
 within the database.
 .
-.IP "\fBlist\-columns\fI server database \fR[\fItable\fR]"
+.IP "\fBlist\-columns\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR"
 Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
 prints a table listing the name and type of each
 column.  If \fItable\fR is specified, only columns in that table are
 listed; otherwise, the tables include columns in all tables.
 .
-.IP "\fBtransact\fI server transaction\fR"
+.IP "\fBtransact\fI \fR[\fIserver\fR] \fItransaction\fR"
 Connects to \fIserver\fR, sends it the specified \fItransaction\fR,
 which must be a JSON array containing one or more valid OVSDB
 operations, and prints the received reply on stdout.
 .
-.IP "\fBdump\fI server database\fR"
+.IP "\fBdump\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]\fR"
 Connects to \fIserver\fR, retrieves all of the data in \fIdatabase\fR,
 and prints it on stdout as a series of tables.
 .
-.IP "\fBmonitor\fI server database table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
+.IP "\fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR"
 Connects to \fIserver\fR and monitors the contents of \fItable\fR in
 \fIdatabase\fR.  By default, the initial contents of \fItable\fR are
 printed, followed by each change as it occurs.  If at least one
index 800d3e1729867db5ca5ee0928fec0a8c2149c630..2ed1189523afcea597b250c890b1c4584fd1aa45 100644 (file)
@@ -17,6 +17,7 @@
 #include <config.h>
 
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <getopt.h>
 #include <limits.h>
@@ -29,6 +30,7 @@
 #include "column.h"
 #include "compiler.h"
 #include "daemon.h"
+#include "dirs.h"
 #include "dynamic-string.h"
 #include "json.h"
 #include "jsonrpc.h"
@@ -37,6 +39,7 @@
 #include "ovsdb-data.h"
 #include "ovsdb-error.h"
 #include "sort.h"
+#include "sset.h"
 #include "stream.h"
 #include "stream-ssl.h"
 #include "table.h"
 
 VLOG_DEFINE_THIS_MODULE(ovsdb_client);
 
+enum args_needed {
+    NEED_NONE,            /* No JSON-RPC connection or database name needed. */
+    NEED_RPC,             /* JSON-RPC connection needed. */
+    NEED_DATABASE         /* JSON-RPC connection and database name needed. */
+};
+
+struct ovsdb_client_command {
+    const char *name;
+    enum args_needed need;
+    int min_args;
+    int max_args;
+    void (*handler)(struct jsonrpc *rpc, const char *database,
+                    int argc, char *argv[]);
+};
+
 /* Format for table output. */
 static struct table_style table_style = TABLE_STYLE_DEFAULT;
 
-static const struct command all_commands[];
+static const struct ovsdb_client_command all_commands[];
 
 static void usage(void) NO_RETURN;
 static void parse_options(int argc, char *argv[]);
+static struct jsonrpc *open_jsonrpc(const char *server);
+static void fetch_dbs(struct jsonrpc *, struct sset *dbs);
 
 int
 main(int argc, char *argv[])
 {
+    const struct ovsdb_client_command *command;
+    const char *database;
+    struct jsonrpc *rpc;
+
     proctitle_init(argc, argv);
     set_program_name(argv[0]);
     parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-    run_command(argc - optind, argv + optind, all_commands);
+
+    if (optind >= argc) {
+        ovs_fatal(0, "missing command name; use --help for help");
+    }
+
+    for (command = all_commands; ; command++) {
+        if (!command->name) {
+            VLOG_FATAL("unknown command '%s'; use --help for help",
+                       argv[optind]);
+        } else if (!strcmp(command->name, argv[optind])) {
+            break;
+        }
+    }
+    optind++;
+
+    if (command->need != NEED_NONE) {
+        if (argc - optind > command->min_args
+            && (isalpha((unsigned char) argv[optind][0])
+                && strchr(argv[optind], ':'))) {
+            rpc = open_jsonrpc(argv[optind++]);
+        } else {
+            char *sock = xasprintf("unix:%s/db.sock", ovs_rundir());
+            rpc = open_jsonrpc(sock);
+            free(sock);
+        }
+    } else {
+        rpc = NULL;
+    }
+
+    if (command->need == NEED_DATABASE) {
+        struct sset dbs;
+
+        sset_init(&dbs);
+        fetch_dbs(rpc, &dbs);
+        if (argc - optind > command->min_args
+            && sset_contains(&dbs, argv[optind])) {
+            database = argv[optind++];
+        } else if (sset_count(&dbs) == 1) {
+            database = xstrdup(SSET_FIRST(&dbs));
+        } else if (sset_contains(&dbs, "Open_vSwitch")) {
+            database = "Open_vSwitch";
+        } else {
+            ovs_fatal(0, "no default database for `%s' command, please "
+                      "specify a database name", command->name);
+        }
+        sset_destroy(&dbs);
+    } else {
+        database = NULL;
+    }
+
+    if (argc - optind < command->min_args ||
+        argc - optind > command->max_args) {
+        VLOG_FATAL("invalid syntax for '%s' (use --help for help)",
+                    command->name);
+    }
+
+    command->handler(rpc, database, argc - optind, argv + optind);
+
+    jsonrpc_close(rpc);
+
+    if (ferror(stdout)) {
+        VLOG_FATAL("write to stdout failed");
+    }
+    if (ferror(stderr)) {
+        VLOG_FATAL("write to stderr failed");
+    }
+
     return 0;
 }
 
@@ -137,27 +227,29 @@ usage(void)
     printf("%s: Open vSwitch database JSON-RPC client\n"
            "usage: %s [OPTIONS] COMMAND [ARG...]\n"
            "\nValid commands are:\n"
-           "\n  list-dbs SERVER\n"
+           "\n  list-dbs [SERVER]\n"
            "    list databases available on SERVER\n"
-           "\n  get-schema SERVER DATABASE\n"
+           "\n  get-schema [SERVER] [DATABASE]\n"
            "    retrieve schema for DATABASE from SERVER\n"
-           "\n  get-schema-version SERVER DATABASE\n"
+           "\n  get-schema-version [SERVER] [DATABASE]\n"
            "    retrieve schema for DATABASE from SERVER and report only its\n"
            "    version number on stdout\n"
-           "\n  list-tables SERVER DATABASE\n"
+           "\n  list-tables [SERVER] [DATABASE]\n"
            "    list tables for DATABASE on SERVER\n"
-           "\n  list-columns SERVER DATABASE [TABLE]\n"
+           "\n  list-columns [SERVER] [DATABASE] [TABLE]\n"
            "    list columns in TABLE (or all tables) in DATABASE on SERVER\n"
-           "\n  transact SERVER TRANSACTION\n"
+           "\n  transact [SERVER] TRANSACTION\n"
            "    run TRANSACTION (a JSON array of operations) on SERVER\n"
            "    and print the results as JSON on stdout\n"
-           "\n  monitor SERVER DATABASE TABLE [COLUMN,...]...\n"
+           "\n  monitor [SERVER] [DATABASE] TABLE [COLUMN,...]...\n"
            "    monitor contents of COLUMNs in TABLE in DATABASE on SERVER.\n"
            "    COLUMNs may include !initial, !insert, !delete, !modify\n"
            "    to avoid seeing the specified kinds of changes.\n"
-           "\n  dump SERVER DATABASE\n"
-           "    dump contents of DATABASE on SERVER to stdout\n",
-           program_name, program_name);
+           "\n  dump [SERVER] [DATABASE]\n"
+           "    dump contents of DATABASE on SERVER to stdout\n"
+           "\nThe default SERVER is unix:%s/db.sock.\n"
+           "The default DATABASE is Open_vSwitch.\n",
+           program_name, program_name, ovs_rundir());
     stream_usage("SERVER", true, true, true);
     printf("\nOutput formatting options:\n"
            "  -f, --format=FORMAT         set output formatting to FORMAT\n"
@@ -236,7 +328,7 @@ check_ovsdb_error(struct ovsdb_error *error)
 }
 
 static struct ovsdb_schema *
-fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database)
+fetch_schema(struct jsonrpc *rpc, const char *database)
 {
     struct jsonrpc_msg *request, *reply;
     struct ovsdb_schema *schema;
@@ -256,29 +348,13 @@ fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database)
     return schema;
 }
 
-static struct ovsdb_schema *
-fetch_schema(const char *server, const char *database)
-{
-    struct ovsdb_schema *schema;
-    struct jsonrpc *rpc;
-
-    rpc = open_jsonrpc(server);
-    schema = fetch_schema_from_rpc(rpc, database);
-    jsonrpc_close(rpc);
-
-    return schema;
-}
-\f
-\f
 static void
-do_list_dbs(int argc OVS_UNUSED, char *argv[])
+fetch_dbs(struct jsonrpc *rpc, struct sset *dbs)
 {
     struct jsonrpc_msg *request, *reply;
-    struct jsonrpc *rpc;
     int error;
     size_t i;
 
-    rpc = open_jsonrpc(argv[1]);
     request = jsonrpc_create_request("list_dbs", json_array_create_empty(),
                                      NULL);
     error = jsonrpc_transact_block(rpc, request, &reply);
@@ -296,35 +372,53 @@ do_list_dbs(int argc OVS_UNUSED, char *argv[])
         if (name->type != JSON_STRING) {
             ovs_fatal(0, "list_dbs response %zu is not string", i);
         }
-        puts(name->u.string);
+        sset_add(dbs, name->u.string);
     }
     jsonrpc_msg_destroy(reply);
 }
+\f
+static void
+do_list_dbs(struct jsonrpc *rpc, const char *database OVS_UNUSED,
+            int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    const char *db_name;
+    struct sset dbs;
+
+    sset_init(&dbs);
+    fetch_dbs(rpc, &dbs);
+    SSET_FOR_EACH (db_name, &dbs) {
+        puts(db_name);
+    }
+    sset_destroy(&dbs);
+}
 
 static void
-do_get_schema(int argc OVS_UNUSED, char *argv[])
+do_get_schema(struct jsonrpc *rpc, const char *database,
+              int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
-    struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]);
+    struct ovsdb_schema *schema = fetch_schema(rpc, database);
     print_and_free_json(ovsdb_schema_to_json(schema));
     ovsdb_schema_destroy(schema);
 }
 
 static void
-do_get_schema_version(int argc OVS_UNUSED, char *argv[])
+do_get_schema_version(struct jsonrpc *rpc, const char *database,
+                      int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
-    struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]);
+    struct ovsdb_schema *schema = fetch_schema(rpc, database);
     puts(schema->version);
     ovsdb_schema_destroy(schema);
 }
 
 static void
-do_list_tables(int argc OVS_UNUSED, char *argv[])
+do_list_tables(struct jsonrpc *rpc, const char *database,
+               int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
     struct ovsdb_schema *schema;
     struct shash_node *node;
     struct table t;
 
-    schema = fetch_schema(argv[1], argv[2]);
+    schema = fetch_schema(rpc, database);
     table_init(&t);
     table_add_column(&t, "Table");
     SHASH_FOR_EACH (node, &schema->tables) {
@@ -338,14 +432,15 @@ do_list_tables(int argc OVS_UNUSED, char *argv[])
 }
 
 static void
-do_list_columns(int argc OVS_UNUSED, char *argv[])
+do_list_columns(struct jsonrpc *rpc, const char *database,
+                int argc OVS_UNUSED, char *argv[])
 {
-    const char *table_name = argv[3];
+    const char *table_name = argv[0];
     struct ovsdb_schema *schema;
     struct shash_node *table_node;
     struct table t;
 
-    schema = fetch_schema(argv[1], argv[2]);
+    schema = fetch_schema(rpc, database);
     table_init(&t);
     if (!table_name) {
         table_add_column(&t, "Table");
@@ -375,16 +470,15 @@ do_list_columns(int argc OVS_UNUSED, char *argv[])
 }
 
 static void
-do_transact(int argc OVS_UNUSED, char *argv[])
+do_transact(struct jsonrpc *rpc, const char *database OVS_UNUSED,
+            int argc OVS_UNUSED, char *argv[])
 {
     struct jsonrpc_msg *request, *reply;
     struct json *transaction;
-    struct jsonrpc *rpc;
     int error;
 
-    transaction = parse_json(argv[2]);
+    transaction = parse_json(argv[0]);
 
-    rpc = open_jsonrpc(argv[1]);
     request = jsonrpc_create_request("transact", transaction, NULL);
     error = jsonrpc_transact_block(rpc, request, &reply);
     if (error) {
@@ -397,7 +491,6 @@ do_transact(int argc OVS_UNUSED, char *argv[])
     print_json(reply->result);
     putchar('\n');
     jsonrpc_msg_destroy(reply);
-    jsonrpc_close(rpc);
 }
 
 static void
@@ -565,22 +658,19 @@ parse_monitor_columns(char *arg, const char *server, const char *database,
 }
 
 static void
-do_monitor(int argc, char *argv[])
+do_monitor(struct jsonrpc *rpc, const char *database,
+           int argc, char *argv[])
 {
-    const char *server = argv[1];
-    const char *database = argv[2];
-    const char *table_name = argv[3];
+    const char *server = jsonrpc_get_name(rpc);
+    const char *table_name = argv[0];
     struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
     struct ovsdb_table_schema *table;
     struct ovsdb_schema *schema;
     struct jsonrpc_msg *request;
-    struct jsonrpc *rpc;
     struct json *monitor, *monitor_request_array,
         *monitor_requests, *request_id;
 
-    rpc = open_jsonrpc(server);
-
-    schema = fetch_schema_from_rpc(rpc, database);
+    schema = fetch_schema(rpc, database);
     table = shash_find_data(&schema->tables, table_name);
     if (!table) {
         ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
@@ -588,10 +678,10 @@ do_monitor(int argc, char *argv[])
     }
 
     monitor_request_array = json_array_create_empty();
-    if (argc > 4) {
+    if (argc > 1) {
         int i;
 
-        for (i = 4; i < argc; i++) {
+        for (i = 1; i < argc; i++) {
             json_array_add(
                 monitor_request_array,
                 parse_monitor_columns(argv[i], server, database, table,
@@ -795,15 +885,12 @@ dump_table(const struct ovsdb_table_schema *ts, struct json_array *rows)
 }
 
 static void
-do_dump(int argc OVS_UNUSED, char *argv[])
+do_dump(struct jsonrpc *rpc, const char *database,
+        int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
-    const char *server = argv[1];
-    const char *database = argv[2];
-
     struct jsonrpc_msg *request, *reply;
     struct ovsdb_schema *schema;
     struct json *transaction;
-    struct jsonrpc *rpc;
     int error;
 
     const struct shash_node **tables;
@@ -811,9 +898,7 @@ do_dump(int argc OVS_UNUSED, char *argv[])
 
     size_t i;
 
-    rpc = open_jsonrpc(server);
-
-    schema = fetch_schema_from_rpc(rpc, database);
+    schema = fetch_schema(rpc, database);
     tables = shash_sort(&schema->tables);
     n_tables = shash_count(&schema->tables);
 
@@ -872,20 +957,30 @@ do_dump(int argc OVS_UNUSED, char *argv[])
 }
 
 static void
-do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+do_help(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,
+        int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
     usage();
 }
 
-static const struct command all_commands[] = {
-    { "list-dbs", 1, 1, do_list_dbs },
-    { "get-schema", 2, 2, do_get_schema },
-    { "get-schema-version", 2, 2, do_get_schema_version },
-    { "list-tables", 2, 2, do_list_tables },
-    { "list-columns", 2, 3, do_list_columns },
-    { "transact", 2, 2, do_transact },
-    { "monitor", 3, INT_MAX, do_monitor },
-    { "dump", 2, 2, do_dump },
-    { "help", 0, INT_MAX, do_help },
-    { NULL, 0, 0, NULL },
+/* All command handlers (except for "help") are expected to take an optional
+ * server socket name (e.g. "unix:...") as their first argument.  The socket
+ * name argument must be included in max_args (but left out of min_args).  The
+ * command name and socket name are not included in the arguments passed to the
+ * handler: the argv[0] passed to the handler is the first argument after the
+ * optional server socket name.  The connection to the server is available as
+ * global variable 'rpc'. */
+static const struct ovsdb_client_command all_commands[] = {
+    { "list-dbs",           NEED_RPC,      0, 0,       do_list_dbs },
+    { "get-schema",         NEED_DATABASE, 0, 0,       do_get_schema },
+    { "get-schema-version", NEED_DATABASE, 0, 0,       do_get_schema_version },
+    { "list-tables",        NEED_DATABASE, 0, 0,       do_list_tables },
+    { "list-columns",       NEED_DATABASE, 0, 1,       do_list_columns },
+    { "transact",           NEED_RPC,      1, 1,       do_transact },
+    { "monitor",            NEED_DATABASE, 1, INT_MAX, do_monitor },
+    { "dump",               NEED_DATABASE, 0, 0,       do_dump },
+
+    { "help",               NEED_NONE,     0, INT_MAX, do_help },
+
+    { NULL,                 0,             0, 0,       NULL },
 };
index a7a2829e26f8280a57e3985ada6a459986ca39cc..7d79d220d3e024a981fd91896c77b96dac65de38 100644 (file)
@@ -155,9 +155,9 @@ AT_CHECK(
   [0], [ordinals
 ], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK(
-  [[ovsdb-client get-schema unix:socket nonexistent]], 
-  [1], [], [[ovsdb-client: syntax "{"details":"get_schema request specifies unknown database nonexistent","error":"unknown database","syntax":"[\"nonexistent\"]"}": syntax error: Parsing database schema failed: Required 'name' member is missing.
-]], [test ! -e pid || kill `cat pid`])
+  [[test-jsonrpc request unix:socket get_schema [\"nonexistent\"]]], [0],
+  [[{"error":null,"id":0,"result":{"details":"get_schema request specifies unknown database nonexistent","error":"unknown database","syntax":"[\"nonexistent\"]"}}
+]], [], [test ! -e pid || kill `cat pid`])
 OVSDB_SERVER_SHUTDOWN
 AT_CLEANUP