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 <[email protected]> Authored: Mon Nov 9 21:55:20 2015 -0700 Committer: James Peach <[email protected]> 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 = "[email protected]"; - - 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 = "[email protected]"; + + 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)); +}
