[ 
https://issues.apache.org/jira/browse/TS-4723?focusedWorklogId=26270&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-26270
 ]

ASF GitHub Bot logged work on TS-4723:
--------------------------------------

                Author: ASF GitHub Bot
            Created on: 10/Aug/16 02:50
            Start Date: 10/Aug/16 02:50
    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_r74178543
  
    --- 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());
    --- End diff --
    
    Not clear it's worth checking for an empty list, as `find` will do that as 
well.


Issue Time Tracking
-------------------

    Worklog Id:     (was: 26270)
    Time Spent: 1h 40m  (was: 1.5h)

> 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 40m
>  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)

Reply via email to