trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zw...@apache.org
Subject [trafficserver] 01/02: Adding verify plugin TS maintenance commands
Date Sun, 22 Dec 2019 20:12:56 GMT
This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 0cb00040f02e7724d63f2edb8d3eb1fd4759b47f
Author: bneradt <bneradt@verizonmedia.com>
AuthorDate: Mon Oct 14 17:41:16 2019 +0000

    Adding verify plugin TS maintenance commands
    
    This adds the verify_global_plugin and verify_remap_plugin maintenance
    commands.
    
    (cherry picked from commit 8fcb137d361ee063e720b183be4c767b7ecda53e)
    
     Conflicts:
    	doc/release-notes/whats-new.en.rst
---
 .gitignore                                         |   2 +
 doc/appendices/command-line/traffic_server.en.rst  |  44 ++++-
 include/tscore/ts_file.h                           |   9 +
 proxy/Plugin.cc                                    |  46 +++--
 proxy/Plugin.h                                     |   2 +
 proxy/http/remap/RemapPluginInfo.h                 |  10 +-
 src/traffic_server/traffic_server.cc               | 169 +++++++++++++++++-
 src/tscore/ts_file.cc                              |  12 +-
 src/tscore/unit_tests/test_ts_file.cc              |  18 +-
 .../command_argument/verify_global_plugin.test.py  | 198 +++++++++++++++++++++
 .../command_argument/verify_remap_plugin.test.py   | 146 +++++++++++++++
 tests/tools/plugins/conf_remap_stripped.cc         |  49 +++++
 tests/tools/plugins/missing_mangled_definition.c   |  31 ++++
 tests/tools/plugins/missing_mangled_definition.cc  |  33 ++++
 tests/tools/plugins/missing_mangled_definition.h   |  19 ++
 tests/tools/plugins/missing_ts_plugin_init.cc      |  28 +++
 16 files changed, 782 insertions(+), 34 deletions(-)

diff --git a/.gitignore b/.gitignore
index bccd632..e5629e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 *.o
+*.c_o
+*.cc_o
 *.a
 *.so
 *.pyc
diff --git a/doc/appendices/command-line/traffic_server.en.rst b/doc/appendices/command-line/traffic_server.en.rst
index 7a2dae2..165267c 100644
--- a/doc/appendices/command-line/traffic_server.en.rst
+++ b/doc/appendices/command-line/traffic_server.en.rst
@@ -71,7 +71,49 @@ the available tests.
 
 .. option:: -M, --remote_management
 
-.. option:: -C CMD, --command CMD
+.. option:: -C 'CMD [ARGS]', --command 'CMD [ARGS]'
+
+Run a |TS| maintenance command. These commands perform various administrative
+actions or queries against |TS|. Note that some commands (such as ``help`` and
+``verify_global_plugin``) take an argument. To invoke the command and its
+argument, surround the ``CMD`` and its argument in quotes. For instance, to
+request help for the ``verify_global_plugin`` command, format your command like
+so::
+    traffic_server -C "help verify_global_plugin"
+
+The following commands are supported:
+
+list
+   List the sizes of the host database and cache index as well as the storage
+   available to the cache.
+check
+   Check the cache for inconsistencies or corruption. ``check`` does not make
+   any changes to the data stored in the cache. ``check`` requires a scan of
+   the contents of the cache and may take a long time for large caches.
+clear
+   Clear the entire cache, both the document and the host database caches.  All
+   data in the cache is lost and the cache is reconfigured based on the current
+   description of database sizes and available storage.
+clear_cache
+   Clear the document cache.  All documents in the cache are lost and the cache
+   is reconfigured based on the current description of database sizes and
+   available storage.
+clear_hostdb
+   Clear the entire host database cache.  All host name resolution information
+   is lost.
+verify_config
+   Load the config and verify |TS| comes up correctly.
+verify_global_plugin PLUGIN_SO_FILE
+   Load a global plugin's shared object file and verify it meets minimal global
+   plugin API requirements.
+verify_remap_plugin PLUGIN_SO_FILE
+   Load a remap plugin's shared object file and verify it meets minimal remap
+   plugin API requirements.
+help [CMD]
+    Obtain a short description of a command. For example, ``'help clear'``
+    prints a description of the ``clear`` maintenance command. If no argument
+    is passed to ``help`` then a list of the supported maintenance commands are
+    printed along with a brief description of each.
 
 .. option:: -k, --clear_hostdb
 
diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h
index e9bfd8b..c058c00 100644
--- a/include/tscore/ts_file.h
+++ b/include/tscore/ts_file.h
@@ -178,6 +178,15 @@ namespace file
   // Returns return the canonicalized absolute pathname
   path canonical(const path &p, std::error_code &ec);
 
+  // Return the filename derived from path p.
+  //
+  // Examples:
+  //   given "/foo/bar.txt", this returns "bar.txt"
+  //   given "/foo/bar", this returns "bar"
+  //   given "/foo/bar/", this returns ""
+  //   given "/", this returns ""
+  path filename(const path &p);
+
   // Checks if the file/directory exists
   bool exists(const path &p);
 
diff --git a/proxy/Plugin.cc b/proxy/Plugin.cc
index b658e31..79b3b08 100644
--- a/proxy/Plugin.cc
+++ b/proxy/Plugin.cc
@@ -70,8 +70,29 @@ PluginRegInfo::~PluginRegInfo()
   }
 }
 
+bool
+plugin_dso_load(const char *path, void *&handle, void *&init, std::string &error)
+{
+  handle = dlopen(path, RTLD_NOW);
+  init   = nullptr;
+  if (!handle) {
+    error.assign("unable to load '").append(path).append("': ").append(dlerror());
+    Error("%s", error.c_str());
+    return false;
+  }
+
+  init = dlsym(handle, "TSPluginInit");
+  if (!init) {
+    error.assign("unable to find TSPluginInit function in '").append(path).append("': ").append(dlerror());
+    Error("%s", error.c_str());
+    return false;
+  }
+
+  return true;
+}
+
 static bool
