You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jp...@apache.org on 2015/03/09 06:03:07 UTC

trafficserver git commit: TS-3408: add a "config describe" command to traffic_ctl

Repository: trafficserver
Updated Branches:
  refs/heads/master c6c910bc0 -> 857766c5c


TS-3408: add a "config describe" command to traffic_ctl

Add a new management API TSConfigRecordDescribe() to publish all
the information that we know about a configuration record. Plumb
this through the messaging layer and expose it in traffic_ctl as
the "config describe" subcommand.


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/857766c5
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/857766c5
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/857766c5

Branch: refs/heads/master
Commit: 857766c5caefd2c1b890e34e379cfcd7bbde475d
Parents: c6c910b
Author: James Peach <jp...@apache.org>
Authored: Wed Feb 11 16:27:00 2015 -0800
Committer: James Peach <jp...@apache.org>
Committed: Sun Mar 8 22:02:55 2015 -0700

----------------------------------------------------------------------
 CHANGES                        |   2 +
 cmd/traffic_ctl/Makefile.am    |   1 +
 cmd/traffic_ctl/config.cc      | 122 ++++++++++++++++++++++++++--
 cmd/traffic_ctl/metric.cc      |   4 +-
 cmd/traffic_ctl/traffic_ctl.cc |  69 +++++++++++-----
 cmd/traffic_ctl/traffic_ctl.h  |  26 +++++-
 lib/records/I_RecCore.h        |   6 +-
 lib/records/RecCore.cc         |  25 ++++++
 mgmt/api/CoreAPI.cc            |   6 ++
 mgmt/api/CoreAPI.h             |   1 +
 mgmt/api/CoreAPIRemote.cc      | 157 ++++++++++++++++++++++++++----------
 mgmt/api/INKMgmtAPI.cc         |  42 ++++++----
 mgmt/api/NetworkMessage.cc     |  18 ++++-
 mgmt/api/NetworkMessage.h      |   3 +
 mgmt/api/TSControlMain.cc      | 128 ++++++++++++++++++++++++++++-
 mgmt/api/include/mgmtapi.h     |  50 ++++++++++--
 16 files changed, 559 insertions(+), 101 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 320d0c6..17f59bc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.3.0
 
+  *) [TS-3408] Add a "config describe" command to traffic_ctl.
+
   *) [TS-3393] Fix records with missing validation expressions.
 
   *) [TS-3424] SSL Failed: decryption failed or bad record mac.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/cmd/traffic_ctl/Makefile.am
----------------------------------------------------------------------
diff --git a/cmd/traffic_ctl/Makefile.am b/cmd/traffic_ctl/Makefile.am
index 51cc05c..d83fd19 100644
--- a/cmd/traffic_ctl/Makefile.am
+++ b/cmd/traffic_ctl/Makefile.am
@@ -21,6 +21,7 @@ AM_CPPFLAGS = \
   $(iocore_include_dirs) \
   -I$(top_srcdir)/lib \
   -I$(top_srcdir)/lib/ts \
+  -I$(top_srcdir)/lib/records \
   -I$(top_srcdir)/mgmt/api/include
 
 bin_PROGRAMS = traffic_ctl

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/cmd/traffic_ctl/config.cc
----------------------------------------------------------------------
diff --git a/cmd/traffic_ctl/config.cc b/cmd/traffic_ctl/config.cc
index 66e10de..f8b4886 100644
--- a/cmd/traffic_ctl/config.cc
+++ b/cmd/traffic_ctl/config.cc
@@ -23,6 +23,73 @@
 
 #include "traffic_ctl.h"
 #include <time.h>
+#include <I_RecDefs.h>
+
+// Record data type names, indexed by TSRecordT.
+static const char *
+rec_typeof(int rec_type) {
+  switch (rec_type) {
+  case TS_REC_INT: return "INT";
+  case TS_REC_COUNTER: return "COUNTER";
+  case TS_REC_FLOAT: return "FLOAT";
+  case TS_REC_STRING: return "STRING";
+  case TS_REC_UNDEFINED: /* fallthru */
+  default: return "UNDEFINED";
+  }
+}
+
+// Record type name, indexed by RecT.
+static const char *
+rec_classof(int rec_class) {
+  switch (rec_class) {
+  case RECT_CONFIG: return "standard config";
+  case RECT_LOCAL:  return "local config";
+  case RECT_PROCESS:return "process metric";
+  case RECT_NODE:   return "node metric";
+  case RECT_CLUSTER:return "cluster metric";
+  case RECT_PLUGIN: return "plugin metric";
+  default: return "undefined";
+  }
+}
+
+// Record access control, indexed by RecAccessT.
+static const char *
+rec_accessof(int rec_access)
+{
+  switch (rec_access) {
+  case RECA_NO_ACCESS: return "no access";
+  case RECA_READ_ONLY: return "read only";
+  case RECA_NULL: /* fallthru */
+  default: return "default";
+  }
+}
+
+// Record access control, indexed by RecUpdateT.
+static const char *
+rec_updateof(int rec_updatetype)
+{
+  switch (rec_updatetype) {
+  case RECU_DYNAMIC: return "dynamic, no restart";
+  case RECU_RESTART_TS: return "static, restart traffic_server";
+  case RECU_RESTART_TM: return "static, restart traffic_manager";
+  case RECU_RESTART_TC: return "static, full restart";
+  case RECU_NULL: /* fallthru */
+  default: return "unknown";
+  }
+}
+
+// Record check type, indexed by RecCheckT.
+static const char *
+rec_checkof(int rec_checktype)
+{
+  switch (rec_checktype) {
+  case RECC_STR: return "string matching a regular expression";
+  case RECC_INT: return "integer with a specified range";
+  case RECC_IP: return "IP address";
+  case RECC_NULL: /* fallthru */
+  default: return "none";
+  }
+}
 
 static std::string
 timestr(time_t tm)
