diff --git a/doc/admin-guide/plugins/slice.en.rst b/doc/admin-guide/plugins/slice.en.rst index 06ae34e564c..f928fa13ea3 100644 --- a/doc/admin-guide/plugins/slice.en.rst +++ b/doc/admin-guide/plugins/slice.en.rst @@ -69,17 +69,25 @@ The slice plugin supports the following options:: Suffix k,m,g supported Limited to 32k and 32m inclusive. - --test-blockbytes= (optional) + --blockbytes-test= (optional) Suffix k,m,g supported -t for short. Limited to any positive number. Ignored if --blockbytes provided. + --remap-host= (optional) + Uses effective url with given hostname for remapping. + Requires setting up an intermediate loopback remap rule. + -r for short + --pace-errorlog= (optional) Limit stitching error logs to every 'n' second(s) + -p for short --disable-errorlog (optional) Disable writing block stitch errors to the error log. + -d for short + Examples:: @@ -95,10 +103,10 @@ Byte suffix examples:: slice.so -b 512k slice.so --blockbytes=32m -For testing and extreme purposes the parameter ``test-blockbytes`` may +For testing and extreme purposes the parameter ``blockbytes-test`` may be used instead which is unchecked:: - slice.so --test-blockbytes=1G + slice.so --blockbytes-test=1G slice.so -t 13 Because the slice plugin is susceptible to errors during block stitching @@ -128,9 +136,11 @@ Under normal logging these slice block errors tend to show up as:: crc value ERR_READ_ERROR By default more detailed stitching errors are written to ``diags.log``. -An example is as follows:: +Examples are as follows:: -[Apr 19 20:26:13.639] [ET_NET 17] ERROR: [slice] 1555705573.639 reason="Non 206 internal block response" uri="http://localhost:18080/%7Ep.tex/%7Es.50M/%7Eui.20000/" uas="curl/7.29.0" req_range="bytes=1000000-" norm_range="bytes 1000000-52428799/52428800" etag_exp="%221603934496%22" lm_exp="Fri, 19 Apr 2019 18:53:20 GMT" blk_range="21000000-21999999" status_got="400" cr_got="" etag_got="" lm_got="" cc="no-store" via="" +ERROR: [slice.cc: 288] logSliceError(): 1555705573.639 reason="Non 206 internal block response" uri="http://ats_ep/someasset.mp4" uas="curl" req_range="bytes=1000000-" norm_range="bytes 1000000-52428799/52428800" etag_exp="%221603934496%22" lm_exp="Fri, 19 Apr 2019 18:53:20 GMT" blk_range="21000000-21999999" status_got="206" cr_got="" etag_got="%221603934496%22" lm_got="" cc="no-store" via="" + +ERROR: [server.cc: 288] logSliceError(): 1572370000.219 reason="Mismatch block Etag" uri="http://ats_ep/someasset.mp4" uas="curl" req_range="bytes=1092779033-1096299354" norm_range="bytes 1092779033-1096299354/2147483648" etag_exp="%223719843648%22" lm_exp="Tue, 29 Oct 2019 14:40:00 GMT" blk_range="1095000000-1095999999" status_got="206" cr_got="bytes 1095000000-1095999999/2147483648" etag_got="%223719853648%22" lm_got="Tue, 29 Oct 2019 17:26:40 GMT" cc="max-age=10000" via="" Whether or how often these detailed log entries are written are configurable plugin options. @@ -204,14 +214,43 @@ by cache_range_requests. The parent will trim those requests to account for the asset Content-Length so only the appropriate number of bytes are actually transferred and cached. -Current Limitations +Effective URL remap =================== -By restoring the pristine Url the plugin as it works today reuses the -same remap rule for each slice block. This is wasteful in that it reruns +By default the plugin restores the Pristine Url which reuses the same +remap rule for each slice block. This is wasteful in that it reruns the previous remap rules, and those remap rules must be smart enough to -check for the existence of any headers they may have created the -first time they have were visited. +check for the existence of any headers they may have created the first +time they have were visited. + +To get around this the '--remap-host=' or '-r ' option may +be used. This requires an intermediate loopback remap to be defined which +handles each slice block request. + +This works well with any remap rules that use the url_sig or uri_signing +plugins. As the client remap rule is not caching any plugins that +manipulate the cache key would need to go into the loopback to parent +remap rule. + +NOTE: Requests NOT handled by the slice plugin (ie: HEAD requests) are +handled as with a typical remap rule. GET requests intercepted by the +slice plugin are virtually reissued into ATS and are proxied through +another remap rule which must contain the ``cache_range_requests`` plugin + +Examples:: + + map http://ats/ http://parent/ @plugin=slice.so @pparam=--remap-host=loopback + map http://loopback/ http://parent/ @plugin=cache_range_requests.so + +Alternatively:: + + map http://ats/ http://parent/ @plugin=slice.so @pparam=-r @pparam=loopback + map http://loopback/ http://parent/ @plugin=cache_range_requests.so + +Current Limitations +=================== Since the Slice plugin is written as an intercept handler it loses the -ability to use normal state machine hooks and transaction states. +ability to use normal state machine hooks and transaction states. This +functionality is handled by using the ``cache_range_requests`` plugin +to interact with ATS. diff --git a/plugins/experimental/slice/Config.cc b/plugins/experimental/slice/Config.cc index 2198011d0e4..6d27089dbe4 100644 --- a/plugins/experimental/slice/Config.cc +++ b/plugins/experimental/slice/Config.cc @@ -29,21 +29,22 @@ int64_t Config::bytesFrom(char const *const valstr) { - char *endptr = nullptr; - int64_t blockbytes = strtoll(valstr, &endptr, 10); + char *endptr = nullptr; + int64_t blockbytes = strtoll(valstr, &endptr, 10); + constexpr int64_t kib = 1024; if (nullptr != endptr && valstr < endptr) { size_t const dist = endptr - valstr; if (dist < strlen(valstr) && 0 <= blockbytes) { switch (tolower(*endptr)) { case 'g': - blockbytes *= (static_cast(1024) * static_cast(1024) * static_cast(1024)); + blockbytes *= (kib * kib * kib); break; case 'm': - blockbytes *= (static_cast(1024) * static_cast(1024)); + blockbytes *= (kib * kib); break; case 'k': - blockbytes *= static_cast(1024); + blockbytes *= kib; break; default: break; @@ -66,7 +67,7 @@ Config::fromArgs(int const argc, char const *const argv[]) DEBUG_LOG("args[%d] = %s", index, argv[index]); } - // current "best" blockbytes from configuration + // look for lowest priority deprecated blockbytes int64_t blockbytes = 0; // backwards compat: look for blockbytes @@ -91,9 +92,10 @@ Config::fromArgs(int const argc, char const *const argv[]) } // standard parsing - constexpr const struct option longopts[] = { + constexpr struct option longopts[] = { {const_cast("blockbytes"), required_argument, nullptr, 'b'}, - {const_cast("test-blockbytes"), required_argument, nullptr, 't'}, + {const_cast("blockbytes-test"), required_argument, nullptr, 't'}, + {const_cast("remap-host"), required_argument, nullptr, 'r'}, {const_cast("pace-errorlog"), required_argument, nullptr, 'p'}, {const_cast("disable-errorlog"), no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}, @@ -101,9 +103,8 @@ Config::fromArgs(int const argc, char const *const argv[]) // getopt assumes args start at '1' so this hack is needed char *const *argvp = (const_cast(argv) - 1); - for (;;) { - int const opt = getopt_long(argc + 1, argvp, "b:t:p:d", longopts, nullptr); + int const opt = getopt_long(argc + 1, argvp, "b:t:r:p:d", longopts, nullptr); if (-1 == opt) { break; } @@ -120,25 +121,29 @@ Config::fromArgs(int const argc, char const *const argv[]) ERROR_LOG("Invalid blockbytes: %s", optarg); } } break; - case 't': + case 't': { if (0 == blockbytes) { int64_t const bytesread = bytesFrom(optarg); if (0 < bytesread) { - DEBUG_LOG("Using blockbytestest %" PRId64, bytesread); + DEBUG_LOG("Using blockbytes-test %" PRId64, bytesread); blockbytes = bytesread; } else { - ERROR_LOG("Invalid blockbytestest: %s", optarg); + ERROR_LOG("Invalid blockbytes-test: %s", optarg); } } else { - DEBUG_LOG("Skipping blockbytestest in favor of blockbytes"); + DEBUG_LOG("Skipping blockbytes-test in favor of blockbytes"); } + } break; + case 'r': + m_remaphost = optarg; + DEBUG_LOG("Using loopback remap host override: %s", m_remaphost.c_str()); break; case 'p': { int const secsread = atoi(optarg); if (0 < secsread) { m_paceerrsecs = std::min(secsread, 60); } else { - DEBUG_LOG("Ignoring pace-errlog argument"); + ERROR_LOG("Ignoring pace-errlog argument"); } } break; case 'd': @@ -170,8 +175,6 @@ Config::fromArgs(int const argc, char const *const argv[]) bool Config::canLogError() { - std::lock_guard const guard(m_mutex); - if (m_paceerrsecs < 0) { return false; } else if (0 == m_paceerrsecs) { @@ -180,13 +183,18 @@ Config::canLogError() #if !defined(UNITTEST) TSHRTime const timenow = TShrtime(); +#endif + + std::lock_guard const guard(m_mutex); + +#if !defined(UNITTEST) if (timenow < m_nextlogtime) { return false; } m_nextlogtime = timenow + TS_HRTIME_SECONDS(m_paceerrsecs); #else - m_nextlogtime = 0; // thanks clang + m_nextlogtime = 0; // needed by clang #endif return true; diff --git a/plugins/experimental/slice/Config.h b/plugins/experimental/slice/Config.h index 8c4ab2498fa..8c191b06c0c 100644 --- a/plugins/experimental/slice/Config.h +++ b/plugins/experimental/slice/Config.h @@ -29,7 +29,8 @@ struct Config { static constexpr int64_t const blockbytesdefault = 1024 * 1024; // 1MB int64_t m_blockbytes{blockbytesdefault}; - int m_paceerrsecs{0}; // -1 disable logging, 0 no pacing, max 60s + std::string m_remaphost; // remap host to use for loopback slice GET + int m_paceerrsecs{0}; // -1 disable logging, 0 no pacing, max 60s // Convert optarg to bytes static int64_t bytesFrom(char const *const valstr); diff --git a/plugins/experimental/slice/Data.h b/plugins/experimental/slice/Data.h index 45383522103..30996438c31 100644 --- a/plugins/experimental/slice/Data.h +++ b/plugins/experimental/slice/Data.h @@ -39,8 +39,8 @@ struct Data { sockaddr_storage m_client_ip; - // for pristine url coming in - TSMBuffer m_urlbuffer{nullptr}; + // for pristine/effective url coming in + TSMBuffer m_urlbuf{nullptr}; TSMLoc m_urlloc{nullptr}; char m_hostname[8192]; @@ -80,7 +80,7 @@ struct Data { explicit Data(Config *const config) : m_config(config), m_client_ip(), - m_urlbuffer(nullptr), + m_urlbuf(nullptr), m_urlloc(nullptr), m_hostlen(0), m_etaglen(0), @@ -113,11 +113,11 @@ struct Data { ~Data() { // decrData(); - if (nullptr != m_urlbuffer) { + if (nullptr != m_urlbuf) { if (nullptr != m_urlloc) { - TSHandleMLocRelease(m_urlbuffer, TS_NULL_MLOC, m_urlloc); + TSHandleMLocRelease(m_urlbuf, TS_NULL_MLOC, m_urlloc); } - TSMBufferDestroy(m_urlbuffer); + TSMBufferDestroy(m_urlbuf); } if (nullptr != m_http_parser) { TSHttpParserDestroy(m_http_parser); diff --git a/plugins/experimental/slice/README.md b/plugins/experimental/slice/README.md index ec3d1bc420f..31746ffda45 100644 --- a/plugins/experimental/slice/README.md +++ b/plugins/experimental/slice/README.md @@ -15,66 +15,86 @@ The plugin uses TSHttpConnect to delegate each block request to cache_range_requests.so which handles all cache and parent interaction. To enable the plugin, specify the plugin library via @plugin at the end -of a remap line as follows (2MB slice in this example): +of a remap line as follows (default 1MB slice in this example): ``` -map http://ats-cache/ http://parent/ @plugin=slice.so @pparam=--blockbytes=2M @plugin=cache_range_requests.so +map http://ats-cache/ http://parent/ @plugin=slice.so @plugin=cache_range_requests.so +map https://ats-cache/ http://parent/ @plugin=slice.so @plugin=cache_range_requests.so ``` -alternatively +alternatively (2MB slice block) ``` map http://ats-cache/ http://parent/ @plugin=slice.so @pparam=-b @pparam=2M @plugin=cache_range_requests.so -``` - -for global plugins. - -``` -slice.so --blockbytes=2097152 -cache_range_requests.so -``` - -alternatively: - -``` -slice.so -b 2M -cache_range_requests.so +map https://ats-cache/ http://parent/ @plugin=slice.so @pparam=--blockbytes=2M @plugin=cache_range_requests.so ``` Options for the slice plugin (typically last one wins): ``` --blockbytes= (optional) Slice block size. - Default is 1m or 1048576 bytes. + Default is 1m or 1048576 bytes. also -b - Suffix k,m,g supported. - Limited to 32k and 32m inclusive. - For backwards compatibility blockbytes: is also supported. + Suffix k,m,g supported. + Limited to 32k and 32m inclusive. + For backwards compatibility blockbytes: is also supported. ---test-blockbytes= (optional) +--blockbytes-test= (optional) Slice block size for testing. also -t - Suffix k,m,g supported. - Limited to any positive number. - Ignored if --blockbytes is provided. + Suffix k,m,g supported. + Limited to any positive number. + Ignored if --blockbytes is provided. + +--remap-host= (optional) + Uses effective url with given host and port 0 for remapping. + Requires setting up an intermediate loopback remap rule. + -r for short --pace-errorlog= (optional) Limit stitching error logs to every 'n' second(s) Default is to log all errors (no pacing). - also -p + also -e --disable-errorlog (optional) Disable writing stitching errors to the error log. also -d ``` -**Note**: cache_range_requests **MUST** follow slice.so Put these plugins -at the end of the plugin list +By default the plugin uses the pristine url to loopback call back +into the same rule as each range slice is issued. The effective url +with loopback remap host may be used by adding the '-r ' or +'--remap-host=' plugin option. + +Using the `--remap-host` option splits the plugin chain into 2 remap rules. +One remap rule for all the incoming requests and the other for just the block +range requests. This allows for easier trouble shooting via logs and +also allows for more effecient plugin rules. The default pristine method +runs the remap plugins twice, one for the incoming request and one for +eace slice. Splitting the rules allows for plugins like URI signing to +be done on the client request only. + +NOTE: Requests NOT handled by the slice plugin (ie: HEAD requests) are +handled as with a typical remap rule. GET requests intercepted by the +slice plugin are virtually reissued into ATS and are forward proxied +through the cache_range_requests plugin. + +``` +map http://ats/ http://parent/ @plugin=slice.so @pparam=--blockbytes=512k @pparam=--remap-host=loopback +map https://ats/ https://parent/ @plugin=slice.so @pparam=--blockbytes=512k @pparam=--remap-host=loopback + +# Virtual forward proxy for slice range blocks +map http://loopback/ http://parent/ @plugin=cache_range_requests.so +map https://loopback/ http://parent/ @plugin=cache_range_requests.so +``` + +**Note**: For default pristine behavior cache_range_requests **MUST** +follow slice.so Put these plugins at the end of the plugin list **Note**: blockbytes is defined in bytes. Postfix for 'K', 'M' and 'G' may be used. 1048576 (1MB) is the default. -For testing purposes an unchecked value of "bytesover" is also available. +For testing purposes an unchecked value of "blockbytes-test" is also available. Debug output can be enable by setting the debug tag: **slice**. If debug is enabled all block stitch errors will log to diags.log @@ -90,9 +110,9 @@ provided to help with debugging. Below is a sample error log entry:: Current error types logged: ``` Mismatch block Etag - Mismatch block Last-Modified - Non 206 internal block response - Mismatch/Bad block Content-Range + Mismatch block Last-Modified + Non 206 internal block response + Mismatch/Bad block Content-Range ``` diff --git a/plugins/experimental/slice/client.cc b/plugins/experimental/slice/client.cc index 47ac4e3383c..adf0a4a0fa6 100644 --- a/plugins/experimental/slice/client.cc +++ b/plugins/experimental/slice/client.cc @@ -106,7 +106,7 @@ handle_client_req(TSCont contp, TSEvent event, Data *const data) HttpHeader header(data->m_req_hdrmgr.m_buffer, data->m_req_hdrmgr.m_lochdr); // set the request url back to pristine in case of plugin stacking - header.setUrl(data->m_urlbuffer, data->m_urlloc); + header.setUrl(data->m_urlbuf, data->m_urlloc); header.setKeyVal(TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, data->m_hostname, data->m_hostlen); diff --git a/plugins/experimental/slice/slice.cc b/plugins/experimental/slice/slice.cc index 5b5c90b48d9..432e119a966 100644 --- a/plugins/experimental/slice/slice.cc +++ b/plugins/experimental/slice/slice.cc @@ -76,32 +76,75 @@ read_request(TSHttpTxn txnp, Config *const config) return false; } - // need the pristine url, especially for global plugins - TSMBuffer urlbuf; - TSMLoc urlloc; - TSReturnCode rcode = TSHttpTxnPristineUrlGet(txnp, &urlbuf, &urlloc); - - if (TS_SUCCESS == rcode) { - TSMBuffer const newbuf = TSMBufferCreate(); - TSMLoc newloc = nullptr; - rcode = TSUrlClone(newbuf, urlbuf, urlloc, &newloc); - TSHandleMLocRelease(urlbuf, TS_NULL_MLOC, urlloc); - - if (TS_SUCCESS != rcode) { - ERROR_LOG("Error cloning pristine url"); - delete data; - TSMBufferDestroy(newbuf); - return false; + // is the plugin configured to use a remap host? + std::string const &newhost = config->m_remaphost; + if (newhost.empty()) { + TSMBuffer urlbuf; + TSMLoc urlloc; + TSReturnCode rcode = TSHttpTxnPristineUrlGet(txnp, &urlbuf, &urlloc); + + if (TS_SUCCESS == rcode) { + TSMBuffer const newbuf = TSMBufferCreate(); + TSMLoc newloc = nullptr; + rcode = TSUrlClone(newbuf, urlbuf, urlloc, &newloc); + TSHandleMLocRelease(urlbuf, TS_NULL_MLOC, urlloc); + + if (TS_SUCCESS != rcode) { + ERROR_LOG("Error cloning pristine url"); + TSMBufferDestroy(newbuf); + delete data; + return false; + } + + data->m_urlbuf = newbuf; + data->m_urlloc = newloc; } + } else { // grab the effective url, swap out the host and zero the port + int len = 0; + char *const effstr = TSHttpTxnEffectiveUrlStringGet(txnp, &len); + + if (nullptr != effstr) { + TSMBuffer const newbuf = TSMBufferCreate(); + TSMLoc newloc = nullptr; + bool okay = false; + + if (TS_SUCCESS == TSUrlCreate(newbuf, &newloc)) { + char const *start = effstr; + if (TS_PARSE_DONE == TSUrlParse(newbuf, newloc, &start, start + len)) { + if (TS_SUCCESS == TSUrlHostSet(newbuf, newloc, newhost.c_str(), newhost.size()) && + TS_SUCCESS == TSUrlPortSet(newbuf, newloc, 0)) { + okay = true; + } + } + } + + TSfree(effstr); + + if (!okay) { + ERROR_LOG("Error cloning effective url"); + if (nullptr != newloc) { + TSHandleMLocRelease(newbuf, nullptr, newloc); + } + TSMBufferDestroy(newbuf); + delete data; + return false; + } + + data->m_urlbuf = newbuf; + data->m_urlloc = newloc; + } + } - data->m_urlbuffer = newbuf; - data->m_urlloc = newloc; + if (TSIsDebugTagSet(PLUGIN_NAME)) { + int len = 0; + char *const urlstr = TSUrlStringGet(data->m_urlbuf, data->m_urlloc, &len); + DEBUG_LOG("slice url: %.*s", len, urlstr); + TSfree(urlstr); } // we'll intercept this GET and do it ourselves TSCont const icontp(TSContCreate(intercept_hook, TSMutexCreate())); TSContDataSet(icontp, (void *)data); - // TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, icontp); TSHttpTxnIntercept(icontp, txnp); return true; } else { diff --git a/tests/gold_tests/pluginTest/slice/slice.test.py b/tests/gold_tests/pluginTest/slice/slice.test.py index cec66ed4ff6..e029759baa0 100644 --- a/tests/gold_tests/pluginTest/slice/slice.test.py +++ b/tests/gold_tests/pluginTest/slice/slice.test.py @@ -115,7 +115,7 @@ remap_config_path = ts.Disk.remap_config.Name tr.Disk.File(remap_config_path, typename="ats:config").AddLines([ 'map / http://127.0.0.1:{}'.format(server.Variables.Port) + - ' @plugin=slice.so @pparam=--test-blockbytes={}'.format(block_bytes) + ' @plugin=slice.so @pparam=--blockbytes-test={}'.format(block_bytes) ]) tr.StillRunningAfter = ts diff --git a/tests/gold_tests/pluginTest/slice/slice_error.test.py b/tests/gold_tests/pluginTest/slice/slice_error.test.py index 1c42b308609..e1f701196eb 100644 --- a/tests/gold_tests/pluginTest/slice/slice_error.test.py +++ b/tests/gold_tests/pluginTest/slice/slice_error.test.py @@ -244,7 +244,7 @@ # set up whole asset fetch into cache ts.Disk.remap_config.AddLine( 'map / http://127.0.0.1:{}'.format(server.Variables.Port) + - ' @plugin=slice.so @pparam=--test-blockbytes={}'.format(blockbytes) + ' @plugin=slice.so @pparam=--blockbytes-test={}'.format(blockbytes) ) # minimal configuration