blob: ce4f9a990bfdecc279dfe6d64166e6bd40a8c088 [file]
/** @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 "P_CacheHosting.h"
#include "Stripe.h"
#include "iocore/cache/CacheDefs.h"
#include "mgmt/config/ConfigContextDiags.h"
#include "swoc/swoc_file.h"
#include "tscore/HostLookup.h"
#include "tscore/Tokenizer.h"
#include "tscore/Filenames.h"
#include "tsutil/DbgCtl.h"
#include <unordered_map>
namespace
{
DbgCtl dbg_ctl_cache_hosting{"cache_hosting"};
DbgCtl dbg_ctl_matcher{"matcher"};
} // end anonymous namespace
/*************************************************************
* Begin class HostMatcher
*************************************************************/
CacheHostMatcher::CacheHostMatcher(const char *name, CacheType typ) : data_array(nullptr), array_len(-1), num_el(-1), type(typ)
{
host_lookup = std::make_unique<HostLookup>(name);
}
CacheHostMatcher::~CacheHostMatcher()
{
delete[] data_array;
}
//
// template <class Data,class Result>
// void HostMatcher<Data,Result>::Print()
//
// Debugging Method
//
void
CacheHostMatcher::Print() const
{
printf("\tHost/Domain Matcher with %d elements\n", num_el);
host_lookup->Print(PrintFunc);
}
//
// template <class Data,class Result>
// void CacheHostMatcher::PrintFunc(void* opaque_data)
//
// Debugging Method
//
void
CacheHostMatcher::PrintFunc(void *opaque_data)
{
CacheHostRecord *d = static_cast<CacheHostRecord *>(opaque_data);
d->Print();
}
// void CacheHostMatcher::AllocateSpace(int num_entries)
//
// Allocates the HostLeaf and Data arrays
//
void
CacheHostMatcher::AllocateSpace(int num_entries)
{
// Should not have been allocated before
ink_assert(array_len == -1);
host_lookup->AllocateSpace(num_entries);
data_array = new CacheHostRecord[num_entries];
array_len = num_entries;
num_el = 0;
}
// void CacheHostMatcher::Match(std::string_view rdata, Result* result)
//
// Searches our tree and updates argresult for each element matching
// arg hostname
//
void
CacheHostMatcher::Match(std::string_view rdata, CacheHostResult *result) const
{
void *opaque_ptr;
CacheHostRecord *data_ptr;
bool r;
// Check to see if there is any work to do before making
// the string copy
if (num_el <= 0) {
return;
}
if (rdata.empty()) {
return;
}
HostLookupState s;
r = host_lookup->MatchFirst(rdata, &s, &opaque_ptr);
while (r == true) {
ink_assert(opaque_ptr != nullptr);
data_ptr = static_cast<CacheHostRecord *>(opaque_ptr);
data_ptr->UpdateMatch(result);
r = host_lookup->MatchNext(&s, &opaque_ptr);
}
}
//
// char* CacheHostMatcher::NewEntry(bool domain_record,
// char* match_data, char* match_info, int line_num)
//
// Creates a new host/domain record
//
void
CacheHostMatcher::NewEntry(matcher_line *line_info)
{
CacheHostRecord *cur_d;
int errNo;
char *match_data;
// Make sure space has been allocated
ink_assert(num_el >= 0);
ink_assert(array_len >= 0);
// Make sure we do not overrun the array;
ink_assert(num_el < array_len);
match_data = line_info->line[1][line_info->dest_entry];
// Make sure that the line_info is not bogus
ink_assert(line_info->dest_entry < MATCHER_MAX_TOKENS);
ink_assert(match_data != nullptr);
// Remove our consumed label from the parsed line
if (line_info->dest_entry < MATCHER_MAX_TOKENS) {
line_info->line[0][line_info->dest_entry] = nullptr;
}
line_info->num_el--;
// Fill in the parameter info
cur_d = data_array + num_el;
errNo = cur_d->Init(line_info, type);
if (errNo) {
// There was a problem so undo the effects this function
memset(static_cast<void *>(cur_d), 0, sizeof(CacheHostRecord));
return;
}
Dbg(dbg_ctl_cache_hosting, "hostname: %s, host record: %p", match_data, cur_d);
// Fill in the matching info
host_lookup->NewEntry(match_data, (line_info->type == MATCH_DOMAIN) ? true : false, cur_d);
num_el++;
return;
}
/*************************************************************
* End class HostMatcher
*************************************************************/
CacheHostTable::CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx)
{
ats_scoped_str config_path;
type = typ;
cache = c;
matcher_name = "[CacheHosting]";
config_path = RecConfigReadConfigPath("proxy.config.cache.hosting_filename");
ink_release_assert(config_path);
m_numEntries = this->BuildTable(config_path, ctx);
}
CacheHostTable::~CacheHostTable() {}
// void ControlMatcher<Data, Result>::Print()
//
// Debugging method
//
void
CacheHostTable::Print() const
{
printf("Control Matcher Table: %s\n", matcher_name);
if (hostMatch != nullptr) {
hostMatch->Print();
}
}
// void ControlMatcher<Result>::Match(std::string_view rdata
// Result* result)
//
// Queries each table for the Result*
//
void
CacheHostTable::Match(std::string_view rdata, CacheHostResult *result) const
{
hostMatch->Match(rdata, result);
}
int fstat_wrapper(int fd, struct stat *s);
// int ControlMatcher::BuildTable() {
//
// Reads the cache.config file and build the records array
// from it
//
int
CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_buf, ConfigContext ctx)
{
CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::HOSTING);
// Table build locals
Tokenizer bufTok("\n");
tok_iter_state i_state;
const char *tmp = nullptr;
matcher_line *first = nullptr;
matcher_line *current = nullptr;
matcher_line *last = nullptr;
int line_num = 0;
int second_pass = 0;
int numEntries = 0;
const char *errPtr = nullptr;
// type counts
int hostDomain = 0;
if (bufTok.Initialize(file_buf, SHARE_TOKS | ALLOW_EMPTY_TOKS) == 0) {
// We have an empty file
/* no hosting customers -- put all the volumes in the
generic table */
if (gen_host_rec.Init(type)) {
CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume");
}
return 0;
}
// First get the number of entries
tmp = bufTok.iterFirst(&i_state);
while (tmp != nullptr) {
line_num++;
// skip all blank spaces at beginning of line
while (*tmp && isspace(*tmp)) {
tmp++;
}
if (*tmp != '#' && *tmp != '\0') {
current = static_cast<matcher_line *>(ats_malloc(sizeof(matcher_line)));
errPtr = parseConfigLine(const_cast<char *>(tmp), current, &config_tags);
if (errPtr != nullptr) {
CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errPtr);
ats_free(current);
} else {
// Line parsed ok. Figure out what the destination
// type is and link it into our list
numEntries++;
current->line_num = line_num;
switch (current->type) {
case MATCH_HOST:
case MATCH_DOMAIN:
hostDomain++;
break;
case MATCH_NONE:
default:
ink_assert(0);
}
if (first == nullptr) {
ink_assert(last == nullptr);
first = current;
last = current;
} else {
last->next = current;
last = current;
}
}
}
tmp = bufTok.iterNext(&i_state);
}
// Make we have something to do before going on
if (numEntries == 0) {
/* no hosting customers -- put all the volumes in the
generic table */
if (gen_host_rec.Init(type)) {
CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume");
}
CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING);
return 0;
}
if (hostDomain > 0) {
hostMatch = std::make_unique<CacheHostMatcher>(matcher_name, type);
hostMatch->AllocateSpace(hostDomain);
}
// Traverse the list and build the records table
int generic_rec_initd = 0;
current = first;
while (current != nullptr) {
second_pass++;
if ((current->type == MATCH_DOMAIN) || (current->type == MATCH_HOST)) {
char *match_data = current->line[1][current->dest_entry];
ink_assert(match_data != nullptr);
if (!strcasecmp(match_data, "*")) {
// generic volume - initialize the generic hostrecord */
// Make sure that the line_info is not bogus
ink_assert(current->dest_entry < MATCHER_MAX_TOKENS);
// Remove our consumed label from the parsed line
if (current->dest_entry < MATCHER_MAX_TOKENS) {
current->line[0][current->dest_entry] = nullptr;
} else {
CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume");
}
current->num_el--;
if (!gen_host_rec.Init(current, type)) {
generic_rec_initd = 1;
} else {
CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume");
}
} else {
hostMatch->NewEntry(current);
}
} else {
CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry with unknown type at line %d", matcher_name, config_file_path,
current->line_num);
}
// Deallocate the parsing structure
last = current;
current = current->next;
ats_free(last);
}
CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING);
if (!generic_rec_initd) {
const char *cache_type = (type == CacheType::HTTP) ? "http" : "mixt";
CfgLoadLog(ctx, DL_Warning, "No Volumes specified for Generic Hostnames for %s documents: %s cache will be disabled",
cache_type, cache_type);
}
ink_assert(second_pass == numEntries);
if (dbg_ctl_matcher.on()) {
Print();
}
return numEntries;
}
int
CacheHostTable::BuildTable(const char *config_file_path, ConfigContext ctx)
{
std::error_code ec;
std::string content{swoc::file::load(swoc::file::path{config_file_path}, ec)};
if (ec) {
switch (ec.value()) {
case ENOENT:
CfgLoadLog(ctx, DL_Warning, "Cannot open the config file: %s - %s", config_file_path, strerror(ec.value()));
break;
default:
CfgLoadFail(ctx, "%s failed to load: %s", config_file_path, strerror(ec.value()));
gen_host_rec.Init(type);
return 0;
}
}
return BuildTableFromString(config_file_path, content.data(), ctx);
}
int
CacheHostRecord::Init(CacheType typ)
{
int i, j;
extern Queue<CacheVol> cp_list;
extern int cp_list_len;
num_vols = 0;
type = typ;
cp = static_cast<CacheVol **>(ats_malloc(cp_list_len * sizeof(CacheVol *)));
memset(cp, 0, cp_list_len * sizeof(CacheVol *));
num_cachevols = 0;
CacheVol *cachep = cp_list.head;
for (; cachep; cachep = cachep->link.next) {
if (cachep->scheme == type) {
Dbg(dbg_ctl_cache_hosting, "Host Record: %p, Volume: %d, size: %" PRId64, this, cachep->vol_number, (int64_t)cachep->size);
cp[num_cachevols] = cachep;
num_cachevols++;
num_vols += cachep->num_vols;
}
}
if (!num_cachevols) {
Warning("error: No volumes found for Cache Type %d", static_cast<int>(type));
return -1;
}
stripes = static_cast<StripeSM **>(ats_malloc(num_vols * sizeof(StripeSM *)));
int counter = 0;
for (i = 0; i < num_cachevols; i++) {
CacheVol *cachep1 = cp[i];
for (j = 0; j < cachep1->num_vols; j++) {
stripes[counter++] = cachep1->stripes[j];
}
}
ink_assert(counter == num_vols);
build_vol_hash_table(this);
return 0;
}
int
CacheHostRecord::Init(matcher_line *line_info, CacheType typ)
{
int i, j;
extern Queue<CacheVol> cp_list;
int is_vol_present = 0;
char config_file[PATH_NAME_MAX];
RecGetRecordString("proxy.config.cache.hosting_filename", config_file, PATH_NAME_MAX);
type = typ;
for (i = 0; i < MATCHER_MAX_TOKENS; i++) {
char *label = line_info->line[0][i];
if (!label) {
continue;
}
char *val;
if (!strcasecmp(label, "volume")) {
/* parse the list of volumes */
val = ats_strdup(line_info->line[1][i]);
char *vol_no = val;
char *s = val;
int volume_number;
CacheVol *cachep;
/* first find out the number of volumes */
while (*s) {
if (*s == ',') {
num_cachevols++;
s++;
if (!(*s)) {
const char *errptr = "A volume number expected";
Warning("%s discarding %s entry at line %d :%s", "[CacheHosting]", config_file, line_info->line_num, errptr);
if (val != nullptr) {
ats_free(val);
}
return -1;
}
}
if ((*s < '0') || (*s > '9')) {
Warning("%s discarding %s entry at line %d : bad token [%c]", "[CacheHosting]", config_file, line_info->line_num, *s);
if (val != nullptr) {
ats_free(val);
}
return -1;
}
s++;
}
if (val == nullptr) {
return -1;
}
s = val;
num_cachevols++;
cp = static_cast<CacheVol **>(ats_malloc(num_cachevols * sizeof(CacheVol *)));
memset(cp, 0, num_cachevols * sizeof(CacheVol *));
num_cachevols = 0;
while (true) {
char c = *s;
if ((c == ',') || (c == '\0')) {
*s = '\0';
volume_number = atoi(vol_no);
cachep = cp_list.head;
for (; cachep; cachep = cachep->link.next) {
if (cachep->vol_number == volume_number) {
is_vol_present = 1;
if (cachep->scheme == type) {
Dbg(dbg_ctl_cache_hosting, "Host Record: %p, Volume: %d, size: %ld", this, volume_number,
(static_cast<long>(cachep->size * STORE_BLOCK_SIZE)));
cp[num_cachevols] = cachep;
num_cachevols++;
num_vols += cachep->num_vols;
break;
}
}
}
if (!is_vol_present) {
Warning("%s discarding %s entry at line %d : bad volume number [%d]", "[CacheHosting]", config_file,
line_info->line_num, volume_number);
if (val != nullptr) {
ats_free(val);
}
return -1;
}
if (c == '\0') {
break;
}
vol_no = s + 1;
}
s++;
}
ats_free(val);
break;
}
Warning("%s discarding %s entry at line %d : bad token [%s]", "[CacheHosting]", config_file, line_info->line_num, label);
return -1;
}
if (i == MATCHER_MAX_TOKENS) {
Warning("%s discarding %s entry at line %d : No volumes specified", "[CacheHosting]", config_file, line_info->line_num);
return -1;
}
if (!num_vols) {
return -1;
}
stripes = static_cast<StripeSM **>(ats_malloc(num_vols * sizeof(StripeSM *)));
int counter = 0;
for (i = 0; i < num_cachevols; i++) {
CacheVol *cachep = cp[i];
for (j = 0; j < cp[i]->num_vols; j++) {
stripes[counter++] = cachep->stripes[j];
}
}
ink_assert(counter == num_vols);
build_vol_hash_table(this);
return 0;
}
void
CacheHostRecord::UpdateMatch(CacheHostResult *r)
{
r->record = this;
}
void
CacheHostRecord::Print() const
{
}
// Wrapper function for deleting CacheHostRecord from outside the cache module.
void
destroyCacheHostRecord(CacheHostRecord *rec)
{
delete rec;
}
// Build a CacheHostRecord for @volume= directive with comma-separated volumes
// This reuses CacheHostRecord::Init() by constructing a minimal matcher_line
CacheHostRecord *
createCacheHostRecord(const char *volume_str, char *errbuf, size_t errbufsize)
{
if (!volume_str || !*volume_str) {
snprintf(errbuf, errbufsize, "Empty volume specification");
return nullptr;
}
CacheHostRecord *host_rec = new CacheHostRecord();
// Build a minimal matcher_line structure with just the volume= directive
char volume_key[] = "volume";
matcher_line ml;
memset(&ml, 0, sizeof(ml));
ml.line[0][0] = volume_key;
ml.line[1][0] = const_cast<char *>(volume_str);
ml.num_el = 1;
ml.dest_entry = -1;
ml.line_num = 0;
ml.type = MATCH_NONE;
ml.next = nullptr;
int result = host_rec->Init(&ml, CacheType::HTTP);
if (result != 0) {
delete host_rec;
snprintf(errbuf, errbufsize, "Failed to initialize volume record (check volume.config)");
return nullptr;
}
Dbg(dbg_ctl_cache_hosting, "Created remap volume record with %d volumes, %d stripes", host_rec->num_cachevols,
host_rec->num_vols);
return host_rec;
}
bool
ConfigVol::Size::is_empty() const
{
return absolute_value == 0 && !in_percent && percent == 0;
}
/**
Complement missing volumes[].size and volumes[].spans[].size if there
*/
void
ConfigVolumes::complement()
{
struct SizeTracker {
int empty_node = 0;
int remaining_size = 100; ///< in percentage
};
SizeTracker volume_tracker;
std::unordered_map<std::string, SizeTracker> span_size_map;
// Find missing size and remaining size in percentage
for (ConfigVol *conf = cp_queue.head; conf != nullptr; conf = cp_queue.next(conf)) {
if (!conf->spans.empty()) {
for (const auto &span : conf->spans) {
SizeTracker span_size;
if (auto it = span_size_map.find(span.use); it != span_size_map.end()) {
span_size = it->second;
}
if (span.size.is_empty()) {
span_size.empty_node++;
} else if (span.size.in_percent) {
if (span_size.remaining_size < span.size.percent) {
Error("Total volume size (in percent) exceeded 100%% - span.use=%s", span.use.c_str());
continue;
}
span_size.remaining_size -= span.size.percent;
}
span_size_map.insert_or_assign(span.use, span_size);
}
} else {
if (conf->size.is_empty()) {
++volume_tracker.empty_node;
} else if (conf->size.in_percent) {
if (volume_tracker.remaining_size < conf->size.percent) {
Error("Total volume size (in percent) exceeded 100%%");
continue;
}
volume_tracker.remaining_size -= conf->size.percent;
}
}
}
// If there're no missing size, do nothing
if (volume_tracker.empty_node == 0 &&
std::all_of(span_size_map.cbegin(), span_size_map.cend(), [](auto &it) { return it.second.empty_node == 0; })) {
return;
}
// Set size
for (ConfigVol *conf = cp_queue.head; conf != nullptr; conf = cp_queue.next(conf)) {
if (!conf->spans.empty()) {
for (auto &span : conf->spans) {
if (span.size.is_empty()) {
const SizeTracker &tracker = span_size_map[span.use];
if (tracker.empty_node == 0) {
continue;
}
span.size.in_percent = true;
span.size.percent = tracker.remaining_size / tracker.empty_node;
}
}
} else {
if (conf->size.is_empty() && volume_tracker.empty_node != 0) {
conf->size.in_percent = true;
conf->size.percent = volume_tracker.remaining_size / volume_tracker.empty_node;
}
}
}
}