@@ -34,15 +101,13 @@ timestr(time_t tm)
 static void
 format_record(const CtrlMgmtRecord& record, bool recfmt)
 {
-  const char * typestr[] = {
-    "INT", "COUNTER", "FLOAT", "STRING", "UNDEFINED"
-  };
+  CtrlMgmtRecordValue value(record);
 
   if (recfmt) {
     // XXX Detect CONFIG or LOCAL ...
-    printf("CONFIG %s %s %s\n", record.name(), typestr[record.type()], record.c_str());
+    printf("CONFIG %s %s %s\n", record.name(), rec_typeof(record.type()), value.c_str());
   } else {
-    printf("%s: %s\n", record.name(), record.c_str());
+    printf("%s: %s\n", record.name(), value.c_str());
   }
 }
 
@@ -75,6 +140,49 @@ config_get(unsigned argc, const char ** argv)
 }
 
 static int
+config_describe(unsigned argc, const char ** argv)
+{
+  if (!CtrlProcessArguments(argc, argv, NULL, 0) || n_file_arguments < 1) {
+    return CtrlCommandUsage("config describe RECORD [RECORD ...]");
+  }
+
+  for (unsigned i = 0; i < n_file_arguments; ++i) {
+    TSConfigRecordDescription desc;
+    TSMgmtError error;
+
+    ink_zero(desc);
+    error = TSConfigRecordDescribe(file_arguments[i], 0 /* flags */, &desc);
+    if (error != TS_ERR_OKAY) {
+      CtrlMgmtError(error, "failed to describe %s", file_arguments[i]);
+      return CTRL_EX_ERROR;
+    }
+
+    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);
+
+    if (strlen(desc.rec_checkexpr)) {
+      printf("%-16s: %s, '%s'\n", "Syntax Check", rec_checkof(desc.rec_checktype), desc.rec_checkexpr);
+    } else {
+      printf("%-16s: %s\n", "Syntax Check", rec_checkof(desc.rec_checktype));
+    }
+
+    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);
+
+    TSConfigRecordDescriptionFree(&desc);
+  }
+
+  return CTRL_EX_OK;
+}
+
+static int
 config_set(unsigned argc, const char ** argv)
 {
   TSMgmtError error;
@@ -181,7 +289,7 @@ config_status(unsigned argc, const char ** argv)
   CTRL_MGMT_CHECK(manager.fetch("proxy.node.config.restart_required.manager"));
   CTRL_MGMT_CHECK(cop.fetch("proxy.node.config.restart_required.cop"));
 
-  printf("%s\n", version.c_str());
+  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");
@@ -204,7 +312,7 @@ subcommand_config(unsigned argc, const char ** argv)
 {
   const subcommand commands[] =
   {
-    { CtrlUnimplementedCommand, "describe", "Show detailed information about configuration values" },
+    { config_describe, "describe", "Show detailed information about 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" },

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/cmd/traffic_ctl/metric.cc
----------------------------------------------------------------------
diff --git a/cmd/traffic_ctl/metric.cc b/cmd/traffic_ctl/metric.cc
index fb4da30..4a90958 100644
--- a/cmd/traffic_ctl/metric.cc
+++ b/cmd/traffic_ctl/metric.cc
@@ -40,7 +40,7 @@ metric_get(unsigned argc, const char ** argv)
       return CTRL_EX_ERROR;
     }
 
-    printf("%s %s\n", record.name(), record.c_str());
+    printf("%s %s\n", record.name(), CtrlMgmtRecordValue(record).c_str());
   }
 
   return CTRL_EX_OK;
@@ -67,7 +67,7 @@ metric_match(unsigned argc, const char ** argv)
 
     while (!reclist.empty()) {
       CtrlMgmtRecord record(reclist.next());
-      printf("%s %s\n", record.name(), record.c_str());
+      printf("%s %s\n", record.name(), CtrlMgmtRecordValue(record).c_str());
     }
   }
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/cmd/traffic_ctl/traffic_ctl.cc
----------------------------------------------------------------------
diff --git a/cmd/traffic_ctl/traffic_ctl.cc b/cmd/traffic_ctl/traffic_ctl.cc
index 39b81fb..2b627f5 100644
--- a/cmd/traffic_ctl/traffic_ctl.cc
+++ b/cmd/traffic_ctl/traffic_ctl.cc
@@ -50,26 +50,6 @@ CtrlMgmtRecord::as_int() const
   }
 }
 
-const char *
-CtrlMgmtRecord::c_str() const
-{
-  switch (this->ele->rec_type) {
-  case TS_REC_INT:
-    snprintf(this->nbuf, sizeof(this->nbuf), "%" PRId64, this->ele->valueT.int_val);
-    return this->nbuf;
-  case TS_REC_COUNTER:
-    snprintf(this->nbuf, sizeof(this->nbuf), "%" PRId64, this->ele->valueT.counter_val);
-    return this->nbuf;
-  case TS_REC_FLOAT:
-    snprintf(this->nbuf, sizeof(this->nbuf), "%f", this->ele->valueT.float_val);
-    return this->nbuf;
-  case TS_REC_STRING:
-    return this->ele->valueT.string_val;
-  default:
-    return "(invalid)";
-  }
-}
-
 TSMgmtError
 CtrlMgmtRecord::fetch(const char * name)
 {
@@ -82,6 +62,55 @@ CtrlMgmtRecordList::match(const char * name)
   return TSRecordGetMatchMlt(name, this->list);
 }
 
