Whamcloud - gitweb
EX-2818 lipe: Parse arguments and config file
authorLei Feng <flei@whamcloud.com>
Tue, 27 Apr 2021 10:50:10 +0000 (18:50 +0800)
committerAndreas Dilger <adilger@whamcloud.com>
Thu, 22 Jul 2021 17:34:52 +0000 (17:34 +0000)
Add the code to parse arguments and config file, and some arguments.
Add the framework of wait-scan-free.

Change-Id: I792715badcb5a1fb1aa9062f04c2afab3229b747
Signed-off-by: Lei Feng <flei@whamcloud.com>
Test-Parameters: trivial
Reviewed-on: https://review.whamcloud.com/43194
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: John L. Hammond <jhammond@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
lipe/src/lpcc_purge.c

index a47c91a..e0bff19 100644 (file)
@@ -1,6 +1,9 @@
+#include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
+#include <linux/lustre/lustre_idl.h>
+#include <lustre/lustreapi.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <lustre/lustreapi.h>
+#include <sys/vfs.h>
 #include <unistd.h>
 
+#include "debug.h"
+
 
-#define LPCC_PURGE_DEFAULT_CONFIG_FILE "/etc/lpcc_purge.conf"
-#define LPCC_PURGE_PIDFILE             "/var/run/lpcc_purge.pid"
+#define DEF_HIGH_USAGE                 90
+#define DEF_LOW_USAGE                  75
+#define DEF_INTERVAL                   5
+#define DEF_SCAN_THREADS               1
+
+#define OPT_LOG_LEVEL                  4
 
 struct lpcc_purge_options {
-       char *o_cfg_file;
-       bool o_exit;
+       char *o_cache;
+       char *o_mount;
+       unsigned int o_rwid;
+
+       double o_high_usage;
+       double o_low_usage;
+
+       int o_interval;
+       int o_scan_threads;
 };
 static struct lpcc_purge_options opt = {
-       .o_cfg_file = LPCC_PURGE_DEFAULT_CONFIG_FILE,
-       .o_exit = false,
+       .o_rwid                 = -1,
+       .o_high_usage           = DEF_HIGH_USAGE,
+       .o_low_usage            = DEF_LOW_USAGE,
+       .o_interval             = DEF_INTERVAL,
+       .o_scan_threads         = DEF_SCAN_THREADS,
+};
+
+bool exit_flag = false;
+struct lpcc_purge_stats {
+       double s_start_usage;
+};
+static struct lpcc_purge_stats stats = {
+       .s_start_usage  = -1,
 };
 
 static void lpcc_purge_null_handler(int signal)
@@ -32,35 +59,277 @@ static void lpcc_purge_sigint_handler(int signal)
 {
        psignal(signal, "exiting");
 
-       opt.o_exit = true;
+       exit_flag = true;
 }
 
 static void usage(void)
 {
        printf("Usage: %s [options]\n"
-               "\t-f, --config-file FILE\n"
+               "\t-b, --debug, enable debugging output\n"
+               "\t    --log_level={debug|info|normal|warn|error|fatal|off}, set log level (default: info)\n"
+               "\t-f, --config-file=FILE\n"
+               "\t-C, --cache=DIR root directory of PCC\n"
+               "\t-M, --mount=DIR local Lustre client's mountpoint\n"
+               "\t-A, --rwid=NUM the rwid of PCC\n"
+               "\t-H, --high_usage=NUM %% of space or inode to start purging (default: %u)\n"
+               "\t-L, --low_usage=NUM %% of space or inode to stop purging (default: %u)\n"
+               "\t-i, --interval=NUM, seconds to next check (default: %u)\n"
+               "\t-t, --scan_threads=NUM scanning threads (default: %u)\n"
                "\t-h, --help, print this help message\n",
-               program_invocation_short_name
-       );
 
-       exit(0);
+               program_invocation_short_name,
+               DEF_HIGH_USAGE,
+               DEF_LOW_USAGE,
+               DEF_INTERVAL,
+               DEF_SCAN_THREADS
+       );
 }
 
 static struct option long_options[] = {
+       { "debug", no_argument, NULL, 'b'},
        { "config-file", required_argument, NULL, 'f'},
+       { "log_level", required_argument, NULL, OPT_LOG_LEVEL},
+       { "cache", required_argument, NULL, 'C'},
+       { "mount", required_argument, NULL, 'M'},
+       { "rwid", required_argument, NULL, 'A'},
+       { "high_usage", required_argument, NULL, 'H'},
+       { "low_usage", required_argument, NULL, 'L'},
+       { "interval", required_argument, NULL, 'i'},
+       { "scan_threads", required_argument, NULL, 't'},
        { "help", no_argument, NULL, 'h' },
        { NULL }
 };
 
