[
https://issues.apache.org/jira/browse/TS-4723?focusedWorklogId=26271&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-26271
]
ASF GitHub Bot logged work on TS-4723:
--------------------------------------
Author: ASF GitHub Bot
Created on: 10/Aug/16 02:51
Start Date: 10/Aug/16 02:51
Worklog Time Spent: 10m
Work Description: Github user SolidWallOfCode commented on a diff in the
pull request:
https://github.com/apache/trafficserver/pull/843#discussion_r74178621
--- Diff: plugins/experimental/carp/CarpConfig.cc ---
@@ -0,0 +1,581 @@
+/** @file
+
+ Loads the CARP configuration
+
+ @section license License
+
+ 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.
+ */
+
+//////////////////////////////////////////////////////////////
+// Read CARP configuration file
+// [Servers]
+// host1.yahoo.com:4080 weight=2 # port 4080 on host1.yahoo.com with
weight factor of 2
+// host2.yahoo.com # port 80 on host2.yahoo.com with
(default) weight factor of 1
+//
+// [Values]
+// healthcheck={host}:8001/status.html
+// healthfreq=30
+// global=on
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#include <ts/ts.h>
+#include <sys/time.h>
+
+#include <sstream>
+
+#include "CarpConfig.h"
+#include "Common.h"
+#include "CarpConfigPool.h"
+
+using namespace std;
+
+#define DEFAULT_HEALTH_CHECK_FREQ 30 // 30 second period for health checks
+#define DEFAULT_HEALTH_CHECK_PORT 80 // default to makeing healthcheck
requests against port 80
+#define DEFAULT_CONFIG_RELOAD_FREQ 30 // 30 seconds used in TSContSchedule
+#define DEFAULT_PORT 80 // default to makeing requests against port 80
+#define DEFAULT_WEIGHT 1 // default weight
+#define DEFAULT_SCHEME "http"
+#define DEFAULT_REPLICATION_FACTOR 1
+
+// config section headers
+static const char *const SECTION_SERVERS_STR = "[Servers]";
+static const char *const SECTION_VALUES_STR = "[Values]";
+
+// key strings
+static const char *const KEY_HEALTHCHECK_STR = "healthcheck";
+static const char *const KEY_HEALTHFREQ_STR = "healthfreq";
+static const char *const KEY_RELOADFREQ_STR = "reloadfreq";
+static const char *const KEY_HCTIMEOUT_STR = "hctimeout";
+static const char *const KEY_BLACKLIST_STR = "blacklist";
+static const char *const KEY_WHITELIST_STR = "whitelist";
+static const char *const KEY_MODE_STR = "mode";
+static const char *const KEY_ALLOWFWDPORT_STR = "allowfwdport";
+static const char *const KEY_REPLICATIONFACTOR_STR = "replicationfactor";
+
+// parameter strings
+static const char *const WEIGHT_EQUALS_STRING = "weight=";
+static const char *const GROUP_EQUALS_STRING = "group=";
+static const char *const KEY_MODE_PREREMAP_STR = "pre-remap";
+static const char *const KEY_MODE_POSTREMAP_STR = "post-remap";
+
+
+/**********************************************************/
+bool
+getInt(char** pptr, int *val)
+{
+ bool bReturn = false;
+ int v=0;
+
+ char* ptr = *pptr;
+ // skip white space if any
+ while (*ptr && isspace(*ptr)) ++ptr;
+ // get digits
+ if (*ptr) {
+ while (*ptr && isdigit(*ptr)) {
+ v *= 10;
+ v += (*ptr)-'0';
+ ++ptr;
+ bReturn = true;
+ }
+ }
+ if (bReturn) {
+ *pptr = ptr;
+ *val = v;
+ }
+
+ return bReturn;
+}
+
+/**********************************************************/
+// [http[s]://]host[:port]/path
+bool
+getHostAndPort(char** pptr,string* sHost,int* iPort, string* sScheme)
+{
+ bool bReturn = false;
+ char* ptr = *pptr;
+
+ *iPort = 80;
+ *sScheme = DEFAULT_SCHEME;
+ if(strncmp(*pptr,"https",5) == 0) {
+ *iPort = 443;
+ *sScheme = "https";
+ }
+
+ //skip leading white space
+ while (*ptr && isspace(*ptr)) ++ptr;
+
+ if (*ptr) { // validate not end of string
+ if(strncmp(ptr,"http://",7) == 0) {
+ ptr += 7;
+ } else if(strncmp(ptr,"https://",8) == 0) {
+ ptr += 8;
+ }
+
+ char* ptemp = ptr; // start of host
+ //find white space or ':' or '/'
+ while (*ptr && !isspace(*ptr) && *ptr != ':' && *ptr != '/') ++ptr;
+
+ *sHost = string(ptemp, (ptr - ptemp));
+ // skip white space (if any) after host
+ while (*ptr && isspace(*ptr)) ++ptr;
+ bReturn = true;
+
+ if (*ptr) {// have more to parse
+ // need to get port number?
+ if (*ptr == ':') {
+ ++ptr;
+ if (!getInt(&ptr, iPort)) {
+ // could be our special 'PORT' value, check for that
+ if(!strncmp(ptr,"{port}",6)) { // yes, is '{port}'
+ *iPort = -1;
+ bReturn = true;
+ } else { // really was an error
+ TSError("carp: error parsing port number from '%s'", *pptr);
+ bReturn = false;
+ }
+ } else {
+ // if port number is 443, treat the scheme as https
+ if (*iPort == 443) {
+ *sScheme = "https";
+ }
+ }
+ }
+ }
+ }
+ if(bReturn) {
+ *pptr = ptr;
+ }
+
+ return bReturn;
+}
+/**********************************************************/
+CarpConfig::CarpConfig()
+{
+ _healthCheckPort = DEFAULT_HEALTH_CHECK_PORT;
+ _healthCheckFreq = DEFAULT_HEALTH_CHECK_FREQ;
+ _configCheckFreq = DEFAULT_CONFIG_RELOAD_FREQ;
+ _healthCheckTimeout = DEFAULT_HEALTH_CHECK_TIMEOUT;
+ _setExit = 0;
+ _mode = PRE;
+ _allowForwardPort = 0;
+ _replicationFactor = DEFAULT_REPLICATION_FACTOR;
+ _nGroups = 0;
+}
+
+/**********************************************************/
+CarpConfig::~CarpConfig()
+{
+ for(size_t ptr = 0; ptr < _servers.size(); ptr++) {
+ delete _servers[ptr];
+ }
+
+ for(size_t ptr = 0; ptr < _httpClients.size(); ptr++) {
+ delete _httpClients[ptr];
+ }
+}
+
+/**********************************************************/
+bool
+CarpConfig::loadConfig(string filename)
+{
+ TSFile file;
+ GroupCountList group_counts;
+ GroupCountListIter group_counts_it;
+
+ // attempt to open config file assuming full path provided
+ TSDebug(DEBUG_TAG_INIT, "Trying to open config file in this path: %s",
filename.c_str());
+ file = TSfopen(filename.c_str(), "r");
+ if (file == NULL) {
+ TSError("Failed to open carp config file %s", filename.c_str());
+ return false;
+ }
+
+ TSDebug(DEBUG_TAG_INIT, "Successfully opened config file");
+
+ char buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+
+ enum CONFIG_SECTION {
+ CFG_NONE_SECTION = 0,
+ CFG_SERVER_SECTION,
+ CFG_VALUES_SECTION
+ };
+ int cfg_section = CFG_NONE_SECTION;
+ bool done_parsing = false;
+ while (TSfgets(file, buffer, sizeof(buffer) - 1) != NULL &&
!done_parsing) {
+
+ char *eol = 0;
+ // make sure line was not bigger than buffer
+ if ((eol = strchr(buffer, '\n')) == NULL && (eol = strstr(buffer,
"\r\n")) == NULL) {
+ TSError("carp config line was too long, did not get a good line in
cfg, skipping, line: %s", buffer);
+ memset(buffer, 0, sizeof(buffer));
+ continue;
+ }
+
+ // make sure line has something useful on it
+ if (eol - buffer < 2 || buffer[0] == '#' || isspace(buffer[0])) {
+ memset(buffer, 0, sizeof(buffer));
+ continue;
+ }
+
+ // remove ending CR/LF
+ *eol = 0;
+
+ // check if we are changing sections
+ if (strncasecmp(buffer, SECTION_SERVERS_STR,
strlen(SECTION_SERVERS_STR)) == 0) {
+ cfg_section = CFG_SERVER_SECTION;
+ TSDebug(DEBUG_TAG_INIT, "Parsing [Servers] section");
+ continue;
+ } else if (strncasecmp(buffer, SECTION_VALUES_STR,
strlen(SECTION_VALUES_STR)) == 0) {
+ cfg_section = CFG_VALUES_SECTION;
+ TSDebug(DEBUG_TAG_INIT, "Parsing [Values] section");
+ continue;
+ }
+
+ //TSDebug(DEBUG_TAG_INIT, "config line input:'%s'", buffer);
+
+ switch (cfg_section) {
+ case CFG_SERVER_SECTION:
+ {
+ string sHost;
+ int iPort = DEFAULT_PORT;
+ int iWeight = DEFAULT_WEIGHT;
+ int iGroup = DEFAULT_GROUP;
+ string sScheme = DEFAULT_SCHEME;
+ bool bSuccess = true;
+
+ char* ptr = buffer;
+
+ if(!getHostAndPort(&ptr,&sHost,&iPort, &sScheme)) {
+ TSError("carp: error parsing port number from '%s'", ptr);
+ }
+
+ while (*ptr) {
+ // skip white space (if any)
+ while (*ptr && isspace(*ptr)) ++ptr;
+
+ if (*ptr) { // next we could find weight= or group=
+ if (strncmp(ptr, WEIGHT_EQUALS_STRING,
strlen(WEIGHT_EQUALS_STRING)) == 0) {
+ ptr += strlen(WEIGHT_EQUALS_STRING);
+ if (!getInt(&ptr, &iWeight)) {
+ TSError("carp: error parsing weight value from '%s'",
buffer);
+ bSuccess = false;
+ continue;
+ }
+ } else if (strncmp(ptr, GROUP_EQUALS_STRING,
strlen(GROUP_EQUALS_STRING)) == 0) {
+ ptr += strlen(GROUP_EQUALS_STRING);
+ if (!getInt(&ptr, &iGroup)) {
+ TSError("carp: error parsing group value from '%s'",
buffer);
+ bSuccess = false;
+ continue;
+ }
+ } else {
+ TSError("carp: error parsing from line '%s'", buffer);
+ // malformed entry, skip to next space
+ while (*ptr && !isspace(*ptr)) ++ptr;
+ bSuccess = false;
+ continue;
+ }
+ }
+ }
+
+ if (!bSuccess) continue;
+
+ group_counts_it = group_counts.find(iGroup);
+ if (group_counts_it != group_counts.end()) {
+ group_counts[iGroup]++;
+ } else {
+ _nGroups++;
+ group_counts[iGroup] = 1;
+ }
+
+ TSDebug(DEBUG_TAG_INIT, "Host = %s, port=%d, weight=%d, group=%d",
sHost.c_str(), iPort, iWeight, iGroup);
+ CarpHost* host=new CarpHost(sHost, iPort, sScheme, iWeight, iGroup);
+ TSAssert(host != NULL);
+ // store the parsed data
+ addHost(host);
+ break;
+ }
+ case CFG_VALUES_SECTION:
+ {
+ char* ptr = buffer;
+
+ //skip leading white space
+ while (*ptr && isspace(*ptr)) ++ptr;
+
+ //find end of key
+ while (*ptr && !isspace(*ptr) && *ptr != '=') ++ptr;
+
+ string sKey = string(buffer, (ptr - buffer));
+
+ // skip white space (if any) after key
+ while (*ptr && isspace(*ptr)) ++ptr;
+ if (*ptr != '=') {
+ TSError("carp: expecting '=' after key in line '%s'", buffer);
+ continue;
+ }
+
+ // skip '=' and white space (if any) after '='
+ ++ptr;
+ while (*ptr && isspace(*ptr)) ++ptr;
+
+ char* ptemp = ptr; // get start of value
+
+ // find end of value
+ while (*ptr && !isspace(*ptr)) ++ptr;
+ string sValue = string(ptemp, ptr - ptemp);
+
+ TSDebug(DEBUG_TAG_INIT, "Key=%s Value=%s", sKey.c_str(),
sValue.c_str());
+
+ char* pTemp = (char *)sValue.c_str();
+ if (sKey.compare(KEY_HEALTHCHECK_STR) == 0) {
+ string sH;
+ int iP;
+ string scheme;
+ _healthCheckUrl = string(pTemp);
+ if (!getHostAndPort(&pTemp, &sH, &iP, &scheme)) {
+ TSError("carp: error parsing host and/or port number from '%s'",
buffer);
+ }
+
+ // store the parsed data
+ _healthCheckPort = iP;
+ TSDebug(DEBUG_TAG_INIT, "healthcheck Url=%s port=%d",
_healthCheckUrl.c_str(), _healthCheckPort);
+
+ } else if (sKey.compare(KEY_HEALTHFREQ_STR) == 0) {
+ int iFreq;
+ if (!getInt(&pTemp, &iFreq)) {
+ TSError("carp: error parsing number from '%s'", buffer);
+ } else {
+ TSDebug(DEBUG_TAG_INIT, "healthcheck freq=%d", iFreq);
+ // store the parsed data
+ _healthCheckFreq = iFreq;
+ }
+ } else if (sKey.compare(KEY_HCTIMEOUT_STR) == 0) {
+ int iFreq;
+ if (!getInt(&pTemp, &iFreq)) {
+ TSError("carp: error parsing number from '%s'", buffer);
+ } else {
+ TSDebug(DEBUG_TAG_INIT, "healthcheck timeout value=%d", iFreq);
+ // store the parsed data
+ _healthCheckTimeout = iFreq;
+ }
+ } else if (sKey.compare(KEY_RELOADFREQ_STR) == 0) {
+ int lFreq;
+ if (!getInt(&pTemp, &lFreq)) {
+ TSError("carp: error parsing number from '%s'", buffer);
+ } else {
+ TSDebug(DEBUG_TAG_INIT, "config reload freq=%d", lFreq);
+ _configCheckFreq = lFreq;
+ }
+ } else if (sKey.compare(KEY_BLACKLIST_STR) == 0) {
+ vector<string> results;
+ stringExplode(sValue, string(","), &results);
+ for (vector<string>::iterator it = results.begin(); it !=
results.end();
+ it++) {
+ TSDebug(DEBUG_TAG_INIT, "Adding blacklist hostname %s",
+ (*it).c_str());
+ _blackList.insert(*it);
+ }
+ } else if (sKey.compare(KEY_WHITELIST_STR) == 0) {
+ vector<string> results;
+ stringExplode(sValue, string(","), &results);
+ for(vector<string>::iterator it=results.begin(); it !=
results.end(); it++) {
+ TSDebug(DEBUG_TAG_INIT, "Adding whitelist hostname
%s",(*it).c_str() );
+ _whiteList.insert(*it);
+ }
+ } else if (sKey.compare(KEY_MODE_STR) == 0) {
+ if(sValue.compare(KEY_MODE_PREREMAP_STR) == 0) {
+ _mode = PRE;
+ } else if(sValue.compare(KEY_MODE_POSTREMAP_STR) == 0) {
+ _mode = POST;
+ } else {
+ TSError("carp: invalid mode in '%s'", buffer);
+ }
+ } else if (sKey.compare(KEY_ALLOWFWDPORT_STR) == 0) {
+ int iPort;
+ if (!getInt(&pTemp, &iPort)) {
+ TSError("carp: error parsing number from '%s'", buffer);
+ }
+ else {
+ TSDebug(DEBUG_TAG_INIT, "Allow forwarding port=%d", iPort);
+ // store the parsed data
+ _allowForwardPort = iPort;
+ }
+ } else if (sKey.compare(KEY_REPLICATIONFACTOR_STR) == 0) {
+ int iFactor;
+ if (!getInt(&pTemp, &iFactor)) {
+ TSError("carp: error parsing number from '%s'", buffer);
+ }
+ else {
+ TSDebug(DEBUG_TAG_INIT, "Replication factor=%d", iFactor);
+ // store the parsed data
+ _replicationFactor = iFactor;
+ }
+
+ } else
+ TSError("carp found bad setting under Values section '%s'",
buffer);
+ break;
+ }
+ default:
+ // ignore bad cfg_section
+ TSDebug(DEBUG_TAG_INIT, "hit default in switch, ignoring extra input
'%s'",buffer);
+ break;
+ };
+ } //while
+
+ if (_healthCheckTimeout > _healthCheckFreq - 1 ) {
+ _healthCheckTimeout = _healthCheckFreq - 1;
+ }
+
+ TSfclose(file);
+
+ if (_blackList.size() && _whiteList.size() ) {
+ TSError("Carp configured with both blacklist and whitelist, blacklist
will be ignored");
+ }
+
+ if (_nGroups > _replicationFactor) {
+ TSError("Too many groups configured! Failing config.");
+ return false;
+ } else {
+ TSDebug(DEBUG_TAG_INIT, "Group Config is as follows:");
+ for (map<int, int>::const_iterator it = group_counts.begin(); it !=
group_counts.end(); it++) {
+ TSDebug(DEBUG_TAG_INIT, "Group %d has %d members.", it->first,
it->second);
+ _group_count_list[it->first] = it->second;
+ }
+ }
+
+ return true;
+}
+
+/**********************************************************/
+void
+CarpConfig::addHost(CarpHost* host) {
+ _servers.push_back(host);
+}
+
+void
+CarpConfig::addHealthCheckClient(HttpFetch *client) {
+ client->setHealthcheckTimeout(_healthCheckTimeout);
+ _httpClients.push_back(client);
+
+}
+
+void
+CarpConfig::setPath(string path) {
+ _configPath = path;
+}
+
+string
+CarpConfig::getPath() {
+ return _configPath;
+}
+
+/**********************************************************/
+void *
+CarpConfigHealthCheckThreadStart(void* data)
+{
+ CarpConfigAndHash* cch = static_cast<CarpConfigAndHash *> (data);
+ TSAssert(cch);
+ return cch->_config->run(cch->_hashAlgo);
+}
+
+/**********************************************************/
+bool
+CarpConfig::isBlackListed(const string& sHost)
+{
+ if(!_blackList.size()) return false;
+ return (_blackList.find(sHost) != _blackList.end());
+}
+
+/**********************************************************/
+bool
+CarpConfig::isWhiteListed(const string& sHost)
+{
+ if(!_whiteList.size()) return false;
+ return (_whiteList.find(sHost) != _whiteList.end());
+}
+
+/**********************************************************/
+/*
+ perform healthchecks on the hosts and mark them up/down
+*/
+
+void*
+CarpConfig::run(HashAlgorithm *hash) {
+
+ // every httpClient would send a health request to each peer
+ // would be nice to not wait and just 'go' when the server is 100% up
+ sleep(5);
--- End diff --
Is there some lifecycle hook that would be useful for this?
Issue Time Tracking
-------------------
Worklog Id: (was: 26271)
Time Spent: 1h 50m (was: 1h 40m)
> ATS CARP Plugin
> ---------------
>
> Key: TS-4723
> URL: https://issues.apache.org/jira/browse/TS-4723
> Project: Traffic Server
> Issue Type: New Feature
> Components: Plugins
> Reporter: Eric Schwartz
> Assignee: Eric Schwartz
> Fix For: 7.0.0
>
> Time Spent: 1h 50m
> Remaining Estimate: 0h
>
> Open sourcing this plugin we use internally within Yahoo in place of
> hierarchical caching.
> CARP is a plugin that allows you to group a bunch of ATS hosts into a cluster
> and share cache space across the entire group. This is done with consistent
> hashing on the object URL to generate an "owner" node in the cluster.
> Requests to any other node in the cluster will be forwarded on to the
> corresponding owner. More info in the README.
> Difference from internal version of note:
> I've ripped out some code we weren't entirely sure we could open source
> because of a hash function. If it turns out that we can open source this,
> I'll do so. The CarpHashAlgorithm class is meant to be extensible, so any
> consistent hash function can replace it. The function included here is pretty
> straightforward but not what we use in production, so just wanted to use that
> caveat.
> One last caveat:
> You'll see some code and documentation in here for object replication. This
> is something I added recently to CARP that allows you to specify an object be
> replicated a certain number of times in the cluster. This is useful if you
> have a network partition or if you're performing some sort of update. When an
> object's primary owner is unreachable, a node in the cluster can go to the
> secondary owner if it's available rather than having to fall all the way back
> to origin. While I've done some initial testing on this with my own cluster
> of hosts, it's not been tested in production so use at your own risk for now.
> I'll be sure to keep the open source community informed on the progress of
> our tests with this feature.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)