+CtrlMgmtRecordValue::CtrlMgmtRecordValue(const CtrlMgmtRecord& rec)
+{
+  this->init(rec.ele->rec_type, rec.ele->valueT);
+}
+
+CtrlMgmtRecordValue::CtrlMgmtRecordValue(const TSRecordEle * ele)
+{
+  this->init(ele->rec_type, ele->valueT);
+}
+
+CtrlMgmtRecordValue::CtrlMgmtRecordValue(TSRecordT _t, TSRecordValueT _v)
+{
+  this->init(_t, _v);
+}
+
+void
+CtrlMgmtRecordValue::init(TSRecordT _t, TSRecordValueT _v)
+{
+  this->rec_type = _t;
+  switch (this->rec_type) {
+  case TS_REC_INT:
+    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%" PRId64, _v.int_val);
+    break;
+  case TS_REC_COUNTER:
+    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%" PRId64, _v.counter_val);
+    break;
+  case TS_REC_FLOAT:
+    snprintf(this->fmt.nbuf, sizeof(this->fmt.nbuf), "%f", _v.float_val);
+    break;
+  case TS_REC_STRING:
+    this->fmt.str = _v.string_val;
+    break;
+  default:
+    rec_type = TS_REC_STRING;
+    this->fmt.str = "(invalid)";
+  }
+}
+
+const char *
+CtrlMgmtRecordValue::c_str() const
+{
+  switch (this->rec_type) {
+  case TS_REC_STRING:
+    return this->fmt.str;
+  default:
+    return this->fmt.nbuf;
+  }
+}
+
 void
 CtrlMgmtError(TSMgmtError err, const char * fmt, ...)
 {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/cmd/traffic_ctl/traffic_ctl.h
----------------------------------------------------------------------
diff --git a/cmd/traffic_ctl/traffic_ctl.h b/cmd/traffic_ctl/traffic_ctl.h
index 62d2c5e..17e7af3 100644
--- a/cmd/traffic_ctl/traffic_ctl.h
+++ b/cmd/traffic_ctl/traffic_ctl.h
@@ -75,10 +75,9 @@ struct CtrlMgmtRecord
   }
 
   TSMgmtError fetch(const char *);
+
   const char * name() const;
   TSRecordT type() const;
-  const char * c_str() const;
-
   int64_t as_int() const;
 
 private:
@@ -86,7 +85,28 @@ private:
   CtrlMgmtRecord& operator=(const CtrlMgmtRecord&); // disabled
 
   TSRecordEle * ele;
-  mutable char nbuf[32];
+
+  friend struct CtrlMgmtRecordValue;
+};
+
+struct CtrlMgmtRecordValue
+{
+  explicit CtrlMgmtRecordValue(const TSRecordEle *);
+  explicit CtrlMgmtRecordValue(const CtrlMgmtRecord&);
+
+  CtrlMgmtRecordValue(TSRecordT, TSRecordValueT);
+  const char * c_str() const;
+
+private:
+  CtrlMgmtRecordValue(const CtrlMgmtRecordValue&); // disabled
+  CtrlMgmtRecordValue& operator=(const CtrlMgmtRecordValue&); // disabled
+  void init(TSRecordT, TSRecordValueT);
+
+  TSRecordT rec_type;
+  union {
+    const char * str;
+    char nbuf[32];
+  } fmt;
 };
 
 struct CtrlMgmtRecordList

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/lib/records/I_RecCore.h
----------------------------------------------------------------------
diff --git a/lib/records/I_RecCore.h b/lib/records/I_RecCore.h
index 40bf46c..5b5ffe0 100644
--- a/lib/records/I_RecCore.h
+++ b/lib/records/I_RecCore.h
@@ -31,6 +31,7 @@
 #include "I_RecSignals.h"
 #include "I_RecEvents.h"
 
+struct RecRecord;
 
 //-------------------------------------------------------------------------
 // Diagnostic Output
@@ -163,6 +164,9 @@ int RecGetRecordBool(const char *name, RecBool * rec_byte, bool lock = true);
 //------------------------------------------------------------------------
 // Record Attributes Reading
 //------------------------------------------------------------------------
+
+int RecLookupRecord(const char *name, void (*callback)(const RecRecord *, void *), void * data, bool lock = true);
+
 int RecGetRecordType(const char *name, RecT * rec_type, bool lock = true);
 int RecGetRecordDataType(const char *name, RecDataT * data_type, bool lock = true);
 int RecGetRecordPersistenceType(const char *name, RecPersistT * persist_type, bool lock = true);
@@ -301,6 +305,4 @@ int RecSetSyncRequired(char *name, bool lock = true);
 typedef void *(*RecManagerCb) (void *opaque_cb_data, char *data_raw, int data_len);
 int RecRegisterManagerCb(int _signal, RecManagerCb _fn, void *_data = NULL);
 
-void RecResizeAdditional(int add);
-
 #endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/lib/records/RecCore.cc
----------------------------------------------------------------------
diff --git a/lib/records/RecCore.cc b/lib/records/RecCore.cc
index 848eed4..f24cd84 100644
--- a/lib/records/RecCore.cc
+++ b/lib/records/RecCore.cc
@@ -422,6 +422,31 @@ RecGetRecordBool(const char *name, RecBool *rec_bool, bool lock)
 //-------------------------------------------------------------------------
 // RecGetRec Attributes
 //-------------------------------------------------------------------------
+
+int
+RecLookupRecord(const char *name, void (*callback)(const RecRecord *, void *), void * data, bool lock)
+{
+  int err = REC_ERR_FAIL;
+  RecRecord *r;
+
+  if (lock) {
+    ink_rwlock_rdlock(&g_records_rwlock);
+  }
+
+  if (ink_hash_table_lookup(g_records_ht, name, (void **) &r)) {
+    rec_mutex_acquire(&(r->lock));
+    callback(r, data);
+    err = REC_ERR_OKAY;
+    rec_mutex_release(&(r->lock));
+  }
+
+  if (lock) {
+    ink_rwlock_unlock(&g_records_rwlock);
+  }
+
+  return err;
+}
+
 int
 RecGetRecordType(const char *name, RecT * rec_type, bool lock)
 {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/CoreAPI.cc
----------------------------------------------------------------------
diff --git a/mgmt/api/CoreAPI.cc b/mgmt/api/CoreAPI.cc
index f48fd01..ded05d0 100644
--- a/mgmt/api/CoreAPI.cc
+++ b/mgmt/api/CoreAPI.cc
@@ -557,6 +557,12 @@ MgmtRecordGetMatching(const char * /* regex */, TSList /* rec_vals */)
   return TS_ERR_FAIL;
 }
 
