From 6c657b71157f9c82ed99e2bd99c517e543a48e14 Mon Sep 17 00:00:00 2001 From: Marius Tolzmann Date: Thu, 27 Aug 2015 13:56:54 +0200 Subject: [PATCH 1/3] mxqadmin: Add possibility to close/reopen a group --- .gitignore | 2 + Makefile | 27 ++++ mxq_group.h | 2 + mxqadmin.c | 403 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 434 insertions(+) create mode 100644 mxqadmin.c diff --git a/.gitignore b/.gitignore index 6a317b2..d6af43f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ mx_flock.o mx_log.o mx_util.o mxq_group.o +mxqadmin.o mxqdump.o mxq_job.o mxqd.o @@ -16,6 +17,7 @@ mx_mysql.o mxqsub /mxqsub.1 +mxqadmin mxqdump mxqkill mxqd diff --git a/Makefile b/Makefile index 7d2f246..a5ffcad 100644 --- a/Makefile +++ b/Makefile @@ -316,6 +316,18 @@ mxqdump.o: CFLAGS += $(CFLAGS_MYSQL) clean: CLEAN += mxqdump.o +### mxqadmin.o ---------------------------------------------------------- + +mxqadmin.o: $(mx_log.h) +mxqadmin.o: $(mx_util.h) +mxqadmin.o: $(mx_mysql.h) +mxqadmin.o: $(mx_getopt.h) +mxqadmin.o: $(mxq.h) +mxqadmin.o: $(mxq_group.h) +mxqadmin.o: CFLAGS += $(CFLAGS_MYSQL) + +clean: CLEAN += mxqadmin.o + ### mxqkill.o ---------------------------------------------------------- mxqkill.o: $(mx_log.h) @@ -433,6 +445,21 @@ clean: CLEAN += mxqdump install:: mxqdump $(call quiet-installforuser,$(SUID_MODE),$(UID_CLIENT),$(GID_CLIENT),mxqdump,${DESTDIR}${BINDIR}/mxqdump) +### mxqadmin ------------------------------------------------------------ + +mxqadmin: mx_log.o +mxqadmin: mx_mysql.o +mxqadmin: mx_util.o +mxqadmin: mx_getopt.o +mxqadmin: LDLIBS += $(LDLIBS_MYSQL) + +build: mxqadmin + +clean: CLEAN += mxqadmin + +install:: mxqadmin + $(call quiet-installforuser,$(SUID_MODE),$(UID_CLIENT),$(GID_CLIENT),mxqadmin,${DESTDIR}${BINDIR}/mxqadmin) + ### mxqkill ------------------------------------------------------------ mxqkill: mx_log.o diff --git a/mxq_group.h b/mxq_group.h index 7d0e5e9..3e2ff59 100644 --- a/mxq_group.h +++ b/mxq_group.h @@ -57,6 +57,8 @@ struct mxq_group { #define MXQ_GROUP_STATUS_OK 0 #define MXQ_GROUP_STATUS_CANCELLED 99 +#define MXQ_GROUP_FLAG_CLOSED (1<<0) + void mxq_group_free_content(struct mxq_group *g); inline uint64_t mxq_group_jobs_done(struct mxq_group *g); diff --git a/mxqadmin.c b/mxqadmin.c new file mode 100644 index 0000000..5b7d3de --- /dev/null +++ b/mxqadmin.c @@ -0,0 +1,403 @@ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include "mx_log.h" +#include "mx_util.h" +#include "mx_mysql.h" +#include "mx_getopt.h" + +#include "mxq_group.h" +#include "mxq_job.h" + +#include "mxq.h" + +#define UINT64_UNSET (uint64_t)(-1) +#define UINT64_ALL (uint64_t)(-2) +#define UINT64_SPECIAL_MIN (uint64_t)(-2) +#define UINT64_HASVALUE(x) ((x) < UINT64_SPECIAL_MIN) + +enum mode { + MODE_UNSET=0, + MODE_CLOSE, + MODE_REOPEN +}; + +static void print_usage(void) +{ + mxq_print_generic_version(); + printf( + "\n" + "Usage:\n" + " %s [options]\n" + "\n" + "options:\n" + "\n" + " -c, --close=GROUPID close group \n" + " -o, --reopen=GROUPID reopen group \n" + "\n" + " -v, --verbose be more verbose\n" + " --debug set debug log level (default: warning log level)\n" + "\n" + " -V, --version\n" + " -h, --help\n" + "\n" + "Change how to connect to the mysql server:\n" + "\n" + " -M, --mysql-default-file[=MYSQLCNF] (default: %s)\n" + " -S, --mysql-default-group[=MYSQLGROUP] (default: %s)\n" + "\n" + "Environment:\n" + " MXQ_MYSQL_DEFAULT_FILE change default for MYSQLCNF\n" + " MXQ_MYSQL_DEFAULT_GROUP change default for MYSQLGROUP\n" + "\n", + program_invocation_short_name, + MXQ_MYSQL_DEFAULT_FILE_STR, + MXQ_MYSQL_DEFAULT_GROUP_STR + ); +} + +static int update_group_flags_closed(struct mx_mysql *mysql, uint64_t group_id, uint32_t user_uid) +{ + struct mx_mysql_stmt *stmt = NULL; + unsigned long long num_rows = 0; + int res; + uint64_t newflags = 0; + + newflags |= MXQ_GROUP_FLAG_CLOSED; + + stmt = mx_mysql_statement_prepare(mysql, + "UPDATE mxq_group SET" + " group_flags = group_flags | ?" + " WHERE group_id = ?" + " AND user_uid = ?" + ); + if (!stmt) { + mx_log_err("mx_mysql_statement_prepare(): %m"); + return -(errno=EIO); + } + + res = mx_mysql_statement_param_bind(stmt, 0, uint64, &(newflags)); + res += mx_mysql_statement_param_bind(stmt, 1, uint64, &(group_id)); + res += mx_mysql_statement_param_bind(stmt, 2, uint32, &(user_uid)); + assert(res == 0); + + res = mx_mysql_statement_execute(stmt, &num_rows); + + if (res < 0) + mx_log_err("mx_mysql_statement_execute(): %m"); + + mx_mysql_statement_close(&stmt); + + if (res < 0) + return -(errno=-res); + + assert(num_rows <= 1); + return (int)num_rows; +} + +static int update_group_flags_reopen(struct mx_mysql *mysql, uint64_t group_id, uint32_t user_uid) +{ + struct mx_mysql_stmt *stmt = NULL; + unsigned long long num_rows = 0; + int res; + uint64_t newflags = 0; + + newflags |= MXQ_GROUP_FLAG_CLOSED; + + stmt = mx_mysql_statement_prepare(mysql, + "UPDATE mxq_group SET" + " group_flags = group_flags & ~(?)" + " WHERE group_id = ?" + " AND user_uid = ?" + ); + if (!stmt) { + mx_log_err("mx_mysql_statement_prepare(): %m"); + return -(errno=EIO); + } + + res = mx_mysql_statement_param_bind(stmt, 0, uint64, &(newflags)); + res += mx_mysql_statement_param_bind(stmt, 1, uint64, &(group_id)); + res += mx_mysql_statement_param_bind(stmt, 2, uint32, &(user_uid)); + assert(res == 0); + + res = mx_mysql_statement_execute(stmt, &num_rows); + + if (res < 0) + mx_log_err("mx_mysql_statement_execute(): %m"); + + mx_mysql_statement_close(&stmt); + + if (res < 0) + return -(errno=-res); + + assert(num_rows <= 1); + return (int)num_rows; +} + +int _close_group_for_user(struct mx_mysql *mysql, uint64_t group_id, uint64_t user_uid) +{ + int res; + + res = update_group_flags_closed(mysql, group_id, user_uid); + + if (res == 0) { + mx_log_warning("no group with group_id=%lu found for user with uid=%d", + group_id, user_uid); + return -(errno=ENOENT); + } + + if (res < 0) { + mx_log_err("closing group failed: %m"); + return res; + } + + assert(res == 1); + + mx_log_notice("closing group %lu succeded.", group_id); + return 0; +} + +int _reopen_group_for_user(struct mx_mysql *mysql, uint64_t group_id, uint64_t user_uid) +{ + int res; + + res = update_group_flags_reopen(mysql, group_id, user_uid); + + if (res == 0) { + mx_log_warning("no group with group_id=%lu found for user with uid=%d", + group_id, user_uid); + return -(errno=ENOENT); + } + + if (res < 0) { + mx_log_err("opening group failed: %m"); + return res; + } + + assert(res == 1); + + mx_log_notice("opening group %lu succeded.", group_id); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct mx_mysql *mysql = NULL; + + uid_t ruid, euid, suid; + struct passwd *passwd; + + int res; + + uint64_t arg_group_id; + char arg_debug; + uint64_t arg_uid; + enum mode arg_mode; + + char *arg_mysql_default_group; + char *arg_mysql_default_file; + + int i; + int opt; + struct mx_getopt_ctl optctl; + struct mx_option opts[] = { + MX_OPTION_NO_ARG("help", 'h'), + MX_OPTION_NO_ARG("version", 'V'), + + MX_OPTION_NO_ARG("debug", 5), + MX_OPTION_NO_ARG("verbose", 'v'), + + MX_OPTION_REQUIRED_ARG("user", 'u'), + + MX_OPTION_REQUIRED_ARG("close", 'c'), + MX_OPTION_REQUIRED_ARG("reopen", 'o'), + + MX_OPTION_OPTIONAL_ARG("mysql-default-file", 'M'), + MX_OPTION_OPTIONAL_ARG("mysql-default-group", 'S'), + MX_OPTION_END + }; + + arg_mysql_default_group = getenv("MXQ_MYSQL_DEFAULT_GROUP"); + if (!arg_mysql_default_group) + arg_mysql_default_group = MXQ_MYSQL_DEFAULT_GROUP; + + arg_mysql_default_file = getenv("MXQ_MYSQL_DEFAULT_FILE"); + if (!arg_mysql_default_file) + arg_mysql_default_file = MXQ_MYSQL_DEFAULT_FILE; + + arg_group_id = 0; + arg_debug = 0; + arg_mode = MODE_UNSET; + arg_uid = UINT64_UNSET; + + mx_log_level_set(MX_LOG_NOTICE); + + res = getresuid(&ruid, &euid, &suid); + assert(res != -1); + + mx_getopt_init(&optctl, argc-1, &argv[1], opts); + optctl.flags = MX_FLAG_STOPONNOOPT; + + while ((opt=mx_getopt(&optctl, &i)) != MX_GETOPT_END) { + if (opt == MX_GETOPT_ERROR) { + exit(EX_USAGE); + } + + switch (opt) { + case 'V': + mxq_print_generic_version(); + exit(EX_USAGE); + + case 'h': + print_usage(); + exit(EX_USAGE); + + case 5: + arg_debug = 1; + mx_log_level_set(MX_LOG_DEBUG); + break; + + case 'v': + if (!arg_debug) + mx_log_level_set(MX_LOG_INFO); + break; + + case 'M': + arg_mysql_default_file = optctl.optarg; + break; + + case 'S': + arg_mysql_default_group = optctl.optarg; + break; + + case 'u': + passwd = getpwnam(optctl.optarg); + if (passwd) { + arg_uid = passwd->pw_uid; + break; + } + mx_log_debug("user %s not found. trying numeric uid.", optctl.optarg); + + if (!isdigit(*optctl.optarg)) { + mx_log_err("Invalid argument for --user '%s': User not found.", optctl.optarg); + exit(EX_USAGE); + } + + if (mx_strtou64(optctl.optarg, &arg_uid) < 0 || arg_uid >= UINT64_SPECIAL_MIN) { + if (arg_uid >= UINT64_SPECIAL_MIN) + errno = ERANGE; + mx_log_err("Invalid argument for --user '%s': %m", optctl.optarg); + exit(EX_USAGE); + } + errno = 0; + passwd = getpwuid(arg_uid); + if (!passwd) { + if (errno) + mx_log_err("Can't load user '%s': %m"); + else + mx_log_err("Invalid argument for --user '%s': User not found.", optctl.optarg); + exit(EX_USAGE); + } + break; + + case 'c': + if (mx_strtou64(optctl.optarg, &arg_group_id) < 0 || !arg_group_id) { + if (!arg_group_id) + errno = ERANGE; + mx_log_err("Invalid argument for --close '%s': %m", optctl.optarg); + exit(EX_CONFIG); + } + arg_mode = MODE_CLOSE; + break; + + case 'o': + if (mx_strtou64(optctl.optarg, &arg_group_id) < 0 || !arg_group_id) { + if (!arg_group_id) + errno = ERANGE; + mx_log_err("Invalid argument for --reopen '%s': %m", optctl.optarg); + exit(EX_CONFIG); + } + arg_mode = MODE_REOPEN; + break; + } + } + + MX_GETOPT_FINISH(optctl, argc, argv); + + if (!arg_group_id) { + print_usage(); + exit(EX_USAGE); + } + + if (arg_uid == UINT64_UNSET) + arg_uid = ruid; + + if (arg_uid != ruid && ruid != 0) { + mx_log_err("Nice try, but only root user may kill jobs of other users! Better luck next time."); + exit(EX_USAGE); + } + + if (!passwd) { + errno = 0; + passwd = getpwuid(arg_uid); + if (!passwd && errno) { + mx_log_err("Can't load user with uid '%lu': %m", arg_uid); + exit(EX_IOERR); + } + if (!passwd) { + assert(arg_uid == ruid); + mx_log_err("Can't load current user with uid '%lu'.", arg_uid); + exit(EX_NOUSER); + } + } + + res = mx_mysql_initialize(&mysql); + assert(res == 0); + + mx_mysql_option_set_default_file(mysql, arg_mysql_default_file); + mx_mysql_option_set_default_group(mysql, arg_mysql_default_group); + + res = mx_mysql_connect_forever(&mysql); + assert(res == 0); + + mx_log_info("MySQL: Connection to database established."); + + if (arg_mode == MODE_CLOSE) { + res = _close_group_for_user(mysql, arg_group_id, arg_uid); + + mx_mysql_finish(&mysql); + mx_log_info("MySQL: Connection to database closed."); + + return (res < 0); + } else if (arg_mode == MODE_REOPEN) { + res = _reopen_group_for_user(mysql, arg_group_id, arg_uid); + + mx_mysql_finish(&mysql); + mx_log_info("MySQL: Connection to database closed."); + + return (res < 0); + } + + mx_mysql_finish(&mysql); + mx_log_info("MySQL: Connection to database closed."); + return 1; +} + From b374d1c77e338e4482bba09b95664225fc3a9f10 Mon Sep 17 00:00:00 2001 From: Marius Tolzmann Date: Thu, 27 Aug 2015 13:57:25 +0200 Subject: [PATCH 2/3] mxqdump: Add group_flags to group view --- mxqdump.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mxqdump.c b/mxqdump.c index 142b9a4..2cac9c1 100644 --- a/mxqdump.c +++ b/mxqdump.c @@ -111,6 +111,7 @@ static int print_group(struct mxq_group *g) " uid=%u" " group_id=%lu" " pri=%d" + " flags=%lu" " jobs_total=%lu" " run_jobs=%lu" " run_slots=%lu" @@ -137,6 +138,7 @@ static int print_group(struct mxq_group *g) g->user_uid, g->group_id, g->group_priority, + g->group_flags, g->group_jobs, g->group_jobs_running, g->group_slots_running, From f60599ef7d3290cb0e755413c3b0262b45688cb5 Mon Sep 17 00:00:00 2001 From: Marius Tolzmann Date: Thu, 27 Aug 2015 14:11:46 +0200 Subject: [PATCH 3/3] mxqsub: Do not add jobs to closed groups --- mxqsub.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/mxqsub.c b/mxqsub.c index 4312034..2aae6a7 100644 --- a/mxqsub.c +++ b/mxqsub.c @@ -141,6 +141,7 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g) struct mx_mysql_stmt *stmt = NULL; unsigned long long num_rows = 0; int res; + uint64_t flags; assert(mysql); assert(g); @@ -153,6 +154,8 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g) assert(g->job_command); assert(*g->job_command); assert(g->job_threads); assert(g->job_memory); assert(g->job_time); + flags = MXQ_GROUP_FLAG_CLOSED; + stmt = mx_mysql_statement_prepare(mysql, "SELECT" " group_id" @@ -168,6 +171,7 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g) " AND job_time = ?" " AND group_priority = ?" " AND group_status = 0" + " AND group_flags & ? = 0 " " ORDER BY group_id DESC" " LIMIT 1"); if (!stmt) { @@ -185,6 +189,7 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g) res += mx_mysql_statement_param_bind(stmt, 7, uint64, &(g->job_memory)); res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time)); res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority)); + res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(flags)); assert(res == 0); res = mx_mysql_statement_execute(stmt, &num_rows); @@ -216,6 +221,7 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g struct mx_mysql_stmt *stmt = NULL; unsigned long long num_rows = 0; int res; + uint64_t flags; assert(mysql); assert(g); @@ -228,6 +234,8 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g assert(g->job_command); assert(*g->job_command); assert(g->job_threads); assert(g->job_memory); assert(g->job_time); + flags = MXQ_GROUP_FLAG_CLOSED; + stmt = mx_mysql_statement_prepare(mysql, "SELECT" " group_id" @@ -244,6 +252,7 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g " AND group_priority = ?" " AND group_status = 0" " AND group_id = ?" + " AND group_flags & ? = 0 " " ORDER BY group_id DESC" " LIMIT 1"); if (!stmt) { @@ -262,6 +271,7 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time)); res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority)); res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(g->group_id)); + res += mx_mysql_statement_param_bind(stmt, 11, uint64, &(flags)); assert(res == 0); res = mx_mysql_statement_execute(stmt, &num_rows); @@ -286,6 +296,7 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g struct mx_mysql_stmt *stmt = NULL; unsigned long long num_rows = 0; int res; + uint64_t flags; assert(mysql); assert(g); @@ -298,6 +309,8 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g assert(g->job_command); assert(*g->job_command); assert(g->job_threads); assert(g->job_memory); assert(g->job_time); + flags = MXQ_GROUP_FLAG_CLOSED; + stmt = mx_mysql_statement_prepare(mysql, "SELECT" " group_id" @@ -318,6 +331,7 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g " OR group_jobs_inq > 0" " OR group_jobs = 0" ")" + " AND group_flags & ? = 0 " " ORDER BY group_id DESC" " LIMIT 1"); if (!stmt) { @@ -335,6 +349,7 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g res += mx_mysql_statement_param_bind(stmt, 7, uint64, &(g->job_memory)); res += mx_mysql_statement_param_bind(stmt, 8, uint32, &(g->job_time)); res += mx_mysql_statement_param_bind(stmt, 9, uint16, &(g->group_priority)); + res += mx_mysql_statement_param_bind(stmt, 10, uint64, &(flags)); assert(res == 0); res = mx_mysql_statement_execute(stmt, &num_rows); @@ -511,7 +526,7 @@ static int mxq_submit_task(struct mx_mysql *mysql, struct mxq_job *j, int flags, g->group_id = group_id; res = load_group_id_by_group_id(mysql, g); if (res == 0) { - mx_log_crit("Could not load group with group_id=%lu: No matching group found. Aborting.", group_id); + mx_log_err("Could not load group with group_id=%lu: No matching open group found. Aborting.", group_id); return -(errno=ENOENT); } } @@ -983,7 +998,8 @@ int main(int argc, char *argv[]) mx_log_info("MySQL: Connection to database closed."); if (res < 0) { - mx_log_err("Job submission failed: %m"); + if (res != -ENOENT) + mx_log_err("Job submission failed: %m"); return 1; }