
The `script_timeout` and `journal_max_age` parameters weren't handled in the monitor alteration code. Also added missing documentation to maxadmin help output for `alter monitor`.
2706 lines
76 KiB
C
2706 lines
76 KiB
C
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2020-01-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
/**
|
|
* @file debugcmd.c - The debug CLI command line interpreter
|
|
*
|
|
* The command interpreter for the dbug user interface. The command
|
|
* structure is such that there are a numerb of commands, notably
|
|
* show and a set of subcommands, the things to show in this case.
|
|
*
|
|
* Each subcommand has a handler function defined for it that is passeed
|
|
* the DCB to use to print the output of the commands and up to 3 arguments
|
|
* as numeric values.
|
|
*
|
|
* There are two "built in" commands, the help command and the quit
|
|
* command.
|
|
*/
|
|
#include <maxscale/cdefs.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/syslog.h>
|
|
#include <telnetd.h>
|
|
|
|
#include <maxscale/adminusers.h>
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/atomic.h>
|
|
#include <maxscale/buffer.h>
|
|
#include <maxscale/config.h>
|
|
#include <maxscale/dcb.h>
|
|
#include <maxscale/filter.h>
|
|
#include <maxscale/housekeeper.h>
|
|
#include <maxscale/log_manager.h>
|
|
#include <maxscale/maxscale.h>
|
|
#include <maxscale/modulecmd.h>
|
|
#include <maxscale/router.h>
|
|
#include <maxscale/server.h>
|
|
#include <maxscale/service.h>
|
|
#include <maxscale/spinlock.h>
|
|
#include <maxscale/users.h>
|
|
#include <maxscale/utils.h>
|
|
#include <maxscale/version.h>
|
|
#include <maxscale/worker.h>
|
|
|
|
#include <debugcli.h>
|
|
|
|
#include "../../../core/maxscale/config_runtime.h"
|
|
#include "../../../core/maxscale/maxscale.h"
|
|
#include "../../../core/maxscale/modules.h"
|
|
#include "../../../core/maxscale/monitor.h"
|
|
#include "../../../core/maxscale/poll.h"
|
|
#include "../../../core/maxscale/session.h"
|
|
|
|
#define MAXARGS 12
|
|
|
|
#define ARG_TYPE_NONE 0
|
|
#define ARG_TYPE_ADDRESS 1
|
|
#define ARG_TYPE_STRING 2
|
|
#define ARG_TYPE_SERVICE 3
|
|
#define ARG_TYPE_SERVER 4
|
|
#define ARG_TYPE_DBUSERS 5
|
|
#define ARG_TYPE_SESSION 6
|
|
#define ARG_TYPE_DCB 7
|
|
#define ARG_TYPE_MONITOR 8
|
|
#define ARG_TYPE_FILTER 9
|
|
#define ARG_TYPE_NUMERIC 10
|
|
|
|
/**
|
|
* The subcommand structure
|
|
*
|
|
* These are the options that may be passed to a command
|
|
*/
|
|
struct subcommand
|
|
{
|
|
char *arg1;
|
|
int argc_min;
|
|
int argc_max;
|
|
void (*fn)();
|
|
char *help;
|
|
char *devhelp;
|
|
int arg_types[MAXARGS];
|
|
};
|
|
|
|
#define EMPTY_OPTION
|
|
|
|
static void telnetdShowUsers(DCB *);
|
|
static void show_log_throttling(DCB *);
|
|
|
|
static void showVersion(DCB *dcb)
|
|
{
|
|
dcb_printf(dcb, "%s\n", MAXSCALE_VERSION);
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the show command
|
|
*/
|
|
struct subcommand showoptions[] =
|
|
{
|
|
#if defined(BUFFER_TRACE)
|
|
{
|
|
"buffers", 0, dprintAllBuffers,
|
|
"Show all buffers with backtrace",
|
|
"Show all buffers with backtrace",
|
|
{0}
|
|
},
|
|
#endif
|
|
{
|
|
"dcbs", 0, 0, dprintAllDCBs,
|
|
"Show all DCBs",
|
|
"Usage: show dcbs",
|
|
{0}
|
|
},
|
|
{
|
|
"dbusers", 1, 1, service_print_users,
|
|
"[deprecated] Show user statistics",
|
|
"See `show authenticators`",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"authenticators", 1, 1, service_print_users,
|
|
"Show authenticator diagnostics for a service",
|
|
"Usage: show authenticators SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service to inspect\n"
|
|
"\n"
|
|
"Example : show authenticators my-service",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"epoll", 0, 0, dprintPollStats,
|
|
"Show the polling system statistics",
|
|
"Usage: show epoll",
|
|
{0}
|
|
},
|
|
{
|
|
"eventstats", 0, 0, dShowEventStats,
|
|
"Show event queue statistics",
|
|
"Usage: show eventstats",
|
|
{0}
|
|
},
|
|
{
|
|
"filter", 1, 1, dprintFilter,
|
|
"Show filter details",
|
|
"Usage: show filter FILTER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"FILTER Filter to show\n"
|
|
"\n"
|
|
"Example: show filter my-filter",
|
|
{ARG_TYPE_FILTER}
|
|
},
|
|
{
|
|
"filters", 0, 0, dprintAllFilters,
|
|
"Show all filters",
|
|
"Usage: show filters",
|
|
{0}
|
|
},
|
|
{
|
|
"log_throttling", 0, 0, show_log_throttling,
|
|
"Show the current log throttling setting (count, window (ms), suppression (ms))",
|
|
"Usage: show log_throttling",
|
|
{0}
|
|
},
|
|
{
|
|
"modules", 0, 0, dprintAllModules,
|
|
"Show all currently loaded modules",
|
|
"Usage: show modules",
|
|
{0}
|
|
},
|
|
{
|
|
"monitor", 1, 1, monitorShow,
|
|
"Show monitor details",
|
|
"Usage: show monitor MONITOR\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"MONITOR Monitor to show\n"
|
|
"\n"
|
|
"Example: show monitor \"Cluster Monitor\"",
|
|
{ARG_TYPE_MONITOR}
|
|
},
|
|
{
|
|
"monitors", 0, 0, monitorShowAll,
|
|
"Show all monitors",
|
|
"Usage: show monitors",
|
|
{0}
|
|
},
|
|
{
|
|
"persistent", 1, 1, dprintPersistentDCBs,
|
|
"Show the persistent connection pool of a server",
|
|
"Usage: show persistent SERVER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVER Server to show\n"
|
|
"\n"
|
|
"Example: show persistent db-server-1",
|
|
{ARG_TYPE_SERVER}
|
|
},
|
|
{
|
|
"server", 1, 1, dprintServer,
|
|
"Show server details",
|
|
"Usage: show server SERVER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVER Server to show\n"
|
|
"\n"
|
|
"Example: show server db-server-1",
|
|
{ARG_TYPE_SERVER}
|
|
},
|
|
{
|
|
"servers", 0, 0, dprintAllServers,
|
|
"Show all servers",
|
|
"Usage: show servers",
|
|
{0}
|
|
},
|
|
{
|
|
"serversjson", 0, 0, dprintAllServersJson,
|
|
"Show all servers in JSON",
|
|
"Usage: show serversjson",
|
|
{0}
|
|
},
|
|
{
|
|
"services", 0, 0, dprintAllServices,
|
|
"Show all configured services in MaxScale",
|
|
"Usage: show services",
|
|
{0}
|
|
},
|
|
{
|
|
"service", 1, 1, dprintService,
|
|
"Show a single service in MaxScale",
|
|
"Usage: show service SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service to show\n"
|
|
"\n"
|
|
"Example: show service my-service",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"session", 1, 1, dprintSession,
|
|
"Show session details",
|
|
"Usage: show session SESSION\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SESSION Session ID of the session to show\n"
|
|
"\n"
|
|
"Example: show session 5",
|
|
{ARG_TYPE_SESSION}
|
|
},
|
|
{
|
|
"sessions", 0, 0, dprintAllSessions,
|
|
"Show all active sessions in MaxScale",
|
|
"Usage: show sessions",
|
|
{0}
|
|
},
|
|
{
|
|
"tasks", 0, 0, hkshow_tasks,
|
|
"Show all active housekeeper tasks in MaxScale",
|
|
"Usage: show tasks",
|
|
{0}
|
|
},
|
|
{
|
|
"threads", 0, 0, dShowThreads,
|
|
"Show the status of the worker threads in MaxScale",
|
|
"Usage: show threads",
|
|
{0}
|
|
},
|
|
{
|
|
"users", 0, 0, telnetdShowUsers,
|
|
"Show enabled Linux accounts",
|
|
"Usage: show users",
|
|
{0}
|
|
},
|
|
{
|
|
"version", 0, 0, showVersion,
|
|
"Show the MaxScale version number",
|
|
"Usage: show version",
|
|
{0}
|
|
},
|
|
{ EMPTY_OPTION}
|
|
};
|
|
|
|
bool listfuncs_cb(const MODULECMD *cmd, void *data)
|
|
{
|
|
DCB *dcb = (DCB*)data;
|
|
|
|
dcb_printf(dcb, "Command:\t%s %s\n", cmd->domain, cmd->identifier);
|
|
dcb_printf(dcb, "Description:\t%s\n", cmd->description);
|
|
dcb_printf(dcb, "Parameters:\t");
|
|
|
|
for (int i = 0; i < cmd->arg_count_max; i++)
|
|
{
|
|
modulecmd_arg_type_t *type = &cmd->arg_types[i];
|
|
|
|
if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT)
|
|
{
|
|
dcb_printf(dcb, "%s%s",
|
|
modulecmd_argtype_to_str(&cmd->arg_types[i]),
|
|
i < cmd->arg_count_max - 1 ? " " : "");
|
|
}
|
|
}
|
|
|
|
dcb_printf(dcb, "\n\n");
|
|
|
|
for (int i = 0; i < cmd->arg_count_max; i++)
|
|
{
|
|
modulecmd_arg_type_t *type = &cmd->arg_types[i];
|
|
|
|
if (MODULECMD_GET_TYPE(type) != MODULECMD_ARG_OUTPUT)
|
|
{
|
|
dcb_printf(dcb, " %s - %s\n",
|
|
modulecmd_argtype_to_str(&cmd->arg_types[i]),
|
|
cmd->arg_types[i].description);
|
|
}
|
|
}
|
|
|
|
dcb_printf(dcb, "\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
void dListCommands(DCB *dcb, const char *domain, const char *ident)
|
|
{
|
|
modulecmd_foreach(domain, ident, listfuncs_cb, dcb);
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the list command
|
|
*/
|
|
struct subcommand listoptions[] =
|
|
{
|
|
{
|
|
"clients", 0, 0, dListClients,
|
|
"List all the client connections to MaxScale",
|
|
"Usage: list clients",
|
|
{0}
|
|
},
|
|
{
|
|
"dcbs", 0, 0, dListDCBs,
|
|
"List all active connections within MaxScale",
|
|
"Usage: list dcbs",
|
|
{0}
|
|
},
|
|
{
|
|
"filters", 0, 0, dListFilters,
|
|
"List all filters",
|
|
"Usage: list filters",
|
|
{0}
|
|
},
|
|
{
|
|
"listeners", 0, 0, dListListeners,
|
|
"List all listeners",
|
|
"Usage: list listeners",
|
|
{0}
|
|
},
|
|
{
|
|
"modules", 0, 0, dprintAllModules,
|
|
"List all currently loaded modules",
|
|
"Usage: list modules",
|
|
{0}
|
|
},
|
|
{
|
|
"monitors", 0, 0, monitorList,
|
|
"List all monitors",
|
|
"Usage: list monitors",
|
|
{0}
|
|
},
|
|
{
|
|
"services", 0, 0, dListServices,
|
|
"List all services",
|
|
"Usage: list services",
|
|
{0}
|
|
},
|
|
{
|
|
"servers", 0, 0, dListServers,
|
|
"List all servers",
|
|
"Usage: list servers",
|
|
{0}
|
|
},
|
|
{
|
|
"sessions", 0, 0, dListSessions,
|
|
"List all the active sessions within MaxScale",
|
|
"Usage: list sessions",
|
|
{0}
|
|
},
|
|
{
|
|
"threads", 0, 0, dShowThreads,
|
|
"List the status of the polling threads in MaxScale",
|
|
"Usage: list threads",
|
|
{0}
|
|
},
|
|
{
|
|
"commands", 0, 2, dListCommands,
|
|
"List registered commands",
|
|
"Usage: list commands [MODULE] [COMMAND]\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"MODULE Regular expressions for filtering module names\n"
|
|
"COMMAND Regular expressions for filtering module command names\n"
|
|
"\n"
|
|
"Example: list commands my-module my-command",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{ EMPTY_OPTION}
|
|
};
|
|
|
|
static void shutdown_server()
|
|
{
|
|
maxscale_shutdown();
|
|
}
|
|
|
|
static void shutdown_service(DCB *dcb, SERVICE *service);
|
|
static void shutdown_monitor(DCB *dcb, MXS_MONITOR *monitor);
|
|
|
|
static void
|
|
shutdown_listener(DCB *dcb, SERVICE *service, const char *name)
|
|
{
|
|
if (serviceStopListener(service, name))
|
|
{
|
|
dcb_printf(dcb, "Stopped listener '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to stop listener '%s'\n", name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the shutdown command
|
|
*/
|
|
struct subcommand shutdownoptions[] =
|
|
{
|
|
{
|
|
"maxscale",
|
|
0, 0,
|
|
shutdown_server,
|
|
"Initiate a controlled shutdown of MaxScale",
|
|
"Usage: shutdown maxscale",
|
|
{0}
|
|
},
|
|
{
|
|
"monitor",
|
|
1, 1,
|
|
shutdown_monitor,
|
|
"Stop a monitor",
|
|
"Usage: shutdown monitor MONITOR\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"MONITOR Monitor to stop\n"
|
|
"\n"
|
|
"Example: shutdown monitor db-cluster-monitor",
|
|
{ARG_TYPE_MONITOR}
|
|
},
|
|
{
|
|
"service",
|
|
1, 1,
|
|
shutdown_service,
|
|
"Stop a service",
|
|
"Usage: shutdown service SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service to stop\n"
|
|
"\n"
|
|
"Example: shutdown service \"Sales Database\"",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"listener",
|
|
2, 2,
|
|
shutdown_listener,
|
|
"Stop a listener",
|
|
"Usage: shutdown listener SERVICE LISTENER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service where LISTENER points to\n"
|
|
"LISTENER The listener to stop\n"
|
|
"\n"
|
|
"Example: shutdown listener \"RW Service\" \"RW Listener\"",
|
|
{ARG_TYPE_SERVICE, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
static void sync_logs(DCB *dcb)
|
|
{
|
|
if (mxs_log_flush_sync() == 0)
|
|
{
|
|
dcb_printf(dcb, "Logs flushed to disk\n");
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to flush logs to disk. Read the error log for "
|
|
"more details.\n");
|
|
}
|
|
}
|
|
|
|
struct subcommand syncoptions[] =
|
|
{
|
|
{
|
|
"logs",
|
|
0, 0,
|
|
sync_logs,
|
|
"Flush log files to disk",
|
|
"Usage: flush logs",
|
|
{0}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
static void restart_service(DCB *dcb, SERVICE *service);
|
|
static void restart_monitor(DCB *dcb, MXS_MONITOR *monitor);
|
|
|
|
static void
|
|
restart_listener(DCB *dcb, SERVICE *service, const char *name)
|
|
{
|
|
if (serviceStartListener(service, name))
|
|
{
|
|
dcb_printf(dcb, "Restarted listener '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to restart listener '%s'\n", name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the restart command
|
|
*/
|
|
struct subcommand restartoptions[] =
|
|
{
|
|
{
|
|
"monitor", 1, 1, restart_monitor,
|
|
"Restart a monitor",
|
|
"Usage: restart monitor NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Monitor to restart\n"
|
|
"\n"
|
|
"Example: restart monitor db-cluster-monitor",
|
|
{ARG_TYPE_MONITOR}
|
|
},
|
|
{
|
|
"service", 1, 1, restart_service,
|
|
"Restart a service",
|
|
"Usage: restart service NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Service to restart\n"
|
|
"\n"
|
|
"Example: restart service \"Sales Database\"",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"listener", 2, 2, restart_listener,
|
|
"Restart a listener",
|
|
"Usage: restart listener NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Listener to restart\n"
|
|
"\n"
|
|
"Example: restart listener \"RW Service\" \"RW Listener\"",
|
|
{ARG_TYPE_SERVICE, ARG_TYPE_STRING}
|
|
},
|
|
{ EMPTY_OPTION }
|
|
};
|
|
|
|
static void set_server(DCB *dcb, SERVER *server, char *bit);
|
|
static void set_pollsleep(DCB *dcb, int);
|
|
static void set_nbpoll(DCB *dcb, int);
|
|
static void set_log_throttling(DCB *dcb, int count, int window_ms, int suppress_ms);
|
|
/**
|
|
* The subcommands of the set command
|
|
*/
|
|
struct subcommand setoptions[] =
|
|
{
|
|
{
|
|
"server", 2, 2, set_server,
|
|
"Set the status of a server",
|
|
"Usage: set server NAME STATUS\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Server name\n"
|
|
"STATUS The status to set\n"
|
|
"\n"
|
|
"Example: set server dbnode4 master",
|
|
{ARG_TYPE_SERVER, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"pollsleep", 1, 1, set_pollsleep,
|
|
"Set poll sleep period",
|
|
"Usage: set pollsleep VALUE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"VALUE Poll sleep in milliseconds\n"
|
|
"\n"
|
|
"Sets the maximum poll sleep period in milliseconds\n"
|
|
"\n"
|
|
"Example: set pollsleep 100",
|
|
{ARG_TYPE_NUMERIC}
|
|
},
|
|
{
|
|
"nbpolls", 1, 1, set_nbpoll,
|
|
"Set non-blocking polls",
|
|
"Usage: set nbpolls VALUE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"VALUE Number of non-blocking polls\n"
|
|
"\n"
|
|
"Sets the number of non-blocking polls\n"
|
|
"\n"
|
|
"Example: set nbpolls 5",
|
|
{ARG_TYPE_NUMERIC}
|
|
},
|
|
{
|
|
"log_throttling", 3, 3, set_log_throttling,
|
|
"Set the log throttling configuration",
|
|
"Usage: set log_throttling COUNT WINDOW SUPPRESS\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"COUNT Number of messages to log before throttling\n"
|
|
"WINDOW The time window in milliseconds where COUNT messages can be logged\n"
|
|
"SUPPRESS The log suppression in milliseconds once COUNT messages have been logged\n"
|
|
"\n"
|
|
"Example: set log_throttling 5 1000 25000",
|
|
{ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC, ARG_TYPE_NUMERIC}
|
|
},
|
|
{ EMPTY_OPTION }
|
|
};
|
|
|
|
static void clear_server(DCB *dcb, SERVER *server, char *bit);
|
|
/**
|
|
* The subcommands of the clear command
|
|
*/
|
|
struct subcommand clearoptions[] =
|
|
{
|
|
{
|
|
"server", 2, 2, clear_server,
|
|
"Clear server status",
|
|
"Usage: clear server NAME STATUS\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Server name\n"
|
|
"STATUS The status to clear\n"
|
|
"\n"
|
|
"Example: clear server dbnode2 master",
|
|
{ARG_TYPE_SERVER, ARG_TYPE_STRING}
|
|
},
|
|
{ EMPTY_OPTION }
|
|
};
|
|
|
|
static void reload_dbusers(DCB *dcb, SERVICE *service);
|
|
static void reload_config(DCB *dcb);
|
|
|
|
/**
|
|
* The subcommands of the reload command
|
|
*/
|
|
struct subcommand reloadoptions[] =
|
|
{
|
|
{
|
|
"config", 0, 0, reload_config,
|
|
"[Deprecated] Reload the configuration",
|
|
"Usage: reload config",
|
|
{0}
|
|
},
|
|
{
|
|
"dbusers", 1, 1, reload_dbusers,
|
|
"Reload the database users for a service",
|
|
"Usage: reload dbusers SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Reload database users for this service\n"
|
|
"\n"
|
|
"Example: reload dbusers \"splitter service\"",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{ EMPTY_OPTION }
|
|
};
|
|
|
|
static void enable_log_priority(DCB *, char *);
|
|
static void disable_log_priority(DCB *, char *);
|
|
static void enable_sess_log_priority(DCB *dcb, char *arg1, char *arg2);
|
|
static void disable_sess_log_priority(DCB *dcb, char *arg1, char *arg2);
|
|
static void enable_service_root(DCB *dcb, SERVICE *service);
|
|
static void disable_service_root(DCB *dcb, SERVICE *service);
|
|
static void enable_syslog();
|
|
static void disable_syslog();
|
|
static void enable_maxlog();
|
|
static void disable_maxlog();
|
|
static void enable_account(DCB *, char *user);
|
|
static void enable_admin_account(DCB *, char *user);
|
|
static void disable_account(DCB *, char *user);
|
|
|
|
/**
|
|
* * The subcommands of the enable command
|
|
* */
|
|
struct subcommand enableoptions[] =
|
|
{
|
|
{
|
|
"log-priority",
|
|
1, 1,
|
|
enable_log_priority,
|
|
"Enable a logging priority",
|
|
"Usage: enable log-priority PRIORITY\n"
|
|
"\n"
|
|
"Parameters:"
|
|
"PRIORITY One of 'err', 'warning', 'notice','info' or 'debug'\n"
|
|
"\n"
|
|
"Example: enable log-priority info",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"sessionlog-priority",
|
|
2, 2,
|
|
enable_sess_log_priority,
|
|
"[Deprecated] Enable a logging priority for a session",
|
|
"This command is deprecated",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"root",
|
|
1, 1,
|
|
enable_service_root,
|
|
"Enable root user access to a service",
|
|
"Usage: enable root SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service where root user is enabled\n"
|
|
"\n"
|
|
"Example: enable root my-service",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"syslog",
|
|
0, 0,
|
|
enable_syslog,
|
|
"Enable syslog logging",
|
|
"Usage: enable syslog",
|
|
{0}
|
|
},
|
|
{
|
|
"maxlog",
|
|
0, 0,
|
|
enable_maxlog,
|
|
"Enable MaxScale logging",
|
|
"Usage: enable maxlog",
|
|
{0}
|
|
},
|
|
{
|
|
"account",
|
|
1, 1,
|
|
enable_admin_account,
|
|
"Activate a Linux user account for administrative MaxAdmin use",
|
|
"Usage: enable account USER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER The user account to enable\n"
|
|
"\n"
|
|
"Example: enable account alice",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"readonly-account",
|
|
1, 1,
|
|
enable_account,
|
|
"Activate a Linux user account for read-only MaxAdmin use",
|
|
"Usage: enable account USER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER The user account to enable\n"
|
|
"\n"
|
|
"Example: enable account alice",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* * The subcommands of the disable command
|
|
* */
|
|
struct subcommand disableoptions[] =
|
|
{
|
|
{
|
|
"log-priority",
|
|
1, 1,
|
|
disable_log_priority,
|
|
"Disable a logging priority",
|
|
"Usage: disable log-priority PRIORITY\n"
|
|
"\n"
|
|
"Parameters:"
|
|
"PRIORITY One of 'err', 'warning', 'notice','info' or 'debug'\n"
|
|
"\n"
|
|
"Example: disable log-priority info",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"sessionlog-priority",
|
|
2, 2,
|
|
disable_sess_log_priority,
|
|
"[Deprecated] Disable a logging priority for a particular session",
|
|
"This command is deprecated",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"root",
|
|
1, 1,
|
|
disable_service_root,
|
|
"Disable root access",
|
|
"Usage: disable root SERVICE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVICE Service where root user is disabled\n"
|
|
"\n"
|
|
"Example: disable root my-service",
|
|
{ARG_TYPE_SERVICE}
|
|
},
|
|
{
|
|
"syslog",
|
|
0, 0,
|
|
disable_syslog,
|
|
"Disable syslog logging",
|
|
"Usage: disable syslog",
|
|
{0}
|
|
},
|
|
{
|
|
"maxlog",
|
|
0, 0,
|
|
disable_maxlog,
|
|
"Disable MaxScale logging",
|
|
"Usage: disable maxlog",
|
|
{0}
|
|
},
|
|
{
|
|
"account",
|
|
1, 1,
|
|
disable_account,
|
|
"Disable Linux user",
|
|
"Usage: disable account USER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER The user account to disable\n"
|
|
"\n"
|
|
"Example: disable account alice",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
static void inet_add_user(DCB *, char *user, char *password);
|
|
static void inet_add_admin_user(DCB *, char *user, char *password);
|
|
|
|
static void cmd_AddServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
if (runtime_link_server(server, values[i]))
|
|
{
|
|
dcb_printf(dcb, "Added server '%s' to '%s'\n", server->unique_name, values[i]);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Could not add server '%s' to object '%s'. See error log for more details.\n",
|
|
server->unique_name, values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the ping command.
|
|
*/
|
|
void ping_workers(DCB* dcb)
|
|
{
|
|
int n_workers = config_threadcount();
|
|
|
|
for (int i = 0; i < n_workers; ++i)
|
|
{
|
|
MXS_WORKER *worker = mxs_worker_get(i);
|
|
|
|
if (mxs_worker_post_message(worker, MXS_WORKER_MSG_PING, 0, 0))
|
|
{
|
|
dcb_printf(dcb, "Posted message to worker %d.\n", i);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Could not post message to worker %d: %s\n", i, mxs_strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
|
|
struct subcommand pingoptions[] =
|
|
{
|
|
{
|
|
"workers", 0, 0, ping_workers,
|
|
"Ping Workers",
|
|
"Ping Workers",
|
|
{ARG_TYPE_NONE}
|
|
},
|
|
{ EMPTY_OPTION }
|
|
};
|
|
|
|
/**
|
|
* The subcommands of the add command
|
|
*/
|
|
struct subcommand addoptions[] =
|
|
{
|
|
{
|
|
"user", 2, 2, inet_add_admin_user,
|
|
"Add an administrative account for using maxadmin over the network",
|
|
"Usage: add user USER PASSWORD\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER User to add\n"
|
|
"PASSWORD Password for the user\n"
|
|
"\n"
|
|
"Example: add user bob somepass",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"readonly-user", 2, 2, inet_add_user,
|
|
"Add a read-only account for using maxadmin over the network",
|
|
"Usage: add user USER PASSWORD\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER User to add\n"
|
|
"PASSWORD Password for the user\n"
|
|
"\n"
|
|
"Example: add user bob somepass",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"server", 2, 12, cmd_AddServer,
|
|
"Add a new server to a service",
|
|
"Usage: add server SERVER TARGET...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVER The server that is added to TARGET\n"
|
|
"TARGET List of service and/or monitor names separated by spaces\n"
|
|
"\n"
|
|
"A server can be assigned to a maximum of 11 objects in one command\n"
|
|
"\n"
|
|
"Example: add server my-db my-service \"Cluster Monitor\"",
|
|
{
|
|
ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{ EMPTY_OPTION}
|
|
};
|
|
|
|
|
|
static void telnetdRemoveUser(DCB *, char *user);
|
|
|
|
static void cmd_RemoveServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
if (runtime_unlink_server(server, values[i]))
|
|
{
|
|
dcb_printf(dcb, "Removed server '%s' from '%s'\n", server->unique_name, values[i]);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "No service or monitor with the name '%s'\n", values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The subcommands of the remove command
|
|
*/
|
|
struct subcommand removeoptions[] =
|
|
{
|
|
{
|
|
"user",
|
|
1, 1,
|
|
telnetdRemoveUser,
|
|
"Remove account for using maxadmin over the network",
|
|
"Usage: remove user USER\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"USER User to remove\n"
|
|
"\n"
|
|
"Example: remove user bob",
|
|
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"server", 2, 12, cmd_RemoveServer,
|
|
"Remove a server from a service or a monitor",
|
|
"Usage: remove server SERVER TARGET...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"SERVER The server that is removed from TARGET\n"
|
|
"TARGET List of service and/or monitor names separated by spaces\n"
|
|
"\n"
|
|
"A server can be removed from a maximum of 11 objects in one command\n"
|
|
"\n"
|
|
"Example: remove server my-db my-service \"Cluster Monitor\"",
|
|
{
|
|
ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
/**
|
|
* User command to flush a single logfile
|
|
*
|
|
* @param pdcb The stream to write output to
|
|
* @param logname The name of the log
|
|
*/
|
|
static void
|
|
flushlog(DCB *pdcb, char *logname)
|
|
{
|
|
bool unrecognized = false;
|
|
bool deprecated = false;
|
|
|
|
if (!strcasecmp(logname, "error"))
|
|
{
|
|
deprecated = true;
|
|
}
|
|
else if (!strcasecmp(logname, "message"))
|
|
{
|
|
deprecated = true;
|
|
}
|
|
else if (!strcasecmp(logname, "trace"))
|
|
{
|
|
deprecated = true;
|
|
}
|
|
else if (!strcasecmp(logname, "debug"))
|
|
{
|
|
deprecated = true;
|
|
}
|
|
else if (!strcasecmp(logname, "maxscale"))
|
|
{
|
|
; // nop
|
|
}
|
|
else
|
|
{
|
|
unrecognized = true;
|
|
}
|
|
|
|
if (unrecognized)
|
|
{
|
|
dcb_printf(pdcb, "Unexpected logfile name '%s', expected: 'maxscale'.\n", logname);
|
|
}
|
|
else
|
|
{
|
|
mxs_log_rotate();
|
|
|
|
if (deprecated)
|
|
{
|
|
dcb_printf(pdcb,
|
|
"'%s' is deprecated, currently there is only one log 'maxscale', "
|
|
"which was rotated.\n", logname);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* User command to flush all logfiles
|
|
*
|
|
* @param pdcb The stream to write output to
|
|
*/
|
|
static void
|
|
flushlogs(DCB *pdcb)
|
|
{
|
|
mxs_log_rotate();
|
|
}
|
|
|
|
|
|
/**
|
|
* The subcommands of the flush command
|
|
*/
|
|
struct subcommand flushoptions[] =
|
|
{
|
|
{
|
|
"log",
|
|
1, 1,
|
|
flushlog,
|
|
"Flush the content of a log file and reopen it",
|
|
"Usage: flush log",
|
|
{ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"logs",
|
|
0, 0,
|
|
flushlogs,
|
|
"Flush the content of a log file and reopen it",
|
|
"Usage: flush logs",
|
|
{0}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
/** This is used to prevent concurrent creation or removal of servers */
|
|
static SPINLOCK server_mod_lock = SPINLOCK_INIT;
|
|
|
|
/**
|
|
* Create a new server
|
|
*
|
|
* @param dcb Client DCB
|
|
* @param name Server name
|
|
* @param address Server network address
|
|
* @param port Server port
|
|
* @param protocol Protocol, NULL for default (MySQLBackend)
|
|
* @param authenticator Authenticator module, NULL for default (MySQLBackendAuth)
|
|
* @param authenticator_options Authenticator options, NULL for no options
|
|
*/
|
|
static void createServer(DCB *dcb, char *name, char *address, char *port,
|
|
char *protocol, char *authenticator, char *authenticator_options)
|
|
{
|
|
spinlock_acquire(&server_mod_lock);
|
|
|
|
if (server_find_by_unique_name(name) == NULL)
|
|
{
|
|
if (runtime_create_server(name, address, port, protocol, authenticator, authenticator_options))
|
|
{
|
|
dcb_printf(dcb, "Created server '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to create new server, see log file for more details\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Server '%s' already exists.\n", name);
|
|
}
|
|
|
|
spinlock_release(&server_mod_lock);
|
|
}
|
|
|
|
static void createListener(DCB *dcb, SERVICE *service, char *name, char *address,
|
|
char *port, char *protocol, char *authenticator,
|
|
char *authenticator_options, char *key, char *cert,
|
|
char *ca, char *version, char *depth)
|
|
{
|
|
if (runtime_create_listener(service, name, address, port, protocol,
|
|
authenticator, authenticator_options,
|
|
key, cert, ca, version, depth))
|
|
{
|
|
dcb_printf(dcb, "Listener '%s' created\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to create listener '%s', see log for more details\n", name);
|
|
}
|
|
}
|
|
|
|
static void createMonitor(DCB *dcb, const char *name, const char *module)
|
|
{
|
|
if (monitor_find(name))
|
|
{
|
|
dcb_printf(dcb, "Monitor '%s' already exists\n", name);
|
|
}
|
|
else if (runtime_create_monitor(name, module))
|
|
{
|
|
dcb_printf(dcb, "Created monitor '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to create monitor '%s', see log for more details\n", name);
|
|
}
|
|
}
|
|
|
|
struct subcommand createoptions[] =
|
|
{
|
|
{
|
|
"server", 2, 6, createServer,
|
|
"Create a new server",
|
|
"Usage: create server NAME HOST [PORT] [PROTOCOL] [AUTHENTICATOR] [OPTIONS]\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Server name\n"
|
|
"HOST Server host address\n"
|
|
"PORT Server port (default 3306)\n"
|
|
"PROTOCOL Server protocol (default MySQLBackend)\n"
|
|
"AUTHENTICATOR Authenticator module name (default MySQLAuth)\n"
|
|
"OPTIONS Comma separated list of options for the authenticator\n"
|
|
"\n"
|
|
"The first two parameters are required, the others are optional.\n"
|
|
"\n"
|
|
"Example: create server my-db-1 192.168.0.102 3306",
|
|
{
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
"listener", 2, 12, createListener,
|
|
"Create a new listener for a service",
|
|
"Usage: create listener SERVICE NAME [HOST] [PORT] [PROTOCOL] [AUTHENTICATOR] [OPTIONS]\n"
|
|
" [SSL_KEY] [SSL_CERT] [SSL_CA] [SSL_VERSION] [SSL_VERIFY_DEPTH]\n"
|
|
"\n"
|
|
"Parameters\n"
|
|
"SERVICE Service where this listener is added\n"
|
|
"NAME Listener name\n"
|
|
"HOST Listener host address (default [::])\n"
|
|
"PORT Listener port (default 3306)\n"
|
|
"PROTOCOL Listener protocol (default MySQLClient)\n"
|
|
"AUTHENTICATOR Authenticator module name (default MySQLAuth)\n"
|
|
"OPTIONS Options for the authenticator module\n"
|
|
"SSL_KEY Path to SSL private key\n"
|
|
"SSL_CERT Path to SSL certificate\n"
|
|
"SSL_CA Path to CA certificate\n"
|
|
"SSL_VERSION SSL version (default MAX)\n"
|
|
"SSL_VERIFY_DEPTH Certificate verification depth\n"
|
|
"\n"
|
|
"The first two parameters are required, the others are optional.\n"
|
|
"Any of the optional parameters can also have the value 'default'\n"
|
|
"which will be replaced with the default value.\n"
|
|
"\n"
|
|
"Example: create listener my-service my-new-listener 192.168.0.101 4006",
|
|
{
|
|
ARG_TYPE_SERVICE, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
}
|
|
},
|
|
{
|
|
"monitor", 2, 2, createMonitor,
|
|
"Create a new monitor",
|
|
"Usage: create monitor NAME MODULE\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Monitor name\n"
|
|
"MODULE Monitor module\n"
|
|
"\n"
|
|
"Example: create monitor my-monitor mysqlmon",
|
|
{
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
static void destroyServer(DCB *dcb, SERVER *server)
|
|
{
|
|
/** Do this so that we don't directly access the server. Currently, the
|
|
* destruction of a server does not free any memory and the server stays
|
|
* valid. */
|
|
char name[strlen(server->unique_name) + 1];
|
|
strcpy(name, server->unique_name);
|
|
|
|
if (runtime_destroy_server(server))
|
|
{
|
|
dcb_printf(dcb, "Destroyed server '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to destroy server '%s', see log file for more details\n", name);
|
|
}
|
|
}
|
|
|
|
static void destroyListener(DCB *dcb, SERVICE *service, const char *name)
|
|
{
|
|
if (runtime_destroy_listener(service, name))
|
|
{
|
|
dcb_printf(dcb, "Destroyed listener '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to destroy listener '%s', see log file for more details\n", name);
|
|
}
|
|
}
|
|
|
|
|
|
static void destroyMonitor(DCB *dcb, MXS_MONITOR *monitor)
|
|
{
|
|
char name[strlen(monitor->name) + 1];
|
|
strcpy(name, monitor->name);
|
|
|
|
if (runtime_destroy_monitor(monitor))
|
|
{
|
|
dcb_printf(dcb, "Destroyed monitor '%s'\n", name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to destroy monitor '%s', see log file for more details\n", name);
|
|
}
|
|
}
|
|
|
|
struct subcommand destroyoptions[] =
|
|
{
|
|
{
|
|
"server", 1, 1, destroyServer,
|
|
"Destroy a server",
|
|
"Usage: destroy server NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Server to destroy\n"
|
|
"\n"
|
|
"Example: destroy server my-db-1",
|
|
{ARG_TYPE_SERVER}
|
|
},
|
|
{
|
|
"listener", 2, 2, destroyListener,
|
|
"Destroy a listener",
|
|
"Usage: destroy listener SERVICE NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Listener to destroy\n"
|
|
"\n"
|
|
"The listener is stopped and it will be removed on the next restart of MaxScale\n"
|
|
"\n"
|
|
"Example: destroy listener my-listener",
|
|
{ARG_TYPE_SERVICE, ARG_TYPE_STRING}
|
|
},
|
|
{
|
|
"monitor", 1, 1, destroyMonitor,
|
|
"Destroy a monitor",
|
|
"Usage: destroy monitor NAME\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Monitor to destroy\n"
|
|
"\n"
|
|
"The monitor is stopped and it will be removed on the next restart of MaxScale\n"
|
|
"\n"
|
|
"Example: destroy monitor my-monitor",
|
|
{ARG_TYPE_MONITOR}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Process multiple alter operations at once
|
|
*
|
|
* This is a somewhat ugly way to handle multiple key-value changes in one operation
|
|
* with one function. This could be handled with a variadic function but the
|
|
* required complexity would probably negate any benefits.
|
|
*/
|
|
static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
CONFIG_CONTEXT *obj = NULL;
|
|
char *ssl_key = NULL;
|
|
char *ssl_cert = NULL;
|
|
char *ssl_ca = NULL;
|
|
char *ssl_version = NULL;
|
|
char *ssl_depth = NULL;
|
|
bool enable = false;
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
char *key = values[i];
|
|
char *value = strchr(key, '=');
|
|
|
|
if (value)
|
|
{
|
|
*value++ = '\0';
|
|
|
|
if (config_is_ssl_parameter(key))
|
|
{
|
|
if (strcmp("ssl_cert", key) == 0)
|
|
{
|
|
ssl_cert = value;
|
|
}
|
|
else if (strcmp("ssl_ca_cert", key) == 0)
|
|
{
|
|
ssl_ca = value;
|
|
}
|
|
else if (strcmp("ssl_key", key) == 0)
|
|
{
|
|
ssl_key = value;
|
|
}
|
|
else if (strcmp("ssl_version", key) == 0)
|
|
{
|
|
ssl_version = value;
|
|
}
|
|
else if (strcmp("ssl_cert_verify_depth", key) == 0)
|
|
{
|
|
ssl_depth = value;
|
|
}
|
|
else
|
|
{
|
|
enable = strcmp("ssl", key) == 0 && strcmp(value, "required") == 0;
|
|
/** Must be 'ssl' */
|
|
}
|
|
}
|
|
else if (!runtime_alter_server(server, key, value))
|
|
{
|
|
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
|
|
}
|
|
}
|
|
|
|
if (enable || ssl_key || ssl_cert || ssl_ca)
|
|
{
|
|
if (enable && ssl_key && ssl_cert && ssl_ca)
|
|
{
|
|
/** We have SSL parameters, try to process them */
|
|
if (!runtime_enable_server_ssl(server, ssl_key, ssl_cert, ssl_ca,
|
|
ssl_version, ssl_depth))
|
|
{
|
|
dcb_printf(dcb, "Enabling SSL for server '%s' failed, see log "
|
|
"for more details.\n", server->unique_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: SSL configuration requires the following parameters:\n"
|
|
"ssl=required ssl_key=PATH ssl_cert=PATH ssl_ca_cert=PATH\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void alterMonitor(DCB *dcb, MXS_MONITOR *monitor, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
char *key = values[i];
|
|
char *value = strchr(key, '=');
|
|
|
|
if (value)
|
|
{
|
|
*value++ = '\0';
|
|
|
|
if (!runtime_alter_monitor(monitor, key, value))
|
|
{
|
|
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void alterService(DCB *dcb, SERVICE *service, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
char *key = values[i];
|
|
char *value = strchr(key, '=');
|
|
|
|
if (value)
|
|
{
|
|
*value++ = '\0';
|
|
|
|
if (!runtime_alter_service(service, key, value))
|
|
{
|
|
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void alterMaxScale(DCB *dcb, char *v1, char *v2, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11)
|
|
{
|
|
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
|
|
const int items = sizeof(values) / sizeof(values[0]);
|
|
|
|
for (int i = 0; i < items && values[i]; i++)
|
|
{
|
|
char *key = values[i];
|
|
char *value = strchr(key, '=');
|
|
|
|
if (value)
|
|
{
|
|
*value++ = '\0';
|
|
|
|
if (!runtime_alter_maxscale(key, value))
|
|
{
|
|
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct subcommand alteroptions[] =
|
|
{
|
|
{
|
|
"server", 2, 12, alterServer,
|
|
"Alter server parameters",
|
|
"Usage: alter server NAME KEY=VALUE ...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Server name\n"
|
|
"KEY=VALUE List of `key=value` pairs separated by spaces\n"
|
|
"\n"
|
|
"This will alter an existing parameter of a server. The accepted values for KEY are:\n"
|
|
"\n"
|
|
"address Server address\n"
|
|
"port Server port\n"
|
|
"monitoruser Monitor user for this server\n"
|
|
"monitorpw Monitor password for this server\n"
|
|
"ssl Enable SSL, value must be 'required'\n"
|
|
"ssl_key Path to SSL private key\n"
|
|
"ssl_cert Path to SSL certificate\n"
|
|
"ssl_ca_cert Path to SSL CA certificate\n"
|
|
"ssl_version SSL version\n"
|
|
"ssl_cert_verify_depth Certificate verification depth\n"
|
|
"persistpoolmax Persisted connection pool size\n"
|
|
"persistmaxtime Persisted connection maximum idle time\n"
|
|
"\n"
|
|
"To configure SSL for a newly created server, the 'ssl', 'ssl_cert',\n"
|
|
"'ssl_key' and 'ssl_ca_cert' parameters must be given at the same time.\n"
|
|
"\n"
|
|
"Example: alter server my-db-1 address=192.168.0.202 port=3307",
|
|
{
|
|
ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
"monitor", 2, 12, alterMonitor,
|
|
"Alter monitor parameters",
|
|
"Usage: alter monitor NAME KEY=VALUE ...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Monitor name\n"
|
|
"KEY=VALUE List of `key=value` pairs separated by spaces\n"
|
|
"\n"
|
|
"All monitors support the following values for KEY:\n"
|
|
"user Username used when connecting to servers\n"
|
|
"password Password used when connecting to servers\n"
|
|
"monitor_interval Monitoring interval in milliseconds\n"
|
|
"backend_connect_timeout Server coneection timeout in seconds\n"
|
|
"backend_write_timeout Server write timeout in seconds\n"
|
|
"backend_read_timeout Server read timeout in seconds\n"
|
|
"backend_connect_attempts Number of re-connection attempts\n"
|
|
"journal_max_age Maximum age of server state journal\n"
|
|
"script_timeout Timeout in seconds for monitor scripts\n"
|
|
"\n"
|
|
"This will alter an existing parameter of a monitor. To remove parameters,\n"
|
|
"pass an empty value for a key e.g. 'maxadmin alter monitor my-monitor my-key='\n"
|
|
"\n"
|
|
"Example: alter monitor my-monitor user=maxuser password=maxpwd",
|
|
{
|
|
ARG_TYPE_MONITOR, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
"service", 2, 12, alterService,
|
|
"Alter service parameters",
|
|
"Usage: alter service NAME KEY=VALUE ...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"NAME Service name\n"
|
|
"KEY=VALUE List of `key=value` pairs separated by spaces\n"
|
|
"\n"
|
|
"All services support the following values for KEY:\n"
|
|
"user Username used when connecting to servers\n"
|
|
"password Password used when connecting to servers\n"
|
|
"enable_root_user Allow root user access through this service\n"
|
|
"max_retry_interval Maximum restart retry interval\n"
|
|
"max_connections Maximum connection limit\n"
|
|
"connection_timeout Client idle timeout in seconds\n"
|
|
"auth_all_servers Retrieve authentication data from all servers\n"
|
|
"strip_db_esc Strip escape characters from database names\n"
|
|
"localhost_match_wildcard_host Match wildcard host to 'localhost' address\n"
|
|
"version_string The version string given to client connections\n"
|
|
"weightby Weighting parameter name\n"
|
|
"log_auth_warnings Log authentication warnings\n"
|
|
"retry_on_failure Retry service start on failure\n"
|
|
"\n"
|
|
"Example: alter service my-service user=maxuser password=maxpwd",
|
|
{
|
|
ARG_TYPE_SERVICE, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
"maxscale", 1, 11, alterMaxScale,
|
|
"Alter maxscale parameters",
|
|
"Usage: alter maxscale KEY=VALUE ...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"KEY=VALUE List of `key=value` pairs separated by spaces\n"
|
|
"\n"
|
|
"The following configuration values can be altered:\n"
|
|
"auth_connect_timeout Connection timeout for permission checks\n"
|
|
"auth_read_timeout Read timeout for permission checks\n"
|
|
"auth_write_timeout Write timeout for permission checks\n"
|
|
"admin_auth Enable admin interface authentication\n"
|
|
"admin_log_auth_failures Log admin interface authentication failures\n"
|
|
"\n"
|
|
"Example: alter maxscale auth_connect_timeout=10",
|
|
{
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
static inline bool requires_output_dcb(const MODULECMD *cmd)
|
|
{
|
|
modulecmd_arg_type_t *type = &cmd->arg_types[0];
|
|
return cmd->arg_count_max > 0 && MODULECMD_GET_TYPE(type) == MODULECMD_ARG_OUTPUT;
|
|
}
|
|
|
|
static void callModuleCommand(DCB *dcb, char *domain, char *id, char *v3,
|
|
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
|
char *v10, char *v11, char *v12)
|
|
{
|
|
const void *values[11] = {v3, v4, v5, v6, v7, v8, v9, v10, v11, v12};
|
|
const int valuelen = sizeof(values) / sizeof(values[0]);
|
|
int numargs = 0;
|
|
|
|
while (numargs < valuelen && values[numargs])
|
|
{
|
|
numargs++;
|
|
}
|
|
|
|
const MODULECMD *cmd = modulecmd_find_command(domain, id);
|
|
|
|
if (cmd)
|
|
{
|
|
if (requires_output_dcb(cmd))
|
|
{
|
|
/** The command requires a DCB for output, add the client DCB
|
|
* as the first argument */
|
|
for (int i = valuelen - 1; i > 0; i--)
|
|
{
|
|
values[i] = values[i - 1];
|
|
}
|
|
values[0] = dcb;
|
|
numargs += numargs + 1 < valuelen - 1 ? 1 : 0;
|
|
}
|
|
|
|
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, numargs, values);
|
|
|
|
if (arg)
|
|
{
|
|
json_t* output = NULL;
|
|
|
|
if (!modulecmd_call_command(cmd, arg, &output))
|
|
{
|
|
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
|
}
|
|
else if (output)
|
|
{
|
|
dcb_printf(dcb, "%s\n", json_dumps(output, JSON_INDENT(4)));
|
|
}
|
|
|
|
json_decref(output);
|
|
modulecmd_arg_free(arg);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
|
}
|
|
}
|
|
|
|
struct subcommand calloptions[] =
|
|
{
|
|
{
|
|
"command", 2, 12, callModuleCommand,
|
|
"Call module command",
|
|
"Usage: call command MODULE COMMAND ARGS...\n"
|
|
"\n"
|
|
"Parameters:\n"
|
|
"MODULE The module name\n"
|
|
"COMMAND The command to call\n"
|
|
"ARGS... Arguments for the command\n"
|
|
"\n"
|
|
"To list all registered commands, run 'list commands'.\n"
|
|
"\n"
|
|
"Example: call command my-module my-command hello world!",
|
|
{
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
|
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING
|
|
}
|
|
},
|
|
{
|
|
EMPTY_OPTION
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The debug command table
|
|
*/
|
|
static struct
|
|
{
|
|
char *cmd;
|
|
struct subcommand *options;
|
|
} cmds[] =
|
|
{
|
|
{ "add", addoptions },
|
|
{ "remove", removeoptions },
|
|
{ "create", createoptions },
|
|
{ "destroy", destroyoptions },
|
|
{ "alter", alteroptions },
|
|
{ "set", setoptions },
|
|
{ "clear", clearoptions },
|
|
{ "disable", disableoptions },
|
|
{ "enable", enableoptions },
|
|
{ "flush", flushoptions },
|
|
{ "list", listoptions },
|
|
{ "reload", reloadoptions },
|
|
{ "restart", restartoptions },
|
|
{ "shutdown", shutdownoptions },
|
|
{ "show", showoptions },
|
|
{ "sync", syncoptions },
|
|
{ "call", calloptions },
|
|
{ "ping", pingoptions },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static bool command_requires_admin_privileges(const char* cmd)
|
|
{
|
|
return strcmp(cmd, "list") != 0 && strcmp(cmd, "show") != 0;
|
|
}
|
|
|
|
/**
|
|
* Convert a string argument to a numeric, observing prefixes
|
|
* for number bases, e.g. 0x for hex, 0 for octal
|
|
*
|
|
* @param mode The CLI mode
|
|
* @param arg The string representation of the argument
|
|
* @param arg_type The target type for the argument
|
|
* @return The argument as a long integer
|
|
*/
|
|
static unsigned long
|
|
convert_arg(char *arg, int arg_type)
|
|
{
|
|
unsigned long rval = 0;
|
|
|
|
switch (arg_type)
|
|
{
|
|
case ARG_TYPE_ADDRESS:
|
|
rval = (unsigned long)strtol(arg, NULL, 0);
|
|
break;
|
|
|
|
case ARG_TYPE_STRING:
|
|
rval = (unsigned long)arg;
|
|
break;
|
|
|
|
case ARG_TYPE_SERVICE:
|
|
rval = (unsigned long)service_find(arg);
|
|
break;
|
|
|
|
case ARG_TYPE_SERVER:
|
|
rval = (unsigned long)server_find_by_unique_name(arg);
|
|
break;
|
|
|
|
case ARG_TYPE_SESSION:
|
|
rval = (unsigned long)session_get_by_id(strtoul(arg, NULL, 0));
|
|
break;
|
|
|
|
case ARG_TYPE_MONITOR:
|
|
rval = (unsigned long)monitor_find(arg);
|
|
break;
|
|
|
|
case ARG_TYPE_FILTER:
|
|
rval = (unsigned long)filter_def_find(arg);
|
|
break;
|
|
|
|
case ARG_TYPE_NUMERIC:
|
|
|
|
for (int i = 0; arg[i]; i++)
|
|
{
|
|
if (isdigit(arg[i]))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
rval = atoi(arg);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static void free_arg(int arg_type, void *value)
|
|
{
|
|
switch (arg_type)
|
|
{
|
|
case ARG_TYPE_SESSION:
|
|
session_put_ref(value);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool user_is_authorized(DCB* dcb)
|
|
{
|
|
bool rval = true;
|
|
|
|
if (strcmp(dcb->remote, "localhost") == 0)
|
|
{
|
|
if (!admin_user_is_unix_admin(dcb->user))
|
|
{
|
|
rval = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!admin_user_is_inet_admin(dcb->user))
|
|
{
|
|
rval = false;
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static SPINLOCK debugcmd_lock = SPINLOCK_INIT;
|
|
|
|
static const char item_separator[] =
|
|
"----------------------------------------------------------------------------\n";
|
|
|
|
/**
|
|
* We have a complete line from the user, lookup the commands and execute them
|
|
*
|
|
* Commands are tokenised based on white space and then the first
|
|
* word is checked againts the cmds table. If a match is found the
|
|
* second word is compared to the different options for that command.
|
|
*
|
|
* Commands may also take up to 3 additional arguments, these are all
|
|
* assumed to the numeric values and will be converted before being passed
|
|
* to the handler function for the command.
|
|
*
|
|
* @param cli The CLI_SESSION
|
|
* @return Returns 0 if the interpreter should exit
|
|
*/
|
|
int
|
|
execute_cmd(CLI_SESSION *cli)
|
|
{
|
|
DCB *dcb = cli->session->client_dcb;
|
|
int argc, i, j, found = 0;
|
|
char *args[MAXARGS + 4];
|
|
int in_quotes = 0, escape_next = 0;
|
|
char *ptr, *lptr;
|
|
bool in_space = false;
|
|
int nskip = 0;
|
|
|
|
args[0] = trim_leading(cli->cmdbuf);
|
|
ptr = args[0];
|
|
lptr = ptr;
|
|
i = 1;
|
|
/*
|
|
* Break the command line into a number of words. Whitespace is used
|
|
* to delimit words and may be escaped by use of the \ character or
|
|
* the use of double quotes.
|
|
* The array args contains the broken down words, one per index.
|
|
*/
|
|
|
|
while (*ptr && i <= MAXARGS + 2)
|
|
{
|
|
if (escape_next)
|
|
{
|
|
*lptr++ = *ptr++;
|
|
escape_next = 0;
|
|
}
|
|
else if (*ptr == '\\')
|
|
{
|
|
escape_next = 1;
|
|
ptr++;
|
|
}
|
|
else if (in_quotes == 0 && ((in_space = *ptr == ' ') || *ptr == '\t' || *ptr == '\r' || *ptr == '\n'))
|
|
{
|
|
|
|
*lptr = 0;
|
|
lptr += nskip;
|
|
nskip = 0;
|
|
|
|
if (!in_space)
|
|
{
|
|
break;
|
|
}
|
|
|
|
args[i++] = ptr + 1;
|
|
ptr++;
|
|
lptr++;
|
|
}
|
|
else if (*ptr == '\"' && in_quotes == 0)
|
|
{
|
|
in_quotes = 1;
|
|
ptr++;
|
|
nskip++;
|
|
}
|
|
else if (*ptr == '\"' && in_quotes == 1)
|
|
{
|
|
in_quotes = 0;
|
|
ptr++;
|
|
nskip++;
|
|
}
|
|
else
|
|
{
|
|
*lptr++ = *ptr++;
|
|
}
|
|
}
|
|
*lptr = 0;
|
|
args[i] = NULL;
|
|
|
|
if (args[0] == NULL || *args[0] == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
argc = i - 2; /* The number of extra arguments to commands */
|
|
|
|
spinlock_acquire(&debugcmd_lock);
|
|
|
|
if (!strcasecmp(args[0], "help"))
|
|
{
|
|
if (args[1] == NULL || *args[1] == 0)
|
|
{
|
|
found = 1;
|
|
dcb_printf(dcb, "Available commands:\n");
|
|
for (i = 0; cmds[i].cmd; i++)
|
|
{
|
|
dcb_printf(dcb, "%s:\n", cmds[i].cmd);
|
|
|
|
for (j = 0; cmds[i].options[j].arg1; j++)
|
|
{
|
|
dcb_printf(dcb, " %s %s - %s\n", cmds[i].cmd,
|
|
cmds[i].options[j].arg1, cmds[i].options[j].help);
|
|
}
|
|
dcb_printf(dcb, "\n");
|
|
}
|
|
dcb_printf(dcb, "\nType `help COMMAND` to see details of each command.\n");
|
|
dcb_printf(dcb, "Where commands require names as arguments and these names contain\n");
|
|
dcb_printf(dcb, "whitespace either the \\ character may be used to escape the whitespace\n");
|
|
dcb_printf(dcb, "or the name may be enclosed in double quotes \".\n\n");
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; cmds[i].cmd; i++)
|
|
{
|
|
if (!strcasecmp(args[1], cmds[i].cmd))
|
|
{
|
|
found = 1;
|
|
dcb_printf(dcb, "Available options to the `%s` command:\n", cmds[i].cmd);
|
|
for (j = 0; cmds[i].options[j].arg1; j++)
|
|
{
|
|
if (j != 0)
|
|
{
|
|
dcb_printf(dcb, item_separator);
|
|
}
|
|
|
|
dcb_printf(dcb, "\n%s %s - %s\n\n%s\n\n", cmds[i].cmd, cmds[i].options[j].arg1,
|
|
cmds[i].options[j].help, cmds[i].options[j].devhelp);
|
|
|
|
}
|
|
}
|
|
}
|
|
if (found == 0)
|
|
{
|
|
dcb_printf(dcb, "No command %s to offer help with\n", args[1]);
|
|
}
|
|
}
|
|
found = 1;
|
|
}
|
|
else if (strcasecmp(args[0], "quit") && argc >= 0)
|
|
{
|
|
for (i = 0; cmds[i].cmd; i++)
|
|
{
|
|
if (strcasecmp(args[0], cmds[i].cmd) == 0)
|
|
{
|
|
for (j = 0; cmds[i].options[j].arg1; j++)
|
|
{
|
|
if (strcasecmp(args[1], cmds[i].options[j].arg1) == 0)
|
|
{
|
|
found = 1; /**< command and sub-command match */
|
|
|
|
if (command_requires_admin_privileges(cmds[i].cmd) &&
|
|
!user_is_authorized(dcb))
|
|
{
|
|
dcb_printf(dcb, "Access denied, administrative privileges required.\n");
|
|
break;
|
|
}
|
|
|
|
if (cmds[i].options[j].argc_min == cmds[i].options[j].argc_max &&
|
|
argc != cmds[i].options[j].argc_min)
|
|
{
|
|
/** Wrong number of arguments */
|
|
dcb_printf(dcb, "Incorrect number of arguments: %s %s expects %d arguments\n",
|
|
cmds[i].cmd, cmds[i].options[j].arg1,
|
|
cmds[i].options[j].argc_min);
|
|
}
|
|
else if (argc < cmds[i].options[j].argc_min)
|
|
{
|
|
/** Not enough arguments */
|
|
dcb_printf(dcb, "Incorrect number of arguments: %s %s expects at least %d arguments\n",
|
|
cmds[i].cmd, cmds[i].options[j].arg1,
|
|
cmds[i].options[j].argc_min);
|
|
}
|
|
else if (argc > cmds[i].options[j].argc_max)
|
|
{
|
|
/** Too many arguments */
|
|
dcb_printf(dcb, "Incorrect number of arguments: %s %s expects at most %d arguments\n",
|
|
cmds[i].cmd, cmds[i].options[j].arg1,
|
|
cmds[i].options[j].argc_max);
|
|
}
|
|
else
|
|
{
|
|
unsigned long arg_list[MAXARGS] = {};
|
|
bool ok = true;
|
|
|
|
for (int k = 0; k < cmds[i].options[j].argc_max && k < argc; k++)
|
|
{
|
|
arg_list[k] = convert_arg(args[k + 2], cmds[i].options[j].arg_types[k]);
|
|
if (arg_list[k] == 0)
|
|
{
|
|
dcb_printf(dcb, "Invalid argument: %s\n", args[k + 2]);
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
switch (cmds[i].options[j].argc_max)
|
|
{
|
|
case 0:
|
|
cmds[i].options[j].fn(dcb);
|
|
break;
|
|
case 1:
|
|
cmds[i].options[j].fn(dcb, arg_list[0]);
|
|
break;
|
|
case 2:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1]);
|
|
break;
|
|
case 3:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2]);
|
|
break;
|
|
case 4:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3]);
|
|
break;
|
|
case 5:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4]);
|
|
break;
|
|
case 6:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5]);
|
|
break;
|
|
case 7:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6]);
|
|
break;
|
|
case 8:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6], arg_list[7]);
|
|
break;
|
|
case 9:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6], arg_list[7], arg_list[8]);
|
|
break;
|
|
case 10:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6], arg_list[7], arg_list[8],
|
|
arg_list[9]);
|
|
break;
|
|
case 11:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6], arg_list[7], arg_list[8],
|
|
arg_list[9], arg_list[10]);
|
|
break;
|
|
case 12:
|
|
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
|
arg_list[3], arg_list[4], arg_list[5],
|
|
arg_list[6], arg_list[7], arg_list[8],
|
|
arg_list[9], arg_list[10], arg_list[11]);
|
|
break;
|
|
default:
|
|
dcb_printf(dcb, "Error: Maximum argument count is %d.\n", MAXARGS);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int k = 0; k < cmds[i].options[j].argc_max && k < argc; k++)
|
|
{
|
|
free_arg(cmds[i].options[j].arg_types[k], (void*)arg_list[k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
dcb_printf(dcb,
|
|
"Unknown or missing option for the %s command. Valid sub-commands are:\n",
|
|
cmds[i].cmd);
|
|
for (j = 0; cmds[i].options[j].arg1; j++)
|
|
{
|
|
dcb_printf(dcb, " %-10s %s\n", cmds[i].options[j].arg1,
|
|
cmds[i].options[j].help);
|
|
}
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (argc == -1)
|
|
{
|
|
dcb_printf(dcb,
|
|
"Commands must consist of at least two words. Type help for a list of commands\n");
|
|
found = 1;
|
|
}
|
|
if (!found)
|
|
{
|
|
dcb_printf(dcb,
|
|
"Command '%s' not known, type help for a list of available commands\n", args[0]);
|
|
}
|
|
|
|
spinlock_release(&debugcmd_lock);
|
|
|
|
memset(cli->cmdbuf, 0, CMDBUFLEN);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Debug command to stop a service
|
|
*
|
|
* @param dcb The DCB to print any output to
|
|
* @param service The service to shutdown
|
|
*/
|
|
static void
|
|
shutdown_service(DCB *dcb, SERVICE *service)
|
|
{
|
|
serviceStop(service);
|
|
}
|
|
|
|
/**
|
|
* Debug command to restart a stopped service
|
|
*
|
|
* @param dcb The DCB to print any output to
|
|
* @param service The service to restart
|
|
*/
|
|
static void
|
|
restart_service(DCB *dcb, SERVICE *service)
|
|
{
|
|
serviceStart(service);
|
|
}
|
|
|
|
/**
|
|
* Set the status bit of a server
|
|
*
|
|
* @param dcb DCB to send output to
|
|
* @param server The server to set the status of
|
|
* @param bit String representation of the status bit
|
|
*/
|
|
static void
|
|
set_server(DCB *dcb, SERVER *server, char *bit)
|
|
{
|
|
unsigned int bitvalue;
|
|
|
|
if ((bitvalue = server_map_status(bit)) != 0)
|
|
{
|
|
server_set_status(server, bitvalue);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Unknown status bit %s\n", bit);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Clear the status bit of a server
|
|
*
|
|
* @param dcb DCB to send output to
|
|
* @param server The server to set the status of
|
|
* @param bit String representation of the status bit
|
|
*/
|
|
static void
|
|
clear_server(DCB *dcb, SERVER *server, char *bit)
|
|
{
|
|
unsigned int bitvalue;
|
|
|
|
if ((bitvalue = server_map_status(bit)) != 0)
|
|
{
|
|
server_clear_status(server, bitvalue);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Unknown status bit %s\n", bit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reload the authenticaton data from the backend database of a service.
|
|
*
|
|
* @param dcb DCB to send output
|
|
* @param service The service to update
|
|
*/
|
|
static void
|
|
reload_dbusers(DCB *dcb, SERVICE *service)
|
|
{
|
|
if (service_refresh_users(service) == 0)
|
|
{
|
|
dcb_printf(dcb, "Reloaded database users for service %s.\n", service->name);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Error: Failed to reloaded database users for service %s.\n", service->name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reload the configuration data from the config file
|
|
*
|
|
* @param dcb DCB to use to send output
|
|
*/
|
|
static void
|
|
reload_config(DCB *dcb)
|
|
{
|
|
dcb_printf(dcb, "Reloading configuration from file.\n\n"
|
|
"Warning! This command has been deprecated, please use the `alter`\n"
|
|
"commands or use the MaxScale REST API to change the configuration\n"
|
|
"at runtime.\n");
|
|
config_reload();
|
|
}
|
|
|
|
/**
|
|
* Add a new remote (insecure, over the network) maxscale admin user
|
|
*
|
|
* @param dcb The DCB for messages
|
|
* @param user The user name
|
|
* @param user The user password
|
|
*/
|
|
static void do_inet_add_user(DCB *dcb, char *user, char *password, enum user_account_type type)
|
|
{
|
|
const char *err;
|
|
|
|
if (admin_inet_user_exists(user))
|
|
{
|
|
dcb_printf(dcb, "Account %s for remote (network) usage already exists.\n", user);
|
|
return;
|
|
}
|
|
|
|
if ((err = admin_add_inet_user(user, password, type)) == NULL)
|
|
{
|
|
dcb_printf(dcb, "Account %s for remote (network) usage has been successfully added.\n", user);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to add new remote account %s: %s.\n", user, err);
|
|
}
|
|
}
|
|
|
|
static void inet_add_user(DCB *dcb, char *user, char *password)
|
|
{
|
|
if (admin_have_admin())
|
|
{
|
|
do_inet_add_user(dcb, user, password, USER_ACCOUNT_BASIC);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "No admin user created, create an admin account first\n"
|
|
"by executing `add admin USER PASSWORD`\n");
|
|
}
|
|
}
|
|
|
|
static void inet_add_admin_user(DCB *dcb, char *user, char *password)
|
|
{
|
|
do_inet_add_user(dcb, user, password, USER_ACCOUNT_ADMIN);
|
|
}
|
|
|
|
/**
|
|
* Remove a remote (insecure, over the network) maxscale admin user
|
|
*
|
|
* @param dcb The DCB for messages
|
|
* @param user The user name
|
|
* @param user The user password
|
|
*/
|
|
static void telnetdRemoveUser(DCB *dcb, char *user)
|
|
{
|
|
const char* err;
|
|
|
|
if (!admin_inet_user_exists(user))
|
|
{
|
|
dcb_printf(dcb, "Account '%s' for remote usage does not exist.\n", user);
|
|
}
|
|
else if (admin_is_last_admin(user))
|
|
{
|
|
dcb_printf(dcb, "Cannot remove the last admin account '%s'.\n", user);
|
|
}
|
|
else if ((err = admin_remove_inet_user(user)))
|
|
{
|
|
dcb_printf(dcb, "Failed to remove remote account '%s': %s\n", user, err);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Account '%s' for remote usage has been successfully removed.\n", user);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Print the adminsitration users
|
|
*
|
|
* @param dcb The DCB to print the user data to
|
|
*/
|
|
static void
|
|
telnetdShowUsers(DCB *dcb)
|
|
{
|
|
dcb_PrintAdminUsers(dcb);
|
|
}
|
|
|
|
/**
|
|
* Print the log throttling state
|
|
*
|
|
* @param dcb The DCB to print the state to.
|
|
*/
|
|
static void
|
|
show_log_throttling(DCB *dcb)
|
|
{
|
|
MXS_LOG_THROTTLING t;
|
|
mxs_log_get_throttling(&t);
|
|
|
|
dcb_printf(dcb, "%lu %lu %lu\n", t.count, t.window_ms, t.suppress_ms);
|
|
}
|
|
|
|
/**
|
|
* Command to shutdown a running monitor
|
|
*
|
|
* @param dcb The DCB to use to print messages
|
|
* @param monitor The monitor to shutdown
|
|
*/
|
|
static void
|
|
shutdown_monitor(DCB *dcb, MXS_MONITOR *monitor)
|
|
{
|
|
monitorStop(monitor);
|
|
}
|
|
|
|
/**
|
|
* Command to restart a stopped monitor
|
|
*
|
|
* @param dcb The DCB to use to print messages
|
|
* @param monitor The monitor to restart
|
|
*/
|
|
static void
|
|
restart_monitor(DCB *dcb, MXS_MONITOR *monitor)
|
|
{
|
|
monitorStart(monitor, monitor->parameters);
|
|
}
|
|
|
|
/**
|
|
* Enable root access to a service
|
|
*
|
|
* @param dcb Connection to user interface
|
|
* @param service The service
|
|
*/
|
|
static void
|
|
enable_service_root(DCB *dcb, SERVICE *service)
|
|
{
|
|
serviceEnableRootUser(service, 1);
|
|
}
|
|
|
|
/**
|
|
* Disable root access to a service
|
|
*
|
|
* @param dcb Connection to user interface
|
|
* @param service The service
|
|
*/
|
|
static void
|
|
disable_service_root(DCB *dcb, SERVICE *service)
|
|
{
|
|
serviceEnableRootUser(service, 0);
|
|
}
|
|
|
|
struct log_action_entry
|
|
{
|
|
const char* name;
|
|
int priority;
|
|
const char* replacement;
|
|
};
|
|
|
|
struct log_priority_entry
|
|
{
|
|
const char* name;
|
|
int priority;
|
|
};
|
|
|
|
static int compare_log_priority_entries(const void* l, const void* r)
|
|
{
|
|
const struct log_priority_entry* l_entry = (const struct log_priority_entry*) l;
|
|
const struct log_priority_entry* r_entry = (const struct log_priority_entry*) r;
|
|
|
|
return strcmp(l_entry->name, r_entry->name);
|
|
}
|
|
|
|
static int string_to_priority(const char* name)
|
|
{
|
|
static const struct log_priority_entry LOG_PRIORITY_ENTRIES[] =
|
|
{
|
|
// NOTE: If you make changes to this array, ensure that it remains alphabetically ordered.
|
|
{ "debug", LOG_DEBUG },
|
|
{ "info", LOG_INFO },
|
|
{ "notice", LOG_NOTICE },
|
|
{ "warning", LOG_WARNING },
|
|
};
|
|
|
|
const size_t N_LOG_PRIORITY_ENTRIES = sizeof(LOG_PRIORITY_ENTRIES) / sizeof(LOG_PRIORITY_ENTRIES[0]);
|
|
|
|
struct log_priority_entry key = { name, -1 };
|
|
struct log_priority_entry* result = bsearch(&key,
|
|
LOG_PRIORITY_ENTRIES,
|
|
N_LOG_PRIORITY_ENTRIES,
|
|
sizeof(struct log_priority_entry),
|
|
compare_log_priority_entries);
|
|
|
|
return result ? result->priority : -1;
|
|
}
|
|
|
|
/**
|
|
* Enables a log priority for a single session
|
|
* @param session The session in question
|
|
* @param dcb Client DCB
|
|
* @param type Which log to enable
|
|
*/
|
|
static void enable_sess_log_priority(DCB *dcb, char *arg1, char *arg2)
|
|
{
|
|
MXS_WARNING("'enable sessionlog-priority' is deprecated.");
|
|
}
|
|
|
|
/**
|
|
* Disable a log priority for a single session
|
|
* @param session The session in question
|
|
* @param dcb Client DCB
|
|
* @param type Which log to enable
|
|
*/
|
|
static void disable_sess_log_priority(DCB *dcb, char *arg1, char *arg2)
|
|
{
|
|
MXS_WARNING("'disable sessionlog-priority' is deprecated.");
|
|
}
|
|
|
|
/**
|
|
* The log-priority enable action
|
|
*/
|
|
|
|
static void enable_log_priority(DCB *dcb, char *arg1)
|
|
{
|
|
int priority = string_to_priority(arg1);
|
|
|
|
if (priority != -1)
|
|
{
|
|
mxs_log_set_priority_enabled(priority, true);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "'%s' is not a supported log priority.\n", arg1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The log-priority disable action
|
|
*/
|
|
|
|
static void disable_log_priority(DCB *dcb, char *arg1)
|
|
{
|
|
int priority = string_to_priority(arg1);
|
|
|
|
if (priority != -1)
|
|
{
|
|
mxs_log_set_priority_enabled(priority, false);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "'%s' is not a supported log priority.\n", arg1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the duration of the sleep passed to the poll wait
|
|
*
|
|
* @param dcb DCB for output
|
|
* @param sleeptime Sleep time in milliseconds
|
|
*/
|
|
static void
|
|
set_pollsleep(DCB *dcb, int sleeptime)
|
|
{
|
|
poll_set_maxwait(sleeptime);
|
|
}
|
|
|
|
/**
|
|
* Set the number of non-blockign spins to make
|
|
*
|
|
* @param dcb DCB for output
|
|
* @param nb Number of spins
|
|
*/
|
|
static void
|
|
set_nbpoll(DCB *dcb, int nb)
|
|
{
|
|
poll_set_nonblocking_polls(nb);
|
|
}
|
|
|
|
static void
|
|
set_log_throttling(DCB *dcb, int count, int window_ms, int suppress_ms)
|
|
{
|
|
if ((count >= 0) || (window_ms >= 0) || (suppress_ms >= 0))
|
|
{
|
|
MXS_LOG_THROTTLING t = { count, window_ms, suppress_ms };
|
|
|
|
mxs_log_set_throttling(&t);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb,
|
|
"set log_throttling expect 3 integers X Y Z, equal to or larger than 0, "
|
|
"where the X denotes how many times particular message may be logged "
|
|
"during a period of Y milliseconds before it is suppressed for Z milliseconds.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable syslog logging.
|
|
*/
|
|
static void
|
|
enable_syslog()
|
|
{
|
|
mxs_log_set_syslog_enabled(true);
|
|
}
|
|
|
|
/**
|
|
* Disable syslog logging.
|
|
*/
|
|
static void
|
|
disable_syslog()
|
|
{
|
|
mxs_log_set_syslog_enabled(false);
|
|
}
|
|
|
|
/**
|
|
* Enable maxlog logging.
|
|
*/
|
|
static void
|
|
enable_maxlog()
|
|
{
|
|
mxs_log_set_maxlog_enabled(true);
|
|
}
|
|
|
|
/**
|
|
* Disable maxlog logging.
|
|
*/
|
|
static void
|
|
disable_maxlog()
|
|
{
|
|
mxs_log_set_maxlog_enabled(false);
|
|
}
|
|
|
|
/**
|
|
* Enable a Linux account
|
|
*
|
|
* @param dcb The DCB for messages
|
|
* @param user The Linux user name
|
|
*/
|
|
static void do_enable_account(DCB *dcb, char *user, enum user_account_type type)
|
|
{
|
|
const char *err;
|
|
|
|
if (admin_linux_account_enabled(user))
|
|
{
|
|
dcb_printf(dcb, "The Linux user %s has already been enabled.\n", user);
|
|
return;
|
|
}
|
|
|
|
if ((err = admin_enable_linux_account(user, type)) == NULL)
|
|
{
|
|
dcb_printf(dcb, "The Linux user %s has successfully been enabled.\n", user);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "Failed to enable the Linux user %s: %s\n", user, err);
|
|
}
|
|
}
|
|
|
|
static void enable_account(DCB *dcb, char *user)
|
|
{
|
|
if (admin_have_admin())
|
|
{
|
|
do_enable_account(dcb, user, USER_ACCOUNT_BASIC);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "No admin user created, create an admin account first\n"
|
|
"by executing `enable admin-account USER PASSWORD`\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable a Linux account
|
|
*
|
|
* @param dcb The DCB for messages
|
|
* @param user The Linux user name
|
|
*/
|
|
static void enable_admin_account(DCB *dcb, char *user)
|
|
{
|
|
do_enable_account(dcb, user, USER_ACCOUNT_ADMIN);
|
|
}
|
|
|
|
/**
|
|
* Disable a Linux account
|
|
*
|
|
* @param dcb The DCB for messages
|
|
* @param user The Linux user name
|
|
*/
|
|
static void
|
|
disable_account(DCB *dcb, char *user)
|
|
{
|
|
const char* err;
|
|
|
|
if (!admin_linux_account_enabled(user))
|
|
{
|
|
dcb_printf(dcb, "The Linux user '%s' has not been enabled.\n", user);
|
|
return;
|
|
}
|
|
else if (admin_is_last_admin(user))
|
|
{
|
|
dcb_printf(dcb, "Cannot remove the last admin account '%s'.\n", user);
|
|
}
|
|
else if ((err = admin_disable_linux_account(user)))
|
|
{
|
|
dcb_printf(dcb, "Failed to disable the Linux user '%s': %s\n", user, err);
|
|
}
|
|
else
|
|
{
|
|
dcb_printf(dcb, "The Linux user '%s' has successfully been disabled.\n", user);
|
|
}
|
|
}
|