+static struct option *lpcc_purge_keyword_lookup(const char *keyword)
+{
+       int i = 0;
+
+       while (long_options[i].name) {
+               if (strcmp(keyword, long_options[i].name) == 0)
+                       return long_options + i;
+               i++;
+       }
+       return NULL;
+}
+
+static void lpcc_purge_process_opt(int c, char *optarg);
+
+static void load_config(const char *path)
+{
+       size_t len = 0;
+       char *buf = NULL;
+
+       FILE *f = fopen(path, "r");
+       if (!f) {
+               llapi_error(LLAPI_MSG_FATAL, errno,
+                            "cannot open config file '%s'", path);
+               exit(1);
+       }
+
+       while (!feof(f)) {
+               struct option *opt;
+               char *s, *t;
+               len = PATH_MAX;
+
+               if (getline(&buf, &len, f) <= 0)
+                       break;
+               s = buf;
+               while (isspace(*s))
+                       s++;
+               t = strchr(s, '#');
+               if (t)
+                       *t = '\0';
+               if (*s == '#')
+                       continue;
+               t = strsep(&s, "=\n");
+               if (!t || *t == 0)
+                       continue;
+               opt = lpcc_purge_keyword_lookup(t);
+               if (!opt) {
+                       llapi_error(LLAPI_MSG_ERROR, EINVAL,
+                                    "unknown tunable '%s'", t);
+                       continue;
+               }
+               if (opt->val == 'f') {
+                       /* you shall not pass! */
+                       continue;
+               }
+
+               if (opt->has_arg == required_argument ||
+                   opt->has_arg == optional_argument) {
+                       optarg = strsep(&s, "\n ");
+                       if (!optarg &&
+                            opt->has_arg == required_argument) {
+                               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                                            "no argument for '%s'", t);
+                               exit(1);
+                       }
+               }
+               else {
+                       optarg = NULL;
+               }
+               llapi_printf(LLAPI_MSG_DEBUG, "conf: %s %s\n", t,
+                            optarg ? optarg : "");
+               lpcc_purge_process_opt(opt->val, optarg);
+       }
+
+       free(buf);
+       fclose(f);
+}
+
+static const char *log_level_strs[] = {
+       [LLAPI_MSG_OFF]         = "off",
+       [LLAPI_MSG_FATAL]       = "fatal",
+       [LLAPI_MSG_ERROR]       = "error",
+       [LLAPI_MSG_WARN]        = "warn",
+       [LLAPI_MSG_NORMAL]      = "normal",
+       [LLAPI_MSG_INFO]        = "info",
+       [LLAPI_MSG_DEBUG]       = "debug",
+};
+
+static enum llapi_message_level parse_log_level(const char *level_str)
+{
+       enum llapi_message_level level = LLAPI_MSG_INFO;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(log_level_strs); i++) {
+               if (strcasecmp(log_level_strs[i], level_str) == 0) {
+                       level = i;
+                       break;
+               }
+       }
+
+       if (i == sizeof(log_level_strs) / sizeof(char *)) {
+               llapi_error(LLAPI_MSG_WARN, -EINVAL,
+                       "Unrecognized log level string '%s'",
+                       optarg);
+       }
+
+       return level;
+}
+
+static void parse_mountpoint(const char *name)
+{
+       int rc;
+       struct statfs statbuf;
+
+       rc = statfs(name, &statbuf);
+       if (rc != 0) {
+               llapi_error(LLAPI_MSG_FATAL, -rc,
+                            "cannot statfs '%s'", name);
+               exit(1);
+       }
+       if (statbuf.f_type != LL_SUPER_MAGIC) {
+               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                       "'%s' is not a lustre mount point", name);
+               exit(1);
+       }
+}
+
+static void parse_cache_dir(const char *name)
+{
+       struct stat stat1;
+       int rc;
+
+       rc = stat(name, &stat1);
+       if (rc) {
+               llapi_error(LLAPI_MSG_FATAL, errno,
+                            "cannot stat cache dir '%s'", name);
+               exit(1);
+       }
+
+       if (!S_ISDIR(stat1.st_mode)) {
+               llapi_error(LLAPI_MSG_FATAL, errno,
+                            "cache path '%s' is not a directory", name);
+               exit(1);
+       }
+}
+
 static void lpcc_purge_process_opt(int c, char *optarg)
 {
+       long value;
+       char *endptr = NULL;
+       enum llapi_message_level log_level;
+
        switch(c) {
        case 'f':
-               opt.o_cfg_file = optarg;
+               load_config(optarg);
                break;
-       }
+       case 'b':
+               llapi_msg_set_level(LLAPI_MSG_MAX);
+               break;
+       case OPT_LOG_LEVEL:
+               log_level = parse_log_level(optarg);
+               llapi_printf(LLAPI_MSG_INFO, "Set log level: %s\n",
+                       log_level_strs[log_level]);
+               llapi_msg_set_level(log_level);
 
-       return;
+               break;
+       case 'C':
+               parse_cache_dir(optarg);
+               opt.o_cache = strdup(optarg);
+               break;
+       case 'M':
+               parse_mountpoint(optarg);
+               opt.o_mount = strdup(optarg);
+               break;
+       case 'A':
+               value = strtol(optarg, &endptr, 10);
+               if (*endptr != '\0' || value <= 0) {
+                       llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid rwid: '%s'",
+                                   optarg);
+                       exit(1);
+               }
+               opt.o_rwid = value;
+               break;
+       case 'H':
+               value = strtol(optarg, &endptr, 10);
+               if (*endptr != '\0' || value < 1 || value > 99) {
+                       llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid high watermark: '%s'",
+                                   optarg);
+                       exit(1);
+               }
+               opt.o_high_usage = value;
+               break;
+       case 'L':
+               value = strtol(optarg, &endptr, 10);
+               if (*endptr != '\0' || value < 1 || value > 99) {
+                       llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid low watermark: '%s'",
+                                   optarg);
+                       exit(1);
+               }
+               opt.o_low_usage = value;
+               break;
+       case 'i':
+               value = strtol(optarg, &endptr, 10);
+               if (*endptr != '\0' || value <= 0) {
+                       llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid check interval: '%s'",
+                                   optarg);
+                       exit(1);
+               }
+               opt.o_interval = value;
+               break;
+       case 't':
+               value = strtol(optarg, &endptr, 10);
+               if (*endptr != '\0' || value <= 0) {
+                       llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid scan threads count: '%s'",
+                                   optarg);
+                       exit(1);
+               }
+               opt.o_scan_threads = value;
+               break;
+       default:
+               llapi_error(LLAPI_MSG_FATAL, -EINVAL,
+                                   "invalid argument: '%s'",
+                                   optarg);
+               exit(1);
+               break;
+       }
 }
 
 static void lpcc_purge_parse_opts(int argc, char **argv)