+TSMgmtError
+MgmtConfigRecordDescribe(const char * /* rec_name */, unsigned /* flags */, TSConfigRecordDescription * /* val */)
+{
+  return TS_ERR_NOT_SUPPORTED;
+}
+
 /*-------------------------------------------------------------------------
  * reads the RecordsConfig info to determine which type of action is needed
  * when the record rec_name is changed; if the rec_name is invalid,

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/CoreAPI.h
----------------------------------------------------------------------
diff --git a/mgmt/api/CoreAPI.h b/mgmt/api/CoreAPI.h
index b7a434f..5c79843 100644
--- a/mgmt/api/CoreAPI.h
+++ b/mgmt/api/CoreAPI.h
@@ -66,6 +66,7 @@ TSMgmtError MgmtRecordSetFloat(const char * rec_name, MgmtFloat float_val, TSAct
 TSMgmtError MgmtRecordSetString(const char * rec_name, const char * string_val, TSActionNeedT * action_need);
 TSMgmtError MgmtRecordGetMatching(const char * regex, TSList rec_vals);
 
+TSMgmtError MgmtConfigRecordDescribe(const char * rec_name, unsigned flags, TSConfigRecordDescription * val);
 
 /***************************************************************************
  * File Operations

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/CoreAPIRemote.cc
----------------------------------------------------------------------
diff --git a/mgmt/api/CoreAPIRemote.cc b/mgmt/api/CoreAPIRemote.cc
index cc462d8..24e3c0b 100644
--- a/mgmt/api/CoreAPIRemote.cc
+++ b/mgmt/api/CoreAPIRemote.cc
@@ -427,7 +427,6 @@ Restart(unsigned options)
   return ret;
 }
 
-
 /*-------------------------------------------------------------------------
  * Bounce
  *-------------------------------------------------------------------------
@@ -464,6 +463,35 @@ StorageDeviceCmdOffline(char const* dev)
 /***************************************************************************
  * Record Operations
  ***************************************************************************/
+
+static void
+mgmt_record_convert_value(TSRecordT rec_type, const MgmtMarshallData& data, TSRecordValueT& value)
+{
+  // convert the record value to appropriate type
+  if (data.ptr) {
+    switch (rec_type) {
+    case TS_REC_INT:
+      ink_assert(data.len == sizeof(TSInt));
+      value.int_val = *(TSInt *)data.ptr;
+      break;
+    case TS_REC_COUNTER:
+      ink_assert(data.len == sizeof(TSCounter));
+      value.counter_val = *(TSCounter *)data.ptr;
+      break;
+    case TS_REC_FLOAT:
+      ink_assert(data.len == sizeof(TSFloat));
+      value.float_val = *(TSFloat *)data.ptr;
+      break;
+    case TS_REC_STRING:
+      ink_assert(data.len == strlen((char *)data.ptr) + 1);
+      value.string_val = ats_strdup((char *)data.ptr);
+      break;
+    default:
+      ; // nothing ... shut up compiler!
+    }
+  }
+}
+
 static TSMgmtError
 mgmt_record_get_reply(OpType op, TSRecordEle * rec_ele)
 {
@@ -472,8 +500,8 @@ mgmt_record_get_reply(OpType op, TSRecordEle * rec_ele)
   MgmtMarshallData reply = { NULL, 0 };
   MgmtMarshallInt err;
   MgmtMarshallInt type;
-  MgmtMarshallString name;
-  MgmtMarshallData value;
+  MgmtMarshallString name = NULL;
+  MgmtMarshallData value = { NULL, 0 };
 
   ink_zero(*rec_ele);
   rec_ele->rec_type = TS_REC_UNDEFINED;
@@ -487,50 +515,22 @@ mgmt_record_get_reply(OpType op, TSRecordEle * rec_ele)
   ret = recv_mgmt_response(reply.ptr, reply.len, op, &err, &type, &name, &value);
   ats_free(reply.ptr);
   if (ret != TS_ERR_OKAY) {
-    return ret;
+    goto done;
   }
 
   if (err != TS_ERR_OKAY) {
-    ats_free(name);
-    ats_free(value.ptr);
-    return (TSMgmtError)err;
+    ret = (TSMgmtError)err;
+    goto done;
   }
 
   rec_ele->rec_type = (TSRecordT)type;
+  rec_ele->rec_name = ats_strdup(name);
+  mgmt_record_convert_value(rec_ele->rec_type, value, rec_ele->valueT);
 
-  // convert the record value to appropriate type
-  if (value.ptr) {
-    switch (rec_ele->rec_type) {
-    case TS_REC_INT:
-      ink_assert(value.len == sizeof(TSInt));
-      rec_ele->valueT.int_val = *(TSInt *)value.ptr;
-      break;
-    case TS_REC_COUNTER:
-      ink_assert(value.len == sizeof(TSCounter));
-      rec_ele->valueT.counter_val = *(TSCounter *)value.ptr;
-      break;
-    case TS_REC_FLOAT:
-      ink_assert(value.len == sizeof(TSFloat));
-      rec_ele->valueT.float_val = *(TSFloat *)value.ptr;
-      break;
-    case TS_REC_STRING:
-      ink_assert(value.len == strlen((char *)value.ptr) + 1);
-      rec_ele->valueT.string_val = ats_strdup((char *)value.ptr);
-      break;
-    default:
-      ; // nothing ... shut up compiler!
-    }
-  }
-
-  // The record takes ownership of the (non-empty) name.
-  if (strlen(name)) {
-    rec_ele->rec_name = name;
-  } else {
-    ats_free(name);
-  }
-
+done:
+  ats_free(name);
   ats_free(value.ptr);
-  return TS_ERR_OKAY;
+  return ret;
 }
 
 // note that the record value is being sent as chunk of memory, regardless of
