[
https://issues.apache.org/jira/browse/TS-4723?focusedWorklogId=26906&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-26906
]
ASF GitHub Bot logged work on TS-4723:
--------------------------------------
Author: ASF GitHub Bot
Created on: 23/Aug/16 13:04
Start Date: 23/Aug/16 13:04
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_r75860439
--- Diff: plugins/experimental/carp/carp.cc ---
@@ -0,0 +1,713 @@
+/** @file
+
+ A brief file description
+
+ @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.
+ */
+
+#include <errno.h>
+
+#include <string>
+#include <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory.h>
+
+#include <ts/ts.h>
+
+#include "Common.h"
+#include "CarpConfig.h"
+#include "CarpConfigPool.h"
+#include "CarpHashAlgorithm.h"
+#include "UrlComponents.h"
+
+using namespace std;
+
+CarpConfigPool* g_CarpConfigPool = NULL;
+int g_carpSelectedHostArgIndex = 0;
+TSTextLogObject g_logObject = NULL;
+
+const char *logFileName = "carp";
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+/*
+ check for our carp routed header, dump status if requested
+ */
+static int
+processCarpRoutedHeader(TSHttpTxn txnp, TSMBuffer bufp, TSMLoc hdr_loc)
+{
+ string value;
+ if (getHeader(bufp, hdr_loc, CARP_ROUTED_HEADER, value)) { // if found
header
+ if (value.compare("1") == 0) { // is loop prevention value
+ TSDebug(DEBUG_TAG_HOOK, "Found %s header with loop prevention value,
not forwarding again", CARP_ROUTED_HEADER.c_str());
+ return 0;
+ } else if (value.compare("dump") == 0) { // is dump status request
+ TSDebug(DEBUG_TAG_HOOK, "Found %s header with dump request",
CARP_ROUTED_HEADER.c_str());
+ string status;
+ g_CarpConfigPool->getGlobalHashAlgo()->dump(status);
+ TSHttpTxnSetHttpRetStatus(txnp, TS_HTTP_STATUS_MULTI_STATUS);
+ TSHttpTxnErrorBodySet(txnp, TSstrdup(status.c_str()),
status.length(), NULL);
+ return -1;
+ }
+ TSDebug(DEBUG_TAG_HOOK, "Found %s header with unknown value of %s,
ignoring", CARP_ROUTED_HEADER.c_str(), value.c_str());
+ removeHeader(bufp, hdr_loc, CARP_ROUTED_HEADER);
+ }
+ return 1; // all OK
+}
+
+static bool
+checkListForSelf(std::vector<HashNode *> list)
+{
+ for (size_t k = 0; k < list.size(); k++) {
+ if (list[k]->isSelf) return true;
+ }
+ return false;
+}
+
+/**
+ bIsPOSTRemap = false --- Hash request and forward to peer
+ bIsPOSTRemap = true --- hash request, extract OS sockaddr, insert
forwarding header, forward
+ */
+static int
+handleRequestProcessing(TSCont contp, TSEvent event, void *edata, bool
bIsPOSTRemap)
+{
+ TSHttpTxn txnp = (TSHttpTxn) edata;
+ TSMBuffer bufp;
+ TSMLoc hdr_loc;
+ TSMLoc url_loc;
+
+ // get the client request so we can get URL and add header
+ if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+ TSError("carp couldn't get request headers");
+ return -1;
+ }
+
+ int method_len;
+ const char *method = TSHttpHdrMethodGet(bufp, hdr_loc, &method_len);
+ if (NULL == method) {
+ TSError("carp couldn't get http method");
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return -1;
+ }
+ if (((method_len == TS_HTTP_LEN_DELETE) && (strncasecmp(method,
TS_HTTP_METHOD_DELETE, TS_HTTP_LEN_DELETE) == 0)) ||
+ ((method_len == TS_HTTP_LEN_PURGE) && (strncasecmp(method,
TS_HTTP_METHOD_PURGE, TS_HTTP_LEN_PURGE) == 0))) {
+ TSDebug(DEBUG_TAG_HOOK, "Request method is '%s' so not routing
request", string(method,method_len).c_str());
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return 0;
+ }
+
+ if (TSHttpHdrUrlGet(bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
+ TSError("carp couldn't get url");
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return -1;
+ }
+ // if has carp loop prevention header, do not remap
+ int iTemp = processCarpRoutedHeader(txnp, bufp, hdr_loc);
+ if(iTemp <= 0) { // if dump or do not remap
+ // check origin client request's scheme for premap mode
+ if (!bIsPOSTRemap) {
+ string oriScheme;
+ if (!getHeader(bufp, hdr_loc, CARP_PREMAP_SCHEME, oriScheme)) {
+ TSDebug(DEBUG_TAG_HOOK, "couldn't get '%s' header",
CARP_PREMAP_SCHEME.c_str());
+ } else {
+ bool isHttps = (oriScheme == TS_URL_SCHEME_HTTPS);
+
+ if (isHttps) {
+ TSUrlSchemeSet(bufp, url_loc, TS_URL_SCHEME_HTTPS,
TS_URL_LEN_HTTPS);
+ } else {
+ TSUrlSchemeSet(bufp, url_loc, TS_URL_SCHEME_HTTP,
TS_URL_LEN_HTTP);
+ }
+
+ removeHeader(bufp, hdr_loc, CARP_STATUS_HEADER);
+ removeHeader(bufp, hdr_loc, CARP_ROUTED_HEADER);
+ removeHeader(bufp, hdr_loc, CARP_PREMAP_SCHEME.c_str());
+ TSDebug(DEBUG_TAG_HOOK, "Set client request's scheme to %s through
%s header",
+ isHttps?"https":"http", CARP_PREMAP_SCHEME.c_str());
+ }
+ } else {
+ removeHeader(bufp, hdr_loc, CARP_ROUTED_HEADER);
+ }
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return iTemp;
+ }
+
+ UrlComponents reqUrl;
+ reqUrl.populate(bufp, url_loc);
+ // the url ONLY used to determine the cache owner
+ string sUrl;
+
+ if (!bIsPOSTRemap) { // if pre-remap, then host not in URL so get from
header
+ string sHost;
+ if (!getHeader(bufp, hdr_loc, TS_MIME_FIELD_HOST, sHost)) {
+ TSDebug(DEBUG_TAG_HOOK, "Could not find host header, ignoring it");
+ }
+ reqUrl.setHost(sHost);
+
+ //[YTSATS-836] heuristically ignore the scheme and port when calculate
cache owner
+ UrlComponents normalizedUrl = reqUrl;
+ normalizedUrl.setScheme(CARP_SCHEME_FOR_HASH);
+ normalizedUrl.setPort(CARP_PORT_FOR_HASH);
+ normalizedUrl.construct(sUrl);
+ } else {
+ reqUrl.construct(sUrl);
+ }
+
+
+ if (g_CarpConfigPool->getGlobalConfig()->hasWhiteList()) {
+ string sCarpable;
+ if (!getHeader(bufp, hdr_loc, CARPABLE_HEADER, sCarpable)) { // if no
carpable header check whitelist
+ if
(!g_CarpConfigPool->getGlobalConfig()->isWhiteListed(reqUrl.getHost())) { // if
white list exists, then host must be present
+ TSDebug(DEBUG_TAG_HOOK, "Host '%s' is not whitelisted, not going
through carp", reqUrl.getHost().c_str());
+ TSHandleMLocRelease(bufp, hdr_loc, url_loc);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return 0;
+ } else {
+ TSDebug(DEBUG_TAG_HOOK, "Found host (%s) whitelisted,
routing...",reqUrl.getHost().c_str());
+ }
+ } else { // found carpable header, make sure it's 1
+ if (sCarpable.compare("1") != 0) { // carpable header present but
not 0, be strict and do not forward request
+ TSDebug(DEBUG_TAG_HOOK, "Carpable (%s) present but value not
acceptable (%s)", CARPABLE_HEADER.c_str(), sCarpable.c_str());
+ TSHandleMLocRelease(bufp, hdr_loc, url_loc);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return 0;
+ } else {
+ TSDebug(DEBUG_TAG_HOOK, "Found Carpable header, routing...");
+ }
+ }
+ } else { // no whitelist so blacklist could be used
+ if
(g_CarpConfigPool->getGlobalConfig()->isBlackListed(reqUrl.getHost())) { // if
host black listed, do not carp
+ TSDebug(DEBUG_TAG_HOOK, "Host '%s' is blacklisted, not going through
carp", reqUrl.getHost().c_str());
+ TSHandleMLocRelease(bufp, hdr_loc, url_loc);
+ TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+ return 0;
+ }
+ }
+
+ TSDebug(DEBUG_TAG_HOOK, "URL to hash with=%s", sUrl.c_str());
+
+ // get nodeList and select node to forward to
+ std::vector<HashNode *> nodeList =
g_CarpConfigPool->getGlobalHashAlgo()->getRemapProxyList(sUrl);
+ bool bAddHeaderResult=false;
+ bool bAddForwardedResult = false;
+ HashNode* node = NULL;
+ bool bIsOwner = false;
+
+ if (nodeList.size() == 0) { // no hosts available to forward to
+ TSDebug(DEBUG_TAG_HOOK, "no hosts available to forward to, will handle
locally");
+ goto done;
+ } else {
+ node = nodeList[0];
+ }
+
+ bIsOwner = checkListForSelf(nodeList);
+ for (size_t k = 0; k < nodeList.size(); k++) {
+ TSDebug(DEBUG_TAG_HOOK, "nodeList host %d name is %s",
static_cast<int>(k), nodeList[k]->name.c_str());
+ }
+
+ //handle forwarding
+ TSDebug(DEBUG_TAG_HOOK, "forwarding to '%s' (isSelf=%d)",
node->name.c_str(), node->isSelf);
+ if (!node->isSelf) { // carp does not forward if we choose ourself
+ node->carpForward();
+ TSDebug(DEBUG_TAG_HOOK, "carp forwarded to %s.", node->name.c_str());
+ // insert carp loop prevention header
+ bAddHeaderResult = addHeader(bufp, hdr_loc, CARP_ROUTED_HEADER,
string("1"));
+ if (!bAddHeaderResult) {
+ TSError("Carp, error inserting '%s' header",
CARP_ROUTED_HEADER.c_str());
+ }
+ bAddForwardedResult = addHeader(bufp, hdr_loc, CARP_STATUS_HEADER,
string(CARP_FORWARDED));
+ if (!bAddForwardedResult) {
+ TSError("Carp, error inserting '%s' header",
CARP_STATUS_HEADER.c_str());
+ }
+
+ if (bIsPOSTRemap) { // if post remap, get remapped/OS Server Addr and
add as header
+ const struct sockaddr* sa = TSHttpTxnServerAddrGet(txnp);
+ // const struct sockaddr* sa = TSHttpTxnNextHopAddrGet(txnp);
+ if (sa) { // sanity check
+ struct sockaddr_storage ss;
+ memcpy(static_cast<void *> (&ss), sa, sizeof (sockaddr_storage));
+ if ((reinterpret_cast<sockaddr_in *> (&ss))->sin_port == 0) { //
set port from client request URL
+ (reinterpret_cast<sockaddr_in *> (&ss))->sin_port =
htons(reqUrl.getPort());
+ }
+
+ string sTemp;
+ getStringFromSockaddr(reinterpret_cast<const sockaddr *> (&ss),
sTemp);
+ TSDebug(DEBUG_TAG_HOOK, "Inserting forward header with
sockaddr:%s", sTemp.c_str());
+ string sSockaddr;
+ sSockaddr.reserve(32 + sizeof (sockaddr_storage) * 2);
+ for (unsigned int i = 0; i<sizeof (sockaddr_storage); i++) {
+ char val[8];
+ sprintf(val, "%02X", reinterpret_cast<const unsigned char *>
(&ss)[i]);
+ sSockaddr += val;
+ }
+ sSockaddr += "/" + reqUrl.getScheme();
+ // insert carp forwarding header
+ bool bAddFwdHeaderResult = addHeader(bufp, hdr_loc,
CARP_FORWARD_HEADER, sSockaddr);
+ if (!bAddFwdHeaderResult) {
+ TSError("Carp, error inserting '%s' header",
CARP_FORWARD_HEADER.c_str());
+ }
+ }
+ } else { // for premap mode
+ string sScheme = reqUrl.getScheme();
+
+ if (!addHeader(bufp, hdr_loc, CARP_PREMAP_SCHEME, sScheme)) {
+ TSError("Carp, error inserting '%s' header in premap mode",
CARP_PREMAP_SCHEME.c_str());
+ } else {
+ TSDebug(DEBUG_TAG_HOOK, "Insert client request scheme %s in premap
mode", sScheme.c_str());
+ }
+ }
+ // set origin server/destination
+ // TSHttpTxnConfigIntSet(txnp,
TS_CONFIG_HTTP_SHARE_SERVER_SESSIONS, 0); // disable conn sharing due to bug
when used with TSHttpTxnServerAddrSet
+ if (TSHttpTxnServerAddrSet(txnp, reinterpret_cast<const struct
sockaddr *> (&node->forwardAddr)) != TS_SUCCESS) {
+ TSDebug(DEBUG_TAG_HOOK, "Error calling TSHttpTxnServerAddrSet");
+ } else {
+ // set scheme appropriately based on destination
+ TSDebug(DEBUG_TAG_HOOK, "Setting scheme to '%s'",
node->getSchemeString());
+ TSUrlSchemeSet(bufp, url_loc, node->getSchemeString(), -1);
+ if (!bIsPOSTRemap) { // since we are forwarding, do not remap
request and do not cache result
+ TSSkipRemappingSet(txnp, true);
+ }
+ TSHttpTxnArgSet(txnp, g_carpSelectedHostArgIndex, static_cast<void
*> (node));
+ if (!bIsOwner) {
+ TSHttpTxnServerRespNoStoreSet(txnp, 1); // do not cache the
response
+ } else {
+ TSHttpTxnServerRespNoStoreSet(txnp, 0); // we are replicate owner,
cache response
--- End diff --
Be aware this doesn't force a cache, it inhibits the inhibition of caching.
It's a messed up API and there's an outstanding bug (TS-3426) on fixing it.
Issue Time Tracking
-------------------
Worklog Id: (was: 26906)
Time Spent: 5h (was: 4h 50m)
> 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: 5h
> 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)