This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new e137c7d  traffic_ctl: Refactor and convert traffic_ctl to use ArgParser
e137c7d is described below

commit e137c7d3941be638701a2a2b9427a3a2f9424be1
Author: Xavier Chi <[email protected]>
AuthorDate: Tue Nov 13 14:28:38 2018 -0600

    traffic_ctl: Refactor and convert traffic_ctl to use ArgParser
---
 include/tscore/runroot.h       |   2 +-
 src/traffic_ctl/alarm.cc       |  67 +++---------
 src/traffic_ctl/config.cc      | 243 ++++++++++++++---------------------------
 src/traffic_ctl/host.cc        | 113 ++++++-------------
 src/traffic_ctl/metric.cc      |  83 +++++---------
 src/traffic_ctl/plugin.cc      |  28 ++---
 src/traffic_ctl/server.cc      | 151 ++++++-------------------
 src/traffic_ctl/storage.cc     |  31 ++----
 src/traffic_ctl/traffic_ctl.cc | 231 ++++++++++++++++++++-------------------
 src/traffic_ctl/traffic_ctl.h  | 123 +++++++++++----------
 10 files changed, 399 insertions(+), 673 deletions(-)

diff --git a/include/tscore/runroot.h b/include/tscore/runroot.h
index 0f2efa1..5d1b607 100644
--- a/include/tscore/runroot.h
+++ b/include/tscore/runroot.h
@@ -55,7 +55,7 @@ bool exists(const std::string &dir);
 bool is_directory(const std::string &directory);
 
 // argparser_runroot_handler should replace runroot_handler below when all 
program use ArgParser.
-void argparser_runroot_handler(std::string const &value, const char 
*executable, bool json);
+void argparser_runroot_handler(std::string const &value, const char 
*executable, bool json = false);
 void runroot_handler(const char **argv, bool json = false);
 
 // get a map from default layout
diff --git a/src/traffic_ctl/alarm.cc b/src/traffic_ctl/alarm.cc
index d3229f3..d8df03a 100644
--- a/src/traffic_ctl/alarm.cc
+++ b/src/traffic_ctl/alarm.cc
@@ -41,46 +41,38 @@ struct AlarmListPolicy {
 
 using CtrlAlarmList = CtrlMgmtList<AlarmListPolicy>;
 
-static int
-alarm_list(unsigned argc, const char **argv)
+void
+CtrlEngine::alarm_list()
 {
   TSMgmtError error;
   CtrlAlarmList alarms;
 
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("alarm list", nullptr, 0);
-  }
-
   error = TSActiveEventGetMlt(alarms.list);
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "failed to fetch active alarms");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
   while (!alarms.empty()) {
     char *a = alarms.next();
-    printf("%s\n", a);
+    std::cout << a << std::endl;
     TSfree(a);
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-alarm_clear(unsigned argc, const char **argv)
+void
+CtrlEngine::alarm_clear()
 {
   TSMgmtError error;
   CtrlAlarmList alarms;
 
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("alarm clear", nullptr, 0);
-  }
-
   // First get the active alarms ...
   error = TSActiveEventGetMlt(alarms.list);
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "failed to fetch active alarms");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
   // Now resolve them all ...