@@ -552,6 +552,82 @@ MgmtRecordGet(const char *rec_name, TSRecordEle * rec_ele)
 }
 
 TSMgmtError
+MgmtConfigRecordDescribe(const char *rec_name, unsigned options, TSConfigRecordDescription * val)
+{
+  TSMgmtError ret;
+  MgmtMarshallInt optype = RECORD_DESCRIBE_CONFIG;
+  MgmtMarshallInt flags = options;
+  MgmtMarshallString record = const_cast<MgmtMarshallString>(rec_name);
+
+  MgmtMarshallData reply = { NULL, 0 };
+
+  // create and send request
+  ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, RECORD_DESCRIBE_CONFIG, &optype, &record, &flags);
+  if (ret != TS_ERR_OKAY) {
+    return ret;
+  }
+
+  ret = recv_mgmt_message(main_socket_fd, reply);
+  if (ret != TS_ERR_OKAY) {
+    return ret;
+  } else {
+    MgmtMarshallInt err;
+    MgmtMarshallString name = NULL;
+    MgmtMarshallString expr = NULL;
+    MgmtMarshallData value = { NULL, 0 };
+    MgmtMarshallData deflt = { NULL, 0 };
+
+    MgmtMarshallInt rtype;
+    MgmtMarshallInt rclass;
+    MgmtMarshallInt version;
+    MgmtMarshallInt rsb;
+    MgmtMarshallInt order;
+    MgmtMarshallInt access;
+    MgmtMarshallInt update;
+    MgmtMarshallInt updatetype;
+    MgmtMarshallInt checktype;
+
+    ret = recv_mgmt_response(reply.ptr, reply.len, RECORD_DESCRIBE_CONFIG, &err, &name, &value, &deflt, &rtype,
+        &rclass, &version, &rsb, &order, &access, &update, &updatetype, &checktype, &expr);
+
+    ats_free(reply.ptr);
+
+    if (ret != TS_ERR_OKAY) {
+      goto done;
+    }
+
+    if (err != TS_ERR_OKAY) {
+      ret = (TSMgmtError)err;
+      goto done;
+    }
+
+    // Everything is cool, populate the description ...
+    val->rec_name = ats_strdup(name);
+    val->rec_checkexpr = ats_strdup(expr);
+    val->rec_type = (TSRecordT)rtype;
+    val->rec_class = rclass;
+    val->rec_version = version;
+    val->rec_rsb = rsb;
+    val->rec_order = order;
+    val->rec_access = access;
+    val->rec_updatetype = updatetype;
+    val->rec_checktype = checktype;
+
+    mgmt_record_convert_value(val->rec_type, value, val->rec_value);
+    mgmt_record_convert_value(val->rec_type, deflt, val->rec_default);
+
+done:
+    ats_free(name);
+    ats_free(expr);
+    ats_free(value.ptr);
+    ats_free(deflt.ptr);
+    return ret;
+  }
+
+  return ret;
+}
+
+TSMgmtError
 MgmtRecordGetMatching(const char * regex, TSList rec_vals)
 {
   TSMgmtError       ret;
@@ -661,7 +737,6 @@ MgmtRecordSetFloat(const char *rec_name, MgmtFloat float_val, TSActionNeedT * ac
   return ret;
 }
 
-
 TSMgmtError
 MgmtRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT * action_need)
 {
@@ -674,7 +749,6 @@ MgmtRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT
   return ret;
 }
 
-
 /***************************************************************************
  * File Operations
  ***************************************************************************/
@@ -981,4 +1055,3 @@ StatsReset(bool cluster, const char * stat_name)
   ret = MGMTAPI_SEND_MESSAGE(main_socket_fd, op, &optype, &name);
   return (ret == TS_ERR_OKAY) ? parse_generic_response(op, main_socket_fd) : ret;
 }
-

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/INKMgmtAPI.cc
----------------------------------------------------------------------
diff --git a/mgmt/api/INKMgmtAPI.cc b/mgmt/api/INKMgmtAPI.cc
index c84d83f..c7e1bbe 100644
--- a/mgmt/api/INKMgmtAPI.cc
+++ b/mgmt/api/INKMgmtAPI.cc
@@ -45,7 +45,6 @@
 // forward declarations
 void init_pdss_format(TSPdSsFormat& info);
 
-
 /***************************************************************************
  * API Memory Management
  ***************************************************************************/
@@ -73,7 +72,6 @@ _TSfree(void *ptr)
   ats_free(ptr);
 }
 
-
 /***************************************************************************
  * API Helper Functions for Data Carrier Structures
  ***************************************************************************/
@@ -212,7 +210,6 @@ TSIpAddrListEnqueue(TSIpAddrList ip_addrl, TSIpAddrEle * ip_addr)
   }
 }
 
-
 /* The the TSIpAddrEle returned is actually removed from the end of list */
 tsapi TSIpAddrEle *
 TSIpAddrListDequeue(TSIpAddrList ip_addrl)
@@ -224,7 +221,6 @@ TSIpAddrListDequeue(TSIpAddrList ip_addrl)
   return (TSIpAddrEle *) dequeue((LLQ *) ip_addrl);
 }
 
-
 tsapi int
 TSIpAddrListLen(TSIpAddrList ip_addrl)
 {
@@ -370,7 +366,6 @@ TSPortListIsValid(TSPortList portl)
   return true;
 }
 
-
 /*--- TSDomainList operations -----------------------------------------*/
 tsapi TSDomainList
 TSDomainListCreate()
@@ -657,7 +652,6 @@ TSIntListIsValid(TSIntList intl, int min, int max)
   return true;
 }
 
-
 // helper fn that sets default values for the info passed in
 void
 init_pdss_format(TSPdSsFormat& info)
@@ -948,7 +942,6 @@ TSCongestionEleDestroy(TSCongestionEle * ele)
   return;
 }
 
-
 /*-------------------------------------------------------------
  * HostingObj
  *-------------------------------------------------------------*/