-plugin_load(int argc, char *argv[], bool validateOnly)
+single_plugin_init(int argc, char *argv[], bool validateOnly)
 {
   char path[PATH_NAME_MAX];
   init_func_t init;
@@ -98,12 +119,17 @@ plugin_load(int argc, char *argv[], bool validateOnly)
     REC_ReadConfigInteger(elevate_access, "proxy.config.plugin.load_elevated");
     ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
 
-    void *handle = dlopen(path, RTLD_NOW);
-    if (!handle) {
+    void *handle, *initptr = nullptr;
+    std::string error;
+    bool loaded = plugin_dso_load(path, handle, initptr, error);
+    init        = reinterpret_cast<init_func_t>(initptr);
+
+    if (!loaded) {
       if (validateOnly) {
         return false;
       }
-      Fatal("unable to load '%s': %s", path, dlerror());
+      Fatal("%s", error.c_str());
+      return false; // this line won't get called since Fatal brings down ATS
     }
 
     // Allocate a new registration structure for the
@@ -113,16 +139,6 @@ plugin_load(int argc, char *argv[], bool validateOnly)
     plugin_reg_current->plugin_path = ats_strdup(path);
     plugin_reg_current->dlh         = handle;
 
-    init = reinterpret_cast<init_func_t>(dlsym(plugin_reg_current->dlh, "TSPluginInit"));
-    if (!init) {
-      delete plugin_reg_current;
-      if (validateOnly) {
-        return false;
-      }
-      Fatal("unable to find TSPluginInit function in '%s': %s", path, dlerror());
-      return false; // this line won't get called since Fatal brings down ATS
-    }
-
 #if (!defined(kfreebsd) && defined(freebsd)) || defined(darwin)
     optreset = 1;
 #endif
@@ -303,7 +319,7 @@ plugin_init(bool validateOnly)
     } else {
       argv[MAX_PLUGIN_ARGS - 1] = nullptr;
     }
-    retVal = plugin_load(argc, argv, validateOnly);
+    retVal = single_plugin_init(argc, argv, validateOnly);
 
     for (i = 0; i < argc; i++) {
       ats_free(vars[i]);
diff --git a/proxy/Plugin.h b/proxy/Plugin.h
index 09c3de6..8c445a9 100644
--- a/proxy/Plugin.h
+++ b/proxy/Plugin.h
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include <string>
 #include "tscore/List.h"
 
 struct PluginRegInfo {
@@ -46,6 +47,7 @@ extern DLL<PluginRegInfo> plugin_reg_list;
 extern PluginRegInfo *plugin_reg_current;
 
 bool plugin_init(bool validateOnly = false);
+bool plugin_dso_load(const char *path, void *&handle, void *&init, std::string &error);
 
 /** Abstract interface class for plugin based continuations.
 
diff --git a/proxy/http/remap/RemapPluginInfo.h b/proxy/http/remap/RemapPluginInfo.h
index 6d06b45..16156dc 100644
--- a/proxy/http/remap/RemapPluginInfo.h
+++ b/proxy/http/remap/RemapPluginInfo.h
@@ -83,11 +83,11 @@ public:
   ~RemapPluginInfo();
 
   /* Overload to add / execute remap plugin specific tasks during the plugin loading */
-  virtual bool load(std::string &error);
+  bool load(std::string &error) override;
 
   /* Used by the factory to invoke callbacks during plugin load, init and unload  */
-  virtual bool init(std::string &error);
-  virtual void done(void);
+  bool init(std::string &error) override;
+  void done(void) override;
 
   /* Used by the facility that handles remap plugin instances to invoke callbacks per plugin
instance */
   bool initInstance(int argc, char **argv, void **ih, std::string &error);
@@ -98,8 +98,8 @@ public:
   void osResponse(void *ih, TSHttpTxn rh, int os_response_type);
 
   /* Used by traffic server core to indicate configuration reload */
-  virtual void indicatePreReload();
-  virtual void indicatePostReload(TSRemapReloadStatus reloadStatus);
+  void indicatePreReload() override;
+  void indicatePostReload(TSRemapReloadStatus reloadStatus) override;
 
 protected:
   /* Utility to be used only with unit testing */
diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc
index d57e1a3..b45174e 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -39,11 +39,14 @@
 #include "tscore/hugepages.h"
 #include "tscore/runroot.h"
 #include "tscore/Filenames.h"
+#include "tscore/ts_file.h"
 
 #include "ts/ts.h" // This is sadly needed because of us using TSThreadInit() for some reason.
 
 #include <syslog.h>
 #include <algorithm>
+#include <list>
+#include <string>
 
 #if !defined(linux)
 #include <sys/lock.h>
@@ -91,6 +94,7 @@ extern "C" int plock(int);
 #include "DiagsConfig.h"
 #include "CoreUtils.h"
 #include "RemapConfig.h"
+#include "RemapPluginInfo.h"
 #include "RemapProcessor.h"
 #include "I_Tasks.h"
 #include "InkAPIInternal.h"
@@ -205,7 +209,7 @@ static ArgumentDescription argument_descriptions[] = {
   {"remote_management", 'M', "Remote Management", "T", &remote_management_flag, "PROXY_REMOTE_MANAGEMENT",
nullptr},
   {"command", 'C',
    "Maintenance Command to Execute\n"
-   "      Commands: list, check, clear, clear_cache, clear_hostdb, verify_config, help",
+   "      Commands: list, check, clear, clear_cache, clear_hostdb, verify_config, verify_global_plugin,
verify_remap_plugin, help",
    "S511", &command_string, "PROXY_COMMAND_STRING", nullptr},
   {"conf_dir", 'D', "config dir to verify", "S511", &conf_dir, "PROXY_CONFIG_CONFIG_DIR",
nullptr},
   {"clear_hostdb", 'k', "Clear HostDB on Startup", "F", &auto_clear_hostdb_flag, "PROXY_CLEAR_HOSTDB",
nullptr},
@@ -679,17 +683,27 @@ cmd_list(char * /* cmd ATS_UNUSED */)
   }
 }
 
+/** Parse the given string and skip the first word.
+ *
+ * Words are assumed to be separated by spaces or tabs.
+ *
+ * @param[in] cmd The string whose first word will be skipped.
+ *
+ * @return The pointer in the string cmd to the second word in the string, or
+ * nullptr if there is no second word.
+ */
 static char *
-skip(char *cmd, int null_ok = 0)
+skip(char *cmd)
 {
+  // Skip initial white space.
   cmd += strspn(cmd, " \t");
+  // Point to the beginning of the next white space.
   cmd = strpbrk(cmd, " \t");
   if (!cmd) {
-    if (!null_ok) {
-      printf("Error: argument missing\n");
-    }
     return cmd;
   }
+  // Skip the second white space so that cmd now points to the beginning of the
+  // second word.
   cmd += strspn(cmd, " \t");
   return cmd;
 }
@@ -901,6 +915,115 @@ cmd_verify(char * /* cmd ATS_UNUSED */)
   return 0;
 }
 
+enum class plugin_type_t {
+  GLOBAL,
+  REMAP,
+};
+
+/** Attempt to load a plugin shared object file.
+ *
+ * @param[in] plugin_type The type of plugin for which to create a PluginInfo.
+ * @param[in] plugin_path The path to the plugin's shared object file.
+ * @param[out] error Some description of why the plugin failed to load if
+ * loading it fails.
+ *
+ * @return True if the plugin loaded successfully, false otherwise.
+ */
+static bool
+load_plugin(plugin_type_t plugin_type, const fs::path &plugin_path, std::string &error)
+{
+  switch (plugin_type) {
+  case plugin_type_t::GLOBAL: {
+    void *handle, *initptr;
+    return plugin_dso_load(plugin_path.c_str(), handle, initptr, error);
+  }
+  case plugin_type_t::REMAP: {
+    auto temporary_directory = fs::temp_directory_path();
+    temporary_directory /= fs::path(std::string("verify_plugin_") + std::to_string(getpid()));
+    std::error_code ec;
+    if (!fs::create_directories(temporary_directory, ec)) {
+      std::ostringstream error_os;
+      error_os << "Could not create temporary directory " << temporary_directory.string()
<< ": " << ec.message();
+      error = error_os.str();
+      return false;
+    }
+    const auto runtime_path = temporary_directory / ts::file::filename(plugin_path);
+    const fs::path unused_config;
+    auto plugin_info = std::make_unique<RemapPluginInfo>(unused_config, plugin_path,
runtime_path);
+    bool loaded      = plugin_info->load(error);
+    if (!fs::remove(temporary_directory, ec)) {
+      fprintf(stderr, "ERROR: could not remove temporary directory '%s': %s\n", temporary_directory.c_str(),
ec.message().c_str());
+    }
+    return loaded;
+  }
+  }
+  // Unreached.
+  return false;
+}
+
+/** A helper for the verify plugin command functions.
+ *
+ * @param[in] args The arguments passed to the -C command option. This includes
+ * verify_global_plugin.
+ *
+ * @param[in] symbols The expected symbols to verify exist in the plugin file.
+ *
+ * @return a CMD status code. See the CMD_ defines above in this file.
+ */
+static int
+verify_plugin_helper(char *args, plugin_type_t plugin_type)
+{
+  const auto *plugin_filename = skip(args);
+  if (!plugin_filename) {
+    fprintf(stderr, "ERROR: verifying a plugin requires a plugin SO file path argument\n");
+    return CMD_FAILED;
+  }
+
+  fs::path plugin_path(plugin_filename);
+  fprintf(stderr, "NOTE: verifying plugin '%s'...\n", plugin_filename);
+
+  if (!fs::exists(plugin_path)) {
+    fprintf(stderr, "ERROR: verifying plugin '%s' Fail: No such file or directory\n", plugin_filename);
+    return CMD_FAILED;
+  }
+
+  auto ret = CMD_OK;
+  std::string error;
+  if (load_plugin(plugin_type, plugin_path, error)) {
+    fprintf(stderr, "NOTE: verifying plugin '%s' Success\n", plugin_filename);
+  } else {
+    fprintf(stderr, "ERROR: verifying plugin '%s' Fail: %s\n", plugin_filename, error.c_str());
+    ret = CMD_FAILED;
+  }
+  return ret;
+}
+
+/** Verify whether a given SO file looks like a valid global plugin.
+ *
+ * @param[in] args The arguments passed to the -C command option. This includes
+ * verify_global_plugin.
+ *
+ * @return a CMD status code. See the CMD_ defines above in this file.
+ */
+static int
+cmd_verify_global_plugin(char *args)
+{
+  return verify_plugin_helper(args, plugin_type_t::GLOBAL);
+}
+
+/** Verify whether a given SO file looks like a valid remap plugin.
+ *
+ * @param[in] args The arguments passed to the -C command option. This includes
+ * verify_global_plugin.
+ *
+ * @return a CMD status code. See the CMD_ defines above in this file.
+ */
+static int
+cmd_verify_remap_plugin(char *args)
+{
+  return verify_plugin_helper(args, plugin_type_t::REMAP);
+}
+
 static int cmd_help(char *cmd);
 
 static const struct CMD {
@@ -961,6 +1084,22 @@ static const struct CMD {
    "\n"
    "Load the config and verify traffic_server comes up correctly. \n",
    cmd_verify, true},
+  {"verify_global_plugin", "Verify a global plugin's shared object file",
+   "VERIFY_GLOBAL_PLUGIN\n"
+   "\n"
+   "FORMAT: verify_global_plugin [global_plugin_so_file]\n"
+   "\n"
+   "Load a global plugin's shared object file and verify it meets\n"
+   "minimal plugin API requirements. \n",
+   cmd_verify_global_plugin, false},
+  {"verify_remap_plugin", "Verify a remap plugin's shared object file",
+   "VERIFY_REMAP_PLUGIN\n"
+   "\n"
+   "FORMAT: verify_remap_plugin [remap_plugin_so_file]\n"
+   "\n"
+   "Load a remap plugin's shared object file and verify it meets\n"
+   "minimal plugin API requirements. \n",
+   cmd_verify_remap_plugin, false},
   {"help", "Obtain a short description of a command (e.g. 'help clear')",
    "HELP\n"
    "\n"
@@ -993,16 +1132,24 @@ find_cmd_index(const char *p)
   return -1;
 }
 
+/** Print the maintenance command help output.
+ */
+static void
+print_cmd_help()
+{
+  for (unsigned i = 0; i < countof(commands); i++) {
+    printf("%25s  %s\n", commands[i].n, commands[i].d);
+  }
+}
+
 static int
 cmd_help(char *cmd)
 {
   (void)cmd;
   printf("HELP\n\n");
-  cmd = skip(cmd, true);
+  cmd = skip(cmd);
   if (!cmd) {
-    for (unsigned i = 0; i < countof(commands); i++) {
-      printf("%15s  %s\n", commands[i].n, commands[i].d);
-    }
+    print_cmd_help();
   } else {
     int i;
     if ((i = find_cmd_index(cmd)) < 0) {
@@ -1045,6 +1192,10 @@ cmd_mode()
     return commands[command_index].f(command_string);
   } else if (*command_string) {
     Warning("unrecognized command: '%s'", command_string);
+    printf("\n");
+    printf("WARNING: Unrecognized command: '%s'\n", command_string);
+    printf("\n");
+    print_cmd_help();
     return CMD_FAILED; // in error
   } else {
     printf("\n");
diff --git a/src/tscore/ts_file.cc b/src/tscore/ts_file.cc
index c990adf..f77faae 100644
--- a/src/tscore/ts_file.cc
+++ b/src/tscore/ts_file.cc
@@ -100,6 +100,13 @@ namespace file
     return path();
   }
 
+  path
+  filename(const path &p)
+  {
+    const size_t last_slash_idx = p.string().find_last_of(p.preferred_separator);
+    return p.string().substr(last_slash_idx + 1);
+  }
+
   bool
   exists(const path &p)
   {
@@ -170,9 +177,8 @@ namespace file
     path final_to;
     file_status s = status(to, err);
     if (!(err && ENOENT == err.value()) && is_dir(s)) {
-      const size_t last_slash_idx = from.string().find_last_of(from.preferred_separator);
-      std::string filename        = from.string().substr(last_slash_idx + 1);
-      final_to                    = to / filename;
+      const auto file = filename(from);
+      final_to        = to / file;
     } else {
       final_to = to;
     }
diff --git a/src/tscore/unit_tests/test_ts_file.cc b/src/tscore/unit_tests/test_ts_file.cc
index 603ded3..b8f788f 100644
--- a/src/tscore/unit_tests/test_ts_file.cc
+++ b/src/tscore/unit_tests/test_ts_file.cc
@@ -222,6 +222,22 @@ TEST_CASE("ts_file::path::canonical", "[libts][fs_file]")
   CHECK_FALSE(ts::file::exists(testdir1));
 }
 
+TEST_CASE("ts_file::path::filename", "[libts][fs_file]")
+{
+  CHECK(ts::file::filename(path("/foo/bar.txt")) == path("bar.txt"));
+  CHECK(ts::file::filename(path("/foo/.bar")) == path(".bar"));
+  CHECK(ts::file::filename(path("/foo/bar")) == path("bar"));
+  CHECK(ts::file::filename(path("/foo/bar/")) == path(""));
+  CHECK(ts::file::filename(path("/foo/.")) == path("."));
+  CHECK(ts::file::filename(path("/foo/..")) == path(".."));
+  CHECK(ts::file::filename(path("/foo/../bar")) == path("bar"));
+  CHECK(ts::file::filename(path("/foo/../bar/")) == path(""));
+  CHECK(ts::file::filename(path(".")) == path("."));
+  CHECK(ts::file::filename(path("..")) == path(".."));
+  CHECK(ts::file::filename(path("/")) == path(""));
+  CHECK(ts::file::filename(path("//host")) == path("host"));
+}
+
 TEST_CASE("ts_file::path::copy", "[libts][fs_file]")
 {
   std::error_code ec;
@@ -259,4 +275,4 @@ TEST_CASE("ts_file::path::copy", "[libts][fs_file]")
   // Cleanup
   CHECK(ts::file::remove(testdir1, ec));
   CHECK_FALSE(ts::file::exists(testdir1));
-}
\ No newline at end of file
+}
diff --git a/tests/gold_tests/command_argument/verify_global_plugin.test.py b/tests/gold_tests/command_argument/verify_global_plugin.test.py
new file mode 100644
index 0000000..e7bfae3
--- /dev/null
+++ b/tests/gold_tests/command_argument/verify_global_plugin.test.py
@@ -0,0 +1,198 @@
+'''
+Test the verify_global_plugin TrafficServer command.
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+
+Test.Summary = '''
+Test that the TrafficServer verify_global_plugin command works as expected.
+'''
+
+process_counter = 0
+
+
+def create_ts_process():
+    """
+    Create a unique ATS process with each call to this function.
+    """
+    global process_counter
+    process_counter += 1
+
+    ts = Test.MakeATSProcess("ts{counter}".format(counter=process_counter))
+
+    # Ideally we would set the test run's Processes.Default to ts, but deep
+    # copy of processes is not currently implemented in autest. Therefore we
+    # replace the command which ts runs with a dummy command, and pull in
+    # piecemeal the values from ts that we want into the test run.
+    ts.Command = "sleep 100"
+    # sleep will return -2 when autest kills it. We set the expectation for the
+    # -2 return code here so the test doesn't fail because of this.
+    ts.ReturnCode = -2
+    # Clear the ready criteria because sleep is ready as soon as it is running.
+    ts.Ready = None
+    return ts
+
+
+"""
+TEST: verify_global_plugin should complain if an argument is not passed to it.
+"""
+tr = Test.AddTestRun("Verify the requirement of an argument")
+ts = create_ts_process()
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = "traffic_server -C 'verify_global_plugin'"
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: verifying a plugin requires a plugin SO file path argument",
+    "Should warn about the need for an SO file argument")
+
+
+"""
+TEST: verify_global_plugin should complain if the argument doesn't reference a shared
+object file.
+"""
+tr = Test.AddTestRun("Verify the requirement of a file")
+ts = create_ts_process()
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_global_plugin {filename}'".format(
+        filename="/this/file/does/not/exist.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*No such file or directory",
+    "Should warn about the non-existent SO file argument")
+
+
+"""
+TEST: verify_global_plugin should complain if the shared object file doesn't
+have the expected Plugin symbols.
+"""
+tr = Test.AddTestRun("Verify the requirement of our Plugin API.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'missing_ts_plugin_init.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_global_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/missing_ts_plugin_init.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*unable to find TSPluginInit function in",
+    "Should warn about the need for the TSPluginInit symbol")
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR",
+    "ERROR: .*unable to find TSPluginInit function in")
+
+
+"""
+TEST: Verify that passing a remap plugin produces a warning because
+it doesn't have the global plugin symbols.
+"""
+tr = Test.AddTestRun("Verify a properly formed plugin works as expected.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'conf_remap_stripped.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_global_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/conf_remap_stripped.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*unable to find TSPluginInit function in",
+    "Should warn about the need for the TSPluginInit symbol")
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR",
+    "ERROR: .*unable to find TSPluginInit function in")
+
+
+"""
+TEST: The happy case: a global plugin shared object file is passed as an
+argument that has the definition for the expected Plugin symbols.
+"""
+tr = Test.AddTestRun("Verify a properly formed plugin works as expected.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'ssl_hook_test.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_global_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/ssl_hook_test.so")
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "NOTE: verifying plugin '.*' Success",
+    "Verification should succeed")
+
+
+def prepare_undefined_symbol_plugin(tsproc, path_c, path_cpp, path_h):
+    """
+    Intentionally create an SO file with an undefined symbol.
+
+    We've seen issues where a plugin is created in which a C++ file
+    includes a function declaration and then expects a definition
+    of the mangled version of that function. However, the definition
+    was created with a c-compiler and thus is not mangled. This
+    builds a plugin with just such an undefined mangled symbol.
+    """
+    plugin_dir = tsproc.Env['PROXY_CONFIG_PLUGIN_PLUGIN_DIR']
+    tsproc.Setup.Copy(path_c, plugin_dir)
+    tsproc.Setup.Copy(path_cpp, plugin_dir)
+    tsproc.Setup.Copy(path_h, plugin_dir)
+
+    in_basename = os.path.basename(path_c)
+    out_basename = os.path.splitext(in_basename)[0] + '.so'
+    out_path = os.path.join(plugin_dir, out_basename)
+    tsproc.Setup.RunCommand(
+        ("gcc -c -fPIC {path_c} -o {path_c}_o; "
+            "g++ -c -fPIC {path_cpp} -o {path_cpp}_o; "
+            "g++ {path_c}_o {path_cpp}_o -shared -o {out_path}").format(
+                **locals())
+    )
+
+
+"""
+TEST: This is a regression test for a shared object file that doesn't have all
+of the required symbols defined because of a malformed interaction between C
+and C++ files.
+"""
+tr = Test.AddTestRun("Regression test for an undefined, mangled C++ symbol.")
+ts = create_ts_process()
+plugins_dir = os.path.join(Test.Variables.AtsTestToolsDir, 'plugins')
+prepare_undefined_symbol_plugin(
+    ts,
+    os.path.join(plugins_dir, 'missing_mangled_definition.c'),
+    os.path.join(plugins_dir, 'missing_mangled_definition.cc'),
+    os.path.join(plugins_dir, 'missing_mangled_definition.h'))
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_global_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/missing_mangled_definition.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*: undefined symbol: .*foo.*",
+    "Should warn about the need for the TSPluginInit symbol")
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR",
+    "ERROR: .*: undefined symbol: .*foo.*")
diff --git a/tests/gold_tests/command_argument/verify_remap_plugin.test.py b/tests/gold_tests/command_argument/verify_remap_plugin.test.py
new file mode 100644
index 0000000..004c9c8
--- /dev/null
+++ b/tests/gold_tests/command_argument/verify_remap_plugin.test.py
@@ -0,0 +1,146 @@
+'''
+Test the verify_remap_plugin TrafficServer command.
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+
+Test.Summary = '''
+Test that the TrafficServer verify_remap_plugin command works as expected.
+'''
+
+process_counter = 0
+
+
+def create_ts_process():
+    """
+    Create a unique ATS process with each call to this function.
+    """
+    global process_counter
+    process_counter += 1
+
+    ts = Test.MakeATSProcess("ts{counter}".format(counter=process_counter))
+
+    # Ideally we would set the test run's Processes.Default to ts, but deep
+    # copy of processes is not currently implemented in autest. Therefore we
+    # replace the command which ts runs with a dummy command, and pull in
+    # piecemeal the values from ts that we want into the test run.
+    ts.Command = "sleep 100"
+    # sleep will return -2 when autest kills it. We set the expectation for the
+    # -2 return code here so the test doesn't fail because of this.
+    ts.ReturnCode = -2
+    # Clear the ready criteria because sleep is ready as soon as it is running.
+    ts.Ready = None
+    return ts
+
+
+"""
+TEST: verify_remap_plugin should complain if an argument is not passed to it.
+"""
+tr = Test.AddTestRun("Verify the requirement of an argument")
+ts = create_ts_process()
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = "traffic_server -C 'verify_remap_plugin'"
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: verifying a plugin requires a plugin SO file path argument",
+    "Should warn about the need for an SO file argument")
+
+
+"""
+TEST: verify_remap_plugin should complain if the argument doesn't reference a shared
+object file.
+"""
+tr = Test.AddTestRun("Verify the requirement of a file")
+ts = create_ts_process()
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_remap_plugin {filename}'".format(
+        filename="/this/file/does/not/exist.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*No such file or directory",
+    "Should warn about the non-existent SO file argument")
+
+
+"""
+TEST: verify_remap_plugin should complain if the shared object file doesn't
+have the expected Plugin symbols.
+"""
+tr = Test.AddTestRun("Verify the requirement of our Plugin API.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'missing_ts_plugin_init.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_remap_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/missing_ts_plugin_init.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*missing required function TSRemapInit",
+    "Should warn about the need for the TSRemapInit symbol")
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR",
+    "ERROR: .*missing required function TSRemapInit")
+
+
+"""
+TEST: verify_remap_plugin should complain if the plugin has the global
+plugin symbols but not the remap ones.
+"""
+tr = Test.AddTestRun("Verify a global plugin argument produces warning.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'ssl_hook_test.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_remap_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/ssl_hook_test.so")
+tr.Processes.Default.ReturnCode = 1
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "ERROR: .*missing required function TSRemapInit",
+    "Should warn about the need for the TSRemapInit symbol")
+ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR",
+    "ERROR: .*missing required function TSRemapInit")
+
+
+"""
+TEST: The happy case: a remap plugin shared object file is passed as an
+argument that has the definition for the expected Plugin symbols.
+"""
+tr = Test.AddTestRun("Verify a properly formed plugin works as expected.")
+ts = create_ts_process()
+Test.PreparePlugin(
+    os.path.join(Test.Variables.AtsTestToolsDir,
+                 'plugins', 'conf_remap_stripped.cc'),
+    ts)
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = \
+    "traffic_server -C 'verify_remap_plugin {filename}'".format(
+        filename="${PROXY_CONFIG_PLUGIN_PLUGIN_DIR}/conf_remap_stripped.so")
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.StartBefore(ts)
+tr.Processes.Default.Streams.stderr = Testers.ContainsExpression(
+    "NOTE: verifying plugin '.*' Success",
+    "Verification should succeed")
diff --git a/tests/tools/plugins/conf_remap_stripped.cc b/tests/tools/plugins/conf_remap_stripped.cc
new file mode 100644
index 0000000..5d80642
--- /dev/null
+++ b/tests/tools/plugins/conf_remap_stripped.cc
@@ -0,0 +1,49 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+
+///////////////////////////////////////////////////////////////////////////////
+// Initialize the plugin as a remap plugin.
+//
+TSReturnCode
+TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
+{
+  return TS_SUCCESS; /* success */
+}
+
+TSReturnCode
+TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf ATS_UNUSED */, int
/* errbuf_size ATS_UNUSED */)
+{
+  return TS_ERROR;
+}
+
+void
+TSRemapDeleteInstance(void *ih)
+{
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Main entry point when used as a remap plugin.
+//
+TSRemapStatus
+TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo * /* rri ATS_UNUSED */)
+{
+  return TSREMAP_NO_REMAP;
+}
diff --git a/tests/tools/plugins/missing_mangled_definition.c b/tests/tools/plugins/missing_mangled_definition.c
new file mode 100644
index 0000000..4c62e2a
--- /dev/null
+++ b/tests/tools/plugins/missing_mangled_definition.c
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "missing_mangled_definition.h"
+
+void
+foo()
+{
+  return;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  foo();
+}
diff --git a/tests/tools/plugins/missing_mangled_definition.cc b/tests/tools/plugins/missing_mangled_definition.cc
new file mode 100644
index 0000000..02c8339
--- /dev/null
+++ b/tests/tools/plugins/missing_mangled_definition.cc
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "missing_mangled_definition.h"
+
+/*
+ * Assume the definition of foo from the corresponding .c
+ * object. This will result in an undefined symbol, however,
+ * because the .c file will be compiled with a C compiler
+ * while this will be compiled with a C++ compiler such that
+ * it will expect a definition for a mangled foo symbol.
+ */
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  foo();
+}
diff --git a/tests/tools/plugins/missing_mangled_definition.h b/tests/tools/plugins/missing_mangled_definition.h
new file mode 100644
index 0000000..161ee1b
--- /dev/null
+++ b/tests/tools/plugins/missing_mangled_definition.h
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+void foo();
diff --git a/tests/tools/plugins/missing_ts_plugin_init.cc b/tests/tools/plugins/missing_ts_plugin_init.cc
new file mode 100644
index 0000000..3aa761b
--- /dev/null
+++ b/tests/tools/plugins/missing_ts_plugin_init.cc
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This simple file will be compiled to a supposed Plugin object that is
+ * missing the TSPluginInit function.
+ */
+
+void
+foo()
+{
+  return;
+}


Mime
View raw message