@@ -68,7 +337,7 @@ static void lpcc_purge_parse_opts(int argc, char **argv)
        int c;
 
        while ((c = getopt_long(argc, argv,
-                               "hf:",
+                               "bf:C:M:A:H:L:i:t:h",
                                long_options, NULL))
               != EOF) {
                switch(c) {
@@ -88,73 +357,138 @@ static void lpcc_purge_parse_opts(int argc, char **argv)
        }
 }
 
-static int load_config()
+void lpcc_purge_verify_opts(void)
 {
-       return 0;
+       if (opt.o_mount == NULL) {
+               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                            "lustre mount point must be specified");
+               exit(1);
+       }
+       if (opt.o_cache == NULL) {
+               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                            "pcc cache dir must be specified");
+               exit(1);
+       }
+       if (opt.o_rwid == -1) {
+               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                            "rwid mount point must be specified");
+               exit(1);
+       }
+       // check freehi > freelo
+       if (opt.o_high_usage <= opt.o_low_usage) {
+               llapi_error(LLAPI_MSG_FATAL, EINVAL,
+                           "high usage (%.1f) must be larger than low usage (%.1f)",
+                           opt.o_high_usage, opt.o_low_usage);
+               exit(1);
+       }
 }
 
-static void lpcc_purge_lock_pidfile(void)
+/* It seems that lustre/utils/pid_file.c does not exist in RHEL7.x,
+ * so copy the function here
+ */
+static int create_pid_file(const char *path)
 {
-       char buf[1024];
-       int fd, rc, sz;
+       char buf[3 * sizeof(long long) + 2];
+       size_t buf_len;
+       int fd = -1;
+       int rc2;
 
-       snprintf(buf, sizeof(buf), LPCC_PURGE_PIDFILE);
-       fd = open(buf, O_RDWR | O_CREAT, 0600);
+       fd = open(path, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
        if (fd < 0) {
-               llapi_error(LLAPI_MSG_FATAL, errno,
-                            "cannot create pidfile");
-               exit(1);
+               fprintf(stderr, "%s: cannot open '%s': %s\n",
+                       program_invocation_short_name, path, strerror(errno));
+               return -1;
        }
-       rc = flock(fd, LOCK_EX | LOCK_NB);
-       if (rc < 0) {
-               sz = read(fd, buf, sizeof(buf));
-               /* protect against garbage */
-               if (sz > 10)
-                       sz = 0;
-               if (sz > 0)
-                       buf[sz] = 0;
-               llapi_error(LLAPI_MSG_FATAL, EBUSY,
-                            "another lpcc_purge is running, locked by %s",
-                            sz > 0 ? buf : "[unknown]");
-               exit(1);
+
+       struct flock fl = {
+               .l_type = F_WRLCK,
+               .l_whence = SEEK_SET,
+       };
+
+       rc2 = fcntl(fd, F_SETLK, &fl);
+       if (rc2 < 0) {
+               fprintf(stderr, "%s: cannot lock '%s': %s\n",
+                       program_invocation_short_name, path, strerror(errno));
+               goto out;
        }
 
-       rc = ftruncate(fd, 0);
-       if (rc < 0) {
-               llapi_error(LLAPI_MSG_FATAL, errno,
-                       "cannot truncate pidfile");
-               exit(1);
+       rc2 = ftruncate(fd, 0);
+       if (rc2 < 0) {
+               fprintf(stderr, "%s: cannot truncate '%s': %s\n",
+                       program_invocation_short_name, path, strerror(errno));
+               goto out;
        }
 
-       sz = snprintf(buf, sizeof(buf), "%d\n", getpid());
-       rc = write(fd, buf, sz);
-       if (rc < 0 || rc != sz) {
+       buf_len = snprintf(buf, sizeof(buf), "%lld\n", (long long)getpid());
+       rc2 = write(fd, buf, buf_len);
+       if (rc2 < 0) {
+               fprintf(stderr, "%s: cannot write '%s': %s\n",
+                       program_invocation_short_name, path, strerror(errno));
+               goto out;
+       }
+out:
+       if (rc2 < 0 && !(fd < 0)) {
+               close(fd);
+               fd = -1;
+       }
+
+       return fd;
+}
+
+static void lpcc_purge_lock_pidfile(void)
+{
+       char buf[PATH_MAX];
+       int fd;
+
+       snprintf(buf, sizeof(buf), "/var/run/lpcc_purge-%d.pid", opt.o_rwid);
+       fd = create_pid_file(buf);
+       if (fd < 0) {
                llapi_error(LLAPI_MSG_FATAL, errno,
-                            "cannot write pidfile");
+                       "cannot create pidfile '%s'", buf);
                exit(1);
        }
-
+       /* we keep the fd open to hold the flock,
+          it will be closed automatically when the process exits */
 }
 
-static void lpcc_purge_unlock_pidfile(void)
+static double lpcc_purge_get_fs_usage(const char *fs)
 {
-       char buf[1024];
        int rc;
+       struct statfs statfs_buf;
 
-       snprintf(buf, sizeof(buf), LPCC_PURGE_PIDFILE);
+       rc = statfs(opt.o_cache, &statfs_buf);
+       if (rc) {
+               llapi_error(LLAPI_MSG_FATAL, errno, "cannot statfs '%s'", fs);
+               exit(1);
+       }
 
-       rc = unlink(buf);
-       if (rc < 0) {
-               llapi_error(LLAPI_MSG_ERROR, errno,
-                       "cannot unlink pidfile");
+       return 100.0 * (statfs_buf.f_blocks - statfs_buf.f_bfree) / statfs_buf.f_blocks;
+}
+
+static void lpcc_purge_wait_for_scan(void)
+{
+       while (!exit_flag) {
+               double usage = lpcc_purge_get_fs_usage(opt.o_cache);
+               if (usage >= opt.o_high_usage) {
+                       stats.s_start_usage = usage;
+                       break;
+               } else {
+                       sleep(opt.o_interval);
+               }
        }
+}
 
-       return;
+static void lpcc_purge_scan(void)
+{
+}
+
+static void lpcc_purge_free_space(void)
+{
 }
 
 int main(int argc, char *argv[])
 {
-       int rc = -1;
+       int rc = 0;
        llapi_msg_set_level(LLAPI_MSG_INFO);
 
        signal(SIGUSR1, &lpcc_purge_null_handler);
@@ -165,18 +499,18 @@ int main(int argc, char *argv[])
 
        lpcc_purge_parse_opts(argc, argv);
 
-       rc = load_config();
-       if (rc) {
-               goto out;
-       }
+       lpcc_purge_verify_opts();
 
        lpcc_purge_lock_pidfile();
 
-       while(!opt.o_exit) {
-               sleep(10);
+       while(!exit_flag) {
+
+               lpcc_purge_wait_for_scan();
+
+               lpcc_purge_scan();
+
+               lpcc_purge_free_space();
        }
 
-out:
-       lpcc_purge_unlock_pidfile();
        return rc == 0 ? 0 : 255;
 }