@@ -1043,7 +1036,6 @@ TSIpAllowEleDestroy(TSIpAllowEle * ele)
 
 }
 
-
 /*-------------------------------------------------------------
  * TSLogFilterEle
  *-------------------------------------------------------------*/
@@ -1513,7 +1505,6 @@ END:
   return ret;
 }
 
-
 /*-------------------------------------------------------------------------
  * TSRecordGetMlt
  *-------------------------------------------------------------------------
@@ -1586,7 +1577,6 @@ TSRecordSet(const char *rec_name, const char *val, TSActionNeedT * action_need)
   return MgmtRecordSet(rec_name, val, action_need);
 }
 
-
 tsapi TSMgmtError
 TSRecordSetInt(const char *rec_name, TSInt int_val, TSActionNeedT * action_need)
 {
@@ -1611,7 +1601,6 @@ TSRecordSetString(const char *rec_name, const char *str_val, TSActionNeedT * act
   return MgmtRecordSetString(rec_name, str_val, action_need);
 }
 
-
 /*-------------------------------------------------------------------------
  * TSRecordSetMlt
  *-------------------------------------------------------------------------
@@ -1822,7 +1811,6 @@ TSStorageDeviceCmdOffline(char const* dev)
   return StorageDeviceCmdOffline(dev);
 }
 
-
 /*--- diags output operations ---------------------------------------------*/
 tsapi void
 TSDiags(TSDiagsT mode, const char *fmt, ...)
@@ -1900,7 +1888,6 @@ TSGetErrorMessage(TSMgmtError err_id)
   return err_msg;
 }
 
-
 /*--- password operations -------------------------------------------------*/
 tsapi TSMgmtError
 TSEncryptPassword(char *passwd, char **e_passwd)
@@ -1941,7 +1928,6 @@ TSConfigFileWrite(TSFileNameT file, char *text, int size, int version)
   return WriteFile(file, text, size, version);
 }
 
-
 /* ReadFromUrl: reads a remotely located config file into a buffer
  * Input:  url        - remote location of the file
  *         header     - a buffer is allocated on the header char* pointer
@@ -2376,7 +2362,6 @@ TSCfgContextMoveEleDown(TSCfgContext ctx, int index)
   return CfgContextMoveEleDown((CfgContext *) ctx, index);
 }
 
-
 TSMgmtError
 TSCfgContextAppendEle(TSCfgContext ctx, TSCfgEle * ele)
 {
@@ -2413,3 +2398,30 @@ TSIsValid(TSCfgEle * ele)
   ele_obj = create_ele_obj_from_ele(ele);
   return (ele_obj->isValid());
 }
+
+void
+TSConfigRecordDescriptionFree(TSConfigRecordDescription * val)
+{
+  if (val) {
+    ats_free(val->rec_name);
+    ats_free(val->rec_checkexpr);
+
+    if (val->rec_type == TS_REC_STRING) {
+      ats_free(val->rec_value.string_val);
+    }
+
+    ink_zero(*val);
+    val->rec_type = TS_REC_UNDEFINED;
+  }
+}
+
+TSMgmtError
+TSConfigRecordDescribe(const char * rec_name, unsigned flags, TSConfigRecordDescription * val)
+{
+  if (!rec_name || !val) {
+    return TS_ERR_PARAMS;
+  }
+
+  TSConfigRecordDescriptionFree(val);
+  return MgmtConfigRecordDescribe(rec_name, flags, val);
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/NetworkMessage.cc
----------------------------------------------------------------------
diff --git a/mgmt/api/NetworkMessage.cc b/mgmt/api/NetworkMessage.cc
index 828be32..66c7e6f 100644
--- a/mgmt/api/NetworkMessage.cc
+++ b/mgmt/api/NetworkMessage.cc
@@ -29,7 +29,7 @@
 #include "NetworkMessage.h"
 
 #define MAX_OPERATION_BUFSZ   1024
-#define MAX_OPERATION_FIELDS  10
+#define MAX_OPERATION_FIELDS  16
 
 struct NetCmdOperation
 {
@@ -65,6 +65,7 @@ static const struct NetCmdOperation requests[] = {
   /* RECORD_MATCH_GET           */ { 2, { MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING } },
   /* API_PING                   */ { 2, { MGMT_MARSHALL_INT, MGMT_MARSHALL_INT } },
   /* SERVER_BACKTRACE           */ { 2, { MGMT_MARSHALL_INT, MGMT_MARSHALL_INT } },
+  /* RECORD_DESCRIBE_CONFIG     */ { 3, { MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_INT } },
 };
 
 // Responses always begin with a TSMgmtError code, followed by additional fields.
@@ -95,6 +96,12 @@ static const struct NetCmdOperation responses[] = {
   /* RECORD_MATCH_GET           */ { 4, { MGMT_MARSHALL_INT, MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING, MGMT_MARSHALL_DATA } },
   /* API_PING                   */ { 0, {} }, // no reply
   /* SERVER_BACKTRACE           */ { 2, { MGMT_MARSHALL_INT, MGMT_MARSHALL_STRING } },
+  /* RECORD_DESCRIBE_CONFIG     */ { 14, { MGMT_MARSHALL_INT /* status */,
+    MGMT_MARSHALL_STRING /* name */,  MGMT_MARSHALL_DATA /* value */,     MGMT_MARSHALL_DATA /* default */,
+    MGMT_MARSHALL_INT /* type */,     MGMT_MARSHALL_INT /* class */,      MGMT_MARSHALL_INT /* version */,
+    MGMT_MARSHALL_INT /* rsb */,      MGMT_MARSHALL_INT /* order */,      MGMT_MARSHALL_INT /* access */,
+    MGMT_MARSHALL_INT /* update */,   MGMT_MARSHALL_INT /* updatetype */, MGMT_MARSHALL_INT /* checktype */,
+    MGMT_MARSHALL_STRING /* checkexpr */} },
 };
 
 #define GETCMD(ops, optype, cmd) do { \
