trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jpe...@apache.org
Subject trafficserver git commit: TS-4006 - Promote the healthchecks plugin out of the experimental plugins
Date Tue, 10 Nov 2015 06:16:57 GMT
Repository: trafficserver
Updated Branches:
  refs/heads/master a8804a209 -> c7610467e


TS-4006 - Promote the healthchecks plugin out of the experimental plugins

This closes #331.


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

Branch: refs/heads/master
Commit: c7610467e9604b1d6c4ce933109030499057ae8b
Parents: a8804a2
Author: Steven Feltner <sfeltner@godaddy.com>
Authored: Mon Nov 9 21:55:20 2015 -0700
Committer: James Peach <jpeach@apache.org>
Committed: Mon Nov 9 22:16:00 2015 -0800

----------------------------------------------------------------------
 configure.ac                                    |   2 +-
 doc/admin-guide/plugins/healthchecks.en.rst     |  63 ++
 doc/admin-guide/plugins/index.en.rst            |   1 +
 plugins/Makefile.am                             |   1 +
 plugins/experimental/Makefile.am                |   1 -
 plugins/experimental/healthchecks/Makefile.am   |  25 -
 plugins/experimental/healthchecks/README        |  21 -
 .../experimental/healthchecks/healthchecks.c    | 572 -------------------
 plugins/healthchecks/Makefile.am                |  25 +
 plugins/healthchecks/README                     |  21 +
 plugins/healthchecks/healthchecks.c             | 572 +++++++++++++++++++
 11 files changed, 684 insertions(+), 620 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index b341efa..f3d7283 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1853,6 +1853,7 @@ AC_CONFIG_FILES([
   plugins/conf_remap/Makefile
   plugins/gzip/Makefile
   plugins/header_rewrite/Makefile
+  plugins/healthchecks/Makefile
   plugins/libloader/Makefile
   plugins/regex_remap/Makefile
   plugins/stats_over_http/Makefile
@@ -1905,7 +1906,6 @@ AS_IF([test "x$enable_experimental_plugins" = "xyes"], [
     plugins/experimental/generator/Makefile
     plugins/experimental/geoip_acl/Makefile
     plugins/experimental/header_normalize/Makefile
-    plugins/experimental/healthchecks/Makefile
     plugins/experimental/hipes/Makefile
     plugins/experimental/memcached_remap/Makefile
     plugins/experimental/metalink/Makefile

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/doc/admin-guide/plugins/healthchecks.en.rst
----------------------------------------------------------------------
diff --git a/doc/admin-guide/plugins/healthchecks.en.rst b/doc/admin-guide/plugins/healthchecks.en.rst
new file mode 100644
index 0000000..52b5d35
--- /dev/null
+++ b/doc/admin-guide/plugins/healthchecks.en.rst
@@ -0,0 +1,63 @@
+.. _healthcheck-plugin:
+
+Health Check Plugin
+**************
+
+.. 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 is a simple plugin, to provide basic (but configurable) health checks.
+This is a server intercept plugin, and it takes one single configuration
+option in plugin.config, the configuration file name.
+
+Configuration
+=============
+To enable the healthchecks plugin, insert the following line in
+:file:`plugin.config`::
+
+    healthchecks.so <healthcheck-configuration-file>
+
+The required ``<healthcheck-configuration-file>`` may reference either an
+absolute or relative path to the file containing the healthcheck configuration.
+
+This configuration may contain one or more lines of the format::
+
+   <URI-path> <file-path> <mime> <file-exists-code> <file-missing-code>
+
+.. note:: The ``URI-path`` can *not* be "/" only.
+
+.. note:: This configuration is *not* reloadable.
+
+The content of the file specified in the ``file-path``, if any, is sent as the 
+body of the response. The existence of the file is sufficient to get an "OK"
+status.  Performance wise, everything is served out of memory, and it only
+stats / opens files as necessary. However, the content of the status file is
+limited to 16KB, so this is not a generic static file serving plugin.
+
+Example
+=======
+
+This line would define a health check link available at
+http://www.example.com/__hc that would check if the file
+``/var/run/ts-alive`` existed on the server.  If the file exists,
+a response is built with the contents of the ``ts-alive`` file, a mime
+type of ``text/plain`` and a status code of ``200``.  If the file does not
+exist, a ``403`` response is sent::
+   /__hc  /var/run/ts-alive  text/plain 200  403
+
+
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/doc/admin-guide/plugins/index.en.rst
----------------------------------------------------------------------
diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst
index 0b1bce6..25dcadb 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -49,6 +49,7 @@ Plugins that are considered stable are installed by default in |TS| releases.
   Configuration Remap: allows you to override configuration directives dependent on actual remapping rules <conf_remap.en>
   GZip: gzips or deflates responses <gzip.en>
   Header Rewrite: allows you to modify various headers based on defined rules (operations) on a request or response <header_rewrite.en>
+  Health Checks: allows you to define health check links <healthchecks.en>
   Regex Remap: allows you to configure mapping rules based on regular expressions <regex_remap.en>
   Stats over HTTP: implements an HTTP interface to all Traffic Server statistics <stats_over_http.en>
   TCPInfo: logs TCP metrics at various points in the HTTP processing pipeline <tcpinfo.en>

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index f2912d0..0c34f82 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -21,6 +21,7 @@ SUBDIRS = \
   conf_remap \
   gzip \
   header_rewrite \
+  healthchecks \
   libloader \
   regex_remap \
   stats_over_http \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index 0ec2e37..cfef4a3 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -29,7 +29,6 @@ SUBDIRS = \
  generator \
  geoip_acl \
  header_normalize \
- healthchecks \
  hipes \
  metalink \
  multiplexer \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/experimental/healthchecks/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/healthchecks/Makefile.am b/plugins/experimental/healthchecks/Makefile.am
deleted file mode 100644
index 9af3a5e..0000000
--- a/plugins/experimental/healthchecks/Makefile.am
+++ /dev/null
@@ -1,25 +0,0 @@
-#  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 $(top_srcdir)/build/plugins.mk
-
-if BUILD_HEALTHCHECK_PLUGIN
-
-pkglib_LTLIBRARIES = healthchecks.la
-healthchecks_la_SOURCES = healthchecks.c
-healthchecks_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
-
-endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/experimental/healthchecks/README
----------------------------------------------------------------------
diff --git a/plugins/experimental/healthchecks/README b/plugins/experimental/healthchecks/README
deleted file mode 100644
index e3e9791..0000000
--- a/plugins/experimental/healthchecks/README
+++ /dev/null
@@ -1,21 +0,0 @@
-This is a simple plugin, to provide basic (but configurable) health checks.
-This is a server intercept plugin, and it takes one single  configuration
-option in plugin.config, the configuration file name. 
-
-This configuration contains one, or several, lines of the format
-
-   <URI-path> <file-path> <mime> <file-exists-code> <file-missing-code>
-
-The URI-path can *not* be "/" only.
-
-
-Examples:
-
-   /__hc  /var/run/ts-alive  text/plain 200  403
-
-
-The content of the file, if any, is sent as the body of the response. The
-existence of the file is sufficient to get an "OK" status. Performance wise,
-everything is served out of memory, and it only stats / opens files as
-necessary. However, the content of the status file is limited to 16KB, so
-this is not a generic static file serving plugin.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/experimental/healthchecks/healthchecks.c
----------------------------------------------------------------------
diff --git a/plugins/experimental/healthchecks/healthchecks.c b/plugins/experimental/healthchecks/healthchecks.c
deleted file mode 100644
index b6f262f..0000000
--- a/plugins/experimental/healthchecks/healthchecks.c
+++ /dev/null
@@ -1,572 +0,0 @@
-/** @file
-
-This is an origin server / intercept plugin, which implements flexible health checks.
-
-@section license
-
-Licensed 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 <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <limits.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <inttypes.h>
-
-/* ToDo: Linux specific */
-#include <sys/inotify.h>
-#include <libgen.h>
-
-#include "ts/ts.h"
-#include "ts/ink_platform.h"
-#include "ts/ink_defs.h"
-
-static const char PLUGIN_NAME[] = "healthchecks";
-static const char SEPARATORS[] = " \t\n";
-
-#define MAX_PATH_LEN 4096
-#define MAX_BODY_LEN 16384
-#define FREELIST_TIMEOUT 300
-
-/* Some atomic stuff, from ATS core */
-typedef volatile void *vvoidp;
-
-static inline void *
-ink_atomic_swap_ptr(vvoidp mem, void *value)
-{
-  return __sync_lock_test_and_set((void **)mem, value);
-}
-
-/* Directories that we are watching for inotify IN_CREATE events. */
-typedef struct HCDirEntry_t {
-  char dname[MAX_PATH_LEN];   /* Directory name */
-  int wd;                     /* Watch descriptor */
-  struct HCDirEntry_t *_next; /* Linked list */
-} HCDirEntry;
-
-/* Information about a status file. This is never modified (only replaced, see HCFileInfo_t) */
-typedef struct HCFileData_t {
-  int exists;                 /* Does this file exist */
-  char body[MAX_BODY_LEN];    /* Body from fname. NULL means file is missing */
-  int b_len;                  /* Length of data */
-  time_t remove;              /* Used for deciding when the old object can be permanently removed */
-  struct HCFileData_t *_next; /* Only used when these guys end up on the freelist */
-} HCFileData;
-
-/* The only thing that should change in this struct is data, atomically swapping ptrs */
-typedef struct HCFileInfo_t {
-  char fname[MAX_PATH_LEN];   /* Filename */
-  char *basename;             /* The "basename" of the file */
-  char path[PATH_NAME_MAX];   /* URL path for this HC */
-  int p_len;                  /* Length of path */
-  const char *ok;             /* Header for an OK result */
-  int o_len;                  /* Length of OK header */
-  const char *miss;           /* Header for miss results */
-  int m_len;                  /* Length of miss header */
-  HCFileData *data;           /* Holds the current data for this health check file */
-  int wd;                     /* Watch descriptor */
-  HCDirEntry *dir;            /* Reference to the directory this file resides in */
-  struct HCFileInfo_t *_next; /* Linked list */
-} HCFileInfo;
-
-/* Global configuration */
-HCFileInfo *g_config;
-
-/* State used for the intercept plugin. ToDo: Can this be improved ? */
-typedef struct HCState_t {
-  TSVConn net_vc;
-  TSVIO read_vio;
-  TSVIO write_vio;
-
-  TSIOBuffer req_buffer;
-  TSIOBuffer resp_buffer;
-  TSIOBufferReader resp_reader;
-
-  int output_bytes;
-
-  /* We actually need both here, so that our lock free switches works safely */
-  HCFileInfo *info;
-  HCFileData *data;
-} HCState;
-
-/* Read / check the status files */
-static void
-reload_status_file(HCFileInfo *info, HCFileData *data)
-{
-  FILE *fd;
-
-  memset(data, 0, sizeof(HCFileData));
-  if (NULL != (fd = fopen(info->fname, "r"))) {
-    data->exists = 1;
-    do {
-      data->b_len = fread(data->body, 1, MAX_BODY_LEN, fd);
-    } while (!feof(fd)); /*  Only save the last 16KB of the file ... */
-    fclose(fd);
-  }
-}
-
-/* Find a HCDirEntry from the linked list */
-static HCDirEntry *
-find_direntry(const char *dname, HCDirEntry *dir)
-{
-  while (dir) {
-    if (!strncmp(dname, dir->dname, MAX_PATH_LEN))
-      return dir;
-    dir = dir->_next;
-  }
-  return NULL;
-}
-
-/* Setup up watchers, directory as well as initial files */
-static HCDirEntry *
-setup_watchers(int fd)
-{
-  HCFileInfo *conf = g_config;
-  HCDirEntry *head_dir = NULL, *last_dir = NULL, *dir;
-  char fname[MAX_PATH_LEN];
-  char *dname;
-
-  while (conf) {
-    conf->wd = inotify_add_watch(fd, conf->fname, IN_DELETE_SELF | IN_CLOSE_WRITE | IN_ATTRIB);
-    TSDebug(PLUGIN_NAME, "Setting up a watcher for %s", conf->fname);
-    strncpy(fname, conf->fname, MAX_PATH_LEN - 1);
-    dname = dirname(fname);
-    /* Make sure to only watch each directory once */
-    if (!(dir = find_direntry(dname, head_dir))) {
-      TSDebug(PLUGIN_NAME, "Setting up a watcher for directory %s", dname);
-      dir = TSmalloc(sizeof(HCDirEntry));
-      memset(dir, 0, sizeof(HCDirEntry));
-      strncpy(dir->dname, dname, MAX_PATH_LEN - 1);
-      dir->wd = inotify_add_watch(fd, dname, IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB);
-      if (!head_dir)
-        head_dir = dir;
-      else
-        last_dir->_next = dir;
-      last_dir = dir;
-    }
-    conf->dir = dir;
-    conf = conf->_next;
-  }
-
-  return head_dir;
-}
-
-/* Separate thread to monitor status files for reload */
-#define INOTIFY_BUFLEN (1024 * sizeof(struct inotify_event))
-
-static void *
-hc_thread(void *data ATS_UNUSED)
-{
-  int fd = inotify_init();
-  HCDirEntry *dirs;
-  int len;
-  HCFileData *fl_head = NULL;
-  char buffer[INOTIFY_BUFLEN];
-  struct timeval last_free, now;
-
-  gettimeofday(&last_free, NULL);
-
-  /* Setup watchers for the directories, these are a one time setup */
-  setup_watchers(fd); // This is a leak, but since we enter an infinite loop this is ok?
-
-  while (1) {
-    HCFileData *fdata = fl_head, *fdata_prev = NULL;
-
-    /* Read the inotify events, blocking until we get something */
-    len = read(fd, buffer, INOTIFY_BUFLEN);
-    gettimeofday(&now, NULL);
-
-    /* The fl_head is a linked list of previously released data entries. They
-       are ordered "by time", so once we find one that is scheduled for deletion,
-       we can also delete all entries after it in the linked list. */
-    while (fdata) {
-      if (now.tv_sec > fdata->remove) {
-        /* Now drop off the "tail" from the freelist */
-        if (fdata_prev)
-          fdata_prev->_next = NULL;
-        else
-          fl_head = NULL;
-
-        /* free() everything in the "tail" */
-        do {
-          HCFileData *next = fdata->_next;
-
-          TSDebug(PLUGIN_NAME, "Cleaning up entry from frelist");
-          TSfree(fdata);
-          fdata = next;
-        } while (fdata);
-        break; /* Stop the loop, there's nothing else left to examine */
-      }
-      fdata_prev = fdata;
-      fdata = fdata->_next;
-    }
-
-    if (len >= 0) {
-      int i = 0;
-
-      while (i < len) {
-        struct inotify_event *event = (struct inotify_event *)&buffer[i];
-        HCFileInfo *finfo = g_config;
-
-        while (
-          finfo &&
-          !((event->wd == finfo->wd) || ((event->wd == finfo->dir->wd) && !strncmp(event->name, finfo->basename, event->len)))) {
-          finfo = finfo->_next;
-        }
-        if (finfo) {
-          HCFileData *new_data = TSmalloc(sizeof(HCFileData));
-          HCFileData *old_data;
-
-          if (event->mask & (IN_CLOSE_WRITE | IN_ATTRIB)) {
-            TSDebug(PLUGIN_NAME, "Modify file event (%d) on %s", event->mask, finfo->fname);
-          } else if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
-            TSDebug(PLUGIN_NAME, "Create file event (%d) on %s", event->mask, finfo->fname);
-            finfo->wd = inotify_add_watch(fd, finfo->fname, IN_DELETE_SELF | IN_CLOSE_WRITE | IN_ATTRIB);
-          } else if (event->mask & (IN_DELETE_SELF | IN_MOVED_FROM)) {
-            TSDebug(PLUGIN_NAME, "Delete file event (%d) on %s", event->mask, finfo->fname);
-            finfo->wd = inotify_rm_watch(fd, finfo->wd);
-          }
-          /* Load the new data and then swap this atomically */
-          memset(new_data, 0, sizeof(HCFileData));
-          reload_status_file(finfo, new_data);
-          TSDebug(PLUGIN_NAME, "Reloaded %s, len == %d, exists == %d", finfo->fname, new_data->b_len, new_data->exists);
-          old_data = ink_atomic_swap_ptr(&(finfo->data), new_data);
-
-          /* Add the old data to the head of the freelist */
-          old_data->remove = now.tv_sec + FREELIST_TIMEOUT;
-          old_data->_next = fl_head;
-          fl_head = old_data;
-        }
-        i += sizeof(struct inotify_event) + event->len;
-      }
-    }
-  }
-
-  /* Cleanup, in case we later exit this thread ... */
-  while (dirs) {
-    HCDirEntry *d = dirs;
-
-    dirs = dirs->_next;
-    TSfree(d);
-  }
-
-  return NULL; /* Yeah, that never happens */
-}
-
-/* Config file parsing */
-static const char HEADER_TEMPLATE[] = "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nCache-Control: no-cache\r\n";
-
-static char *
-gen_header(char *status_str, char *mime, int *header_len)
-{
-  TSHttpStatus status;
-  char *buf = NULL;
-
-  status = atoi(status_str);
-  if (status > TS_HTTP_STATUS_NONE && status < (TSHttpStatus)999) {
-    const char *status_reason;
-    int len = sizeof(HEADER_TEMPLATE) + 3 + 1;
-
-    status_reason = TSHttpHdrReasonLookup(status);
-    len += strlen(status_reason);
-    len += strlen(mime);
-    buf = TSmalloc(len);
-    *header_len = snprintf(buf, len, HEADER_TEMPLATE, status, status_reason, mime);
-  } else {
-    *header_len = 0;
-  }
-
-  return buf;
-}
-
-static HCFileInfo *
-parse_configs(const char *fname)
-{
-  FILE *fd;
-  char buf[2 * 1024];
-  HCFileInfo *head_finfo = NULL, *finfo = NULL, *prev_finfo = NULL;
-
-  if (NULL == (fd = fopen(fname, "r")))
-    return NULL;
-
-  while (!feof(fd)) {
-    char *str, *save;
-    int state = 0;
-    char *ok = NULL, *miss = NULL, *mime = NULL;
-
-    finfo = TSmalloc(sizeof(HCFileInfo));
-    memset(finfo, 0, sizeof(HCFileInfo));
-
-    if (fgets(buf, sizeof(buf) - 1, fd)) {
-      str = strtok_r(buf, SEPARATORS, &save);
-      while (NULL != str) {
-        if (strlen(str) > 0) {
-          switch (state) {
-          case 0:
-            if ('/' == *str)
-              ++str;
-            strncpy(finfo->path, str, PATH_NAME_MAX - 1);
-            finfo->p_len = strlen(finfo->path);
-            break;
-          case 1:
-            strncpy(finfo->fname, str, MAX_PATH_LEN - 1);
-            finfo->basename = strrchr(finfo->fname, '/');
-            if (finfo->basename)
-              ++(finfo->basename);
-            break;
-          case 2:
-            mime = str;
-            break;
-          case 3:
-            ok = str;
-            break;
-          case 4:
-            miss = str;
-            break;
-          }
-          ++state;
-        }
-        str = strtok_r(NULL, SEPARATORS, &save);
-      }
-
-      /* Fill in the info if everything was ok */
-      if (state > 4) {
-        TSDebug(PLUGIN_NAME, "Parsed: %s %s %s %s %s", finfo->path, finfo->fname, mime, ok, miss);
-        finfo->ok = gen_header(ok, mime, &finfo->o_len);
-        finfo->miss = gen_header(miss, mime, &finfo->m_len);
-        finfo->data = TSmalloc(sizeof(HCFileData));
-        memset(finfo->data, 0, sizeof(HCFileData));
-        reload_status_file(finfo, finfo->data);
-
-        /* Add it the linked list */
-        TSDebug(PLUGIN_NAME, "Adding path=%s to linked list", finfo->path);
-        if (NULL == head_finfo) {
-          head_finfo = finfo;
-        } else {
-          prev_finfo->_next = finfo;
-        }
-        prev_finfo = finfo;
-      } else {
-        TSfree(finfo);
-      }
-    }
-  }
-  fclose(fd);
-
-  return head_finfo;
-}
-
-/* Cleanup after intercept has completed */
-static void
-cleanup(TSCont contp, HCState *my_state)
-{
-  if (my_state->req_buffer) {
-    TSIOBufferDestroy(my_state->req_buffer);
-    my_state->req_buffer = NULL;
-  }
-
-  if (my_state->resp_buffer) {
-    TSIOBufferDestroy(my_state->resp_buffer);
-    my_state->resp_buffer = NULL;
-  }
-
-  TSVConnClose(my_state->net_vc);
-  TSfree(my_state);
-  TSContDestroy(contp);
-}
-
-/* Add data to the output */
-inline static int
-add_data_to_resp(const char *buf, int len, HCState *my_state)
-{
-  TSIOBufferWrite(my_state->resp_buffer, buf, len);
-  return len;
-}
-
-/* Process a read event from the SM */
-static void
-hc_process_read(TSCont contp, TSEvent event, HCState *my_state)
-{
-  if (event == TS_EVENT_VCONN_READ_READY) {
-    if (my_state->data->exists) {
-      TSDebug(PLUGIN_NAME, "Setting OK response header");
-      my_state->output_bytes = add_data_to_resp(my_state->info->ok, my_state->info->o_len, my_state);
-    } else {
-      TSDebug(PLUGIN_NAME, "Setting MISS response header");
-      my_state->output_bytes = add_data_to_resp(my_state->info->miss, my_state->info->m_len, my_state);
-    }
-    TSVConnShutdown(my_state->net_vc, 1, 0);
-    my_state->write_vio = TSVConnWrite(my_state->net_vc, contp, my_state->resp_reader, INT64_MAX);
-  } else if (event == TS_EVENT_ERROR) {
-    TSError("[healthchecks] hc_process_read: Received TS_EVENT_ERROR");
-  } else if (event == TS_EVENT_VCONN_EOS) {
-    /* client may end the connection, simply return */
-    return;
-  } else if (event == TS_EVENT_NET_ACCEPT_FAILED) {
-    TSError("[healthchecks] hc_process_read: Received TS_EVENT_NET_ACCEPT_FAILED");
-  } else {
-    TSReleaseAssert(!"Unexpected Event");
-  }
-}
-
-/* Process a write event from the SM */
-static void
-hc_process_write(TSCont contp, TSEvent event, HCState *my_state)
-{
-  if (event == TS_EVENT_VCONN_WRITE_READY) {
-    char buf[48];
-    int len;
-
-    len = snprintf(buf, sizeof(buf) - 1, "Content-Length: %d\r\n\r\n", my_state->data->b_len);
-    my_state->output_bytes += add_data_to_resp(buf, len, my_state);
-    if (my_state->data->b_len > 0)
-      my_state->output_bytes += add_data_to_resp(my_state->data->body, my_state->data->b_len, my_state);
-    else
-      my_state->output_bytes += add_data_to_resp("\r\n", 2, my_state);
-    TSVIONBytesSet(my_state->write_vio, my_state->output_bytes);
-    TSVIOReenable(my_state->write_vio);
-  } else if (TS_EVENT_VCONN_WRITE_COMPLETE) {
-    cleanup(contp, my_state);
-  } else if (event == TS_EVENT_ERROR) {
-    TSError("[healthchecks] hc_process_write: Received TS_EVENT_ERROR");
-  } else {
-    TSReleaseAssert(!"Unexpected Event");
-  }
-}
-
-/* Process the accept event from the SM */
-static void
-hc_process_accept(TSCont contp, HCState *my_state)
-{
-  my_state->req_buffer = TSIOBufferCreate();
-  my_state->resp_buffer = TSIOBufferCreate();
-  my_state->resp_reader = TSIOBufferReaderAlloc(my_state->resp_buffer);
-  my_state->read_vio = TSVConnRead(my_state->net_vc, contp, my_state->req_buffer, INT64_MAX);
-}
-
-/* Imlement the server intercept */
-static int
-hc_intercept(TSCont contp, TSEvent event, void *edata)
-{
-  HCState *my_state = TSContDataGet(contp);
-
-  if (event == TS_EVENT_NET_ACCEPT) {
-    my_state->net_vc = (TSVConn)edata;
-    hc_process_accept(contp, my_state);
-  } else if (edata == my_state->read_vio) { /* All read events */
-    hc_process_read(contp, event, my_state);
-  } else if (edata == my_state->write_vio) { /* All write events */
-    hc_process_write(contp, event, my_state);
-  } else {
-    TSReleaseAssert(!"Unexpected Event");
-  }
-
-  return 0;
-}
-
-/* Read-request header continuation, used to kick off the server intercept if necessary */
-static int
-health_check_origin(TSCont contp ATS_UNUSED, TSEvent event ATS_UNUSED, void *edata)
-{
-  TSMBuffer reqp;
-  TSMLoc hdr_loc = NULL, url_loc = NULL;
-  TSCont icontp;
-  HCState *my_state;
-  TSHttpTxn txnp = (TSHttpTxn)edata;
-  HCFileInfo *info = g_config;
-
-  if ((TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc)) && (TS_SUCCESS == TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc))) {
-    int path_len = 0;
-    const char *path = TSUrlPathGet(reqp, url_loc, &path_len);
-
-    /* Short circuit the / path, common case, and we won't allow healthecks on / */
-    if (!path || !path_len)
-      goto cleanup;
-
-    while (info) {
-      if (info->p_len == path_len && !memcmp(info->path, path, path_len)) {
-        TSDebug(PLUGIN_NAME, "Found match for /%.*s", path_len, path);
-        break;
-      }
-      info = info->_next;
-    }
-
-    if (!info)
-      goto cleanup;
-
-    TSSkipRemappingSet(txnp, 1); /* not strictly necessary, but speed is everything these days */
-
-    /* This is us -- register our intercept */
-    icontp = TSContCreate(hc_intercept, TSMutexCreate());
-    my_state = (HCState *)TSmalloc(sizeof(*my_state));
-    memset(my_state, 0, sizeof(*my_state));
-    my_state->info = info;
-    my_state->data = info->data;
-    TSContDataSet(icontp, my_state);
-    TSHttpTxnIntercept(icontp, txnp);
-  }
-
-cleanup:
-  if (url_loc)
-    TSHandleMLocRelease(reqp, hdr_loc, url_loc);
-  if (hdr_loc)
-    TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
-
-  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
-
-  return 0;
-}
-
-/* Initialize the plugin / global continuation hook */
-void
-TSPluginInit(int argc, const char *argv[])
-{
-  TSPluginRegistrationInfo info;
-
-  if (2 != argc) {
-    TSError("[healthchecks] Must specify a configuration file.");
-    return;
-  }
-
-  info.plugin_name = "health_checks";
-  info.vendor_name = "Apache Software Foundation";
-  info.support_email = "dev@trafficserver.apache.org";
-
-  if (TS_SUCCESS != TSPluginRegister(&info)) {
-    TSError("[healthchecks] Plugin registration failed.");
-    return;
-  }
-
-  /* This will update the global configuration file, and is not reloaded at run time */
-  /* ToDo: Support reloading with traffic_ctl config reload ? */
-  if (NULL == (g_config = parse_configs(argv[1]))) {
-    TSError("[healthchecks] Unable to read / parse %s config file", argv[1]);
-    return;
-  }
-
-  /* Setup the background thread */
-  if (!TSThreadCreate(hc_thread, NULL)) {
-    TSError("[healthchecks] Failure in thread creation");
-    return;
-  }
-
-  /* Create a continuation with a mutex as there is a shared global structure
-     containing the headers to add */
-  TSDebug(PLUGIN_NAME, "Started %s plugin", PLUGIN_NAME);
-  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(health_check_origin, NULL));
-}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/healthchecks/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/healthchecks/Makefile.am b/plugins/healthchecks/Makefile.am
new file mode 100644
index 0000000..9af3a5e
--- /dev/null
+++ b/plugins/healthchecks/Makefile.am
@@ -0,0 +1,25 @@
+#  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 $(top_srcdir)/build/plugins.mk
+
+if BUILD_HEALTHCHECK_PLUGIN
+
+pkglib_LTLIBRARIES = healthchecks.la
+healthchecks_la_SOURCES = healthchecks.c
+healthchecks_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
+
+endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/healthchecks/README
----------------------------------------------------------------------
diff --git a/plugins/healthchecks/README b/plugins/healthchecks/README
new file mode 100644
index 0000000..e3e9791
--- /dev/null
+++ b/plugins/healthchecks/README
@@ -0,0 +1,21 @@
+This is a simple plugin, to provide basic (but configurable) health checks.
+This is a server intercept plugin, and it takes one single  configuration
+option in plugin.config, the configuration file name. 
+
+This configuration contains one, or several, lines of the format
+
+   <URI-path> <file-path> <mime> <file-exists-code> <file-missing-code>
+
+The URI-path can *not* be "/" only.
+
+
+Examples:
+
+   /__hc  /var/run/ts-alive  text/plain 200  403
+
+
+The content of the file, if any, is sent as the body of the response. The
+existence of the file is sufficient to get an "OK" status. Performance wise,
+everything is served out of memory, and it only stats / opens files as
+necessary. However, the content of the status file is limited to 16KB, so
+this is not a generic static file serving plugin.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c7610467/plugins/healthchecks/healthchecks.c
----------------------------------------------------------------------
diff --git a/plugins/healthchecks/healthchecks.c b/plugins/healthchecks/healthchecks.c
new file mode 100644
index 0000000..b6f262f
--- /dev/null
+++ b/plugins/healthchecks/healthchecks.c
@@ -0,0 +1,572 @@
+/** @file
+
+This is an origin server / intercept plugin, which implements flexible health checks.
+
+@section license
+
+Licensed 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 <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+/* ToDo: Linux specific */
+#include <sys/inotify.h>
+#include <libgen.h>
+
+#include "ts/ts.h"
+#include "ts/ink_platform.h"
+#include "ts/ink_defs.h"
+
+static const char PLUGIN_NAME[] = "healthchecks";
+static const char SEPARATORS[] = " \t\n";
+
+#define MAX_PATH_LEN 4096
+#define MAX_BODY_LEN 16384
+#define FREELIST_TIMEOUT 300
+
+/* Some atomic stuff, from ATS core */
+typedef volatile void *vvoidp;
+
+static inline void *
+ink_atomic_swap_ptr(vvoidp mem, void *value)
+{
+  return __sync_lock_test_and_set((void **)mem, value);
+}
+
+/* Directories that we are watching for inotify IN_CREATE events. */
+typedef struct HCDirEntry_t {
+  char dname[MAX_PATH_LEN];   /* Directory name */
+  int wd;                     /* Watch descriptor */
+  struct HCDirEntry_t *_next; /* Linked list */
+} HCDirEntry;
+
+/* Information about a status file. This is never modified (only replaced, see HCFileInfo_t) */
+typedef struct HCFileData_t {
+  int exists;                 /* Does this file exist */
+  char body[MAX_BODY_LEN];    /* Body from fname. NULL means file is missing */
+  int b_len;                  /* Length of data */
+  time_t remove;              /* Used for deciding when the old object can be permanently removed */
+  struct HCFileData_t *_next; /* Only used when these guys end up on the freelist */
+} HCFileData;
+
+/* The only thing that should change in this struct is data, atomically swapping ptrs */
+typedef struct HCFileInfo_t {
+  char fname[MAX_PATH_LEN];   /* Filename */
+  char *basename;             /* The "basename" of the file */
+  char path[PATH_NAME_MAX];   /* URL path for this HC */
+  int p_len;                  /* Length of path */
+  const char *ok;             /* Header for an OK result */
+  int o_len;                  /* Length of OK header */
+  const char *miss;           /* Header for miss results */
+  int m_len;                  /* Length of miss header */
+  HCFileData *data;           /* Holds the current data for this health check file */
+  int wd;                     /* Watch descriptor */
+  HCDirEntry *dir;            /* Reference to the directory this file resides in */
+  struct HCFileInfo_t *_next; /* Linked list */
+} HCFileInfo;
+
+/* Global configuration */
+HCFileInfo *g_config;
+
+/* State used for the intercept plugin. ToDo: Can this be improved ? */
+typedef struct HCState_t {
+  TSVConn net_vc;
+  TSVIO read_vio;
+  TSVIO write_vio;
+
+  TSIOBuffer req_buffer;
+  TSIOBuffer resp_buffer;
+  TSIOBufferReader resp_reader;
+
+  int output_bytes;
+
+  /* We actually need both here, so that our lock free switches works safely */
+  HCFileInfo *info;
+  HCFileData *data;
+} HCState;
+
+/* Read / check the status files */
+static void
+reload_status_file(HCFileInfo *info, HCFileData *data)
+{
+  FILE *fd;
+
+  memset(data, 0, sizeof(HCFileData));
+  if (NULL != (fd = fopen(info->fname, "r"))) {
+    data->exists = 1;
+    do {
+      data->b_len = fread(data->body, 1, MAX_BODY_LEN, fd);
+    } while (!feof(fd)); /*  Only save the last 16KB of the file ... */
+    fclose(fd);
+  }
+}
+
+/* Find a HCDirEntry from the linked list */
+static HCDirEntry *
+find_direntry(const char *dname, HCDirEntry *dir)
+{
+  while (dir) {
+    if (!strncmp(dname, dir->dname, MAX_PATH_LEN))
+      return dir;
+    dir = dir->_next;
+  }
+  return NULL;
+}
+
+/* Setup up watchers, directory as well as initial files */
+static HCDirEntry *
+setup_watchers(int fd)
+{
+  HCFileInfo *conf = g_config;
+  HCDirEntry *head_dir = NULL, *last_dir = NULL, *dir;
+  char fname[MAX_PATH_LEN];
+  char *dname;
+
+  while (conf) {
+    conf->wd = inotify_add_watch(fd, conf->fname, IN_DELETE_SELF | IN_CLOSE_WRITE | IN_ATTRIB);
+    TSDebug(PLUGIN_NAME, "Setting up a watcher for %s", conf->fname);
+    strncpy(fname, conf->fname, MAX_PATH_LEN - 1);
+    dname = dirname(fname);
+    /* Make sure to only watch each directory once */
+    if (!(dir = find_direntry(dname, head_dir))) {
+      TSDebug(PLUGIN_NAME, "Setting up a watcher for directory %s", dname);
+      dir = TSmalloc(sizeof(HCDirEntry));
+      memset(dir, 0, sizeof(HCDirEntry));
+      strncpy(dir->dname, dname, MAX_PATH_LEN - 1);
+      dir->wd = inotify_add_watch(fd, dname, IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB);
+      if (!head_dir)
+        head_dir = dir;
+      else
+        last_dir->_next = dir;
+      last_dir = dir;
+    }
+    conf->dir = dir;
+    conf = conf->_next;
+  }
+
+  return head_dir;
+}
+
+/* Separate thread to monitor status files for reload */
+#define INOTIFY_BUFLEN (1024 * sizeof(struct inotify_event))
+
+static void *
+hc_thread(void *data ATS_UNUSED)
+{
+  int fd = inotify_init();
+  HCDirEntry *dirs;
+  int len;
+  HCFileData *fl_head = NULL;
+  char buffer[INOTIFY_BUFLEN];
+  struct timeval last_free, now;
+
+  gettimeofday(&last_free, NULL);
+
+  /* Setup watchers for the directories, these are a one time setup */
+  setup_watchers(fd); // This is a leak, but since we enter an infinite loop this is ok?
+
+  while (1) {
+    HCFileData *fdata = fl_head, *fdata_prev = NULL;
+
+    /* Read the inotify events, blocking until we get something */
+    len = read(fd, buffer, INOTIFY_BUFLEN);
+    gettimeofday(&now, NULL);
+
+    /* The fl_head is a linked list of previously released data entries. They
+       are ordered "by time", so once we find one that is scheduled for deletion,
+       we can also delete all entries after it in the linked list. */
+    while (fdata) {
+      if (now.tv_sec > fdata->remove) {
+        /* Now drop off the "tail" from the freelist */
+        if (fdata_prev)
+          fdata_prev->_next = NULL;
+        else
+          fl_head = NULL;
+
+        /* free() everything in the "tail" */
+        do {
+          HCFileData *next = fdata->_next;
+
+          TSDebug(PLUGIN_NAME, "Cleaning up entry from frelist");
+          TSfree(fdata);
+          fdata = next;
+        } while (fdata);
+        break; /* Stop the loop, there's nothing else left to examine */
+      }
+      fdata_prev = fdata;
+      fdata = fdata->_next;
+    }
+
+    if (len >= 0) {
+      int i = 0;
+
+      while (i < len) {
+        struct inotify_event *event = (struct inotify_event *)&buffer[i];
+        HCFileInfo *finfo = g_config;
+
+        while (
+          finfo &&
+          !((event->wd == finfo->wd) || ((event->wd == finfo->dir->wd) && !strncmp(event->name, finfo->basename, event->len)))) {
+          finfo = finfo->_next;
+        }
+        if (finfo) {
+          HCFileData *new_data = TSmalloc(sizeof(HCFileData));
+          HCFileData *old_data;
+
+          if (event->mask & (IN_CLOSE_WRITE | IN_ATTRIB)) {
+            TSDebug(PLUGIN_NAME, "Modify file event (%d) on %s", event->mask, finfo->fname);
+          } else if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
+            TSDebug(PLUGIN_NAME, "Create file event (%d) on %s", event->mask, finfo->fname);
+            finfo->wd = inotify_add_watch(fd, finfo->fname, IN_DELETE_SELF | IN_CLOSE_WRITE | IN_ATTRIB);
+          } else if (event->mask & (IN_DELETE_SELF | IN_MOVED_FROM)) {
+            TSDebug(PLUGIN_NAME, "Delete file event (%d) on %s", event->mask, finfo->fname);
+            finfo->wd = inotify_rm_watch(fd, finfo->wd);
+          }
+          /* Load the new data and then swap this atomically */
+          memset(new_data, 0, sizeof(HCFileData));
+          reload_status_file(finfo, new_data);
+          TSDebug(PLUGIN_NAME, "Reloaded %s, len == %d, exists == %d", finfo->fname, new_data->b_len, new_data->exists);
+          old_data = ink_atomic_swap_ptr(&(finfo->data), new_data);
+
+          /* Add the old data to the head of the freelist */
+          old_data->remove = now.tv_sec + FREELIST_TIMEOUT;
+          old_data->_next = fl_head;
+          fl_head = old_data;
+        }
+        i += sizeof(struct inotify_event) + event->len;
+      }
+    }
+  }
+
+  /* Cleanup, in case we later exit this thread ... */
+  while (dirs) {
+    HCDirEntry *d = dirs;
+
+    dirs = dirs->_next;
+    TSfree(d);
+  }
+
+  return NULL; /* Yeah, that never happens */
+}
+
+/* Config file parsing */
+static const char HEADER_TEMPLATE[] = "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nCache-Control: no-cache\r\n";
+
+static char *
+gen_header(char *status_str, char *mime, int *header_len)
+{
+  TSHttpStatus status;
+  char *buf = NULL;
+
+  status = atoi(status_str);
+  if (status > TS_HTTP_STATUS_NONE && status < (TSHttpStatus)999) {
+    const char *status_reason;
+    int len = sizeof(HEADER_TEMPLATE) + 3 + 1;
+
+    status_reason = TSHttpHdrReasonLookup(status);
+    len += strlen(status_reason);
+    len += strlen(mime);
+    buf = TSmalloc(len);
+    *header_len = snprintf(buf, len, HEADER_TEMPLATE, status, status_reason, mime);
+  } else {
+    *header_len = 0;
+  }
+
+  return buf;
+}
+
+static HCFileInfo *
+parse_configs(const char *fname)
+{
+  FILE *fd;
+  char buf[2 * 1024];
+  HCFileInfo *head_finfo = NULL, *finfo = NULL, *prev_finfo = NULL;
+
+  if (NULL == (fd = fopen(fname, "r")))
+    return NULL;
+
+  while (!feof(fd)) {
+    char *str, *save;
+    int state = 0;
+    char *ok = NULL, *miss = NULL, *mime = NULL;
+
+    finfo = TSmalloc(sizeof(HCFileInfo));
+    memset(finfo, 0, sizeof(HCFileInfo));
+
+    if (fgets(buf, sizeof(buf) - 1, fd)) {
+      str = strtok_r(buf, SEPARATORS, &save);
+      while (NULL != str) {
+        if (strlen(str) > 0) {
+          switch (state) {
+          case 0:
+            if ('/' == *str)
+              ++str;
+            strncpy(finfo->path, str, PATH_NAME_MAX - 1);
+            finfo->p_len = strlen(finfo->path);
+            break;
+          case 1:
+            strncpy(finfo->fname, str, MAX_PATH_LEN - 1);
+            finfo->basename = strrchr(finfo->fname, '/');
+            if (finfo->basename)
+              ++(finfo->basename);
+            break;
+          case 2:
+            mime = str;
+            break;
+          case 3:
+            ok = str;
+            break;
+          case 4:
+            miss = str;
+            break;
+          }
+          ++state;
+        }
+        str = strtok_r(NULL, SEPARATORS, &save);
+      }
+
+      /* Fill in the info if everything was ok */
+      if (state > 4) {
+        TSDebug(PLUGIN_NAME, "Parsed: %s %s %s %s %s", finfo->path, finfo->fname, mime, ok, miss);
+        finfo->ok = gen_header(ok, mime, &finfo->o_len);
+        finfo->miss = gen_header(miss, mime, &finfo->m_len);
+        finfo->data = TSmalloc(sizeof(HCFileData));
+        memset(finfo->data, 0, sizeof(HCFileData));
+        reload_status_file(finfo, finfo->data);
+
+        /* Add it the linked list */
+        TSDebug(PLUGIN_NAME, "Adding path=%s to linked list", finfo->path);
+        if (NULL == head_finfo) {
+          head_finfo = finfo;
+        } else {
+          prev_finfo->_next = finfo;
+        }
+        prev_finfo = finfo;
+      } else {
+        TSfree(finfo);
+      }
+    }
+  }
+  fclose(fd);
+
+  return head_finfo;
+}
+
+/* Cleanup after intercept has completed */
+static void
+cleanup(TSCont contp, HCState *my_state)
+{
+  if (my_state->req_buffer) {
+    TSIOBufferDestroy(my_state->req_buffer);
+    my_state->req_buffer = NULL;
+  }
+
+  if (my_state->resp_buffer) {
+    TSIOBufferDestroy(my_state->resp_buffer);
+    my_state->resp_buffer = NULL;
+  }
+
+  TSVConnClose(my_state->net_vc);
+  TSfree(my_state);
+  TSContDestroy(contp);
+}
+
+/* Add data to the output */
+inline static int
+add_data_to_resp(const char *buf, int len, HCState *my_state)
+{
+  TSIOBufferWrite(my_state->resp_buffer, buf, len);
+  return len;
+}
+
+/* Process a read event from the SM */
+static void
+hc_process_read(TSCont contp, TSEvent event, HCState *my_state)
+{
+  if (event == TS_EVENT_VCONN_READ_READY) {
+    if (my_state->data->exists) {
+      TSDebug(PLUGIN_NAME, "Setting OK response header");
+      my_state->output_bytes = add_data_to_resp(my_state->info->ok, my_state->info->o_len, my_state);
+    } else {
+      TSDebug(PLUGIN_NAME, "Setting MISS response header");
+      my_state->output_bytes = add_data_to_resp(my_state->info->miss, my_state->info->m_len, my_state);
+    }
+    TSVConnShutdown(my_state->net_vc, 1, 0);
+    my_state->write_vio = TSVConnWrite(my_state->net_vc, contp, my_state->resp_reader, INT64_MAX);
+  } else if (event == TS_EVENT_ERROR) {
+    TSError("[healthchecks] hc_process_read: Received TS_EVENT_ERROR");
+  } else if (event == TS_EVENT_VCONN_EOS) {
+    /* client may end the connection, simply return */
+    return;
+  } else if (event == TS_EVENT_NET_ACCEPT_FAILED) {
+    TSError("[healthchecks] hc_process_read: Received TS_EVENT_NET_ACCEPT_FAILED");
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+}
+
+/* Process a write event from the SM */
+static void
+hc_process_write(TSCont contp, TSEvent event, HCState *my_state)
+{
+  if (event == TS_EVENT_VCONN_WRITE_READY) {
+    char buf[48];
+    int len;
+
+    len = snprintf(buf, sizeof(buf) - 1, "Content-Length: %d\r\n\r\n", my_state->data->b_len);
+    my_state->output_bytes += add_data_to_resp(buf, len, my_state);
+    if (my_state->data->b_len > 0)
+      my_state->output_bytes += add_data_to_resp(my_state->data->body, my_state->data->b_len, my_state);
+    else
+      my_state->output_bytes += add_data_to_resp("\r\n", 2, my_state);
+    TSVIONBytesSet(my_state->write_vio, my_state->output_bytes);
+    TSVIOReenable(my_state->write_vio);
+  } else if (TS_EVENT_VCONN_WRITE_COMPLETE) {
+    cleanup(contp, my_state);
+  } else if (event == TS_EVENT_ERROR) {
+    TSError("[healthchecks] hc_process_write: Received TS_EVENT_ERROR");
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+}
+
+/* Process the accept event from the SM */
+static void
+hc_process_accept(TSCont contp, HCState *my_state)
+{
+  my_state->req_buffer = TSIOBufferCreate();
+  my_state->resp_buffer = TSIOBufferCreate();
+  my_state->resp_reader = TSIOBufferReaderAlloc(my_state->resp_buffer);
+  my_state->read_vio = TSVConnRead(my_state->net_vc, contp, my_state->req_buffer, INT64_MAX);
+}
+
+/* Imlement the server intercept */
+static int
+hc_intercept(TSCont contp, TSEvent event, void *edata)
+{
+  HCState *my_state = TSContDataGet(contp);
+
+  if (event == TS_EVENT_NET_ACCEPT) {
+    my_state->net_vc = (TSVConn)edata;
+    hc_process_accept(contp, my_state);
+  } else if (edata == my_state->read_vio) { /* All read events */
+    hc_process_read(contp, event, my_state);
+  } else if (edata == my_state->write_vio) { /* All write events */
+    hc_process_write(contp, event, my_state);
+  } else {
+    TSReleaseAssert(!"Unexpected Event");
+  }
+
+  return 0;
+}
+
+/* Read-request header continuation, used to kick off the server intercept if necessary */
+static int
+health_check_origin(TSCont contp ATS_UNUSED, TSEvent event ATS_UNUSED, void *edata)
+{
+  TSMBuffer reqp;
+  TSMLoc hdr_loc = NULL, url_loc = NULL;
+  TSCont icontp;
+  HCState *my_state;
+  TSHttpTxn txnp = (TSHttpTxn)edata;
+  HCFileInfo *info = g_config;
+
+  if ((TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &reqp, &hdr_loc)) && (TS_SUCCESS == TSHttpHdrUrlGet(reqp, hdr_loc, &url_loc))) {
+    int path_len = 0;
+    const char *path = TSUrlPathGet(reqp, url_loc, &path_len);
+
+    /* Short circuit the / path, common case, and we won't allow healthecks on / */
+    if (!path || !path_len)
+      goto cleanup;
+
+    while (info) {
+      if (info->p_len == path_len && !memcmp(info->path, path, path_len)) {
+        TSDebug(PLUGIN_NAME, "Found match for /%.*s", path_len, path);
+        break;
+      }
+      info = info->_next;
+    }
+
+    if (!info)
+      goto cleanup;
+
+    TSSkipRemappingSet(txnp, 1); /* not strictly necessary, but speed is everything these days */
+
+    /* This is us -- register our intercept */
+    icontp = TSContCreate(hc_intercept, TSMutexCreate());
+    my_state = (HCState *)TSmalloc(sizeof(*my_state));
+    memset(my_state, 0, sizeof(*my_state));
+    my_state->info = info;
+    my_state->data = info->data;
+    TSContDataSet(icontp, my_state);
+    TSHttpTxnIntercept(icontp, txnp);
+  }
+
+cleanup:
+  if (url_loc)
+    TSHandleMLocRelease(reqp, hdr_loc, url_loc);
+  if (hdr_loc)
+    TSHandleMLocRelease(reqp, TS_NULL_MLOC, hdr_loc);
+
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+
+  return 0;
+}
+
+/* Initialize the plugin / global continuation hook */
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  if (2 != argc) {
+    TSError("[healthchecks] Must specify a configuration file.");
+    return;
+  }
+
+  info.plugin_name = "health_checks";
+  info.vendor_name = "Apache Software Foundation";
+  info.support_email = "dev@trafficserver.apache.org";
+
+  if (TS_SUCCESS != TSPluginRegister(&info)) {
+    TSError("[healthchecks] Plugin registration failed.");
+    return;
+  }
+
+  /* This will update the global configuration file, and is not reloaded at run time */
+  /* ToDo: Support reloading with traffic_ctl config reload ? */
+  if (NULL == (g_config = parse_configs(argv[1]))) {
+    TSError("[healthchecks] Unable to read / parse %s config file", argv[1]);
+    return;
+  }
+
+  /* Setup the background thread */
+  if (!TSThreadCreate(hc_thread, NULL)) {
+    TSError("[healthchecks] Failure in thread creation");
+    return;
+  }
+
+  /* Create a continuation with a mutex as there is a shared global structure
+     containing the headers to add */
+  TSDebug(PLUGIN_NAME, "Started %s plugin", PLUGIN_NAME);
+  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(health_check_origin, NULL));
+}


Mime
View raw message