@@ -91,49 +83,26 @@ alarm_clear(unsigned argc, const char **argv)
     if (error != TS_ERR_OKAY) {
       CtrlMgmtError(error, "failed to resolve %s", a);
       TSfree(a);
-      return CTRL_EX_ERROR;
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
     TSfree(a);
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-alarm_resolve(unsigned argc, const char **argv)
+void
+CtrlEngine::alarm_resolve()
 {
   TSMgmtError error;
   CtrlAlarmList alarms;
 
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments == 0) {
-    return CtrlCommandUsage("alarm resolve ALARM [ALARM ...]", nullptr, 0);
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
-    error = TSEventResolve(file_arguments[i]);
+  for (const auto &it : arguments.get("resolve")) {
+    error = TSEventResolve(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to resolve %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to resolve %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_alarm(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {alarm_clear, "clear", "Clear all current alarms"},
-    {alarm_list, "list", "List all current alarms"},
-
-    // Note that we separate resolve one from resolve all for the same reasons 
that
-    // we have "metric zero" and "metric clear".
-    {alarm_resolve, "resolve", "Resolve the listed alarms"},
-    /* XXX describe a specific alarm? */
-    /* XXX raise an alarm? */
-  };
-
-  return CtrlGenericSubcommand("alarm", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/config.cc b/src/traffic_ctl/config.cc
index 35c7303..378fb47 100644
--- a/src/traffic_ctl/config.cc
+++ b/src/traffic_ctl/config.cc
@@ -181,179 +181,143 @@ format_record(const CtrlMgmtRecord &record, bool recfmt)
   CtrlMgmtRecordValue value(record);
 
   if (recfmt) {
-    printf("%s %s %s %s\n", rec_labelof(record.rclass()), record.name(), 
rec_typeof(record.type()), value.c_str());
+    std::cout << rec_labelof(record.rclass()) << ' ' << record.name() << ' ' 
<< rec_typeof(record.type()) << ' ' << value.c_str()
+              << std::endl;
   } else {
-    printf("%s: %s\n", record.name(), value.c_str());
+    std::cout << record.name() << ": " << value.c_str() << std::endl;
   }
 }
 
-static int
-config_get(unsigned argc, const char **argv)
+void
+CtrlEngine::config_get()
 {
-  int recfmt                       = 0;
-  const ArgumentDescription opts[] = {
-    {"records", '-', "Emit output in records.config format", "F", &recfmt, 
nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments < 1) {
-    return CtrlCommandUsage("config get [OPTIONS] RECORD [RECORD ...]", opts, 
countof(opts));
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("get")) {
     CtrlMgmtRecord record;
     TSMgmtError error;
 
-    error = record.fetch(file_arguments[i]);
+    error = record.fetch(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
     if (REC_TYPE_IS_CONFIG(record.rclass())) {
-      format_record(record, recfmt);
+      format_record(record, arguments.get("records"));
     }
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_describe(unsigned argc, const char **argv)
+void
+CtrlEngine::config_describe()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments < 1) {
-    return CtrlCommandUsage("config describe RECORD [RECORD ...]");
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("describe")) {
     TSConfigRecordDescription desc;
     TSMgmtError error;
 
     ink_zero(desc);
-    error = TSConfigRecordDescribe(file_arguments[i], 0 /* flags */, &desc);
+    error = TSConfigRecordDescribe(it.c_str(), 0 /* flags */, &desc);
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to describe %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to describe %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
-    printf("%-16s: %s\n", "Name", desc.rec_name);
-    printf("%-16s: %s\n", "Current Value", CtrlMgmtRecordValue(desc.rec_type, 
desc.rec_value).c_str());
-    printf("%-16s: %s\n", "Default Value", CtrlMgmtRecordValue(desc.rec_type, 
desc.rec_default).c_str());
-    printf("%-16s: %s\n", "Record Type", rec_classof(desc.rec_class));
-    printf("%-16s: %s\n", "Data Type", rec_typeof(desc.rec_type));
-    printf("%-16s: %s\n", "Access Control ", rec_accessof(desc.rec_access));
-    printf("%-16s: %s\n", "Update Type", rec_updateof(desc.rec_updatetype));
-    printf("%-16s: 0x%" PRIx64 "\n", "Update Status", desc.rec_update);
-    printf("%-16s: %s\n", "Source", rec_sourceof(desc.rec_source));
+    std::cout << "Name: " << desc.rec_name << std::endl;
+    std::cout << "Current Value: " << CtrlMgmtRecordValue(desc.rec_type, 
desc.rec_value).c_str() << std::endl;
+    std::cout << "Default Value: " << CtrlMgmtRecordValue(desc.rec_type, 
desc.rec_default).c_str() << std::endl;
+    std::cout << "Record Type: " << rec_classof(desc.rec_class) << std::endl;
+    std::cout << "Data Type: " << rec_typeof(desc.rec_type) << std::endl;
+    std::cout << "Access Control: " << rec_accessof(desc.rec_access) << 
std::endl;
+    std::cout << "Update Type: " << rec_updateof(desc.rec_updatetype) << 
std::endl;
+    std::cout << "Update Status: " << desc.rec_update << std::endl;
+    std::cout << "Source: " << rec_sourceof(desc.rec_source) << std::endl;
 
     if (strlen(desc.rec_checkexpr)) {
-      printf("%-16s: %s, '%s'\n", "Syntax Check", 
rec_checkof(desc.rec_checktype), desc.rec_checkexpr);
+      std::cout << "Syntax Check: " << rec_checkof(desc.rec_checktype) << 
desc.rec_checkexpr << std::endl;
     } else {
-      printf("%-16s: %s\n", "Syntax Check", rec_checkof(desc.rec_checktype));
+      std::cout << "Syntax Check: " << rec_checkof(desc.rec_checktype) << 
std::endl;
     }
-
-    printf("%-16s: %" PRId64 "\n", "Version", desc.rec_version);
-    printf("%-16s: %" PRId64 "\n", "Order", desc.rec_order);
-    printf("%-16s: %" PRId64 "\n", "Raw Stat Block", desc.rec_rsb);
+    std::cout << "Version: " << desc.rec_version << std::endl;
+    std::cout << "Order: " << desc.rec_order << std::endl;
+    std::cout << "Raw Stat Block: " << desc.rec_rsb << std::endl;
 
     TSConfigRecordDescriptionFree(&desc);
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_set(unsigned argc, const char **argv)
+void
+CtrlEngine::config_set()
 {
   TSMgmtError error;
   TSActionNeedT action;
-
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 2) {
-    return CtrlCommandUsage("config set RECORD VALUE");
-  }
-
-  error = TSRecordSet(file_arguments[0], file_arguments[1], &action);
+  auto set_data        = arguments.get("set");
+  const char *rec_name = set_data[0].c_str();
+  const char *rec_val  = set_data[1].c_str();
+  error                = TSRecordSet(rec_name, rec_val, &action);
   if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "failed to set %s", file_arguments[0]);
-    return CTRL_EX_ERROR;
+    CtrlMgmtError(error, "failed to set %s", rec_name);
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
   switch (action) {
   case TS_ACTION_SHUTDOWN:
-    printf("set %s, full shutdown required\n", file_arguments[0]);
+    std::cout << "set " << rec_name << ", full shutdown required" << std::endl;
     break;
   case TS_ACTION_RESTART:
-    printf("set %s, restart required\n", file_arguments[0]);
+    std::cout << "set " << rec_name << ", restart required" << std::endl;
     break;
   case TS_ACTION_RECONFIGURE:
-    printf("set %s, please wait 10 seconds for traffic server to sync 
configuration, restart is not required\n", file_arguments[0]);
+    std::cout << "set " << rec_name << ", please wait 10 seconds for traffic 
server to sync configuration, restart is not required"
+              << std::endl;
     break;
   case TS_ACTION_DYNAMIC:
   default:
-    printf("set %s\n", file_arguments[0]);
+    printf("set %s\n", rec_name);
     break;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_match(unsigned argc, const char **argv)
+void
+CtrlEngine::config_match()
 {
-  int recfmt                       = 0;
-  const ArgumentDescription opts[] = {
-    {"records", '-', "Emit output in records.config format", "F", &recfmt, 
nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments < 1) {
-    return CtrlCommandUsage("config match [OPTIONS] REGEX [REGEX ...]", opts, 
countof(opts));
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("match")) {
     CtrlMgmtRecordList reclist;
     TSMgmtError error;
 
     // XXX filter the results to only match configuration records.
 
-    error = reclist.match(file_arguments[i]);
+    error = reclist.match(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
     while (!reclist.empty()) {
       CtrlMgmtRecord record(reclist.next());
       if (REC_TYPE_IS_CONFIG(record.rclass())) {
-        format_record(record, recfmt);
+        format_record(record, arguments.get("records"));
       }
     }
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_reload(unsigned argc, const char **argv)
+void
+CtrlEngine::config_reload()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("config reload");
-  }
-
   TSMgmtError error = TSReconfigure();
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "configuration reload request failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_status(unsigned argc, const char **argv)
+void
+CtrlEngine::config_status()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("config status");
-  }
-
   CtrlMgmtRecord version;
   CtrlMgmtRecord configtime;
   CtrlMgmtRecord starttime;
@@ -368,77 +332,57 @@ config_status(unsigned argc, const char **argv)
   CTRL_MGMT_CHECK(proxy.fetch("proxy.node.config.restart_required.proxy"));
   CTRL_MGMT_CHECK(manager.fetch("proxy.node.config.restart_required.manager"));
 
-  printf("%s\n", CtrlMgmtRecordValue(version).c_str());
-  printf("Started at %s", timestr((time_t)starttime.as_int()).c_str());
-  printf("Last reconfiguration at %s", 
timestr((time_t)configtime.as_int()).c_str());
-  printf("%s\n", reconfig.as_int() ? "Reconfiguration required" : 
"Configuration is current");
+  std::cout << CtrlMgmtRecordValue(version).c_str() << std::endl;
+  std::cout << "Started at " << timestr((time_t)starttime.as_int()).c_str();
+  std::cout << "Last reconfiguration at " << 
timestr((time_t)configtime.as_int()).c_str();
+  std::cout << (reconfig.as_int() ? "Reconfiguration required" : 
"Configuration is current") << std::endl;
 
   if (proxy.as_int()) {
-    printf("traffic_server requires restarting\n");
+    std::cout << "traffic_server requires restarting" << std::endl;
   }
   if (manager.as_int()) {
-    printf("traffic_manager requires restarting\n");
+    std::cout << "traffic_manager requires restarting\n" << std::endl;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_defaults(unsigned argc, const char **argv)
+void
+CtrlEngine::config_defaults()
 {
-  int recfmt                       = 0;
-  const ArgumentDescription opts[] = {
-    {"records", '-', "Emit output in records.config format", "F", &recfmt, 
nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage("config diff [OPTIONS]");
-  }
-
   TSMgmtError error;
   CtrlMgmtRecordDescriptionList descriptions;
 
   error = descriptions.match(".*");
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "failed to fetch record metadata");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
   while (!descriptions.empty()) {
     TSConfigRecordDescription *desc = descriptions.next();
     CtrlMgmtRecordValue deflt(desc->rec_type, desc->rec_default);
 
-    if (recfmt) {
-      printf("%s %s %s %s\n", rec_labelof(desc->rec_class), desc->rec_name, 
rec_typeof(desc->rec_type), deflt.c_str());
+    if (arguments.get("records")) {
+      std::cout << rec_labelof(desc->rec_class) << ' ' << desc->rec_name << ' 
' << rec_typeof(desc->rec_type) << ' '
+                << deflt.c_str() << std::endl;
     } else {
-      printf("%s: %s\n", desc->rec_name, deflt.c_str());
+      std::cout << desc->rec_name << ": " << deflt.c_str() << std::endl;
     }
-
     TSConfigRecordDescriptionDestroy(desc);
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-config_diff(unsigned argc, const char **argv)
+void
+CtrlEngine::config_diff()
 {
-  int recfmt                       = 0;
-  const ArgumentDescription opts[] = {
-    {"records", '-', "Emit output in records.config format", "F", &recfmt, 
nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage("config diff [OPTIONS]");
-  }
-
   TSMgmtError error;
   CtrlMgmtRecordDescriptionList descriptions;
 
   error = descriptions.match(".*");
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "failed to fetch record metadata");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
   while (!descriptions.empty()) {
@@ -468,35 +412,16 @@ config_diff(unsigned argc, const char **argv)
       CtrlMgmtRecordValue current(desc->rec_type, desc->rec_value);
       CtrlMgmtRecordValue deflt(desc->rec_type, desc->rec_default);
 
-      if (recfmt) {
-        printf("%s %s %s %s # default: %s\n", rec_labelof(desc->rec_class), 
desc->rec_name, rec_typeof(desc->rec_type),
-               current.c_str(), deflt.c_str());
+      if (arguments.get("records")) {
+        std::cout << rec_labelof(desc->rec_class) << ' ' << desc->rec_name << 
' ' << rec_typeof(desc->rec_type) << ' '
+                  << current.c_str() << " # default: " << deflt.c_str() << 
std::endl;
       } else {
-        printf("%s has changed\n", desc->rec_name);
-        printf("\t%-16s: %s\n", "Current Value", current.c_str());
-        printf("\t%-16s: %s\n", "Default Value", deflt.c_str());
+        std::cout << desc->rec_name << "has changed" << std::endl;
+        std::cout << "\tCurrent Value: " << current.c_str() << std::endl;
+        std::cout << "\tDefault Value: " << deflt.c_str() << std::endl;
       }
     }
 
     TSConfigRecordDescriptionDestroy(desc);
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_config(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {config_defaults, "defaults", "Show default information configuration 
values"},
-    {config_describe, "describe", "Show detailed information about 
configuration values"},
-    {config_diff, "diff", "Show non-default configuration values"},
-    {config_get, "get", "Get one or more configuration values"},
-    {config_match, "match", "Get configuration matching a regular expression"},
-    {config_reload, "reload", "Request a configuration reload"},
-    {config_set, "set", "Set a configuration value"},
-    {config_status, "status", "Check the configuration status"},
-  };
-
-  return CtrlGenericSubcommand("config", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/host.cc b/src/traffic_ctl/host.cc
index 1c69fa3..2b23bc9 100644
--- a/src/traffic_ctl/host.cc
+++ b/src/traffic_ctl/host.cc
@@ -25,121 +25,78 @@
 #include "HostStatus.h"
 #include "records/P_RecUtils.h"
 
-static int
-status_get(unsigned argc, const char **argv)
+void
+CtrlEngine::status_get()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments < 1) {
-    return CtrlCommandUsage("host status HOST  [HOST  ...]", nullptr, 0);
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("status")) {
     CtrlMgmtRecord record;
     TSMgmtError error;
-    std::string str = stat_prefix + file_arguments[i];
+    std::string str = stat_prefix + it;
 
     for (const char *_reason_tag : Reasons::reasons) {
       std::string _stat = str + "_" + _reason_tag;
       error             = record.fetch(_stat.c_str());
       if (error != TS_ERR_OKAY) {
-        CtrlMgmtError(error, "failed to fetch %s", file_arguments[i]);
-        return CTRL_EX_ERROR;
+        CtrlMgmtError(error, "failed to fetch %s", it.c_str());
+        status_code = CTRL_EX_ERROR;
+        return;
       }
 
       if (REC_TYPE_IS_STAT(record.rclass())) {
-        printf("%s %s\n", record.name(), CtrlMgmtRecordValue(record).c_str());
+        std::cout << record.name() << ' ' << 
CtrlMgmtRecordValue(record).c_str() << std::endl;
       }
     }
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-status_down(unsigned argc, const char **argv)
+void
+CtrlEngine::status_down()
 {
-  int down_time     = 0;
-  char *reason      = nullptr;
-  const char *usage = "host down HOST [OPTIONS]";
-
-  const ArgumentDescription opts[] = {
-    {"time", 'I', "number of seconds that a host is marked down", "I", 
&down_time, nullptr, nullptr},
-    // memory is allocated for 'reason', if this option is used
-    {"reason", '-', "reason for marking the host down, one of 
'manual|active|local'", "S*", &reason, nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments < 1) {
-    return CtrlCommandUsage(usage, opts, countof(opts));
-  }
+  int down_time      = 0;
+  std::string reason = arguments.get("reason").value();
 
   // if reason is not set, set it to manual (default)
-  if (reason == nullptr) {
-    reason = ats_strdup(Reasons::MANUAL);
+  if (reason.empty()) {
+    reason = Reasons::MANUAL;
   }
 
-  if (!Reasons::validReason(reason)) {
-    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason);
-    return CtrlCommandUsage(usage, opts, countof(opts));
+  if (!Reasons::validReason(reason.c_str())) {
+    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason.c_str());
+    parser.help_message();
   }
 
   TSMgmtError error = TS_ERR_OKAY;
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
-    error = TSHostStatusSetDown(file_arguments[i], down_time, reason);
+  for (const auto &it : arguments.get("down")) {
+    error = TSHostStatusSetDown(it.c_str(), down_time, reason.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to set %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to set %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
   }
-  ats_free(reason);
-
-  return CTRL_EX_OK;
 }
-static int
-status_up(unsigned argc, const char **argv)
+void
+CtrlEngine::status_up()
 {
-  char *reason      = nullptr;
-  const char *usage = "host up HOST [OPTIONS]";
-
-  const ArgumentDescription opts[] = {
-    // memory is allocated for 'reason', if this option is used
-    {"reason", '-', "reason for marking the host up, one of 
'manual|active|local'", "S*", &reason, nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments < 1) {
-    return CtrlCommandUsage(usage, nullptr, 0);
-  }
+  std::string reason = arguments.get("reason").value();
 
   // if reason is not set, set it to manual (default)
-  if (reason == nullptr) {
-    reason = ats_strdup(Reasons::MANUAL);
+  if (reason.empty()) {
+    reason = Reasons::MANUAL;
   }
 
-  if (!Reasons::validReason(reason)) {
-    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason);
-    return CtrlCommandUsage(usage, opts, countof(opts));
+  if (!Reasons::validReason(reason.c_str())) {
+    fprintf(stderr, "\nInvalid reason: '%s'\n\n", reason.c_str());
+    parser.help_message();
   }
 
   TSMgmtError error;
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
-    error = TSHostStatusSetUp(file_arguments[i], 0, reason);
+  for (const auto &it : arguments.get("up")) {
+    error = TSHostStatusSetUp(it.c_str(), 0, reason.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to set %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to set %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
   }
-  ats_free(reason);
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_host(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {status_get, "status", "Get one or more host statuses"},
-    {status_down, "down", "Set down one or more host(s) "},
-    {status_up, "up", "Set up one or more host(s) "},
-
-  };
-
-  return CtrlGenericSubcommand("host", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/metric.cc b/src/traffic_ctl/metric.cc
index 68796b5..1076ace 100644
--- a/src/traffic_ctl/metric.cc
+++ b/src/traffic_ctl/metric.cc
@@ -24,103 +24,72 @@
 #include "traffic_ctl.h"
 #include "records/P_RecUtils.h"
 
-static int
-metric_get(unsigned argc, const char **argv)
+void
+CtrlEngine::metric_get()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments < 1) {
-    return CtrlCommandUsage("metric get METRIC [METRIC ...]", nullptr, 0);
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("get")) {
     CtrlMgmtRecord record;
     TSMgmtError error;
 
-    error = record.fetch(file_arguments[i]);
+    error = record.fetch(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
     if (REC_TYPE_IS_STAT(record.rclass())) {
-      printf("%s %s\n", record.name(), CtrlMgmtRecordValue(record).c_str());
+      std::cout << record.name() << ' ' << CtrlMgmtRecordValue(record).c_str() 
<< std::endl;
     }
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-metric_match(unsigned argc, const char **argv)
+void
+CtrlEngine::metric_match()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments < 1) {
-    return CtrlCommandUsage("metric match [OPTIONS] REGEX [REGEX ...]", 
nullptr, 0);
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  for (const auto &it : arguments.get("match")) {
     CtrlMgmtRecordList reclist;
     TSMgmtError error;
 
-    error = reclist.match(file_arguments[i]);
+    error = reclist.match(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to fetch %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to fetch %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
 
     while (!reclist.empty()) {
       CtrlMgmtRecord record(reclist.next());
       if (REC_TYPE_IS_STAT(record.rclass())) {
-        printf("%s %s\n", record.name(), CtrlMgmtRecordValue(record).c_str());
+        std::cout << record.name() << ' ' << 
CtrlMgmtRecordValue(record).c_str() << std::endl;
       }
     }
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-metric_clear(unsigned argc, const char **argv)
+void
+CtrlEngine::metric_clear()
 {
   TSMgmtError error;
 
   error = TSStatsReset(nullptr);
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "failed to clear metrics");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-metric_zero(unsigned argc, const char **argv)
+void
+CtrlEngine::metric_zero()
 {
   TSMgmtError error;
 
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
-    error = TSStatsReset(file_arguments[i]);
+  for (const auto &it : arguments.get("zero")) {
+    error = TSStatsReset(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to clear %s", file_arguments[i]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to clear %s", it.c_str());
+      status_code = CTRL_EX_ERROR;
     }
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_metric(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {metric_get, "get", "Get one or more metric values"},
-    {metric_clear, "clear", "Clear all metric values"},
-    {CtrlUnimplementedCommand, "describe", "Show detailed information about 
one or more metric values"},
-    {metric_match, "match", "Get metrics matching a regular expression"},
-    {CtrlUnimplementedCommand, "monitor", "Display the value of a metric over 
time"},
-
-    // We could allow clearing all the metrics in the "clear" subcommand, but 
that seems error-prone. It
-    // would be too easy to just expect a help message and accidentally nuke 
all the metrics.
-    {metric_zero, "zero", "Clear one or more metric values"},
-  };
-
-  return CtrlGenericSubcommand("metric", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/plugin.cc b/src/traffic_ctl/plugin.cc
index cec12fa..8935ee7 100644
--- a/src/traffic_ctl/plugin.cc
+++ b/src/traffic_ctl/plugin.cc
@@ -23,30 +23,16 @@
 
 #include "traffic_ctl.h"
 
-static int
-plugin_msg(unsigned argc, const char **argv)
+void
+CtrlEngine::plugin_msg()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 2) {
-    return CtrlCommandUsage("plugin msg TAG DATA");
-  }
-
   TSMgmtError error;
+  auto msg_data = arguments.get("msg");
 
-  error = TSLifecycleMessage(file_arguments[0], file_arguments[1], 
strlen(file_arguments[1]) + 1);
+  error = TSLifecycleMessage(msg_data[0].c_str(), msg_data[1].c_str(), 
msg_data[1].size() + 1);
   if (error != TS_ERR_OKAY) {
-    CtrlMgmtError(error, "message '%s' not sent", file_arguments[0]);
-    return CTRL_EX_ERROR;
+    CtrlMgmtError(error, "message '%s' not sent", msg_data[0].c_str());
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_plugin(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {plugin_msg, "msg", "Send message to plugins - a TAG and the message 
DATA"},
-  };
-
-  return CtrlGenericSubcommand("plugin", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/server.cc b/src/traffic_ctl/server.cc
index d41064f..58271ba 100644
--- a/src/traffic_ctl/server.cc
+++ b/src/traffic_ctl/server.cc
@@ -23,30 +23,17 @@
 
 #include "traffic_ctl.h"
 
-static int drain   = 0;
-static int manager = 0;
-
-static int
-restart(unsigned argc, const char **argv)
+void
+CtrlEngine::server_restart()
 {
   TSMgmtError error;
-  const char *usage = "server restart [OPTIONS]";
-  unsigned flags    = TS_RESTART_OPT_NONE;
-
-  const ArgumentDescription opts[] = {
-    {"drain", '-', "Wait for client connections to drain before restarting", 
"F", &drain, nullptr, nullptr},
-    {"manager", '-', "Restart traffic_manager as well as traffic_server", "F", 
&manager, nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage(usage, opts, countof(opts));
-  }
+  unsigned flags = TS_RESTART_OPT_NONE;
 
-  if (drain) {
+  if (arguments.get("drain")) {
     flags |= TS_RESTART_OPT_DRAIN;
   }
 
-  if (manager) {
+  if (arguments.get("manager")) {
     error = TSRestart(flags);
   } else {
     error = TSBounce(flags);
@@ -54,79 +41,51 @@ restart(unsigned argc, const char **argv)
 
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "server restart failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
-}
-
-static int
-server_restart(unsigned argc, const char **argv)
-{
-  return restart(argc, argv);
 }
 
-static int
-server_backtrace(unsigned argc, const char **argv)
+void
+CtrlEngine::server_backtrace()
 {
   TSMgmtError error;
   TSString trace = nullptr;
 
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("server backtrace");
-  }
-
   error = TSProxyBacktraceGet(0, &trace);
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "server backtrace failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
 
-  printf("%s\n", trace);
+  std::cout << trace << std::endl;
   TSfree(trace);
-  return CTRL_EX_OK;
 }
 
-static int
-server_status(unsigned argc, const char **argv)
+void
+CtrlEngine::server_status()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments != 0) {
-    return CtrlCommandUsage("server status");
-  }
-
   switch (TSProxyStateGet()) {
   case TS_PROXY_ON:
-    printf("Proxy -- on\n");
+    std::cout << "Proxy -- on" << std::endl;
     break;
   case TS_PROXY_OFF:
-    printf("Proxy -- off\n");
+    std::cout << "Proxy -- off" << std::endl;
     break;
   case TS_PROXY_UNDEFINED:
-    printf("Proxy status undefined\n");
+    std::cout << "Proxy status undefined" << std::endl;
     break;
   }
-
-  // XXX Surely we can report more useful status that this !?!!
-
-  return CTRL_EX_OK;
 }
 
-static int
-server_stop(unsigned argc, const char **argv)
+void
+CtrlEngine::server_stop()
 {
   TSMgmtError error;
-  const char *usage = "server stop [OPTIONS]";
-  unsigned flags    = TS_RESTART_OPT_NONE;
+  unsigned flags = TS_RESTART_OPT_NONE;
 
-  const ArgumentDescription opts[] = {
-    {"drain", '-', "Wait for client connections to drain before stopping", 
"F", &drain, nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage(usage, opts, countof(opts));
-  }
-
-  if (drain) {
+  if (arguments.get("drain")) {
     flags |= TS_STOP_OPT_DRAIN;
   }
 
@@ -134,62 +93,36 @@ server_stop(unsigned argc, const char **argv)
 
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "server stop failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-server_start(unsigned argc, const char **argv)
+void
+CtrlEngine::server_start()
 {
   TSMgmtError error;
-  int cache      = 0;
-  int hostdb     = 0;
   unsigned clear = TS_CACHE_CLEAR_NONE;
 
-  const ArgumentDescription opts[] = {
-    {"clear-cache", '-', "Clear the disk cache on startup", "F", &cache, 
nullptr, nullptr},
-    {"clear-hostdb", '-', "Clear the DNS cache on startup", "F", &hostdb, 
nullptr, nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage("server start [OPTIONS]", opts, countof(opts));
-  }
-
-  clear |= cache ? TS_CACHE_CLEAR_CACHE : TS_CACHE_CLEAR_NONE;
-  clear |= hostdb ? TS_CACHE_CLEAR_HOSTDB : TS_CACHE_CLEAR_NONE;
+  clear |= arguments.get("clear-cache") ? TS_CACHE_CLEAR_CACHE : 
TS_CACHE_CLEAR_NONE;
+  clear |= arguments.get("clear-hostdb") ? TS_CACHE_CLEAR_HOSTDB : 
TS_CACHE_CLEAR_NONE;
 
   error = TSProxyStateSet(TS_PROXY_ON, clear);
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "server start failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
 }
 
-static int
-server_drain(unsigned argc, const char **argv)
+void
+CtrlEngine::server_drain()
 {
   TSMgmtError error;
-  const char *usage = "server drain [OPTIONS]";
-
-  int no_new_connection            = 0;
-  int undo                         = 0;
-  const ArgumentDescription opts[] = {
-    {"no-new-connection", 'N', "Wait for new connections down to threshold 
before starting draining", "F", &no_new_connection,
-     nullptr, nullptr},
-    {"undo", 'U', "Recover server from the drain mode", "F", &undo, nullptr, 
nullptr},
-  };
-
-  if (!CtrlProcessArguments(argc, argv, opts, countof(opts)) || 
n_file_arguments != 0) {
-    return CtrlCommandUsage(usage, opts, countof(opts));
-  }
 
-  if (undo) {
+  if (arguments.get("undo")) {
     error = TSDrain(TS_DRAIN_OPT_UNDO);
-  } else if (no_new_connection) {
+  } else if (arguments.get("no-new-connection")) {
     error = TSDrain(TS_DRAIN_OPT_IDLE);
   } else {
     error = TSDrain(TS_DRAIN_OPT_NONE);
@@ -197,21 +130,7 @@ server_drain(unsigned argc, const char **argv)
 
   if (error != TS_ERR_OKAY) {
     CtrlMgmtError(error, "server drain failed");
-    return CTRL_EX_ERROR;
+    status_code = CTRL_EX_ERROR;
+    return;
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_server(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {{server_backtrace, "backtrace", "Show a full 
stack trace of the traffic_server process"},
-                                 {server_restart, "restart", "Restart Traffic 
Server"},
-                                 {server_start, "start", "Start the proxy"},
-                                 {server_status, "status", "Show the proxy 
status"},
-                                 {server_stop, "stop", "Stop the proxy"},
-                                 {server_drain, "drain", "Drain the 
requests"}};
-
-  return CtrlGenericSubcommand("server", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/storage.cc b/src/traffic_ctl/storage.cc
index ebd9607..1e55bc9 100644
--- a/src/traffic_ctl/storage.cc
+++ b/src/traffic_ctl/storage.cc
@@ -23,33 +23,18 @@
 
 #include "traffic_ctl.h"
 
-static int
-storage_offline(unsigned argc, const char **argv)
+void
+CtrlEngine::storage_offline()
 {
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments == 0) {
-    return CtrlCommandUsage("storage offline DEVICE [DEVICE ...]");
-  }
-
-  for (unsigned i = 0; i < n_file_arguments; ++i) {
+  auto offline_data = arguments.get("offline");
+  for (const auto &it : offline_data) {
     TSMgmtError error;
 
-    error = TSStorageDeviceCmdOffline(file_arguments[i]);
+    error = TSStorageDeviceCmdOffline(it.c_str());
     if (error != TS_ERR_OKAY) {
-      CtrlMgmtError(error, "failed to take %s offline", file_arguments[0]);
-      return CTRL_EX_ERROR;
+      CtrlMgmtError(error, "failed to take %s offline", 
offline_data[0].c_str());
+      status_code = CTRL_EX_ERROR;
+      return;
     }
   }
-
-  return CTRL_EX_OK;
-}
-
-int
-subcommand_storage(unsigned argc, const char **argv)
-{
-  const subcommand commands[] = {
-    {storage_offline, "offline", "Take one or more storage volumes offline"},
-    {CtrlUnimplementedCommand, "status", "Show the storage configuration"},
-  };
-
-  return CtrlGenericSubcommand("storage", commands, countof(commands), argc, 
argv);
 }
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index be61cf0..8efb92a 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -28,8 +28,6 @@
 #include "tscore/I_Layout.h"
 #include "tscore/runroot.h"
 
-AppVersionInfo CtrlVersionInfo;
-
 const char *
 CtrlMgmtRecord::name() const
 {
@@ -145,120 +143,137 @@ CtrlMgmtError(TSMgmtError err, const char *fmt, ...)
   }
 }
 
-int
-CtrlSubcommandUsage(const char *name, const subcommand *cmds, unsigned ncmds, 
const ArgumentDescription *desc, unsigned ndesc)
-{
-  const char *opt = ndesc ? "[OPTIONS]" : "";
-  const char *sep = (ndesc && name) ? " " : "";
-
-  fprintf(stderr, "Usage: traffic_ctl %s%s%s CMD [ARGS 
...]\n\nSubcommands:\n", name ? name : "", sep, opt);
-
-  for (unsigned i = 0; i < ncmds; ++i) {
-    fprintf(stderr, "    %-16s%s\n", cmds[i].name, cmds[i].help);
-  }
-
-  if (ndesc) {
-    usage(desc, ndesc, "\nOptions:");
-  }
-
-  return CTRL_EX_USAGE;
-}
-
-int
-CtrlCommandUsage(const char *msg, const ArgumentDescription *desc, unsigned 
ndesc)
-{
-  fprintf(stderr, "Usage: traffic_ctl %s\n", msg);
-
-  if (ndesc) {
-    usage(desc, ndesc, "\nOptions:");
-  }
-
-  return CTRL_EX_USAGE;
-}
-
-bool
-CtrlProcessArguments(int /* argc */, const char **argv, const 
ArgumentDescription *desc, unsigned ndesc)
-{
-  n_file_arguments = 0;
-  return process_args_ex(&CtrlVersionInfo, desc, ndesc, argv);
-}
-
-int
-CtrlUnimplementedCommand(unsigned /* argc */, const char **argv)
+void
+CtrlEngine::CtrlUnimplementedCommand(std::string_view command)
 {
-  fprintf(stderr, "'%s' command is not implemented\n", *argv);
-  return CTRL_EX_UNIMPLEMENTED;
+  fprintf(stderr, "'%s' command is not implemented\n", command.data());
+  status_code = CTRL_EX_UNIMPLEMENTED;
 }
 
 int
-CtrlGenericSubcommand(const char *name, const subcommand *cmds, unsigned 
ncmds, unsigned argc, const char **argv)
-{
-  CtrlCommandLine cmdline;
-
-  // Process command line arguments and dump into variables
-  if (!CtrlProcessArguments(argc, argv, nullptr, 0) || n_file_arguments < 1) {
-    return CtrlSubcommandUsage(name, cmds, ncmds, nullptr, 0);
-  }
-
-  cmdline.init(n_file_arguments, file_arguments);
-
-  for (unsigned i = 0; i < ncmds; ++i) {
-    if (strcmp(file_arguments[0], cmds[i].name) == 0) {
-      return cmds[i].handler(cmdline.argc(), cmdline.argv());
-    }
-  }
-
-  return CtrlSubcommandUsage(name, cmds, ncmds, nullptr, 0);
-}
-
-static const subcommand commands[] = {
-  {subcommand_alarm, "alarm", "Manipulate alarms"},
-  {subcommand_config, "config", "Manipulate configuration records"},
-  {subcommand_metric, "metric", "Manipulate performance metrics"},
-  {subcommand_server, "server", "Stop, restart and examine the server"},
-  {subcommand_storage, "storage", "Manipulate cache storage"},
-  {subcommand_plugin, "plugin", "Interact with plugins"},
-  {subcommand_host, "host", "Interact with host status"},
-};
-
-int
 main(int argc, const char **argv)
 {
-  CtrlCommandLine cmdline;
-  int debug = false;
-
-  CtrlVersionInfo.setup(PACKAGE_NAME, "traffic_ctl", PACKAGE_VERSION, 
__DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, "");
-  program_name = CtrlVersionInfo.AppStr;
-
-  ArgumentDescription argument_descriptions[] = {
-    {"debug", '-', "Enable debugging output", "F", &debug, nullptr, nullptr},
-    {"help", 'h', "Print usage information", nullptr, nullptr, nullptr,
-     [](const ArgumentDescription *args, unsigned nargs, const char 
*arg_unused) {
-       CtrlSubcommandUsage(nullptr, commands, countof(commands), args, nargs);
-     }},
-    VERSION_ARGUMENT_DESCRIPTION(),
-    RUNROOT_ARGUMENT_DESCRIPTION(),
-  };
+  CtrlEngine engine;
+
+  engine.parser.add_global_usage("traffic_ctl [OPTIONS] CMD [ARGS ...]");
+  engine.parser.require_commands();
+
+  engine.parser.add_option("--debug", "", "Enable debugging output")
+    .add_option("--version", "-V", "Print version string")
+    .add_option("--help", "-h", "Print usage information")
+    .add_option("--run-root", "", "using TS_RUNROOT as sandbox", "TS_RUNROOT", 
1);
+
+  auto &alarm_command   = engine.parser.add_command("alarm", "Manipulate 
alarms").require_commands();
+  auto &config_command  = engine.parser.add_command("config", "Manipulate 
configuration records").require_commands();
+  auto &metric_command  = engine.parser.add_command("metric", "Manipulate 
performance metrics").require_commands();
+  auto &server_command  = engine.parser.add_command("server", "Stop, restart 
and examine the server").require_commands();
+  auto &storage_command = engine.parser.add_command("storage", "Manipulate 
cache storage").require_commands();
+  auto &plugin_command  = engine.parser.add_command("plugin", "Interact with 
plugins").require_commands();
+  auto &host_command    = engine.parser.add_command("host", "Interact with 
host status").require_commands();
+
+  // alarm commands
+  alarm_command.add_command("clear", "Clear all current alarms", [&]() { 
engine.alarm_clear(); })
+    .add_example_usage("traffic_ctl alarm clear");
+  alarm_command.add_command("list", "List all current alarms", [&]() { 
engine.alarm_list(); })
+    .add_example_usage("traffic_ctl alarm list");
+  alarm_command.add_command("resolve", "Resolve the listed alarms", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.alarm_resolve(); })
+    .add_example_usage("traffic_ctl alarm resolve ALARM [ALARM ...]");
+
+  // config commands
+  config_command.add_command("defaults", "Show default information 
configuration values", [&]() { engine.config_defaults(); })
+    .add_example_usage("traffic_ctl config defaults [OPTIONS]")
+    .add_option("--records", "", "Emit output in records.config format");
+  config_command
+    .add_command("describe", "Show detailed information about configuration 
values", "", MORE_THAN_ONE_ARG_N,
+                 [&]() { engine.config_describe(); })
+    .add_example_usage("traffic_ctl config describe RECORD [RECORD ...]");
+  config_command.add_command("diff", "Show non-default configuration values", 
[&]() { engine.config_diff(); })
+    .add_example_usage("traffic_ctl config diff [OPTIONS]")
+    .add_option("--records", "", "Emit output in records.config format");
+  config_command.add_command("get", "Get one or more configuration values", 
"", MORE_THAN_ONE_ARG_N, [&]() { engine.config_get(); })
+    .add_example_usage("traffic_ctl config get [OPTIONS] RECORD [RECORD ...]")
+    .add_option("--records", "", "Emit output in records.config format");
+  config_command
+    .add_command("match", "Get configuration matching a regular expression", 
"", MORE_THAN_ONE_ARG_N,
+                 [&]() { engine.config_match(); })
+    .add_example_usage("traffic_ctl config match [OPTIONS] REGEX [REGEX ...]")
+    .add_option("--records", "", "Emit output in records.config format");
+  config_command.add_command("reload", "Request a configuration reload", [&]() 
{ engine.config_reload(); })
+    .add_example_usage("traffic_ctl config reload");
+  config_command.add_command("status", "Check the configuration status", [&]() 
{ engine.config_status(); })
+    .add_example_usage("traffic_ctl config status");
+  config_command.add_command("set", "Set a configuration value", "", 2, [&]() 
{ engine.config_set(); })
+    .add_example_usage("traffic_ctl config set RECORD VALUE");
+
+  // host commands
+  host_command.add_command("status", "Get one or more host statuses", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.status_get(); })
+    .add_example_usage("traffic_ctl host status HOST  [HOST  ...]");
+  host_command.add_command("down", "Set down one or more host(s)", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.status_down(); })
+    .add_example_usage("traffic_ctl host down HOST [OPTIONS]")
+    .add_option("--time", "-I", "number of seconds that a host is marked 
down", "", 1)
+    .add_option("--reason", "", "reason for marking the host down, one of 
'manual|active|local");
+  host_command.add_command("up", "Set up one or more host(s)", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.status_up(); })
+    .add_example_usage("traffic_ctl host up METRIC value")
+    .add_option("--reason", "", "reason for marking the host up, one of 
'manual|active|local");
+
+  // metric commands
+  metric_command.add_command("get", "Get one or more metric values", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.metric_get(); })
+    .add_example_usage("traffic_ctl metric get METRIC [METRIC ...]");
+  metric_command.add_command("clear", "Clear all metric values", [&]() { 
engine.metric_clear(); });
+  metric_command.add_command("describe", "Show detailed information about one 
or more metric values", "", MORE_THAN_ONE_ARG_N,
+                             [&]() { 
engine.CtrlUnimplementedCommand("describe"); }); // not implemented
+  metric_command.add_command("match", "Get metrics matching a regular 
expression", "", MORE_THAN_ZERO_ARG_N,
+                             [&]() { engine.metric_match(); });
+  metric_command.add_command("monitor", "Display the value of a metric over 
time", "", MORE_THAN_ZERO_ARG_N,
+                             [&]() { 
engine.CtrlUnimplementedCommand("monitor"); }); // not implemented
+  metric_command.add_command("zero", "Clear one or more metric values", "", 
MORE_THAN_ONE_ARG_N, [&]() { engine.metric_zero(); });
+
+  // plugin command
+  plugin_command.add_command("msg", "Send message to plugins - a TAG and the 
message DATA", "", 2, [&]() { engine.plugin_msg(); })
+    .add_example_usage("traffic_ctl plugin msg TAG DATA");
+
+  // server commands
+  server_command.add_command("backtrace", "Show a full stack trace of the 
traffic_server process",
+                             [&]() { engine.server_backtrace(); });
+  server_command.add_command("restart", "Restart Traffic Server", [&]() { 
engine.server_backtrace(); })
+    .add_example_usage("traffic_ctl server restart [OPTIONS]")
+    .add_option("--drain", "", "Wait for client connections to drain before 
restarting")
+    .add_option("--manager", "", "Restart traffic_manager as well as 
traffic_server");
+  server_command.add_command("start", "Start the proxy", [&]() { 
engine.server_start(); })
+    .add_example_usage("traffic_ctl server start [OPTIONS]")
+    .add_option("--clear-cache", "", "Clear the disk cache on startup")
+    .add_option("--clear-hostdb", "", "Clear the DNS cache on startup");
+  server_command.add_command("status", "Show the proxy status", [&]() { 
engine.server_status(); })
+    .add_example_usage("traffic_ctl server status");
+  server_command.add_command("stop", "Stop the proxy", [&]() { 
engine.server_stop(); })
+    .add_example_usage("traffic_ctl server stop [OPTIONS]")
+    .add_option("--drain", "", "Wait for client connections to drain before 
stopping");
+  server_command.add_command("drain", "Drain the requests", [&]() { 
engine.server_drain(); })
+    .add_example_usage("traffic_ctl server drain [OPTIONS]")
+    .add_option("--no-new-connection", "-N", "Wait for new connections down to 
threshold before starting draining")
+    .add_option("--undo", "-U", "Recover server from the drain mode");
+
+  // storage commands
+  storage_command
+    .add_command("offline", "Take one or more storage volumes offline", "", 
MORE_THAN_ONE_ARG_N,
+                 [&]() { engine.storage_offline(); })
+    .add_example_usage("storage offline DEVICE [DEVICE ...]");
+  storage_command.add_command("status", "Show the storage configuration", "", 
MORE_THAN_ZERO_ARG_N,
+                              [&]() { 
engine.CtrlUnimplementedCommand("status"); }); // not implemented
+
+  // parse the arguments
+  engine.arguments = engine.parser.parse(argv);
 
   BaseLogFile *base_log_file = new BaseLogFile("stderr");
-  diags                      = new Diags(program_name, "" /* tags */, "" /* 
actions */, base_log_file);
+  diags                      = new Diags("traffic_ctl", "" /* tags */, "" /* 
actions */, base_log_file);
 
-  // Process command line arguments and dump into variables
-  if (!CtrlProcessArguments(argc, argv, argument_descriptions, 
countof(argument_descriptions))) {
-    return CtrlSubcommandUsage(nullptr, commands, countof(commands), 
argument_descriptions, countof(argument_descriptions));
-  }
-
-  if (debug) {
+  if (engine.arguments.get("debug")) {
     diags->activate_taglist("traffic_ctl", DiagsTagType_Debug);
     diags->config.enabled[DiagsTagType_Debug] = true;
     diags->show_location                      = SHOW_LOCATION_DEBUG;
   }
 
-  if (n_file_arguments < 1) {
-    return CtrlSubcommandUsage(nullptr, commands, countof(commands), 
argument_descriptions, countof(argument_descriptions));
-  }
-
-  runroot_handler(argv);
+  argparser_runroot_handler(engine.arguments.get("--run-root").value(), 
argv[0]);
   Layout::create();
   RecProcessInit(RECM_STAND_ALONE, diags);
   LibRecordsConfigInit();
@@ -272,16 +287,10 @@ main(int argc, const char **argv)
   // error.
   TSInit(rundir, static_cast<TSInitOptionT>(TS_MGMT_OPT_NO_EVENTS | 
TS_MGMT_OPT_NO_SOCK_TESTS));
 
-  for (unsigned i = 0; i < countof(commands); ++i) {
-    if (strcmp(file_arguments[0], commands[i].name) == 0) {
-      CtrlCommandLine cmdline;
-
-      cmdline.init(n_file_arguments, file_arguments);
-      return commands[i].handler(cmdline.argc(), cmdline.argv());
-    }
-  }
+  engine.arguments.invoke();
 
   // Done with the mgmt API.
   TSTerminate();
-  return CtrlSubcommandUsage(nullptr, commands, countof(commands), 
argument_descriptions, countof(argument_descriptions));
+
+  return engine.status_code;
 }
diff --git a/src/traffic_ctl/traffic_ctl.h b/src/traffic_ctl/traffic_ctl.h
index 991c790..f02fe27 100644
--- a/src/traffic_ctl/traffic_ctl.h
+++ b/src/traffic_ctl/traffic_ctl.h
@@ -30,28 +30,22 @@
 #include "tscore/ink_args.h"
 #include "tscore/I_Version.h"
 #include "tscore/BaseLogFile.h"
+#include "tscore/ArgParser.h"
 
 #include <vector>
 #include <string>
+#include <iostream>
 
-struct subcommand {
-  int (*handler)(unsigned, const char **);
-  const char *name;
-  const char *help;
-};
-
-extern AppVersionInfo CtrlVersionInfo;
+// Exit status codes, following BSD's sysexits(3)
+constexpr int CTRL_EX_OK            = 0;
+constexpr int CTRL_EX_ERROR         = 2;
+constexpr int CTRL_EX_UNIMPLEMENTED = 3;
+constexpr int CTRL_EX_USAGE         = EX_USAGE;
+constexpr int CTRL_EX_UNAVAILABLE   = 69;
 
 #define CtrlDebug(...) Debug("traffic_ctl", __VA_ARGS__)
 
 void CtrlMgmtError(TSMgmtError err, const char *fmt, ...) TS_PRINTFLIKE(2, 3);
-int CtrlSubcommandUsage(const char *name, const subcommand *cmds, unsigned 
ncmds, const ArgumentDescription *desc, unsigned ndesc);
-int CtrlCommandUsage(const char *msg, const ArgumentDescription *desc = 
nullptr, unsigned ndesc = 0);
-
-bool CtrlProcessArguments(int argc, const char **argv, const 
ArgumentDescription *desc, unsigned ndesc);
-int CtrlUnimplementedCommand(unsigned argc, const char **argv);
-
-int CtrlGenericSubcommand(const char *, const subcommand *cmds, unsigned 
ncmds, unsigned argc, const char **argv);
 
 #define CTRL_MGMT_CHECK(expr)                          \
   do {                                                 \
@@ -59,7 +53,8 @@ int CtrlGenericSubcommand(const char *, const subcommand 
*cmds, unsigned ncmds,
     if (e != TS_ERR_OKAY) {                            \
       CtrlDebug("%s failed with status %d", #expr, e); \
       CtrlMgmtError(e, nullptr);                       \
-      return CTRL_EX_ERROR;                            \
+      status_code = CTRL_EX_ERROR;                     \
+      return;                                          \
     }                                                  \
   } while (0)
 
@@ -167,47 +162,59 @@ struct CtrlMgmtRecordList : 
CtrlMgmtList<RecordListPolicy> {
   TSMgmtError match(const char *);
 };
 
-struct CtrlCommandLine {
-  CtrlCommandLine() { this->args.push_back(nullptr); }
-  void
-  init(unsigned argc, const char **argv)
-  {
-    this->args.clear();
-    for (unsigned i = 0; i < argc; ++i) {
-      this->args.push_back(argv[i]);
-    }
-
-    // Always nullptr-terminate to keep ink_args happy. Note that we adjust 
arg() accordingly.
-    this->args.push_back(nullptr);
-  }
-
-  unsigned
-  argc()
-  {
-    return args.size() - 1;
-  }
-
-  const char **
-  argv()
-  {
-    return &args[0];
-  }
-
-private:
-  std::vector<const char *> args;
+// this is a engine for traffic_ctl containing the ArgParser and all the 
methods
+// it also has a status code which can be set by these methods to return
+struct CtrlEngine {
+  // the parser for traffic_ctl
+  ts::ArgParser parser;
+  // parsed arguments
+  ts::Arguments arguments;
+  // the return status code from functions
+  // By default it is set to CTRL_EX_OK so we don't need to set it
+  // in each method when they finish successfully.
+  int status_code = CTRL_EX_OK;
+
+  // All traffic_ctl methods:
+  // umimplemented command
+  void CtrlUnimplementedCommand(std::string_view command);
+
+  // alarm methods
+  void alarm_list();
+  void alarm_clear();
+  void alarm_resolve();
+
+  // config methods
+  void config_defaults();
+  void config_diff();
+  void config_status();
+  void config_reload();
+  void config_match();
+  void config_get();
+  void config_set();
+  void config_describe();
+
+  // host methods
+  void status_get();
+  void status_down();
+  void status_up();
+
+  // metric methods
+  void metric_get();
+  void metric_match();
+  void metric_clear();
+  void metric_zero();
+
+  // metric methods
+  void plugin_msg();
+
+  // server methods
+  void server_restart();
+  void server_backtrace();
+  void server_status();
+  void server_stop();
+  void server_start();
+  void server_drain();
+
+  // storage methods
+  void storage_offline();
 };
-
-int subcommand_alarm(unsigned argc, const char **argv);
-int subcommand_config(unsigned argc, const char **argv);
-int subcommand_metric(unsigned argc, const char **argv);
-int subcommand_server(unsigned argc, const char **argv);
-int subcommand_storage(unsigned argc, const char **argv);
-int subcommand_plugin(unsigned argc, const char **argv);
-int subcommand_host(unsigned argc, const char **argv);
-
-// Exit status codes, following BSD's sysexits(3)
-#define CTRL_EX_OK 0
-#define CTRL_EX_ERROR 2
-#define CTRL_EX_UNIMPLEMENTED 3
-#define CTRL_EX_USAGE EX_USAGE
-#define CTRL_EX_UNAVAILABLE 69

Reply via email to