@@ -221,6 +228,15 @@ send_mgmt_error(int fd, OpType optype, TSMgmtError error)
     ink_release_assert(responses[optype].nfields == 4);
     return send_mgmt_response(fd, optype, &ecode, &intval, &strval, &dataval);
 
+  case RECORD_DESCRIBE_CONFIG:
+    ink_release_assert(responses[optype].nfields == 14);
+    return send_mgmt_response(fd, optype, &ecode,
+      &strval /* name */,   &dataval /* value */,     &dataval /* default */,
+      &intval /* type */,   &intval /* class */,      &intval /* version */,
+      &intval /* rsb */,    &intval /* order */,      &intval /* access */,
+      &intval /* update */, &intval /* updatetype */, &intval /* checktype */,
+      &strval /* checkexpr */);
+
   case EVENT_REG_CALLBACK:
   case EVENT_UNREG_CALLBACK:
   case EVENT_NOTIFY:

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/NetworkMessage.h
----------------------------------------------------------------------
diff --git a/mgmt/api/NetworkMessage.h b/mgmt/api/NetworkMessage.h
index feb34cd..923fb7c 100644
--- a/mgmt/api/NetworkMessage.h
+++ b/mgmt/api/NetworkMessage.h
@@ -60,9 +60,12 @@ typedef enum
   RECORD_MATCH_GET,
   API_PING,
   SERVER_BACKTRACE,
+  RECORD_DESCRIBE_CONFIG,
   UNDEFINED_OP /* This must be last */
 } OpType;
 
+#define MGMT_OPERATION_TYPE_MAX (UNDEFINED_OP)
+
 struct mgmt_message_sender
 {
   virtual TSMgmtError send(void * msg, size_t msglen) const = 0;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/TSControlMain.cc
----------------------------------------------------------------------
diff --git a/mgmt/api/TSControlMain.cc b/mgmt/api/TSControlMain.cc
index 9eff803..8f8633e 100644
--- a/mgmt/api/TSControlMain.cc
+++ b/mgmt/api/TSControlMain.cc
@@ -263,6 +263,39 @@ ts_ctrl_main(void *arg)
  */
 
 static TSMgmtError
+marshall_rec_data(RecDataT rec_type, const RecData& rec_data, MgmtMarshallData& data)
+{
+  switch (rec_type) {
+  case TS_REC_INT:
+    data.ptr = const_cast<RecInt *>(&rec_data.rec_int);
+    data.len = sizeof(TSInt);
+    break;
+  case TS_REC_COUNTER:
+    data.ptr = const_cast<RecCounter *>(&rec_data.rec_counter);
+    data.len = sizeof(TSCounter);
+    break;
+  case TS_REC_FLOAT:
+    data.ptr = const_cast<RecFloat *>(&rec_data.rec_float);
+    data.len = sizeof(TSFloat);
+    break;
+  case TS_REC_STRING:
+    // Make sure to send the NULL in the string value response.
+    if (rec_data.rec_string) {
+      data.ptr = rec_data.rec_string;
+      data.len = strlen(rec_data.rec_string) + 1;
+    } else {
+      data.ptr = (void *)"NULL";
+      data.len = countof("NULL");
+    }
+    break;
+  default:                     // invalid record type
+    return TS_ERR_FAIL;
+  }
+
+  return TS_ERR_OKAY;
+}
+
+static TSMgmtError
 send_record_get_response(int fd, TSRecordT rec_type, const char * rec_name, const void * rec_data, size_t data_len)
 {
   MgmtMarshallInt err = TS_ERR_OKAY;
@@ -971,6 +1004,94 @@ handle_server_backtrace(int fd, void * req, size_t reqlen)
   return (TSMgmtError)err;
 }
 
+static void
+send_record_describe(const RecRecord * rec, void * ptr)
+{
+  MgmtMarshallString  rec_name = const_cast<char *>(rec->name);
+  MgmtMarshallData    rec_value = { NULL, 0 };
+  MgmtMarshallData    rec_default { NULL, 0 };
+  MgmtMarshallInt     rec_type = rec->data_type;
+  MgmtMarshallInt     rec_class = rec->rec_type;
+  MgmtMarshallInt     rec_version = rec->version;
+  MgmtMarshallInt     rec_rsb = rec->rsb_id;
+  MgmtMarshallInt     rec_order = rec->order;
+  MgmtMarshallInt     rec_access = rec->config_meta.access_type;
+  MgmtMarshallInt     rec_update = rec->config_meta.update_required;
+  MgmtMarshallInt     rec_updatetype = rec->config_meta.update_type;
+  MgmtMarshallInt     rec_checktype = rec->config_meta.check_type;
+  MgmtMarshallString  rec_checkexpr = rec->config_meta.check_expr;
+
+  MgmtMarshallInt     err = TS_ERR_OKAY;
+
+  int * fderr = (int *)ptr;
+
+  // We only describe config variables (for now).
+  if (!REC_TYPE_IS_CONFIG(rec->rec_type)) {
+    *fderr = TS_ERR_PARAMS;
+    return;
+  }
+
+  switch (rec_type) {
+  case RECD_INT: rec_type = TS_REC_INT; break;
+  case RECD_FLOAT: rec_type = TS_REC_FLOAT; break;
+  case RECD_STRING: rec_type = TS_REC_STRING; break;
+  case RECD_COUNTER: rec_type = TS_REC_COUNTER; break;
+  default: rec_type = TS_REC_UNDEFINED;
+  }
+
+  err = marshall_rec_data(rec->data_type, rec->data, rec_value);
+  if (err != TS_ERR_OKAY) {
+    goto done;
+  }
+
+  err = marshall_rec_data(rec->data_type, rec->data_default, rec_default);
+  if (err != TS_ERR_OKAY) {
+    goto done;
+  }
+
+  err = send_mgmt_response(*fderr, RECORD_DESCRIBE_CONFIG, &err,
+    &rec_name, &rec_value, &rec_default, &rec_type, &rec_class, &rec_version, &rec_rsb, &rec_order,
+    &rec_access, &rec_update, &rec_updatetype, &rec_checktype, &rec_checkexpr);
+
+done:
+  *fderr = err;
+}
+
+static TSMgmtError
+handle_record_describe(int fd, void * req, size_t reqlen)
+{
+  TSMgmtError ret;
+  MgmtMarshallInt optype;
+  MgmtMarshallInt options;
+  MgmtMarshallString name;
+
+  int fderr = fd; // [in,out] variable for the fd and error
+
+  ret = recv_mgmt_request(req, reqlen, RECORD_DESCRIBE_CONFIG, &optype, &name, &options);
+  if (ret != TS_ERR_OKAY) {
+    return ret;
+  }
+
+  if (strlen(name) == 0) {
+    ret = TS_ERR_PARAMS;
+    goto done;
+  }
+
+  if (RecLookupRecord(name, send_record_describe, &fderr) != REC_ERR_OKAY) {
+    ret = TS_ERR_PARAMS;
+    goto done;
+  }
+
+  // If the lookup succeeded, the final error is in "fderr".
+  if (ret == TS_ERR_OKAY) {
+    ret = (TSMgmtError)fderr;
+  }
+
+done:
+  ats_free(name);
+  return ret;
+}
+
 struct control_message_handler
 {
   unsigned flags;
@@ -1003,9 +1124,14 @@ static const control_message_handler handlers[] = {
   /* STORAGE_DEVICE_CMD_OFFLINE */ { MGMT_API_PRIVILEGED, handle_storage_device_cmd_offline },
   /* RECORD_MATCH_GET           */ { 0, handle_record_match },
   /* API_PING                   */ { 0, handle_api_ping },
-  /* SERVER_BACKTRACE           */ { MGMT_API_PRIVILEGED, handle_server_backtrace }
+  /* SERVER_BACKTRACE           */ { MGMT_API_PRIVILEGED, handle_server_backtrace },
+  /* RECORD_DESCRIBE_CONFIG     */ { 0, handle_record_describe }
 };
 
+// This should use countof(), but we need a constexpr :-/
+#define NUM_OP_HANDLERS (sizeof(handlers)/sizeof(handlers[0]))
+extern char __msg_handler_static_assert[NUM_OP_HANDLERS == MGMT_OPERATION_TYPE_MAX ? 0 : -1];
+
 static TSMgmtError
 handle_control_message(int fd, void * req, size_t reqlen)
 {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/857766c5/mgmt/api/include/mgmtapi.h
----------------------------------------------------------------------
diff --git a/mgmt/api/include/mgmtapi.h b/mgmt/api/include/mgmtapi.h
index 156e5b2..47e4b06 100644
--- a/mgmt/api/include/mgmtapi.h
+++ b/mgmt/api/include/mgmtapi.h
@@ -110,6 +110,7 @@ extern "C"
   typedef int64_t TSInt;
   typedef int64_t TSCounter;
   typedef float TSFloat;
+  typedef bool TSBool;
   typedef char *TSString;
   typedef char *TSIpAddr;
 
@@ -432,19 +433,44 @@ extern "C"
 
 /*--- records -------------------------------------------------------------*/
 
+  typedef union
+  {                           /* record value */
+    TSInt     int_val;
+    TSCounter counter_val;
+    TSFloat   float_val;
+    TSString  string_val;
+  } TSRecordValueT;
+
   typedef struct
   {
-    char *rec_name;             /* record name */
+    char *rec_name;            /* record name */
     TSRecordT rec_type;        /* record type {TS_REC_INT...} */
-    union
-    {                           /* record value */
-      TSInt int_val;
-      TSCounter counter_val;
-      TSFloat float_val;
-      TSString string_val;
-    } valueT;
+    TSRecordValueT valueT;    /* record value */
   } TSRecordEle;
 
+  typedef struct
+  {
+    /* Common RecRecord fields ... */
+    char *          rec_name;
+    TSRecordValueT  rec_value;
+    TSRecordValueT  rec_default;
+    TSRecordT       rec_type;     /* data type (RecDataT) */
+    TSInt           rec_class;    /* data class (RecT) */
+    TSInt           rec_version;
+    TSInt           rec_rsb;      /* Raw Stat Block ID */
+    TSInt           rec_order;
+
+    /* RecConfigMeta fields ... */
+    TSInt           rec_access;       /* access rights (RecAccessT) */
+    TSInt           rec_update;   /* update_required bitmask */
+    TSInt           rec_updatetype;   /* update type (RecUpdateT) */
+    TSInt           rec_checktype;    /* syntax check type (RecCheckT) */
+    char *          rec_checkexpr;    /* syntax check expression */
+  } TSConfigRecordDescription;
+
+  /* Free (the contents of) a TSConfigRecordDescription */
+  tsapi void TSConfigRecordDescriptionFree(TSConfigRecordDescription * val);
+
 /*--- events --------------------------------------------------------------*/
 
 /* Note: Each event has a format String associated with it from which the
@@ -1160,6 +1186,14 @@ extern "C"
   tsapi TSMgmtError TSRecordSetFloat(const char *rec_name, TSFloat float_val, TSActionNeedT * action_need);
   tsapi TSMgmtError TSRecordSetString(const char *rec_name, const char *string_val, TSActionNeedT * action_need);
 
+/* TSConfigRecordDescribe: fetch a full description of a configuration record
+ * Input: rec_name  - name of the record
+ *        flags     - (unused) fetch flags bitmask
+ *        val       - output value;
+ * Output: TSMgmtError
+ */
+  tsapi TSMgmtError TSConfigRecordDescribe(const char * rec_name, unsigned flags, TSConfigRecordDescription * val);
+
 /* TSRecordSetMlt: sets a set of records
  * Input:  rec_list     - list of record names the user wants to set;
  *                        if one set fails, transaction will be aborted